import AppKit
import Carbon

// AppKit is unavailable to Catalyst targets. This native bundle uses only
// public AppKit/Carbon APIs and communicates with the client through Foundation.
@MainActor
@objc(SybilMacQuickQuestionPlugin)
final class MacQuickQuestionPlugin: NSObject, SybilQuickQuestionPanelBridging, NSWindowDelegate {
    private weak var delegate: (any SybilQuickQuestionPanelDelegate)?
    private var panel: QuickQuestionPanel?
    private var panelView: QuickQuestionPanelView?
    private var hotKey: EventHotKeyRef?
    private var eventHandler: EventHandlerRef?
    private var outsideClickMonitor: Any?
    private var hotKeyState = SybilQuickQuestionHotKeyState()
    private var isDismissing = false
    private var shortcutAvailable = false
    private var windowlessActivity: NSObjectProtocol?
    private var latestState = SybilQuickQuestionPanelState()
    private var previousApplication: NSRunningApplication?

    required override init() {
        super.init()
    }

    var isVisible: Bool { panel?.isVisible == true }

    func start(delegate: any SybilQuickQuestionPanelDelegate) -> Int32 {
        self.delegate = delegate
        if windowlessActivity == nil {
            // Keep the hotkey service alive with no windows. This option does
            // not prevent sleep/App Nap or intercept an explicit Quit command.
            windowlessActivity = ProcessInfo.processInfo.beginActivity(
                options: .automaticTerminationDisabled,
                reason: "Quick Question global shortcut"
            )
        }
        guard eventHandler == nil else { return noErr }
        var eventTypes = [
            EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)),
            EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyReleased))
        ]
        let context = Unmanaged.passUnretained(self).toOpaque()
        let handlerStatus = InstallEventHandler(
            GetApplicationEventTarget(),
            { _, event, context in
                guard let event, let context else { return OSStatus(eventNotHandledErr) }
                var identifier = EventHotKeyID()
                let status = GetEventParameter(
                    event, EventParamName(kEventParamDirectObject), EventParamType(typeEventHotKeyID),
                    nil, MemoryLayout<EventHotKeyID>.size, nil, &identifier
                )
                guard status == noErr, identifier.signature == 0x53595151, identifier.id == 1 else {
                    return OSStatus(eventNotHandledErr)
                }
                let isKeyDown = GetEventKind(event) == UInt32(kEventHotKeyPressed)
                let plugin = Unmanaged<MacQuickQuestionPlugin>.fromOpaque(context).takeUnretainedValue()
                // Application event handlers run on the main event loop.
                MainActor.assumeIsolated {
                    if plugin.hotKeyState.shouldToggle(isKeyDown: isKeyDown) {
                        plugin.delegate?.quickQuestionToggleRequested()
                    }
                }
                return noErr
            },
            eventTypes.count, &eventTypes, context, &eventHandler
        )
        guard handlerStatus == noErr else { return handlerStatus }
        let status = RegisterEventHotKey(
            UInt32(kVK_Space), UInt32(optionKey),
            EventHotKeyID(signature: 0x53595151, id: 1),
            GetApplicationEventTarget(), 0, &hotKey
        )
        shortcutAvailable = status == noErr
        return status
    }

    func updateState(_ data: Data) {
        guard let state = try? JSONDecoder().decode(SybilQuickQuestionPanelState.self, from: data) else { return }
        // Updating a background request must not create a non-hosting window.
        // The controller creates/orders the panel only after UIKit is foreground.
        latestState = state
        panelView?.update(state, shortcutAvailable: shortcutAvailable)
        if isVisible { resizePanel() }
    }

    func show() {
        createPanelIfNeeded()
        guard let panel, let panelView else { return }
        panelView.update(latestState, shortcutAvailable: shortcutAvailable)
        let screen = NSScreen.screens.first { $0.frame.contains(NSEvent.mouseLocation) } ?? NSScreen.main
        let visibleFrame = screen?.visibleFrame ?? NSRect(x: 0, y: 0, width: 1280, height: 800)
        let width = min(680, visibleFrame.width - 48)
        panel.setContentSize(NSSize(width: width, height: 240))
        panelView.layoutSubtreeIfNeeded()
        let height = min(panelView.preferredHeight, visibleFrame.height - 64)
        let top = visibleFrame.maxY - min(visibleFrame.height * 0.18, 160)
        let y = max(visibleFrame.minY + 24, top - height)
        panel.setFrame(NSRect(x: visibleFrame.midX - width / 2, y: y, width: width, height: height), display: true)
        // The controller has verified UIKit foreground readiness. A
        // nonactivating panel can then take focus without raising a workspace.
        panel.makeKeyAndOrderFront(nil)
        panel.makeFirstResponder(panelView.promptView)
        panelView.preparePromptForPresentation()
        installOutsideClickMonitor()
    }

    func hide() {
        dismissPanel(restoringPreviousApplication: true)
    }

    func activateForQuickQuestion() {
        if let frontmost = NSWorkspace.shared.frontmostApplication,
           frontmost != NSRunningApplication.current {
            previousApplication = frontmost
        }
        // Do not order a window here. Catalyst must finish its own foreground
        // transition before the controller calls show().
        NSApp.activate()
    }

    func activateMainWindow() {
        // An explicit Open in Chat/Open Sybil action keeps focus in Sybil.
        previousApplication = nil
        NSApp.activate()
        NSApp.windows.first { $0 !== panel && $0.canBecomeMain }?.makeKeyAndOrderFront(nil)
    }

    private func dismissPanel(restoringPreviousApplication: Bool) {
        guard !isDismissing else { return }
        let wasVisible = isVisible
        isDismissing = true
        if wasVisible { panel?.orderOut(nil) }
        removeOutsideClickMonitor()
        isDismissing = false
        let applicationToRestore = previousApplication
        previousApplication = nil
        if restoringPreviousApplication, NSApp.isActive,
           let applicationToRestore, !applicationToRestore.isTerminated {
            NSApp.yieldActivation(to: applicationToRestore)
            applicationToRestore.activate(options: [])
        }
        if wasVisible { delegate?.quickQuestionPanelDismissed() }
    }

    func stop() {
        previousApplication = nil
        hide()
        removeOutsideClickMonitor()
        if let hotKey { UnregisterEventHotKey(hotKey) }
        if let eventHandler { RemoveEventHandler(eventHandler) }
        hotKey = nil
        eventHandler = nil
        hotKeyState = SybilQuickQuestionHotKeyState()
        if let windowlessActivity {
            ProcessInfo.processInfo.endActivity(windowlessActivity)
        }
        windowlessActivity = nil
        delegate = nil
    }

    func windowDidResignKey(_ notification: Notification) {
        // Focus is already moving to a window/app the user chose.
        dismissPanel(restoringPreviousApplication: false)
    }

    private func createPanelIfNeeded() {
        guard panel == nil else { return }
        let panel = QuickQuestionPanel(
            contentRect: NSRect(x: 0, y: 0, width: 680, height: 240),
            styleMask: [.borderless, .nonactivatingPanel], backing: .buffered, defer: false
        )
        panel.title = "Quick Question"
        panel.level = .floating
        panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .transient, .ignoresCycle]
        panel.isFloatingPanel = true
        panel.hidesOnDeactivate = false
        panel.becomesKeyOnlyIfNeeded = false
        panel.worksWhenModal = true
        panel.isReleasedWhenClosed = false
        panel.isOpaque = false
        panel.backgroundColor = .clear
        panel.hasShadow = true
        panel.isMovableByWindowBackground = true
        panel.appearance = NSAppearance(named: .darkAqua)
        panel.delegate = self
        panel.onDismiss = { [weak self] in self?.hide() }
        let view = QuickQuestionPanelView()
        view.delegate = delegate
        view.onDismiss = { [weak self] in self?.hide() }
        panel.contentView = view
        panelView = view
        self.panel = panel
    }

    private func resizePanel() {
        guard let panel, let panelView else { return }
        panelView.layoutSubtreeIfNeeded()
        let visibleFrame = panel.screen?.visibleFrame ?? panel.frame
        let height = min(panelView.preferredHeight, visibleFrame.height - 64)
        guard abs(height - panel.frame.height) > 1 else { return }
        var frame = panel.frame
        frame.origin.y = max(visibleFrame.minY + 24, frame.maxY - height)
        frame.size.height = height
        panel.setFrame(frame, display: true)
    }

    private func installOutsideClickMonitor() {
        guard outsideClickMonitor == nil else { return }
        // Mouse-only monitoring does not require Accessibility or Input
        // Monitoring permission. No global keyboard event tap is installed.
        outsideClickMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown]) { [weak self] _ in
            Task { @MainActor [weak self] in self?.hide() }
        }
    }

    private func removeOutsideClickMonitor() {
        if let outsideClickMonitor { NSEvent.removeMonitor(outsideClickMonitor) }
        outsideClickMonitor = nil
    }
}

@MainActor
private final class QuickQuestionPanel: NSPanel {
    var onDismiss: (() -> Void)?
    override var canBecomeKey: Bool { true }
    override var canBecomeMain: Bool { false }
    override func cancelOperation(_ sender: Any?) { onDismiss?() }
}
