import Cocoa
import ApplicationServices
import IOKit.hid

// Skeleton only:
// - Intended to run as a separate macOS helper app / sidecar process.
// - Detects modifier-only down/up transitions via CGEventTap.
// - Detects Fn availability / keyboard metadata via IOHIDManager.
// - Emits newline-delimited JSON events to stdout for the Tauri host.

enum HotkeyState: String, Codable {
  case down
  case up
}

struct HotkeyEvent: Codable {
  let event: String
  let key: String
  let state: HotkeyState
}

struct CapabilityEvent: Codable {
  let event: String
  let platform: String
  let supportsFn: Bool
  let supportsModifierOnly: Bool
  let requiresAccessibilityPermission: Bool
}

struct StatusEvent: Codable {
  let event: String
  let state: String
  let message: String?
}

struct ErrorEvent: Codable {
  let event: String
  let code: String
  let message: String
}

final class HotkeyMonitor {
  private let shouldPromptForAccessibility: Bool
  private var eventTap: CFMachPort?
  private var runLoopSource: CFRunLoopSource?
  private var modifierKeysDown = Set<String>()
  private let keyCodeMap: [Int64: (name: String, flag: CGEventFlags)] = [
    55: ("left_command", .maskCommand),
    54: ("right_command", .maskCommand),
    58: ("left_alt", .maskAlternate),
    61: ("right_alt", .maskAlternate),
    59: ("left_control", .maskControl),
    56: ("left_shift", .maskShift),
    60: ("right_shift", .maskShift),
    63: ("fn", .maskSecondaryFn),
  ]

  init(shouldPromptForAccessibility: Bool) {
    self.shouldPromptForAccessibility = shouldPromptForAccessibility
  }

  func start() {
    emitStatus(state: "starting")
    emitCapability()
    let trusted = checkAccessibilityTrust(prompt: shouldPromptForAccessibility)
    if !trusted {
      emitStatus(
        state: "permission_required",
        message: "Accessibility permission is required for modifier monitoring."
      )
    }
    setupEventTap()
    setupHIDObservation()
    RunLoop.current.run()
  }

  private func checkAccessibilityTrust(prompt: Bool) -> Bool {
    let options = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: prompt] as CFDictionary
    return AXIsProcessTrustedWithOptions(options)
  }

  private func emitCapability() {
    emit(
      event: CapabilityEvent(
        event: "capability",
        platform: "macos",
        supportsFn: true,
        supportsModifierOnly: true,
        requiresAccessibilityPermission: true
      )
    )
  }

  private func setupEventTap() {
    let mask =
      (1 << CGEventType.flagsChanged.rawValue) |
      (1 << CGEventType.keyDown.rawValue) |
      (1 << CGEventType.keyUp.rawValue)

    let callback: CGEventTapCallBack = { _, type, event, userInfo in
      guard let userInfo else { return Unmanaged.passRetained(event) }
      let monitor = Unmanaged<HotkeyMonitor>.fromOpaque(userInfo).takeUnretainedValue()
      monitor.handleEvent(type: type, event: event)
      return Unmanaged.passRetained(event)
    }

    let tap = CGEvent.tapCreate(
      tap: .cgSessionEventTap,
      place: .headInsertEventTap,
      options: .defaultTap,
      eventsOfInterest: CGEventMask(mask),
      callback: callback,
      userInfo: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
    )

    guard let tap else {
      emit(event: ErrorEvent(
        event: "error",
        code: "failed_to_create_event_tap",
        message: "Unable to create CGEvent tap."
      ))
      emitStatus(state: "degraded", message: "CGEvent tap unavailable.")
      return
    }

    eventTap = tap
    runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)

    if let runLoopSource {
      CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
      CGEvent.tapEnable(tap: tap, enable: true)
      emitStatus(state: "ready")
    }
  }

  private func setupHIDObservation() {
    // Planned:
    // - Enumerate keyboards via IOHIDManager
    // - Determine whether the active keyboard exposes an Apple Fn key
    // - Emit capability metadata to the Tauri host
    let manager = IOHIDManagerCreate(kCFAllocatorDefault, IOOptionBits(kIOHIDOptionsTypeNone))
    let deviceMatching = [kIOHIDDeviceUsagePageKey: kHIDPage_GenericDesktop, kIOHIDDeviceUsageKey: kHIDUsage_GD_Keyboard] as CFDictionary
    IOHIDManagerSetDeviceMatching(manager, deviceMatching)
    IOHIDManagerScheduleWithRunLoop(manager, CFRunLoopGetCurrent(), CFRunLoopMode.commonModes.rawValue)
    IOHIDManagerOpen(manager, IOOptionBits(kIOHIDOptionsTypeNone))
  }

  private func handleEvent(type: CGEventType, event: CGEvent) {
    guard type == .flagsChanged else { return }
    let keyCode = event.getIntegerValueField(.keyboardEventKeycode)
    guard let mapped = keyCodeMap[keyCode] else { return }
    emitModifierIfChanged(name: mapped.name, active: event.flags.contains(mapped.flag))
  }

  private func emitModifierIfChanged(name: String, active: Bool) {
    if active {
      if modifierKeysDown.insert(name).inserted {
        emit(event: HotkeyEvent(event: "modifier", key: name, state: .down))
      }
    } else if modifierKeysDown.remove(name) != nil {
      emit(event: HotkeyEvent(event: "modifier", key: name, state: .up))
    }
  }

  private func emitStatus(state: String, message: String? = nil) {
    emit(event: StatusEvent(event: "status", state: state, message: message))
  }

  private func emit<T: Encodable>(event: T) {
    let encoder = JSONEncoder()
    guard let data = try? encoder.encode(event), let line = String(data: data, encoding: .utf8) else { return }
    print(line)
    fflush(stdout)
  }
}

let shouldPromptForAccessibility = CommandLine.arguments.contains("--prompt-accessibility")
HotkeyMonitor(shouldPromptForAccessibility: shouldPromptForAccessibility).start()
