import Cocoa
import ApplicationServices
import Carbon

protocol EventTapManagerDelegate: AnyObject {
    func eventTapManager(_ manager: EventTapManager, didUpdateContext context: String, prefix: String, fullBuffer: String)
    func eventTapManagerDidClearSuggestions(_ manager: EventTapManager)
    func eventTapManager(_ manager: EventTapManager, didSelectSuggestionIndex index: Int)
    func eventTapManagerDidCycleSuggestion(_ manager: EventTapManager)
    func eventTapManagerDidCommitActiveSuggestion(_ manager: EventTapManager)
}

class EventTapManager {
    weak var delegate: EventTapManagerDelegate?
    
    // Tap plumbing is written on the tap thread and read by stop()/watchdog on
    // other threads — every access must go through tapLock.
    private let tapLock = NSLock()
    private var eventTap: CFMachPort?
    private var runLoopSource: CFRunLoopSource?
    private var tapThread: Thread?
    private var tapRunLoop: CFRunLoop?
    /// Incremented on every start()/stop() so a slow-starting tap thread can
    /// detect it has been superseded and tear its tap down instead of leaking
    /// an orphan enabled tap.
    private var tapGeneration = 0
    
    // Typing tracking state. Shared between the tap thread, processingQueue and
    // the main thread (AppDelegate) — Swift String/Array are CoW and not safe
    // for concurrent mutation, so all access goes through typingStateLock.
    private let typingStateLock = NSLock()
    private var _recentWords: [String] = []
    private var _currentWordPrefix: String = ""
    private var _slidingBuffer: String = ""
    private var _isSuggestionsOverlayVisible = false
    private var _isPlaceholderModeActive = false
    private var _isAIPromptMode = false
    private var _lastKeyPressTime: Date = Date.distantPast
    private var _lastActionWasSuggestionWithTrailingSpace = false
    
    var recentWords: [String] {
        get { typingStateLock.lock(); defer { typingStateLock.unlock() }; return _recentWords }
        set { typingStateLock.lock(); _recentWords = newValue; typingStateLock.unlock() }
    }
    var currentWordPrefix: String {
        get { typingStateLock.lock(); defer { typingStateLock.unlock() }; return _currentWordPrefix }
        set { typingStateLock.lock(); _currentWordPrefix = newValue; typingStateLock.unlock() }
    }
    var slidingBuffer: String {
        get { typingStateLock.lock(); defer { typingStateLock.unlock() }; return _slidingBuffer }
        set { typingStateLock.lock(); _slidingBuffer = newValue; typingStateLock.unlock() }
    }
    // Are suggestions active?
    var isSuggestionsOverlayVisible: Bool {
        get { typingStateLock.lock(); defer { typingStateLock.unlock() }; return _isSuggestionsOverlayVisible }
        set { typingStateLock.lock(); _isSuggestionsOverlayVisible = newValue; typingStateLock.unlock() }
    }
    // Is placeholder navigation mode active?
    var isPlaceholderModeActive: Bool {
        get { typingStateLock.lock(); defer { typingStateLock.unlock() }; return _isPlaceholderModeActive }
        set { typingStateLock.lock(); _isPlaceholderModeActive = newValue; typingStateLock.unlock() }
    }
    // Is AI Prompt mode active?
    var isAIPromptMode: Bool {
        get { typingStateLock.lock(); defer { typingStateLock.unlock() }; return _isAIPromptMode }
        set { typingStateLock.lock(); _isAIPromptMode = newValue; typingStateLock.unlock() }
    }
    // Last keypress timestamp to distinguish typing from arbitrary mouse clicks
    var lastKeyPressTime: Date {
        get { typingStateLock.lock(); defer { typingStateLock.unlock() }; return _lastKeyPressTime }
        set { typingStateLock.lock(); _lastKeyPressTime = newValue; typingStateLock.unlock() }
    }
    // Tracks if the last suggestion accepted was padded with a trailing space
    var lastActionWasSuggestionWithTrailingSpace: Bool {
        get { typingStateLock.lock(); defer { typingStateLock.unlock() }; return _lastActionWasSuggestionWithTrailingSpace }
        set { typingStateLock.lock(); _lastActionWasSuggestionWithTrailingSpace = newValue; typingStateLock.unlock() }
    }
    
    // DevOps Terminal Multiline commands clustering state
    private var terminalCommandBuffer: [String] = []
    private var lastTerminalCommandTimestamp: Date = Date.distantPast
    
    // Safety & Timeout watchdog state (tap thread + watchdog timer; guarded by tapLock)
    private var consecutiveTimeouts = 0
    private var isTapPermanentlyDisabled = false
    private var lastTapTimeoutAt: Date = Date.distantPast
    
    // Background serial queue for suggestion processing to protect the EventTap thread!
    private let processingQueue = DispatchQueue(label: "com.doubleslash.app.tapQueue", qos: .userInitiated)
    
    // Shortcut modifiers configuration (defaults to .control and .option)
    var shortcutModifiers: NSEvent.ModifierFlags = [.control, .option]
    
    // Timer to automatically capture repetitive phrases when the user pauses typing
    private var sessionDebounceTimer: Timer?
    
    // Watchdog timer to verify and re-enable the event tap if disabled silently by the system
    private var tapWatchdogTimer: Timer?
    
    init() {
        loadShortcutPreferences()
        
        NotificationCenter.default.addObserver(self, selector: #selector(handleTriggerAIQuickActions), name: Notification.Name("TriggerAIQuickActions"), object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(handleCancelAIPrompt), name: Notification.Name("CancelAIPrompt"), object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(handleCommitAIPrompt), name: Notification.Name("CommitAIPrompt"), object: nil)
    }
    
    @objc private func handleTriggerAIQuickActions() {
        isAIPromptMode = true
    }
    
    @objc private func handleCancelAIPrompt() {
        isAIPromptMode = false
    }
    
    @objc private func handleCommitAIPrompt() {
        isAIPromptMode = false
    }
    
    func loadShortcutPreferences() {
        let defaults = UserDefaults.standard
        
        let hasControl = defaults.object(forKey: "shortcutHasControl") as? Bool ?? true
        let hasOption = defaults.object(forKey: "shortcutHasOption") as? Bool ?? false
        let hasShift = defaults.object(forKey: "shortcutHasShift") as? Bool ?? false
        let hasCommand = defaults.object(forKey: "shortcutHasCommand") as? Bool ?? false
        
        var flags = NSEvent.ModifierFlags()
        if hasControl { flags.insert(.control) }
        if hasOption { flags.insert(.option) }
        if hasShift { flags.insert(.shift) }
        if hasCommand { flags.insert(.command) }
        
        if !hasControl && !hasOption && !hasShift && !hasCommand {
            flags.insert(.control)
        }
        
        shortcutModifiers = flags
    }
    
    func start() {
        guard PermissionsManager.isAccessibilityTrusted() else {
            print("Accessibility not trusted. Skipping event tap creation.")
            return
        }
        
        // Ensure any existing tap/thread is stopped first
        stop()
        
        tapLock.lock()
        tapGeneration += 1
        let generation = tapGeneration
        tapLock.unlock()
        
        let thread = Thread { [weak self] in
            guard let self = self else { return }
            
            let mask = CGEventMask(1 << CGEventType.keyDown.rawValue)
            let context = Unmanaged.passUnretained(self).toOpaque()
            
            guard let tap = CGEvent.tapCreate(
                tap: .cgSessionEventTap,
                place: .headInsertEventTap,
                options: .defaultTap,
                eventsOfInterest: mask,
                callback: eventTapCallback,
                userInfo: context
            ) else {
                print("Failed to create event tap.")
                self.showEventTapFailurePrompt()
                return
            }
            
            let source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
            let runLoop = CFRunLoopGetCurrent()
            
            // If stop()/start() superseded us while the tap was being created,
            // tear it down instead of leaking an orphan enabled tap.
            self.tapLock.lock()
            guard generation == self.tapGeneration else {
                self.tapLock.unlock()
                CGEvent.tapEnable(tap: tap, enable: false)
                CFMachPortInvalidate(tap)
                return
            }
            self.eventTap = tap
            self.tapRunLoop = runLoop
            self.runLoopSource = source
            self.tapLock.unlock()
            
            CFRunLoopAddSource(runLoop, source, .commonModes)
            CGEvent.tapEnable(tap: tap, enable: true)
            print("Event tap successfully started on dedicated background thread.")
            
            CFRunLoopRun()
        }
        
        thread.name = "com.doubleslash.app.EventTapThread"
        thread.qualityOfService = .userInteractive
        tapLock.lock()
        tapThread = thread
        tapLock.unlock()
        thread.start()
        
        startWatchdogTimer()
    }
    
    private func startWatchdogTimer() {
        DispatchQueue.main.async { [weak self] in
            self?.tapWatchdogTimer?.invalidate()
            self?.tapWatchdogTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: true) { [weak self] _ in
                guard let self = self else { return }
                self.tapLock.lock()
                let tap = self.eventTap
                let disabled = self.isTapPermanentlyDisabled
                let lastTimeout = self.lastTapTimeoutAt
                self.tapLock.unlock()
                guard !disabled, let tap = tap else { return }
                // Respect the 2s post-timeout cooldown owned by handleTimeout();
                // re-enabling early defeats the recovery pause and resets the
                // consecutive-timeout counter prematurely.
                guard Date().timeIntervalSince(lastTimeout) > 2.5 else { return }
                if !CGEvent.tapIsEnabled(tap: tap) {
                    print("⚠️ Watchdog: Event tap was disabled! Re-enabling...")
                    CGEvent.tapEnable(tap: tap, enable: true)
                }
            }
        }
    }
    
    private func showEventTapFailurePrompt() {
        DispatchQueue.main.async {
            let alert = NSAlert()
            alert.messageText = "Input Monitoring or Accessibility Permission Required"
            alert.informativeText = "Doubleslash was unable to start its global keyboard listener.\n\nThis usually means that the application lacks 'Input Monitoring' or 'Accessibility' permissions in System Settings.\n\nPlease open 'System Settings > Privacy & Security' and ensure Doubleslash is toggled ON under both 'Accessibility' and 'Input Monitoring', then restart the app."
            alert.alertStyle = .critical
            alert.addButton(withTitle: "Open Settings")
            alert.addButton(withTitle: "Cancel")
            
            let response = alert.runModal()
            if response == .alertFirstButtonReturn {
                if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") {
                    NSWorkspace.shared.open(url)
                }
            }
        }
    }
    
    func stop() {
        DispatchQueue.main.async { [weak self] in
            self?.tapWatchdogTimer?.invalidate()
            self?.tapWatchdogTimer = nil
        }
        tapLock.lock()
        tapGeneration += 1 // Invalidate any tap thread still starting up
        let tap = eventTap
        let source = runLoopSource
        let runLoop = tapRunLoop
        eventTap = nil
        runLoopSource = nil
        tapRunLoop = nil
        tapThread = nil
        tapLock.unlock()
        
        if let tap = tap {
            CGEvent.tapEnable(tap: tap, enable: false)
            CFMachPortInvalidate(tap)
        }
        if let source = source, let runLoop = runLoop {
            CFRunLoopRemoveSource(runLoop, source, .commonModes)
        }
        if let runLoop = runLoop {
            CFRunLoopStop(runLoop)
        }
    }
    
    // Safe timeout watchdog that handles macOS disablements without infinite re-arming loops
    func handleTimeout() {
        // Drop any in-flight capture transition so a hung strip cannot leave the
        // pipeline busy after the OS has already disabled the tap.
        DispatchQueue.main.async {
            CaptureRouterController.shared.cancelCapture()
        }

        tapLock.lock()
        lastTapTimeoutAt = Date()
        consecutiveTimeouts += 1
        let timeouts = consecutiveTimeouts
        tapLock.unlock()
        print("Event tap watchdog: Timeout occurred. Count: \(timeouts)")
        
        if timeouts >= 3 {
            print("Event tap watchdog: Disabling event tap permanently due to multiple timeouts to protect system input stream.")
            tapLock.lock()
            isTapPermanentlyDisabled = true
            tapLock.unlock()
            stop()
            
            // Non-blocking HUD — NSAlert.runModal would itself freeze interaction.
            DispatchQueue.main.async {
                CaptureRouterController.shared.cancelCapture()
                NotificationCenter.default.post(
                    name: Notification.Name("ShowHUDMessage"),
                    object: nil,
                    userInfo: [
                        "message": "Keyboard listener paused after timeouts — restart Doubleslash",
                        "systemImage": "exclamationmark.triangle"
                    ]
                )
            }
            return
        }
        
        // Cooldown: wait 2 seconds before re-enabling to let system recover, preventing infinite freezes
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in
            guard let self = self else { return }
            self.tapLock.lock()
            let disabled = self.isTapPermanentlyDisabled
            let tap = self.eventTap
            self.tapLock.unlock()
            guard !disabled, let tap = tap else { return }
            CGEvent.tapEnable(tap: tap, enable: true)
            print("Event tap watchdog: Re-enabled tap after 2s cooldown.")
        }
    }
    
    // Core keystroke processing logic - 100% thread-safe, sub-millisecond raw CGEvent pipeline
    func handleKeyEvent(cgEvent: CGEvent) -> Bool {
        consecutiveTimeouts = 0 // Reset watchdog counter upon any successful event
        lastKeyPressTime = Date()
        
        // Never intercept keyboard events while the user is typing in our own Settings/onboarding/capture windows.
        if OwnWindowKeyboardGuard.isOwnEditableWindowKey {
            return false
        }

        // Check secure input - NEVER keylog sensitive password entries
        if IsSecureEventInputEnabled() {
            clearContext()
            return false // Let event pass unchanged
        }

        if let frontmostApp = NSWorkspace.shared.frontmostApplication,
           let bundleID = frontmostApp.bundleIdentifier {
            if PrivacyFirewall.shared.isAppExcluded(bundleId: bundleID) {
                clearContext()
                return false
            }
        }

        // While capture overlay owns focus, pass keys through so AppKit delivers them
        // to our text view. During strip→show transition, SWALLOW keys so they do not
        // land in the host mid-strip — but return immediately (no AX/usleep here).
        if CaptureRouterController.shared.isCaptureModeActiveThreadSafe {
            return false
        }
        if CaptureRouterController.shared.isCaptureTransitionThreadSafe {
            return true
        }
        
        // Intercept Capture Router passive trigger detection
        if CaptureRouterController.shared.handleKeyEvent(cgEvent) {
            return true // Swallow key event
        }

        
        let keyCode = UInt16(cgEvent.getIntegerValueField(.keyboardEventKeycode))
        
        let cgFlags = cgEvent.flags
        let hasControl = cgFlags.contains(.maskControl)
        let hasOption = cgFlags.contains(.maskAlternate)
        let hasCommand = cgFlags.contains(.maskCommand)
        let hasShift = cgFlags.contains(.maskShift)
        
        // Ctrl+Shift+Space Hotkey Interceptor (Space is keycode 49)
        if hasControl && hasShift && !hasCommand && !hasOption && keyCode == 49 {
            DispatchQueue.main.async {
                NotificationCenter.default.post(name: Notification.Name("TriggerAIQuickActions"), object: nil)
            }
            return true // Swallow global hotkey
        }
        
        // Ctrl+Space Hotkey Interceptor (Space is keycode 49) - manual trigger for suggestions
        if hasControl && !hasShift && !hasCommand && !hasOption && keyCode == 49 {
            DispatchQueue.main.async {
                NotificationCenter.default.post(name: Notification.Name("TriggerManualSuggestions"), object: nil)
            }
            return true // Swallow global hotkey to avoid inserting spaces
        }
        
        // AI Prompt Mode Key redirection
        if isAIPromptMode {
            if keyCode == 36 { // Return
                DispatchQueue.main.async {
                    NotificationCenter.default.post(name: Notification.Name("CommitAIPrompt"), object: nil)
                }
                return true
            }
            if keyCode == 53 { // Escape
                DispatchQueue.main.async {
                    NotificationCenter.default.post(name: Notification.Name("CancelAIPrompt"), object: nil)
                }
                return true
            }
            if keyCode == 51 { // Backspace
                DispatchQueue.main.async {
                    NotificationCenter.default.post(name: Notification.Name("DeleteAIPromptChar"), object: nil)
                }
                return true
            }
            if keyCode == 123 { // Left Arrow
                DispatchQueue.main.async {
                    NotificationCenter.default.post(name: Notification.Name("CycleAIActionLeft"), object: nil)
                }
                return true
            }
            if keyCode == 124 { // Right Arrow
                DispatchQueue.main.async {
                    NotificationCenter.default.post(name: Notification.Name("CycleAIActionRight"), object: nil)
                }
                return true
            }
            if keyCode == 55 || keyCode == 56 || keyCode == 58 || keyCode == 59 || keyCode == 61 {
                return false // Let standard modifiers pass through
            }
            if let unicodeChars = getUnicodeString(from: cgEvent), !unicodeChars.isEmpty {
                DispatchQueue.main.async {
                    NotificationCenter.default.post(name: Notification.Name("AppendAIPromptChar"), object: unicodeChars)
                }
            }
            return true // Swallow all characters in AI mode
        }
        
        // Tab Cycling: if suggestions are visible, press Tab to cycle active suggestion index
        if isSuggestionsOverlayVisible && !isPlaceholderModeActive && keyCode == 48 {
            DispatchQueue.main.async {
                self.delegate?.eventTapManagerDidCycleSuggestion(self)
            }
            return true // Swallow Tab key event
        }
        
        // Commit suggestion on Return/Enter if suggestions are visible
        if isSuggestionsOverlayVisible && keyCode == 36 {
            DispatchQueue.main.async {
                self.delegate?.eventTapManagerDidCommitActiveSuggestion(self)
            }
            return true // Swallow Return key event
        }
        
        // Intercept Tab key (keycode 48) if placeholder navigation mode is active
        if isPlaceholderModeActive && keyCode == 48 {
            DispatchQueue.main.async {
                NotificationCenter.default.post(name: Notification.Name("TabPlaceholderPressed"), object: nil)
            }
            return true // Swallow Tab key event
        }
        
        // Spacing correction: check if last action added a trailing space and the user is typing punctuation or a space
        if lastActionWasSuggestionWithTrailingSpace {
            if keyCode == 49 { // Space bar
                lastActionWasSuggestionWithTrailingSpace = false
                NSLog("🔍 PredictiveOverlay EventTap: Swallowed space to prevent double space")
                return true // Swallow space bar to avoid double space
            }
            
            if let unicodeChars = getUnicodeString(from: cgEvent), let char = unicodeChars.first {
                if char == "," || char == "." || char == "!" || char == "?" || char == ";" || char == ":" {
                    // Send simulated Backspace to delete the trailing space
                    let source = CGEventSource(stateID: .hidSystemState)
                    let backspaceKeyCode: CGKeyCode = 0x33
                    let keyDown = CGEvent(keyboardEventSource: source, virtualKey: backspaceKeyCode, keyDown: true)
                    let keyUp = CGEvent(keyboardEventSource: source, virtualKey: backspaceKeyCode, keyDown: false)
                    keyDown?.post(tap: .cghidEventTap)
                    keyUp?.post(tap: .cghidEventTap)
                    
                    lastActionWasSuggestionWithTrailingSpace = false
                    NSLog("🔍 PredictiveOverlay EventTap: Deleted trailing space before punctuation '%@'", String(char))
                } else if !char.isWhitespace {
                    lastActionWasSuggestionWithTrailingSpace = false
                }
            } else {
                // Clear state on active key strokes except functional modifiers
                if keyCode != 56 && keyCode != 59 && keyCode != 55 && keyCode != 58 { // Shift, Control, Command, Option
                    lastActionWasSuggestionWithTrailingSpace = false
                }
            }
        }
        

        
        // Global master mute/unmute shortcut: Ctrl + 0 (keycode 29)
        if hasControl && !hasOption && !hasCommand && !hasShift && keyCode == 29 {
            DispatchQueue.main.async {
                NotificationCenter.default.post(name: Notification.Name("TogglePerAppMute"), object: nil)
            }
            return true // Swallow key event to prevent typing '0'
        }
        
        // 2. Intercept selection shortcuts if suggestions are visible
        if isSuggestionsOverlayVisible {
            let matchesModifier = (hasControl == shortcutModifiers.contains(.control)) &&
                                  (hasOption == shortcutModifiers.contains(.option)) &&
                                  (hasShift == shortcutModifiers.contains(.shift)) &&
                                  (hasCommand == shortcutModifiers.contains(.command))
            
            if matchesModifier {
                var index: Int? = nil
                switch keyCode {
                case 18: index = 0 // Key "1"
                case 19: index = 1 // Key "2"
                case 20: index = 2 // Key "3"
                case 21: index = 3 // Key "4"
                case 23: index = 4 // Key "5"
                case 22: index = 5 // Key "6"
                default: break
                }
                
                if let selectedIndex = index {
                    // Trigger selection on the main thread and swallow key event
                    DispatchQueue.main.async {
                        self.delegate?.eventTapManager(self, didSelectSuggestionIndex: selectedIndex)
                    }
                    return true // Swallow (block) event
                }
            }
        }
        
        // 3. Clear suggestions on Escape
        if keyCode == 53 { // Escape
            DispatchQueue.main.async {
                NotificationCenter.default.post(name: Notification.Name("EscapePlaceholderPressed"), object: nil)
                self.delegate?.eventTapManagerDidClearSuggestions(self)
            }
            return false
        }
        
        // 4. Track text inputs for local training and triggering completions
        if hasCommand || hasControl {
            // Skip shortcuts (Cmd+C, Cmd+V, Control shortcuts, etc.) to prevent context pollution
            return false
        }
        
        let appBundleId = NSWorkspace.shared.frontmostApplication?.bundleIdentifier ?? ""
        processingQueue.async { [weak self] in
            guard let self = self else { return }
            let unicodeCharacters = self.getUnicodeString(from: cgEvent)
            self.processKeystroke(keyCode: keyCode, characters: unicodeCharacters, appBundleId: appBundleId)
        }
        
        return false // Do not swallow standard typing events
    }
    
    private func getUnicodeString(from cgEvent: CGEvent) -> String? {
        var actualLength = 0
        var buffer = [UniChar](repeating: 0, count: 16)
        cgEvent.keyboardGetUnicodeString(maxStringLength: 16, actualStringLength: &actualLength, unicodeString: &buffer)
        if actualLength > 0 {
            return String(utf16CodeUnits: buffer, count: min(actualLength, 16))
        }
        return nil
    }
    
    private func processKeystroke(keyCode: UInt16, characters: String?, appBundleId: String) {
        // Build character sliding buffer
        if keyCode == 51 { // Backspace
            if !slidingBuffer.isEmpty {
                slidingBuffer.removeLast()
            }
        } else if keyCode == 36 { // Return
            slidingBuffer.append("\n")
        } else if let unicode = characters, !unicode.isEmpty {
            slidingBuffer.append(unicode)
        }
        
        // Cap sliding buffer to last 500 characters
        if slidingBuffer.count > 500 {
            slidingBuffer = String(slidingBuffer.suffix(500))
        }
        
        // Run Privacy Firewall check on the sliding buffer. If sensitive secrets are detected,
        // instantly purge the sliding buffer and context to protect user secrets!
        if !PrivacyFirewall.shared.isSafeToProcess(text: slidingBuffer, appBundleId: appBundleId) {
            print("🛡️ Privacy Firewall: Redacting sensitive credential from context buffer!")
            clearContext()
            return
        }
        
        // Reset the typing session debounce timer
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            self.sessionDebounceTimer?.invalidate()
            self.sessionDebounceTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { [weak self] _ in
                guard let self = self else { return }
                self.processingQueue.async {
                    let currentApp = NSWorkspace.shared.frontmostApplication?.bundleIdentifier ?? ""
                    let isTerminal = currentApp.lowercased().contains("terminal") || currentApp.lowercased().contains("iterm")
                    
                    if isTerminal {
                        self.flushTerminalCommandBuffer(appBundleId: currentApp)
                    } else {
                        // Unhooked: Disable passive repetition learning
                        // let textToCapture = self.slidingBuffer.trimmingCharacters(in: .whitespacesAndNewlines)
                        // if textToCapture.count >= 20 {
                        //     RepetitionEngine.shared.processCapturedText(textToCapture, appBundleId: currentApp, domain: nil)
                        // }
                    }
                    self.slidingBuffer = ""
                }
            }
        }
        
        guard let char = characters?.first else { return }
        
        // Space key (Keycode 49)
        if keyCode == 49 {
            if !currentWordPrefix.isEmpty {
                recentWords.append(currentWordPrefix)
                if recentWords.count > 5 {
                    recentWords.removeFirst()
                }
                // Train the engine with updated sequence
                // SuggestionEngine.shared.train(words: recentWords)
                currentWordPrefix = ""
            }
            notifyUpdate()
            return
        }
        
        // Backspace key (Keycode 51)
        if keyCode == 51 {
            if !currentWordPrefix.isEmpty {
                currentWordPrefix.removeLast()
            } else if !recentWords.isEmpty {
                // Pull last word back to edit it
                currentWordPrefix = recentWords.removeLast()
            }
            notifyUpdate()
            return
        }
        
        // Return / Tab keys (clear suggestions context and trigger form learning)
        if keyCode == 36 || (keyCode == 48 && !isPlaceholderModeActive) {
            let appCopy = appBundleId
            processingQueue.async { [weak self] in
                guard let self = self else { return }
                self.learnCurrentFieldState(appBundleId: appCopy)
                
                let isTerminal = appCopy.lowercased().contains("terminal") || appCopy.lowercased().contains("iterm")
                if isTerminal && keyCode == 36 {
                    let lines = self.slidingBuffer.components(separatedBy: "\n").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }.filter { !$0.isEmpty }
                    if let lastLine = lines.last {
                        let now = Date()
                        if now.timeIntervalSince(self.lastTerminalCommandTimestamp) <= 5.0 && self.terminalCommandBuffer.count < 10 {
                            self.terminalCommandBuffer.append(lastLine)
                        } else {
                            self.flushTerminalCommandBuffer(appBundleId: appCopy)
                            self.terminalCommandBuffer = [lastLine]
                        }
                        self.lastTerminalCommandTimestamp = now
                    }
                    self.slidingBuffer = ""
                    self.recentWords.removeAll()
                    self.currentWordPrefix = ""
                    DispatchQueue.main.async {
                        self.delegate?.eventTapManagerDidClearSuggestions(self)
                    }
                } else {
                    self.clearContext(appBundleId: appCopy)
                }
            }
            return
        }
        
        // Check if character is alphanumeric
        if char.isLetter || char.isNumber || char == "'" {
            currentWordPrefix.append(char)
            notifyUpdate()
        } else if char.isPunctuation {
            // Completed a sentence/word, clear active word prefix
            if !currentWordPrefix.isEmpty {
                recentWords.append(currentWordPrefix)
                // SuggestionEngine.shared.train(words: recentWords)
                currentWordPrefix = ""
            }
            notifyUpdate()
        }
    }
    
    private func notifyUpdate() {
        let context = recentWords.joined(separator: " ")
        let prefix = currentWordPrefix
        let fullBuffer = slidingBuffer
        
        DispatchQueue.main.async {
            self.delegate?.eventTapManager(self, didUpdateContext: context, prefix: prefix, fullBuffer: fullBuffer)
        }
    }
    
    private func flushTerminalCommandBuffer(appBundleId: String) {
        if !terminalCommandBuffer.isEmpty {
            // Unhooked: Disable passive repetition learning
            // let multiLineBlock = terminalCommandBuffer.joined(separator: "\n")
            // if multiLineBlock.trimmingCharacters(in: .whitespacesAndNewlines).count >= 20 {
            //     RepetitionEngine.shared.processCapturedText(multiLineBlock, appBundleId: appBundleId, domain: nil)
            // }
            terminalCommandBuffer.removeAll()
        }
    }
    
    func clearContext(appBundleId: String? = nil) {
        let app = appBundleId ?? NSWorkspace.shared.frontmostApplication?.bundleIdentifier ?? ""
        let isTerminal = app.lowercased().contains("terminal") || app.lowercased().contains("iterm")
        
        // Cancel the session debounce timer
        DispatchQueue.main.async { [weak self] in
            self?.sessionDebounceTimer?.invalidate()
            self?.sessionDebounceTimer = nil
        }
        
        // Serialize buffer resets on processingQueue so they never interleave
        // with the read-modify-write sequences in processKeystroke.
        processingQueue.async { [weak self] in
            guard let self = self else { return }
            if isTerminal {
                self.flushTerminalCommandBuffer(appBundleId: app)
            }
            self.recentWords = []
            self.currentWordPrefix = ""
            self.slidingBuffer = ""
        }
        CaptureRouterController.shared.clearPassiveBuffer()
        DispatchQueue.main.async {
            self.delegate?.eventTapManagerDidClearSuggestions(self)
        }
    }
    
    private func learnCurrentFieldState(appBundleId: String) {
        let systemWide = AXUIElementCreateSystemWide()
        var focusedElement: AnyObject?
        if AXUIElementCopyAttributeValue(systemWide, kAXFocusedUIElementAttribute as CFString, &focusedElement) == .success,
           let element = focusedElement as! AXUIElement? {
            
            let result = FieldContextDetector.shared.detect(for: element)
            guard result.context != .unknown && result.context != .password else { return }
            
            var value: AnyObject?
            if AXUIElementCopyAttributeValue(element, kAXValueAttribute as CFString, &value) == .success,
               let _ = value as? String {
//                LiveSuggestionEngine.shared.learnFieldValue(
//                    context: result.context,
//                    label: result.label,
//                    value: text,
//                    appBundleId: result.appBundleId ?? appBundleId,
//                    domain: result.domain
//                )
            }
        }
    }
}

// C-style Callback for CGEventTap
private func eventTapCallback(
    proxy: CGEventTapProxy,
    type: CGEventType,
    event: CGEvent,
    refcon: UnsafeMutableRawPointer?
) -> Unmanaged<CGEvent>? {
    guard let refcon = refcon else { return Unmanaged.passUnretained(event) }
    let manager = Unmanaged<EventTapManager>.fromOpaque(refcon).takeUnretainedValue()
    
    // Watchdog check: If tap gets disabled by timeout, trigger safe handling
    if type == .tapDisabledByTimeout {
        manager.handleTimeout()
        return Unmanaged.passUnretained(event)
    }
    
    // Handle standard keys
    if type == .keyDown {
        let swallow = manager.handleKeyEvent(cgEvent: event)
        if swallow {
            return nil // Swallow the keypress event
        }
    }
    
    return Unmanaged.passUnretained(event)
}
