import AppKit
import ApplicationServices
@preconcurrency import CoreGraphics
import Foundation
import AlignerCore

@MainActor
final class CGEventTapTriggerService: TriggerServiceProtocol {
    enum ServiceError: 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."
            }
        }
    }

    var onQuickSwitch: (() -> Void)?
    var onStartFailure: ((ServiceError) -> Void)?
    private(set) var isRunning = false

    private let shortcut: KeyboardShortcut
    private let overlayVisibleProvider: () -> Bool
    private let accessibilityTrustedProvider: () -> Bool
    private let consumeInitialQuickSwitch: Bool
    private var eventTap: CFMachPort?
    private var runLoopSource: CFRunLoopSource?

    init(
        shortcut: KeyboardShortcut = TriggerDefaults.quickSwitchShortcut,
        overlayVisibleProvider: @escaping () -> Bool,
        accessibilityTrustedProvider: @escaping () -> Bool = {
            AXIsProcessTrusted()
        },
        consumeInitialQuickSwitch: Bool = true
    ) {
        self.shortcut = shortcut
        self.overlayVisibleProvider = overlayVisibleProvider
        self.accessibilityTrustedProvider = accessibilityTrustedProvider
        self.consumeInitialQuickSwitch = consumeInitialQuickSwitch
    }

    func start() {
        do {
            try startEventTap()
        } catch let error as ServiceError {
            onStartFailure?(error)
        } catch {
            onStartFailure?(.eventTapUnavailable)
        }
    }

    func startEventTap() 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 ServiceError.eventTapUnavailable
        }

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

        eventTap = tap
        runLoopSource = source
        isRunning = true
    }

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

        runLoopSource = nil
        eventTap = nil
        isRunning = false
    }

    deinit {
        MainActor.assumeIsolated {
            stop()
        }
    }

    private func handle(event: CGEvent, type: CGEventType) -> Unmanaged<CGEvent>? {
        switch type {
        case .tapDisabledByTimeout, .tapDisabledByUserInput:
            guard accessibilityTrustedProvider() else {
                Task { @MainActor [weak self] in
                    self?.stop()
                }
                return Unmanaged.passUnretained(event)
            }
            if let eventTap {
                CGEvent.tapEnable(tap: eventTap, enable: true)
            }
            return Unmanaged.passUnretained(event)
        case .keyDown:
            break
        default:
            return Unmanaged.passUnretained(event)
        }

        guard accessibilityTrustedProvider() else {
            Task { @MainActor [weak self] in
                self?.stop()
            }
            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: overlayVisibleProvider(),
            consumeInitialQuickSwitch: consumeInitialQuickSwitch
        )

        switch decision {
        case .quickSwitch(let consumeEvent):
            Task { @MainActor [weak self] in
                self?.onQuickSwitch?()
            }
            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 service = Unmanaged<CGEventTapTriggerService>.fromOpaque(userInfo).takeUnretainedValue()
        return MainActor.assumeIsolated {
            return service.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
    }
}
