import ArgumentParser
import Foundation

@main
struct AppleBridge: AsyncParsableCommand {
    // Strip --output <path> from arguments before ArgumentParser sees them.
    // This allows any subcommand to write to a file instead of stdout,
    // needed for .app bundle mode on macOS Sequoia where stdout is not capturable.
    static func main() async {
        // Install before parsing so a SIGTERM during arg parsing (rare, but
        // possible if node tears us down before we finish startup) still
        // reaps any osascript spawned by a partial subcommand.
        OsascriptRunner.installSignalHandlers()

        var args = Array(CommandLine.arguments.dropFirst())
        if let idx = args.firstIndex(of: "--output"), idx + 1 < args.count {
            JSONOutput.outputPath = args[idx + 1]
            args.remove(at: idx + 1)
            args.remove(at: idx)
        }
        do {
            var command = try Self.parseAsRoot(args)
            if var asyncCommand = command as? AsyncParsableCommand {
                try await asyncCommand.run()
            } else {
                try command.run()
            }
        } catch {
            Self.exit(withError: error)
        }
    }

    static let configuration = CommandConfiguration(
        commandName: "apple-bridge",
        abstract: "Native macOS bridge for Apple Calendar, Mail, Reminders, Numbers, Pages, and Keynote.",
        version: "0.6.5",
        subcommands: [
            Calendars.self,
            Events.self,
            Search.self,
            MailAccounts.self,
            MailUnread.self,
            MailSearchCmd.self,
            MailMessage.self,
            MailFlagged.self,
            MailCreateDraft.self,
            MailSaveAttachment.self,
            ReminderLists.self,
            RemindersCmd.self,
            RemindersToday.self,
            ReminderCreateList.self,
            ReminderCreate.self,
            ReminderComplete.self,
            ReminderDelete.self,
            ReminderDeleteList.self,
            FileList.self,
            FileInfo.self,
            FileSearchCmd.self,
            FileRead.self,
            FileMove.self,
            FileCopy.self,
            FileCreateFolder.self,
            FileTrash.self,
            Doctor.self,
            // Numbers
            NumbersSearch.self,
            NumbersRead.self,
            NumbersWrite.self,
            NumbersCreate.self,
            NumbersListSheets.self,
            NumbersAddSheet.self,
            NumbersRemoveSheet.self,
            NumbersGetFormulas.self,
            NumbersExport.self,
            NumbersInfo.self,
            // Pages
            PagesSearch.self,
            PagesRead.self,
            PagesWrite.self,
            PagesCreate.self,
            PagesFindReplace.self,
            PagesInsertTable.self,
            PagesListSections.self,
            PagesExport.self,
            PagesInfo.self,
            // Keynote
            KeynoteSearch.self,
            KeynoteRead.self,
            KeynoteCreate.self,
            KeynoteAddSlide.self,
            KeynoteEditSlide.self,
            KeynoteRemoveSlide.self,
            KeynoteReorderSlides.self,
            KeynoteListSlides.self,
            KeynoteListThemes.self,
            KeynoteExport.self,
            KeynoteInfo.self,
            // Notes
            NotesListFolders.self,
            NotesListNotes.self,
            NotesSearchCmd.self,
            NotesRead.self,
            // Contacts
            ContactsListGroups.self,
            ContactsSearchCmd.self,
            ContactsRead.self,
        ]
    )
}

// MARK: - Calendar Subcommands

struct Calendars: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        abstract: "List all calendars with account info."
    )

    func run() async throws {
        await CalendarBridge.listCalendars()
    }
}

struct Events: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        abstract: "List events in a date range. Recurring events are expanded."
    )

    @Option(name: .long, help: "Start date (ISO 8601, e.g. 2026-02-17 or 2026-02-17T00:00:00Z)")
    var start: String

    @Option(name: .long, help: "End date (ISO 8601)")
    var end: String

    @Option(name: .long, help: "Filter by calendar ID (from 'calendars' subcommand)")
    var calendar: String?

    func run() async throws {
        await CalendarBridge.listEvents(startISO: start, endISO: end, calendarID: calendar)
    }
}

struct Search: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        abstract: "Search events by title, notes, or location within a date range."
    )

    @Argument(help: "Search query")
    var query: String

    @Option(name: .long, help: "Start date (ISO 8601)")
    var start: String

    @Option(name: .long, help: "End date (ISO 8601)")
    var end: String

    func run() async throws {
        await CalendarBridge.searchEvents(query: query, startISO: start, endISO: end)
    }
}

// MARK: - Mail Subcommands

struct MailAccounts: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "mail-accounts",
        abstract: "List all mail accounts with mailboxes and unread counts."
    )

    func run() async throws {
        MailBridge.listAccounts()
    }
}

struct MailUnread: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "mail-unread",
        abstract: "Unread summary per account with recent message subjects."
    )

    @Option(name: .long, help: "Max unread messages to return per account (default: 10)")
    var limit: Int = 10

    func run() async throws {
        MailBridge.unreadSummary(limit: limit)
    }
}

struct MailSearchCmd: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "mail-search",
        abstract: "Search messages by subject, sender, body, or all fields."
    )

    @Option(name: .long, help: "Search query (matches subject and sender)")
    var query: String

    @Option(name: .long, help: "Filter to specific account name")
    var account: String?

    @Option(name: .long, help: "Mailbox to search in (default: inbox)")
    var mailbox: String?

    @Option(name: .long, help: "Max results to return (default: 20)")
    var limit: Int = 20

    @Option(name: .long, help: "Fields to search: subject, sender, body, all (default: all)")
    var searchIn: String = "all"

    @Option(name: .long, help: "Number of results to skip for pagination")
    var offset: Int?

    func run() async throws {
        MailBridge.search(query: query, account: account, mailbox: mailbox, limit: limit, searchIn: searchIn, offset: offset)
    }
}

struct MailMessage: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "mail-message",
        abstract: "Get full message content by message ID."
    )

    @Option(name: .long, help: "Message ID (from mail-search or mail-unread)")
    var id: String

    @Option(name: .long, help: "Mail account name from the search/list result")
    var account: String?

    @Option(name: .long, help: "Mailbox name/path from the search/list result")
    var mailbox: String?

    @Option(name: .long, help: "Max body characters to return (default: 4000, 0 = unlimited)")
    var maxBodyLength: Int = 4000

    func run() async throws {
        MailBridge.readMessage(messageId: id, maxBodyLength: maxBodyLength, account: account, mailbox: mailbox)
    }
}

struct MailFlagged: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "mail-flagged",
        abstract: "List flagged messages across all accounts."
    )

    @Option(name: .long, help: "Max results to return (default: 20)")
    var limit: Int = 20

    @Option(name: .long, help: "Number of results to skip for pagination")
    var offset: Int?

    func run() async throws {
        MailBridge.flagged(limit: limit, offset: offset)
    }
}

struct MailCreateDraft: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "mail-create-draft",
        abstract: "Create a draft email in Mail.app."
    )

    @Option(name: .long, help: "Recipient email addresses (comma-separated)")
    var to: String

    @Option(name: .long, help: "CC email addresses (comma-separated)")
    var cc: String?

    @Option(name: .long, help: "BCC email addresses (comma-separated)")
    var bcc: String?

    @Option(name: .long, help: "Email subject")
    var subject: String

    @Option(name: .long, help: "Email body text")
    var body: String

    @Option(name: .long, help: "Sender email address (from mail-accounts)")
    var account: String?

    func run() async throws {
        let toAddrs = to.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespaces) }
        let ccAddrs = cc?.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespaces) }
        let bccAddrs = bcc?.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespaces) }
        MailBridge.createDraft(to: toAddrs, cc: ccAddrs, bcc: bccAddrs, subject: subject, body: body, account: account)
    }
}

struct MailSaveAttachment: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "mail-save-attachment",
        abstract: "Save a message attachment to disk."
    )

    @Option(name: .long, help: "Message ID (from mail-search or mail-unread)")
    var id: String

    @Option(name: .long, help: "Mail account name from the search/list result")
    var account: String?

    @Option(name: .long, help: "Mailbox name/path from the search/list result")
    var mailbox: String?

    @Option(name: .long, help: "Attachment index (0-based, from mail-message output)")
    var index: Int

    @Option(name: .long, help: "Output directory")
    var path: String = "/tmp/orchard-mcp-attachments"

    func run() async throws {
        MailBridge.saveAttachment(messageId: id, index: index, outputDir: path, account: account, mailbox: mailbox)
    }
}

// MARK: - Reminders Subcommands

struct ReminderLists: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "reminder-lists",
        abstract: "List all reminder lists with account and color."
    )

    func run() async throws {
        await RemindersBridge.listLists()
    }
}

struct RemindersCmd: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "reminders",
        abstract: "List reminders with optional filters."
    )

    @Option(name: .long, help: "Filter to a specific list name")
    var list: String?

    @Option(name: .long, help: "Filter: incomplete (default), completed, overdue, dueToday, all")
    var filter: String = "incomplete"

    @Option(name: .long, help: "Max reminders to return (default: 50)")
    var limit: Int = 50

    func run() async throws {
        await RemindersBridge.listReminders(listName: list, filter: filter, limit: limit)
    }
}

struct RemindersToday: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "reminders-today",
        abstract: "Incomplete reminders due today plus overdue across all lists."
    )

    func run() async throws {
        await RemindersBridge.today()
    }
}

struct ReminderCreateList: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "reminder-create-list",
        abstract: "Create a new reminder list."
    )

    @Option(name: .long, help: "Name for the new list")
    var name: String

    func run() async throws {
        await RemindersBridge.createList(name: name)
    }
}

struct ReminderCreate: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "reminder-create",
        abstract: "Create a new reminder in a list."
    )

    @Option(name: .long, help: "List name to add the reminder to")
    var list: String

    @Option(name: .long, help: "Reminder title")
    var title: String

    @Option(name: .long, help: "Due date (ISO 8601, e.g. 2026-02-18 or 2026-02-18T10:00:00Z)")
    var due: String?

    @Option(name: .long, help: "Priority: 0=none, 1=high, 5=medium, 9=low (default: 0)")
    var priority: Int = 0

    @Option(name: .long, help: "Notes for the reminder")
    var notes: String?

    func run() async throws {
        await RemindersBridge.createReminder(listName: list, title: title, dueDate: due, priority: priority, notes: notes)
    }
}

struct ReminderComplete: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "reminder-complete",
        abstract: "Mark a reminder as completed."
    )

    @Option(name: .long, help: "Reminder ID (from reminders command output)")
    var id: String

    func run() async throws {
        await RemindersBridge.completeReminder(id: id)
    }
}

struct ReminderDelete: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "reminder-delete",
        abstract: "Delete a reminder."
    )

    @Option(name: .long, help: "Reminder ID (from reminders command output)")
    var id: String

    func run() async throws {
        await RemindersBridge.deleteReminder(id: id)
    }
}

struct ReminderDeleteList: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "reminder-delete-list",
        abstract: "Delete a reminder list."
    )

    @Option(name: .long, help: "List ID (from reminder-lists command output)")
    var id: String

    @Flag(name: .long, help: "Delete even if the list has reminders")
    var force: Bool = false

    func run() async throws {
        await RemindersBridge.deleteList(id: id, force: force)
    }
}

// MARK: - Files & Folders Subcommands

struct FileList: ParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "file-list",
        abstract: "List directory contents with metadata."
    )

    @Option(name: .long, help: "Directory path (relative to ~ or absolute)")
    var path: String = "."

    @Flag(name: .long, help: "List recursively")
    var recursive: Bool = false

    @Option(name: .long, help: "Max recursion depth (default: 3)")
    var depth: Int = 3

    func run() throws {
        FilesBridge.list(path: path, recursive: recursive, depth: depth)
    }
}

struct FileInfo: ParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "file-info",
        abstract: "Get detailed file or folder metadata."
    )

    @Option(name: .long, help: "File path (relative to ~ or absolute)")
    var path: String

    func run() throws {
        FilesBridge.info(path: path)
    }
}

struct FileSearchCmd: ParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "file-search",
        abstract: "Search files using Spotlight."
    )

    @Option(name: .long, help: "Search query (Spotlight syntax)")
    var query: String

    @Option(name: .long, help: "Filter by kind: folder, image, pdf, document, audio, video, presentation, spreadsheet")
    var kind: String?

    @Option(name: .long, help: "Search scope directory (relative to ~ or absolute)")
    var scope: String?

    func run() throws {
        FilesBridge.search(query: query, kind: kind, scope: scope)
    }
}

struct FileRead: ParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "file-read",
        abstract: "Read and extract text from a file."
    )

    @Option(name: .long, help: "File path (relative to ~ or absolute)")
    var path: String

    func run() throws {
        FilesBridge.read(path: path)
    }
}

struct FileMove: ParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "file-move",
        abstract: "Move or rename files and folders."
    )

    @Option(name: .long, help: "JSON array of {\"source\": \"...\", \"destination\": \"...\"} pairs")
    var items: String

    func run() throws {
        FilesBridge.move(itemsJSON: items)
    }
}

struct FileCopy: ParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "file-copy",
        abstract: "Copy a file or folder."
    )

    @Option(name: .long, help: "Source path")
    var source: String

    @Option(name: .long, help: "Destination path")
    var dest: String

    func run() throws {
        FilesBridge.copy(source: source, destination: dest)
    }
}

struct FileCreateFolder: ParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "file-create-folder",
        abstract: "Create a directory with intermediate directories."
    )

    @Option(name: .long, help: "Directory path to create")
    var path: String

    func run() throws {
        FilesBridge.createFolder(path: path)
    }
}

struct FileTrash: ParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "file-trash",
        abstract: "Move a file or folder to Trash."
    )

    @Option(name: .long, help: "File or folder path to trash")
    var path: String

    func run() throws {
        FilesBridge.trash(path: path)
    }
}

// MARK: - Doctor

struct Doctor: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        abstract: "Check permissions and list accessible resources."
    )

    func run() async throws {
        await DoctorBridge.run()
    }
}

// MARK: - Numbers Subcommands

struct NumbersSearch: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "numbers-search", abstract: "Search for Numbers spreadsheets using Spotlight.")
    @Option(name: .long, help: "Search query") var query: String
    @Option(name: .long, help: "Max results (default: 20)") var limit: Int = 20
    func run() throws { NumbersBridge.search(query: query, limit: limit) }
}

struct NumbersRead: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "numbers-read", abstract: "Read cell data from a Numbers spreadsheet as JSON.")
    @Option(name: .long, help: "Path to .numbers file") var file: String
    @Option(name: .long, help: "Sheet name (default: first sheet)") var sheet: String?
    @Option(name: .long, help: "Table name (default: first table)") var table: String?
    @Option(name: .long, help: "Cell range in A1 notation (e.g. A1:C10)") var range: String?
    func run() throws { NumbersBridge.read(file: file, sheet: sheet, table: table, range: range) }
}

struct NumbersWrite: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "numbers-write", abstract: "Write data to cells in a Numbers spreadsheet.")
    @Option(name: .long, help: "Path to .numbers file") var file: String
    @Option(name: .long, help: "Sheet name (default: first sheet)") var sheet: String?
    @Option(name: .long, help: "Table name (default: first table)") var table: String?
    @Option(name: .long, help: "Starting cell in A1 notation (e.g. A1)") var range: String?
    @Option(name: .long, help: "JSON array of arrays with cell data") var data: String
    func run() throws { NumbersBridge.write(file: file, sheet: sheet, table: table, range: range, dataJSON: data) }
}

struct NumbersCreate: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "numbers-create", abstract: "Create a new Numbers spreadsheet.")
    @Option(name: .long, help: "Output file path") var file: String
    @Option(name: .long, help: "Initial data as JSON array of arrays") var data: String?
    @Option(name: .long, help: "Template name") var template: String?
    func run() throws { NumbersBridge.create(file: file, dataJSON: data, template: template) }
}

struct NumbersListSheets: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "numbers-list-sheets", abstract: "List all sheets and tables in a Numbers document.")
    @Option(name: .long, help: "Path to .numbers file") var file: String
    func run() throws { NumbersBridge.listSheets(file: file) }
}

struct NumbersAddSheet: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "numbers-add-sheet", abstract: "Add a new sheet to a Numbers document.")
    @Option(name: .long, help: "Path to .numbers file") var file: String
    @Option(name: .long, help: "Name for the new sheet") var name: String
    func run() throws { NumbersBridge.addSheet(file: file, name: name) }
}

struct NumbersRemoveSheet: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "numbers-remove-sheet", abstract: "Remove a sheet from a Numbers document.")
    @Option(name: .long, help: "Path to .numbers file") var file: String
    @Option(name: .long, help: "Sheet name to remove") var name: String
    func run() throws { NumbersBridge.removeSheet(file: file, name: name) }
}

struct NumbersGetFormulas: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "numbers-get-formulas", abstract: "Read formulas from cells in a Numbers spreadsheet.")
    @Option(name: .long, help: "Path to .numbers file") var file: String
    @Option(name: .long, help: "Sheet name (default: first sheet)") var sheet: String?
    @Option(name: .long, help: "Table name (default: first table)") var table: String?
    @Option(name: .long, help: "Cell range in A1 notation") var range: String?
    func run() throws { NumbersBridge.getFormulas(file: file, sheet: sheet, table: table, range: range) }
}

struct NumbersExport: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "numbers-export", abstract: "Export a Numbers spreadsheet to CSV, PDF, or Excel.")
    @Option(name: .long, help: "Path to .numbers file") var file: String
    @Option(name: .long, help: "Export format: csv, pdf, xlsx") var format: String
    @Option(name: .long, help: "Output file path (default: same name with new extension)") var dest: String?
    func run() throws { NumbersBridge.export(file: file, format: format, dest: dest) }
}

struct NumbersInfo: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "numbers-info", abstract: "Get metadata about a Numbers spreadsheet.")
    @Option(name: .long, help: "Path to .numbers file") var file: String
    func run() throws { NumbersBridge.info(file: file) }
}

// MARK: - Pages Subcommands

struct PagesSearch: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "pages-search", abstract: "Search for Pages documents using Spotlight.")
    @Option(name: .long, help: "Search query") var query: String
    @Option(name: .long, help: "Max results (default: 20)") var limit: Int = 20
    func run() throws { PagesBridge.search(query: query, limit: limit) }
}

struct PagesRead: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "pages-read", abstract: "Read body text from a Pages document.")
    @Option(name: .long, help: "Path to .pages file") var file: String
    func run() throws { PagesBridge.read(file: file) }
}

struct PagesWrite: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "pages-write", abstract: "Set the body text of a Pages document.")
    @Option(name: .long, help: "Path to .pages file") var file: String
    @Option(name: .long, help: "Text to write") var text: String
    func run() throws { PagesBridge.write(file: file, text: text) }
}

struct PagesCreate: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "pages-create", abstract: "Create a new Pages document.")
    @Option(name: .long, help: "Output file path") var file: String
    @Option(name: .long, help: "Initial body text") var text: String?
    @Option(name: .long, help: "Template name") var template: String?
    func run() throws { PagesBridge.create(file: file, text: text, template: template) }
}

struct PagesFindReplace: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "pages-find-replace", abstract: "Find and replace text in a Pages document.")
    @Option(name: .long, help: "Path to .pages file") var file: String
    @Option(name: .long, help: "Text to find") var find: String
    @Option(name: .long, help: "Replacement text") var replace: String
    @Flag(name: .long, help: "Replace all occurrences") var all: Bool = false
    func run() throws { PagesBridge.findReplace(file: file, find: find, replace: replace, all: all) }
}

struct PagesInsertTable: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "pages-insert-table", abstract: "Insert a table into a Pages document.")
    @Option(name: .long, help: "Path to .pages file") var file: String
    @Option(name: .long, help: "Table data as JSON array of arrays") var data: String
    func run() throws { PagesBridge.insertTable(file: file, dataJSON: data) }
}

struct PagesListSections: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "pages-list-sections", abstract: "List sections in a Pages document with previews.")
    @Option(name: .long, help: "Path to .pages file") var file: String
    func run() throws { PagesBridge.listSections(file: file) }
}

struct PagesExport: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "pages-export", abstract: "Export a Pages document to PDF, Word, TXT, or EPUB.")
    @Option(name: .long, help: "Path to .pages file") var file: String
    @Option(name: .long, help: "Export format: pdf, docx, txt, epub") var format: String
    @Option(name: .long, help: "Output file path (default: same name with new extension)") var dest: String?
    func run() throws { PagesBridge.export(file: file, format: format, dest: dest) }
}

struct PagesInfo: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "pages-info", abstract: "Get metadata about a Pages document.")
    @Option(name: .long, help: "Path to .pages file") var file: String
    func run() throws { PagesBridge.info(file: file) }
}

// MARK: - Keynote Subcommands

struct KeynoteSearch: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "keynote-search", abstract: "Search for Keynote presentations using Spotlight.")
    @Option(name: .long, help: "Search query") var query: String
    @Option(name: .long, help: "Max results (default: 20)") var limit: Int = 20
    func run() throws { KeynoteBridge.search(query: query, limit: limit) }
}

struct KeynoteRead: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "keynote-read", abstract: "Read slide content from a Keynote presentation.")
    @Option(name: .long, help: "Path to .key file") var file: String
    @Option(name: .long, help: "Slide index (1-based, omit to read all)") var slide: Int?
    func run() throws { KeynoteBridge.read(file: file, slideIndex: slide) }
}

struct KeynoteCreate: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "keynote-create", abstract: "Create a new Keynote presentation.")
    @Option(name: .long, help: "Output file path") var file: String
    @Option(name: .long, help: "Theme name") var theme: String?
    func run() throws { KeynoteBridge.create(file: file, theme: theme) }
}

struct KeynoteAddSlide: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "keynote-add-slide", abstract: "Add a slide to a Keynote presentation.")
    @Option(name: .long, help: "Path to .key file") var file: String
    @Option(name: .long, help: "Slide layout name") var layout: String?
    @Option(name: .long, help: "Slide title") var title: String?
    @Option(name: .long, help: "Slide body text") var body: String?
    @Option(name: .long, help: "Presenter notes") var notes: String?
    @Option(name: .long, help: "Insert after this slide index (1-based)") var position: Int?
    func run() throws { KeynoteBridge.addSlide(file: file, layout: layout, title: title, body: body, notes: notes, position: position) }
}

struct KeynoteEditSlide: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "keynote-edit-slide", abstract: "Edit an existing slide in a Keynote presentation.")
    @Option(name: .long, help: "Path to .key file") var file: String
    @Option(name: .long, help: "Slide index (1-based)") var slide: Int
    @Option(name: .long, help: "New title text") var title: String?
    @Option(name: .long, help: "New body text") var body: String?
    @Option(name: .long, help: "New presenter notes") var notes: String?
    func run() throws { KeynoteBridge.editSlide(file: file, slideIndex: slide, title: title, body: body, notes: notes) }
}

struct KeynoteRemoveSlide: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "keynote-remove-slide", abstract: "Remove a slide from a Keynote presentation.")
    @Option(name: .long, help: "Path to .key file") var file: String
    @Option(name: .long, help: "Slide index to remove (1-based)") var slide: Int
    func run() throws { KeynoteBridge.removeSlide(file: file, slideIndex: slide) }
}

struct KeynoteReorderSlides: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "keynote-reorder-slides", abstract: "Move a slide to a new position.")
    @Option(name: .long, help: "Path to .key file") var file: String
    @Option(name: .long, help: "Current slide index (1-based)") var from: Int
    @Option(name: .long, help: "Target slide index (1-based)") var to: Int
    func run() throws { KeynoteBridge.reorderSlides(file: file, from: from, to: to) }
}

struct KeynoteListSlides: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "keynote-list-slides", abstract: "List all slides in a Keynote presentation.")
    @Option(name: .long, help: "Path to .key file") var file: String
    func run() throws { KeynoteBridge.read(file: file, slideIndex: nil) }
}

struct KeynoteListThemes: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "keynote-list-themes", abstract: "List all available Keynote themes.")
    func run() throws { KeynoteBridge.listThemes() }
}

struct KeynoteExport: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "keynote-export", abstract: "Export a Keynote presentation to PDF, PowerPoint, PNG, or JPEG.")
    @Option(name: .long, help: "Path to .key file") var file: String
    @Option(name: .long, help: "Export format: pdf, pptx, png, jpeg") var format: String
    @Option(name: .long, help: "Output file or directory path") var dest: String?
    @Option(name: .long, help: "Export only this slide index (1-based, for image formats)") var slide: Int?
    func run() throws { KeynoteBridge.export(file: file, format: format, dest: dest, slideIndex: slide) }
}

struct KeynoteInfo: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "keynote-info", abstract: "Get metadata about a Keynote presentation.")
    @Option(name: .long, help: "Path to .key file") var file: String
    func run() throws { KeynoteBridge.info(file: file) }
}

// MARK: - Notes Subcommands

struct NotesListFolders: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "notes-folders", abstract: "List all Notes folders grouped by account.")
    func run() throws { NotesBridge.listFolders() }
}

struct NotesListNotes: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "notes-list", abstract: "List notes, optionally filtered by folder and account.")
    @Option(name: .long, help: "Folder name") var folder: String?
    @Option(name: .long, help: "Account name (required when folder is ambiguous)") var account: String?
    @Option(name: .long, help: "Max results (default: 50)") var limit: Int = 50
    func run() throws { NotesBridge.listNotes(folder: folder, account: account, limit: limit) }
}

struct NotesSearchCmd: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "notes-search", abstract: "Search notes by title, body, or both.")
    @Option(name: .long, help: "Search query") var query: String
    @Option(name: .long, help: "Fields to search: title, body, all (default: all)") var searchIn: String = "all"
    @Option(name: .long, help: "Max results (default: 20)") var limit: Int = 20
    func run() throws { NotesBridge.search(query: query, limit: limit, searchIn: searchIn) }
}

struct NotesRead: ParsableCommand {
    static let configuration = CommandConfiguration(commandName: "notes-read", abstract: "Read a note's full content by ID.")
    @Option(name: .long, help: "Note ID (from notes-list or notes-search)") var id: String
    @Option(name: .long, help: "Max body characters (default: 8000, 0 = unlimited)") var maxBodyLength: Int = 8000
    func run() throws { NotesBridge.readNote(id: id, maxBodyLength: maxBodyLength) }
}

// MARK: - Contacts Subcommands

struct ContactsListGroups: AsyncParsableCommand {
    static let configuration = CommandConfiguration(commandName: "contacts-groups", abstract: "List all contact groups with member counts.")
    func run() async throws { await ContactsBridge.listGroups() }
}

struct ContactsSearchCmd: AsyncParsableCommand {
    static let configuration = CommandConfiguration(commandName: "contacts-search", abstract: "Search contacts by name, email, or phone.")
    @Option(name: .long, help: "Search query") var query: String
    @Option(name: .long, help: "Max results (default: 20)") var limit: Int = 20
    func run() async throws { await ContactsBridge.search(query: query, limit: limit) }
}

struct ContactsRead: AsyncParsableCommand {
    static let configuration = CommandConfiguration(commandName: "contacts-read", abstract: "Read a contact's full details by ID.")
    @Option(name: .long, help: "Contact ID (from contacts-search)") var id: String
    func run() async throws { await ContactsBridge.readContact(id: id) }
}
