import AppKit
import CoreGraphics
import Darwin
import Foundation
import AlignerCore

final class CGEventTapTriggerProbe {
    enum ProbeError: Error, CustomStringConvertible {
        case eventTapUnavailable

        var description: String {
            switch self {
            case .eventTapUnavailable:
                return "CGEventTapCreate returned nil. Accessibility may be missing, or macOS may require a different trigger route."
            }
        }
    }

    private let shortcut: KeyboardShortcut
    private var eventTap: CFMachPort?
    private var runLoopSource: CFRunLoopSource?
    private(set) var capturedQuickSwitchCount = 0

    init(shortcut: KeyboardShortcut = TriggerDefaults.quickSwitchShortcut) {
        self.shortcut = shortcut
    }

    func start() throws {
        guard eventTap == nil else { return }

        let eventMask = CGEventMask(1 << CGEventType.keyDown.rawValue)
        guard let tap = CGEvent.tapCreate(
            tap: .cgSessionEventTap,
            place: .headInsertEventTap,
            options: .defaultTap,
            eventsOfInterest: eventMask,
            callback: Self.eventCallback,
            userInfo: Unmanaged.passUnretained(self).toOpaque()
        ) else {
            throw ProbeError.eventTapUnavailable
        }

        let source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
        CFRunLoopAddSource(CFRunLoopGetMain(), source, .commonModes)
        CGEvent.tapEnable(tap: tap, enable: true)

        eventTap = tap
        runLoopSource = source
    }

    func stop() {
        if let eventTap {
            CGEvent.tapEnable(tap: eventTap, enable: false)
        }
        if let runLoopSource {
            CFRunLoopRemoveSource(CFRunLoopGetMain(), runLoopSource, .commonModes)
        }

        runLoopSource = nil
        eventTap = nil
    }

    deinit {
        stop()
    }

    private func handle(event: CGEvent, type: CGEventType) -> Unmanaged<CGEvent>? {
        guard type == .keyDown else {
            return Unmanaged.passUnretained(event)
        }

        let receivedShortcut = KeyboardShortcut(
            keyCode: UInt16(event.getIntegerValueField(.keyboardEventKeycode)),
            modifiers: Self.shortcutModifiers(from: event.flags)
        )
        let decision = TriggerEventPolicy.decision(
            for: receivedShortcut,
            configuredQuickSwitch: shortcut,
            overlayVisible: false,
            consumeInitialQuickSwitch: true
        )

        switch decision {
        case .quickSwitch(let consumeEvent):
            capturedQuickSwitchCount += 1
            print("Round01 trigger probe captured Alt+Tab")
            fflush(stdout)
            return consumeEvent ? nil : Unmanaged.passUnretained(event)
        case .passThrough:
            return Unmanaged.passUnretained(event)
        }
    }

    private static let eventCallback: CGEventTapCallBack = { _, type, event, userInfo in
        guard let userInfo else {
            return Unmanaged.passUnretained(event)
        }

        let probe = Unmanaged<CGEventTapTriggerProbe>.fromOpaque(userInfo).takeUnretainedValue()
        return probe.handle(event: event, type: type)
    }

    private static func shortcutModifiers(from flags: CGEventFlags) -> ShortcutModifiers {
        var modifiers = ShortcutModifiers()

        if flags.contains(.maskAlternate) {
            modifiers.insert(.option)
        }
        if flags.contains(.maskCommand) {
            modifiers.insert(.command)
        }
        if flags.contains(.maskShift) {
            modifiers.insert(.shift)
        }
        if flags.contains(.maskControl) {
            modifiers.insert(.control)
        }

        return modifiers
    }
}

struct Round1TriggerProbeOptions {
    let enabled: Bool
    let quitAfter: TimeInterval?

    static func parse(arguments: [String]) -> Round1TriggerProbeOptions {
        Round1TriggerProbeOptions(
            enabled: arguments.contains("--round01-trigger-probe"),
            quitAfter: timeInterval(for: "--round01-trigger-probe-quit-after", in: arguments)
        )
    }

    private static func timeInterval(for key: String, in arguments: [String]) -> TimeInterval? {
        let prefix = "\(key)="
        guard let argument = arguments.first(where: { $0.hasPrefix(prefix) }) else {
            return nil
        }

        return TimeInterval(argument.dropFirst(prefix.count))
    }
}
