import AppKit
import Carbon
import Foundation

enum LauncherHotkeyValidationError: Equatable {
    case modifierRequired
    case unsupportedKey
    case reservedBySystem
    case duplicate

    var messageKey: String {
        switch self {
        case .modifierRequired:
            return "hotkeys.error.modifierRequired"
        case .unsupportedKey:
            return "hotkeys.error.unsupportedKey"
        case .reservedBySystem:
            return "hotkeys.error.reserved"
        case .duplicate:
            return "hotkeys.error.duplicate"
        }
    }
}

enum LauncherHotkeyCustomizationResult: Equatable {
    case saved
    case restored
    case locked
    case invalid(LauncherHotkeyValidationError)
    case registrationFailed(OSStatus)

    var messageKey: String {
        switch self {
        case .saved:
            return "hotkeys.saved"
        case .restored:
            return "hotkeys.restored"
        case .locked:
            return "hotkeys.proRequired"
        case .invalid(let error):
            return error.messageKey
        case .registrationFailed:
            return "hotkeys.error.registrationFailed"
        }
    }
}

enum LauncherHotkeySettings {
    private static let namespace = "customHotkeys.v1"
    private static let allowedModifierMask = UInt32(
        controlKey | optionKey | shiftKey | cmdKey | kEventKeyModifierFnMask
    )
    private static let functionKeyCodes: Set<Int> = [
        kVK_F1, kVK_F2, kVK_F3, kVK_F4, kVK_F5, kVK_F6,
        kVK_F7, kVK_F8, kVK_F9, kVK_F10, kVK_F11, kVK_F12
    ]

    static func defaultHotkey(for kind: LauncherHotkeyKind) -> LauncherHotkey {
        kind.hotkey
    }

    static func effectiveHotkey(for kind: LauncherHotkeyKind) -> LauncherHotkey {
        guard ProEntitlementPolicy.isUnlocked(.customHotkeys),
              let saved = savedHotkey(for: kind),
              structuralValidationError(for: saved) == nil,
              !duplicatesOtherEffectiveHotkey(saved, kind: kind)
        else {
            return defaultHotkey(for: kind)
        }
        return normalized(saved)
    }

    static func hasCustomHotkey(for kind: LauncherHotkeyKind) -> Bool {
        savedHotkey(for: kind) != nil
    }

    static func savedHotkey(for kind: LauncherHotkeyKind) -> LauncherHotkey? {
        let defaults = UserDefaults.standard
        guard defaults.object(forKey: keyCodeKey(for: kind)) != nil,
              defaults.object(forKey: modifiersKey(for: kind)) != nil
        else {
            return nil
        }
        let keyCode = defaults.integer(forKey: keyCodeKey(for: kind))
        let modifiers = defaults.integer(forKey: modifiersKey(for: kind))
        guard keyCode >= 0, modifiers >= 0 else { return nil }
        return LauncherHotkey(
            keyCode: UInt32(keyCode),
            modifiers: UInt32(modifiers) & allowedModifierMask
        )
    }

    static func save(_ hotkey: LauncherHotkey, for kind: LauncherHotkeyKind) {
        let defaults = UserDefaults.standard
        defaults.set(Int(hotkey.keyCode), forKey: keyCodeKey(for: kind))
        defaults.set(Int(hotkey.modifiers & allowedModifierMask), forKey: modifiersKey(for: kind))
        NotificationCenter.default.post(name: .tagLauncherHotkeyRegistrationChanged, object: nil)
    }

    static func clearCustomHotkey(for kind: LauncherHotkeyKind) {
        let defaults = UserDefaults.standard
        defaults.removeObject(forKey: keyCodeKey(for: kind))
        defaults.removeObject(forKey: modifiersKey(for: kind))
        NotificationCenter.default.post(name: .tagLauncherHotkeyRegistrationChanged, object: nil)
    }

    static func candidate(from event: NSEvent) -> LauncherHotkey {
        LauncherHotkey(
            keyCode: UInt32(event.keyCode),
            modifiers: carbonModifiers(from: event.modifierFlags)
        )
    }

    static func validationError(for hotkey: LauncherHotkey, kind: LauncherHotkeyKind) -> LauncherHotkeyValidationError? {
        let normalizedHotkey = normalized(hotkey)
        if let structuralError = structuralValidationError(for: normalizedHotkey) {
            return structuralError
        }
        if duplicatesOtherEffectiveHotkey(normalizedHotkey, kind: kind) {
            return .duplicate
        }
        return nil
    }

    private static func normalized(_ hotkey: LauncherHotkey) -> LauncherHotkey {
        LauncherHotkey(
            keyCode: hotkey.keyCode,
            modifiers: hotkey.modifiers & allowedModifierMask
        )
    }

    private static func structuralValidationError(for hotkey: LauncherHotkey) -> LauncherHotkeyValidationError? {
        guard isSupportedKey(hotkey.keyCode) else {
            return .unsupportedKey
        }
        guard hasSupportedModifierCombination(hotkey) else {
            return .modifierRequired
        }
        guard !isReservedBySystem(hotkey) else {
            return .reservedBySystem
        }
        return nil
    }

    private static func duplicatesOtherEffectiveHotkey(_ hotkey: LauncherHotkey, kind: LauncherHotkeyKind) -> Bool {
        let otherKind: LauncherHotkeyKind = kind == .main ? .quickSearch : .main
        return hotkey == effectiveHotkeyIgnoringDuplicateCheck(for: otherKind)
    }

    private static func effectiveHotkeyIgnoringDuplicateCheck(for kind: LauncherHotkeyKind) -> LauncherHotkey {
        guard ProEntitlementPolicy.isUnlocked(.customHotkeys),
              let saved = savedHotkey(for: kind),
              structuralValidationError(for: saved) == nil
        else {
            return defaultHotkey(for: kind)
        }
        return normalized(saved)
    }

    private static func keyCodeKey(for kind: LauncherHotkeyKind) -> String {
        "\(namespace).\(kind.rawValue).keyCode"
    }

    private static func modifiersKey(for kind: LauncherHotkeyKind) -> String {
        "\(namespace).\(kind.rawValue).modifiers"
    }

    private static func carbonModifiers(from flags: NSEvent.ModifierFlags) -> UInt32 {
        let normalized = flags.intersection(.deviceIndependentFlagsMask)
        var modifiers: UInt32 = 0
        if normalized.contains(.control) {
            modifiers |= UInt32(controlKey)
        }
        if normalized.contains(.option) {
            modifiers |= UInt32(optionKey)
        }
        if normalized.contains(.shift) {
            modifiers |= UInt32(shiftKey)
        }
        if normalized.contains(.command) {
            modifiers |= UInt32(cmdKey)
        }
        if normalized.contains(.function) {
            modifiers |= UInt32(kEventKeyModifierFnMask)
        }
        return modifiers & allowedModifierMask
    }

    private static func hasSupportedModifierCombination(_ hotkey: LauncherHotkey) -> Bool {
        let modifiers = hotkey.modifiers & allowedModifierMask
        guard modifiers != 0 else { return false }
        if modifiers == UInt32(shiftKey) {
            return false
        }
        if modifiers == UInt32(kEventKeyModifierFnMask) {
            return hotkey.keyCode == UInt32(kVK_Space)
        }
        return true
    }

    private static func isSupportedKey(_ keyCode: UInt32) -> Bool {
        switch Int(keyCode) {
        case kVK_Space, kVK_Return, kVK_Tab, kVK_Delete:
            return true
        case kVK_LeftArrow, kVK_RightArrow, kVK_UpArrow, kVK_DownArrow:
            return true
        default:
            let key = Int(keyCode)
            return functionKeyCodes.contains(key) || LauncherHotkey.printableKeyCodes.contains(key)
        }
    }

    private static func isReservedBySystem(_ hotkey: LauncherHotkey) -> Bool {
        let key = Int(hotkey.keyCode)
        let modifiers = hotkey.modifiers & allowedModifierMask
        let hasCommand = modifiers & UInt32(cmdKey) != 0
        let hasOption = modifiers & UInt32(optionKey) != 0
        let hasControl = modifiers & UInt32(controlKey) != 0

        if key == kVK_Space, hasCommand {
            return true
        }
        if key == kVK_Tab, hasCommand {
            return true
        }
        if key == kVK_Escape, hasCommand && hasOption {
            return true
        }
        if [kVK_ANSI_Q, kVK_ANSI_W, kVK_ANSI_H, kVK_ANSI_M].contains(key), hasCommand {
            return true
        }
        if [kVK_LeftArrow, kVK_RightArrow, kVK_UpArrow, kVK_DownArrow].contains(key), hasControl {
            return true
        }
        return false
    }
}
