import AppIntents
import Cocoa

// MARK: AppEntity

@available(macOS 14.0, *)
struct CommandEntity: AppEntity {
    let id: ID

    // Note: for macOS 26 we can move all the properties to @ComputedProperty.

    @Property(title: "Title")
    var title: String

    @Property(title: "Description")
    var description: String

    @Property(title: "Action")
    var action: String

    /// The underlying data model
    let command: Ghostty.Command

    /// A command identifier is a composite key based on the terminal and action.
    struct ID: Hashable {
        let terminalId: TerminalEntity.ID
        let actionKey: String
    }

    static var typeDisplayRepresentation: TypeDisplayRepresentation {
        TypeDisplayRepresentation(name: "Command Palette Command")
    }

    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(
            title: LocalizedStringResource(stringLiteral: command.title),
            subtitle: LocalizedStringResource(stringLiteral: command.description),
        )
    }

    static var defaultQuery = CommandQuery()

    init(_ command: Ghostty.Command, for terminal: TerminalEntity) {
        self.id = .init(terminalId: terminal.id, actionKey: command.actionKey)
        self.command = command
        self.title = command.title
        self.description = command.description
        self.action = command.action
    }
}

@available(macOS 14.0, *)
extension CommandEntity.ID: RawRepresentable {
    var rawValue: String {
        return "\(terminalId):\(actionKey)"
    }

    init?(rawValue: String) {
        let components = rawValue.split(separator: ":", maxSplits: 1)
        guard components.count == 2 else { return nil }

        guard let terminalId = TerminalEntity.ID(uuidString: String(components[0])) else {
            return nil
        }

        self.terminalId = terminalId
        self.actionKey = String(components[1])
    }
}

// Required by AppEntity
@available(macOS 14.0, *)
extension CommandEntity.ID: EntityIdentifierConvertible {
    static func entityIdentifier(for entityIdentifierString: String) -> CommandEntity.ID? {
        .init(rawValue: entityIdentifierString)
    }

    var entityIdentifierString: String {
        rawValue
    }
}

// MARK: EntityQuery

@available(macOS 14.0, *)
struct CommandQuery: EntityQuery {
    // Inject our terminal parameter from our command palette intent.
    @IntentParameterDependency<CommandPaletteIntent>(\.$terminal)
    var commandPaletteIntent

    @MainActor
    func entities(for identifiers: [CommandEntity.ID]) async throws -> [CommandEntity] {
        guard let appDelegate = NSApp.delegate as? AppDelegate else { return [] }
        let commands = appDelegate.ghostty.config.commandPaletteEntries

        // Extract unique terminal IDs to avoid fetching duplicates
        let terminalIds = Set(identifiers.map(\.terminalId))
        let terminals = try await TerminalEntity.defaultQuery.entities(for: Array(terminalIds))

        // Build a lookup from terminal ID to terminal entity
        let terminalMap: [TerminalEntity.ID: TerminalEntity] =
            terminals.reduce(into: [:]) { result, terminal in
                result[terminal.id] = terminal
            }

        // Map each identifier to its corresponding CommandEntity. If a command doesn't
        // exist it maps to nil and is removed via compactMap.
        return identifiers.compactMap { id in
            guard let terminal = terminalMap[id.terminalId],
                  let command = commands.first(where: { $0.actionKey == id.actionKey }) else {
                return nil
            }

            return CommandEntity(command, for: terminal)
        }
    }

    @MainActor
    func suggestedEntities() async throws -> [CommandEntity] {
        guard let appDelegate = NSApp.delegate as? AppDelegate,
              let terminal = commandPaletteIntent?.terminal else { return [] }
        return appDelegate.ghostty.config.commandPaletteEntries.map { CommandEntity($0, for: terminal) }
    }
}
