import {
  getWindowRendererHandlers,
  showPanelWindowAndStartRecording,
  showPanelWindowAndStartMcpRecording,
  showPanelWindowAndShowTextInput,
  stopRecordingAndHidePanelWindow,
  stopTextInputAndHidePanelWindow,
  emergencyStopAgentMode,
  showMainWindow,
  WINDOWS,
} from "./window"
import { snoozeAgentSessionsAndHidePanelWindow } from "./floating-panel-session-state"
import { systemPreferences } from "electron"
import { configStore } from "./config"
import { state, agentProcessManager } from "./state"
import { conversationService } from "./conversation-service"
import { spawn, ChildProcess } from "child_process"
import path from "path"
import { matchesKeyCombo, getEffectiveShortcut } from "../shared/key-utils"
import { isDebugKeybinds, logKeybinds } from "./debug"
import { captureSelectedScreenRegion } from "./screenshot-capture"

const rdevPath = path
  .join(
    __dirname,
    `../../resources/bin/dotagents-rs${process.platform === "win32" ? ".exe" : ""}`,
  )
  .replace("app.asar", "app.asar.unpacked")

const KEYBOARD_LISTENER_SHUTDOWN_TIMEOUT_MS = 1000
const KEYBOARD_LISTENER_FORCE_KILL_WAIT_MS = 250

let keyboardListenerChild: ChildProcess | null = null
let keyboardListenerStopState: { child: ChildProcess; promise: Promise<void> } | null = null
let isVoiceScreenshotCaptureInProgress = false

type RdevEvent = {
  event_type: "KeyPress" | "KeyRelease"
  data: {
    key:
      | "ControlLeft"
      | "ControlRight"
      | "ShiftLeft"
      | "ShiftRight"
      | "Alt"
      | "AltLeft"
      | "AltRight"
      | "BackSlash"
      | string
  }
  time: {
    secs_since_epoch: number
  }
}

export const writeText = (text: string) => {
  return new Promise<void>((resolve, reject) => {
    const child: ChildProcess = spawn(rdevPath, ["write", text])

    // Register process if agent mode is active
    if (state.isAgentModeActive) {
      agentProcessManager.registerProcess(child)
    }

    let stderr = ""

    child.stderr?.on("data", (data) => {
      stderr += data.toString()
    })

    child.on("error", (error) => {
      reject(new Error(`Failed to spawn process: ${error.message}`))
    })

    child.on("close", (code) => {
      // writeText will trigger KeyPress event of the key A
      // I don't know why
      keysPressed.clear()

      if (code === 0) {
        resolve()
      } else {
        const errorMessage = `child process exited with code ${code}${stderr.trim() ? `. stderr: ${stderr.trim()}` : ""}`
        reject(new Error(errorMessage))
      }
    })
  })
}

export const getFocusedAppInfo = () => {
  return new Promise<string>((resolve, reject) => {
    const child: ChildProcess = spawn(rdevPath, ["get-focus"])

    // Register process if agent mode is active
    if (state.isAgentModeActive) {
      agentProcessManager.registerProcess(child)
    }

    let stdout = ""
    let stderr = ""

    child.stdout?.on("data", (data) => {
      stdout += data.toString()
    })

    child.stderr?.on("data", (data) => {
      stderr += data.toString()
    })

    child.on("error", (error) => {
      reject(new Error(`Failed to spawn process: ${error.message}`))
    })

    child.on("close", (code) => {
      if (code === 0) {
        resolve(stdout.trim())
      } else {
        const errorMessage = `get-focus command failed with code ${code}${stderr.trim() ? `. stderr: ${stderr.trim()}` : ""}`
        reject(new Error(errorMessage))
      }
    })
  })
}

const restoreFocusToApp = (appInfo: string) => {
  return new Promise<void>((resolve, reject) => {
    const child: ChildProcess = spawn(rdevPath, ["restore-focus", appInfo])

    // Register process if agent mode is active
    if (state.isAgentModeActive) {
      agentProcessManager.registerProcess(child)
    }

    let stderr = ""

    child.stderr?.on("data", (data) => {
      stderr += data.toString()
    })

    child.on("error", (error) => {
      reject(new Error(`Failed to spawn process: ${error.message}`))
    })

    child.on("close", (code) => {
      if (code === 0) {
        resolve()
      } else {
        const errorMessage = `restore-focus command failed with code ${code}${stderr.trim() ? `. stderr: ${stderr.trim()}` : ""}`
        reject(new Error(errorMessage))
      }
    })
  })
}

export const writeTextWithFocusRestore = async (text: string) => {
  const focusedApp = state.focusedAppBeforeRecording

  if (focusedApp) {
    try {
      await restoreFocusToApp(focusedApp)

      // Small delay to ensure focus is restored before pasting
      await new Promise((resolve) => setTimeout(resolve, 100))

      await writeText(text)
    } catch (error) {
      // Fallback to regular paste without focus restoration
      await writeText(text)
    }
  } else {
    await writeText(text)
  }
}

const parseEvents = (data: Buffer | string): RdevEvent[] => {
  try {
    const eventStr = String(data).trim()
    if (!eventStr) return []

    // Handle multiple JSON objects in a single buffer by splitting on newlines
    const lines = eventStr.split('\n').filter(line => line.trim())
    const events: RdevEvent[] = []

    for (const line of lines) {
      try {
        const e = JSON.parse(line.trim())
        e.data = JSON.parse(e.data)
        events.push(e as RdevEvent)
      } catch (lineError) {
        if (isDebugKeybinds()) {
          logKeybinds("Failed to parse line:", line, "Error:", lineError)
        }
        // Continue processing other lines
      }
    }

    return events
  } catch (error) {
    if (isDebugKeybinds()) {
      logKeybinds("Failed to parse events:", data, "Error:", error)
    }
    return []
  }
}

// keys that are currently pressed down without releasing
// excluding ctrl
// when other keys are pressed, pressing ctrl will not start recording
const keysPressed = new Map<string, number>()

// Delay before starting hold-to-record (kept small to reduce perceived latency, while still
// allowing common modifier combos like Ctrl+C to cancel before recording begins).
const HOLD_TO_RECORD_DELAY_MS = 250

// Helper to check if a key is a modifier key
const isModifierKey = (key: string): boolean => {
  return (
    key === "ControlLeft" ||
    key === "ControlRight" ||
    key === "ShiftLeft" ||
    key === "ShiftRight" ||
    key === "Alt" ||
    key === "AltLeft" ||
    key === "AltRight" ||
    key === "MetaLeft" ||
    key === "MetaRight"
  )
}

const hasRecentKeyPress = () => {
  if (keysPressed.size === 0) return false

  const now = Date.now() / 1000
  return [...keysPressed.entries()].some(([key, time]) => {
    // Exclude modifier keys from the check - they should not block shortcuts
    // that use only modifier key combinations (like toggle-ctrl-alt)
    if (isModifierKey(key)) return false
    // 10 seconds
    // for some weird reasons sometime KeyRelease event is missing for some keys
    // so they stay in the map
    // therefore we have to check if the key was pressed in the last 10 seconds
    return now - time < 10
  })
}

/**
 * Start an MCP recording that continues the most recent conversation.
 * Used by Shift+<recording hotkey> keybinds.
 * @param isStillHeld - Optional predicate checked after the async history
 *   lookup resolves. If provided and returns false, recording is aborted to
 *   avoid starting a new session when the user has already released the key.
 * @returns true if recording was started (or scheduled to start), false if aborted
 */
const startMcpRecordingWithLastConversation = async (isStillHeld?: () => boolean): Promise<boolean> => {
  const recent = await conversationService.getMostRecentConversation()
  // Abort if the key was released while we were awaiting history
  if (isStillHeld && !isStillHeld()) {
    if (isDebugKeybinds()) {
      logKeybinds("Aborting MCP recording: key released during history lookup")
    }
    return false
  }
  if (recent) {
    if (isDebugKeybinds()) {
      logKeybinds("Continue last conversation:", recent.id, recent.title)
    }
    showPanelWindowAndStartMcpRecording(recent.id, undefined, undefined, undefined, recent.title, isStillHeld)
  } else {
    // No conversations yet — fall back to a fresh MCP recording
    showPanelWindowAndStartMcpRecording(undefined, undefined, undefined, undefined, undefined, isStillHeld)
  }
  return true
}

/**
 * Show text input that continues the most recent conversation.
 * Used by Shift+<text input hotkey> keybinds.
 */
const showTextInputWithLastConversation = async () => {
  const recent = await conversationService.getMostRecentConversation()
  if (recent) {
    if (isDebugKeybinds()) {
      logKeybinds("Continue last conversation (text input):", recent.id, recent.title)
    }
    showPanelWindowAndShowTextInput(undefined, recent.id, recent.title)
  } else {
    showPanelWindowAndShowTextInput()
  }
}

export function listenToKeyboardEvents() {
  if (keyboardListenerChild && keyboardListenerChild.exitCode === null) {
    if (isDebugKeybinds()) {
      logKeybinds("Keyboard event listener already running")
    }
    return
  }

  let isHoldingCtrlKey = false
  let startRecordingTimer: ReturnType<typeof setTimeout> | undefined
  let isPressedCtrlKey = false
  let isPressedShiftKey = false
  let isPressedAltKey = false
  let isPressedMetaKey = false

  // Agent tool calling state
  let isHoldingCtrlAltKey = false
  let startMcpRecordingTimer: ReturnType<typeof setTimeout> | undefined
  let isPressedCtrlAltKey = false

  // Custom hold mode state
  let isHoldingCustomRecordingKey = false
  let startCustomRecordingTimer: ReturnType<typeof setTimeout> | undefined
  let isHoldingCustomMcpKey = false
  let startCustomMcpTimer: ReturnType<typeof setTimeout> | undefined

  // Debug state tracking
  let lastLoggedConfig: string | null = null
  let configChangeCount = 0

  if (process.env.IS_MAC) {
    if (!systemPreferences.isTrustedAccessibilityClient(false)) {
      return
    }
  }

  const cancelRecordingTimer = () => {
    if (startRecordingTimer) {
      clearTimeout(startRecordingTimer)
      startRecordingTimer = undefined
    }
  }

  const cancelMcpRecordingTimer = () => {
    if (startMcpRecordingTimer) {
      clearTimeout(startMcpRecordingTimer)
      startMcpRecordingTimer = undefined
    }
  }

  const cancelCustomRecordingTimer = () => {
    if (startCustomRecordingTimer) {
      clearTimeout(startCustomRecordingTimer)
      startCustomRecordingTimer = undefined
    }
  }

  const cancelCustomMcpTimer = () => {
    if (startCustomMcpTimer) {
      clearTimeout(startCustomMcpTimer)
      startCustomMcpTimer = undefined
    }
  }

  const startVoiceScreenshotRecording = async () => {
    if (isVoiceScreenshotCaptureInProgress || state.isRecording) return

    isVoiceScreenshotCaptureInProgress = true
    cancelRecordingTimer()
    cancelMcpRecordingTimer()
    cancelCustomRecordingTimer()
    cancelCustomMcpTimer()

    try {
      const screenshot = await captureSelectedScreenRegion()
      if (!screenshot) return

      await showPanelWindowAndStartMcpRecording(
        undefined,
        undefined,
        undefined,
        true,
        undefined,
        undefined,
        screenshot,
      )
    } catch (error) {
      if (isDebugKeybinds()) {
        logKeybinds("Voice screenshot capture failed:", error)
      }
    } finally {
      isVoiceScreenshotCaptureInProgress = false
    }
  }

  const tryStartMcpHoldIfEligible = () => {
    const config = configStore.get()
    const agentShortcut = config.agentShortcut || config.mcpToolsShortcut
    if (agentShortcut !== "hold-ctrl-alt") {
      return
    }

    // Both modifiers must be down
    if (!isPressedCtrlKey || !isPressedAltKey) return

    // Guard against recent non-modifier presses
    if (hasRecentKeyPress()) return

    // Prevent duplicate timers
    if (startMcpRecordingTimer) return

    // Cancel regular recording timer since MCP is prioritized when both held
    cancelRecordingTimer()

    startMcpRecordingTimer = setTimeout(async () => {
      // Re-check modifiers before firing
      if (!isPressedCtrlKey || !isPressedAltKey) return
      // Shift+Ctrl+Alt = continue last conversation
      if (isPressedShiftKey) {
        // Only set isHoldingCtrlAltKey if recording actually started
        // This prevents spurious finishMcpRecording calls if user releases key during async lookup
        const started = await startMcpRecordingWithLastConversation(() => isPressedCtrlKey && isPressedAltKey)
        if (started) isHoldingCtrlAltKey = true
      } else {
        isHoldingCtrlAltKey = true
        showPanelWindowAndStartMcpRecording(undefined, undefined, undefined, undefined, undefined, () => isPressedCtrlKey && isPressedAltKey)
      }
    }, HOLD_TO_RECORD_DELAY_MS)
  }

  const tryToggleMcpIfEligible = () => {
    const config = configStore.get()
    const agentShortcut = config.agentShortcut || config.mcpToolsShortcut
    if (agentShortcut !== "toggle-ctrl-alt") {
      return
    }

    // Both modifiers must be down
    if (!isPressedCtrlKey || !isPressedAltKey) return

    // Guard against recent non-modifier presses
    if (hasRecentKeyPress()) return

    // Cancel regular recording timer since MCP is prioritized
    cancelRecordingTimer()

    if (isDebugKeybinds()) {
      logKeybinds("MCP tools triggered: Ctrl+Alt (toggle mode)")
    }

    // Set state.isRecordingMcpMode BEFORE sending the message
    // This ensures the key release handlers know we're in MCP toggle mode
    // and won't prematurely close the panel when the user releases Ctrl/Alt
    if (!state.isRecording) {
      // Starting MCP recording - set the flag now so key release handlers know
      state.isRecordingMcpMode = true
    }
    // Note: When stopping, the recordEvent handler will set isRecordingMcpMode = false

    // Shift+Ctrl+Alt (toggle) = continue last conversation
    if (isPressedShiftKey && !state.isRecording) {
      startMcpRecordingWithLastConversation()
      return
    }

    // Toggle MCP recording on/off
    getWindowRendererHandlers("panel")?.startOrFinishMcpRecording.send()
  }


  const handleEvent = (e: RdevEvent) => {
    if (e.event_type === "KeyPress") {
      if (e.data.key === "ControlLeft" || e.data.key === "ControlRight") {
        isPressedCtrlKey = true
        tryStartMcpHoldIfEligible()
        tryToggleMcpIfEligible()
        if (isDebugKeybinds()) {
          logKeybinds("Ctrl key pressed, isPressedCtrlKey =", isPressedCtrlKey)
        }
      }

      if (e.data.key === "ShiftLeft" || e.data.key === "ShiftRight") {
        isPressedShiftKey = true
        if (isDebugKeybinds()) {
          logKeybinds(
            "Shift key pressed, isPressedShiftKey =",
            isPressedShiftKey,
          )
        }
      }

      if (e.data.key === "Alt" || e.data.key === "AltLeft" || e.data.key === "AltRight") {
        isPressedAltKey = true
        isPressedCtrlAltKey = isPressedCtrlKey && isPressedAltKey
        tryStartMcpHoldIfEligible()
        tryToggleMcpIfEligible()
        if (isDebugKeybinds()) {
          logKeybinds(
            "Alt key pressed, isPressedAltKey =",
            isPressedAltKey,
            "isPressedCtrlAltKey =",
            isPressedCtrlAltKey,
          )
        }
      }

      if (e.data.key === "MetaLeft" || e.data.key === "MetaRight") {
        isPressedMetaKey = true
        if (isDebugKeybinds()) {
          logKeybinds("Meta key pressed, isPressedMetaKey =", isPressedMetaKey)
        }
      }

      // Get config once at the beginning of the function
      const config = configStore.get()

      // Only log config changes, not every key press
      if (isDebugKeybinds()) {
        const configHash = JSON.stringify({
          agentKillSwitchEnabled: config.agentKillSwitchEnabled,
          agentKillSwitchHotkey: config.agentKillSwitchHotkey,
          textInputEnabled: config.textInputEnabled,
          textInputShortcut: config.textInputShortcut,
          agentShortcut: config.agentShortcut || config.mcpToolsShortcut,
          shortcut: config.shortcut,
        })

        if (lastLoggedConfig !== configHash) {
          lastLoggedConfig = configHash
          configChangeCount++
          logKeybinds(`Config change #${configChangeCount}:`, {
            agentKillSwitchEnabled: config.agentKillSwitchEnabled,
            agentKillSwitchHotkey: config.agentKillSwitchHotkey,
            textInputEnabled: config.textInputEnabled,
            textInputShortcut: config.textInputShortcut,
            agentShortcut: config.agentShortcut || config.mcpToolsShortcut,
            shortcut: config.shortcut,
          })
        }
      }

      if (e.data.key === "Escape") {
        if (
          isDebugKeybinds() &&
          (isPressedCtrlKey || isPressedShiftKey || isPressedAltKey)
        ) {
          logKeybinds(
            "Escape key pressed with modifiers, checking kill switch conditions:",
            {
              agentKillSwitchEnabled: config.agentKillSwitchEnabled,
              agentKillSwitchHotkey: config.agentKillSwitchHotkey,
              modifiers: {
                ctrl: isPressedCtrlKey,
                shift: isPressedShiftKey,
                alt: isPressedAltKey,
              },
              isAgentModeActive: state.isAgentModeActive,
            },
          )
        }

        // Handle kill switch hotkey: Ctrl+Shift+Escape
        // Robust behavior: Always allow Ctrl+Shift+Escape as a hard emergency stop,
        // even if the configured hotkey is different. This provides a universal safety combo.
        if (
          config.agentKillSwitchEnabled &&
          isPressedCtrlKey &&
          isPressedShiftKey
        ) {
          if (isDebugKeybinds()) {
            const reason =
              config.agentKillSwitchHotkey === "ctrl-shift-escape"
                ? "Ctrl+Shift+Escape"
                : "Ctrl+Shift+Escape (fallback hard kill)"
            logKeybinds(`Kill switch triggered: ${reason}`)
          }
          // Emergency stop agent mode - always trigger to handle stuck states
          // even if isAgentModeActive flag is not set correctly
          emergencyStopAgentMode()
          return
        }

        const win = WINDOWS.get("panel")
        if (win && win.isVisible()) {
          // Check if we're currently recording
          if (state.isRecording) {
            stopRecordingAndHidePanelWindow()
          } else if (state.isTextInputActive) {
            // Text input needs its dedicated hide path so intentional-dismiss
            // suppression and cleanup run before macOS can bounce focus back to
            // the main window.
            stopTextInputAndHidePanelWindow()
          } else {
            // Panel is visible but not recording - treat ESC like minimize so
            // the current floating sessions stay snoozed until explicitly restored.
            snoozeAgentSessionsAndHidePanelWindow()
          }
        }

        return
      }

      // Handle other kill switch hotkeys
      // Always check killswitch hotkeys to handle stuck states, even if isAgentModeActive is not set
      if (config.agentKillSwitchEnabled) {
        const effectiveKillSwitchHotkey = getEffectiveShortcut(
          config.agentKillSwitchHotkey,
          config.customAgentKillSwitchHotkey,
        )

        if (
          config.agentKillSwitchHotkey === "ctrl-alt-q" &&
          e.data.key === "KeyQ" &&
          isPressedCtrlKey &&
          isPressedAltKey
        ) {
          if (isDebugKeybinds()) {
            logKeybinds("Kill switch triggered: Ctrl+Alt+Q")
          }
          emergencyStopAgentMode()
          return
        }

        if (
          config.agentKillSwitchHotkey === "ctrl-shift-q" &&
          e.data.key === "KeyQ" &&
          isPressedCtrlKey &&
          isPressedShiftKey
        ) {
          if (isDebugKeybinds()) {
            logKeybinds("Kill switch triggered: Ctrl+Shift+Q")
          }
          emergencyStopAgentMode()
          return
        }

        // Handle custom kill switch hotkey
        if (
          config.agentKillSwitchHotkey === "custom" &&
          effectiveKillSwitchHotkey
        ) {
          const matches = matchesKeyCombo(
            e.data,
            {
              ctrl: isPressedCtrlKey,
              shift: isPressedShiftKey,
              alt: isPressedAltKey,
              meta: isPressedMetaKey,
            },
            effectiveKillSwitchHotkey,
          )
          if (isDebugKeybinds() && matches) {
            logKeybinds(
              "Kill switch triggered: Custom hotkey",
              effectiveKillSwitchHotkey,
            )
          }
          if (matches) {
            emergencyStopAgentMode()
            return
          }
        }
      }

      // Handle Enter key to submit recording when triggered from UI button click
      // The panel window is shown with showInactive() so it doesn't receive keyboard focus,
      // which means we need to use the global keyboard hook to detect Enter key
      if (e.data.key === "Return" || e.data.key === "Enter" || e.data.key === "KpReturn") {
        if (state.isRecording && state.isRecordingFromButtonClick && !isPressedShiftKey) {
          if (isDebugKeybinds()) {
            logKeybinds("Enter key pressed during button-click recording, submitting")
          }
          const panelHandlers = getWindowRendererHandlers("panel")
          if (state.isRecordingMcpMode) {
            panelHandlers?.finishMcpRecording.send()
          } else {
            panelHandlers?.finishRecording.send()
          }
          // Reset the button click state
          state.isRecordingFromButtonClick = false
          return
        }
      }

      // Handle text input shortcuts
      if (config.textInputEnabled) {
        const effectiveTextInputShortcut = getEffectiveShortcut(
          config.textInputShortcut,
          config.customTextInputShortcut,
        )

        // Helper to cancel any voice recording in progress when switching to text input
        const cancelVoiceRecordingForTextInput = () => {
          cancelRecordingTimer()
          cancelMcpRecordingTimer()
          cancelCustomRecordingTimer()
          cancelCustomMcpTimer()
          
          // If a recording has already started, explicitly discard it
          // stopRecordingAndHidePanelWindow sends stopRecording which sets isConfirmedRef=false,
          // causing the recording to be discarded rather than processed
          if (state.isRecording) {
            // Reset recording state flags before stopping
            state.isRecordingFromButtonClick = false
            state.isRecordingMcpMode = false
            state.isToggleRecordingActive = false
            // Send stop signal to discard the recording
            // Note: This only discards the blob; showPanelWindowAndShowTextInput will show the panel in text input mode
            const panelHandlers = getWindowRendererHandlers("panel")
            panelHandlers?.stopRecording.send()
          }
          
          isHoldingCtrlKey = false
          isHoldingCtrlAltKey = false
          isHoldingCustomRecordingKey = false
          isHoldingCustomMcpKey = false
        }

        if (
          config.textInputShortcut === "ctrl-t" &&
          e.data.key === "KeyT" &&
          isPressedCtrlKey &&
          !isPressedAltKey
        ) {
          cancelVoiceRecordingForTextInput()
          // Shift+Ctrl+T = continue last conversation (text input)
          if (isPressedShiftKey) {
            if (isDebugKeybinds()) {
              logKeybinds("Text input triggered: Shift+Ctrl+T (continue conversation)")
            }
            showTextInputWithLastConversation()
          } else {
            if (isDebugKeybinds()) {
              logKeybinds("Text input triggered: Ctrl+T")
            }
            showPanelWindowAndShowTextInput()
          }
          return
        }
        if (
          config.textInputShortcut === "ctrl-shift-t" &&
          e.data.key === "KeyT" &&
          isPressedCtrlKey &&
          isPressedShiftKey &&
          !isPressedAltKey
        ) {
          cancelVoiceRecordingForTextInput()
          // When Ctrl+Shift+T is already the shortcut, Alt+Ctrl+Shift+T = continue
          // But that's too complex; just trigger normally. Users can use voice continue instead.
          if (isDebugKeybinds()) {
            logKeybinds("Text input triggered: Ctrl+Shift+T")
          }
          showPanelWindowAndShowTextInput()
          return
        }
        if (
          config.textInputShortcut === "alt-t" &&
          e.data.key === "KeyT" &&
          !isPressedCtrlKey &&
          isPressedAltKey
        ) {
          cancelVoiceRecordingForTextInput()
          // Shift+Alt+T = continue last conversation (text input)
          if (isPressedShiftKey) {
            if (isDebugKeybinds()) {
              logKeybinds("Text input triggered: Shift+Alt+T (continue conversation)")
            }
            showTextInputWithLastConversation()
          } else {
            if (isDebugKeybinds()) {
              logKeybinds("Text input triggered: Alt+T")
            }
            showPanelWindowAndShowTextInput()
          }
          return
        }

        // Handle custom text input shortcut
        if (
          config.textInputShortcut === "custom" &&
          effectiveTextInputShortcut
        ) {
          const matches = matchesKeyCombo(
            e.data,
            {
              ctrl: isPressedCtrlKey,
              shift: isPressedShiftKey,
              alt: isPressedAltKey,
              meta: isPressedMetaKey,
            },
            effectiveTextInputShortcut,
          )
          if (isDebugKeybinds() && matches) {
            logKeybinds(
              "Text input triggered: Custom hotkey",
              effectiveTextInputShortcut,
            )
          }
          if (matches) {
            cancelVoiceRecordingForTextInput()
            showPanelWindowAndShowTextInput()
            return
          }
        }
      }

      // Handle main window hotkey (opens/focuses UI without navigating)
      // Allow UI access during most states, but prevent during recording to avoid interruption
      if (config.settingsHotkeyEnabled && !state.isRecording) {
        const effectiveSettingsHotkey = getEffectiveShortcut(
          config.settingsHotkey,
          config.customSettingsHotkey,
        )

        if (
          config.settingsHotkey === "ctrl-shift-s" &&
          e.data.key === "KeyS" &&
          isPressedCtrlKey &&
          isPressedShiftKey &&
          !isPressedAltKey
        ) {
          if (isDebugKeybinds()) {
            logKeybinds("Main window triggered: Ctrl+Shift+S")
          }
          showMainWindow()
          return
        }
        if (
          config.settingsHotkey === "ctrl-comma" &&
          e.data.key === "Comma" &&
          isPressedCtrlKey &&
          !isPressedShiftKey &&
          !isPressedAltKey
        ) {
          if (isDebugKeybinds()) {
            logKeybinds("Main window triggered: Ctrl+,")
          }
          showMainWindow()
          return
        }
        if (
          config.settingsHotkey === "ctrl-shift-comma" &&
          e.data.key === "Comma" &&
          isPressedCtrlKey &&
          isPressedShiftKey &&
          !isPressedAltKey
        ) {
          if (isDebugKeybinds()) {
            logKeybinds("Main window triggered: Ctrl+Shift+,")
          }
          showMainWindow()
          return
        }

        // Handle custom main window hotkey
        if (
          config.settingsHotkey === "custom" &&
          effectiveSettingsHotkey
        ) {
          const matches = matchesKeyCombo(
            e.data,
            {
              ctrl: isPressedCtrlKey,
              shift: isPressedShiftKey,
              alt: isPressedAltKey,
              meta: isPressedMetaKey,
            },
            effectiveSettingsHotkey,
          )
          if (isDebugKeybinds() && matches) {
            logKeybinds(
              "Main window triggered: Custom hotkey",
              effectiveSettingsHotkey,
            )
          }
          if (matches) {
            showMainWindow()
            return
          }
        }
      }

      if (process.platform === "darwin" && config.voiceScreenshotShortcutEnabled !== false) {
        const effectiveVoiceScreenshotShortcut = getEffectiveShortcut(
          config.voiceScreenshotShortcut || "ctrl-shift-x",
          config.customVoiceScreenshotShortcut,
        ) || "ctrl-shift-x"

        const matchesVoiceScreenshotShortcut = matchesKeyCombo(
          e.data,
          {
            ctrl: isPressedCtrlKey,
            shift: isPressedShiftKey,
            alt: isPressedAltKey,
            meta: isPressedMetaKey,
          },
          effectiveVoiceScreenshotShortcut,
        )

        if (matchesVoiceScreenshotShortcut) {
          if (isDebugKeybinds()) {
            logKeybinds("Voice screenshot recording triggered:", effectiveVoiceScreenshotShortcut)
          }
          void startVoiceScreenshotRecording()
          return
        }
      }

      // Handle agent mode shortcuts
      const effectiveMcpToolsShortcut = getEffectiveShortcut(
        config.agentShortcut || config.mcpToolsShortcut,
        config.customAgentShortcut || config.customMcpToolsShortcut,
      )
      const agentShortcut = config.agentShortcut || config.mcpToolsShortcut

      if (agentShortcut === "ctrl-alt-slash") {
        if (e.data.key === "Slash" && isPressedCtrlKey && isPressedAltKey) {
          // Shift+Ctrl+Alt+/ = continue last conversation
          if (isPressedShiftKey) {
            if (isDebugKeybinds()) {
              logKeybinds("MCP tools triggered: Shift+Ctrl+Alt+/ (continue conversation)")
            }
            startMcpRecordingWithLastConversation()
            return
          }
          if (isDebugKeybinds()) {
            logKeybinds("MCP tools triggered: Ctrl+Alt+/")
          }
          getWindowRendererHandlers("panel")?.startOrFinishMcpRecording.send()
          return
        }
      }

      // Handle custom agent shortcut
      if (agentShortcut === "custom" && effectiveMcpToolsShortcut) {
        const matches = matchesKeyCombo(
          e.data,
          {
            ctrl: isPressedCtrlKey,
            shift: isPressedShiftKey,
            alt: isPressedAltKey,
            meta: isPressedMetaKey,
          },
          effectiveMcpToolsShortcut,
        )
        if (matches) {
          const customMode = config.customAgentShortcutMode || config.customMcpToolsShortcutMode || "hold"

          if (customMode === "toggle") {
            // Toggle mode: press once to start, press again to stop
            if (isDebugKeybinds()) {
              logKeybinds(
                "MCP tools triggered: Custom hotkey (toggle mode)",
                effectiveMcpToolsShortcut,
              )
            }
            getWindowRendererHandlers("panel")?.startOrFinishMcpRecording.send()
            return
          } else {
            // Hold mode: start timer on key press, start recording after a short delay
            if (isDebugKeybinds()) {
              logKeybinds(
                "MCP tools triggered: Custom hotkey (hold mode)",
                effectiveMcpToolsShortcut,
              )
            }

            if (hasRecentKeyPress()) {
              return
            }

            if (startCustomMcpTimer) {
              return
            }

            // Cancel regular recording timer since MCP is prioritized
            cancelRecordingTimer()
            cancelCustomRecordingTimer()

            startCustomMcpTimer = setTimeout(() => {
              // Re-check if keys are still pressed
              const stillMatches = matchesKeyCombo(
                e.data,
                {
                  ctrl: isPressedCtrlKey,
                  shift: isPressedShiftKey,
                  alt: isPressedAltKey,
                  meta: isPressedMetaKey,
                },
                effectiveMcpToolsShortcut,
              )
              if (!stillMatches) return

              isHoldingCustomMcpKey = true
              showPanelWindowAndStartMcpRecording(undefined, undefined, undefined, undefined, undefined, () => isHoldingCustomMcpKey)
            }, HOLD_TO_RECORD_DELAY_MS)
            return
          }
        }
      }

      // Handle toggle voice dictation shortcuts
      if (config.toggleVoiceDictationEnabled) {
        const effectiveToggleShortcut = getEffectiveShortcut(
          config.toggleVoiceDictationHotkey,
          config.customToggleVoiceDictationHotkey,
        )

        const toggleHotkey = config.toggleVoiceDictationHotkey

        if (toggleHotkey === "fn") {
          if (e.data.key === "Function" || e.data.key === "Fn") {
            if (isDebugKeybinds()) {
              logKeybinds("Toggle voice dictation triggered: Fn")
            }
            if (state.isToggleRecordingActive) {
              // Stop toggle recording
              state.isToggleRecordingActive = false
              getWindowRendererHandlers("panel")?.finishRecording.send()
            } else {
              // Start toggle recording
              state.isToggleRecordingActive = true
              showPanelWindowAndStartRecording()
            }
            return
          }
        } else if (toggleHotkey && toggleHotkey !== "custom" && toggleHotkey.startsWith("f")) {
          // Handle F1-F12 keys
          const fKeyMap: Record<string, string> = {
            f1: "F1", f2: "F2", f3: "F3", f4: "F4", f5: "F5", f6: "F6",
            f7: "F7", f8: "F8", f9: "F9", f10: "F10", f11: "F11", f12: "F12"
          }
          const expectedKey = fKeyMap[toggleHotkey]
          if (e.data.key === expectedKey) {
            if (isDebugKeybinds()) {
              logKeybinds(`Toggle voice dictation triggered: ${expectedKey}`)
            }
            if (state.isToggleRecordingActive) {
              // Stop toggle recording
              state.isToggleRecordingActive = false
              getWindowRendererHandlers("panel")?.finishRecording.send()
            } else {
              // Start toggle recording
              state.isToggleRecordingActive = true
              showPanelWindowAndStartRecording()
            }
            return
          }
        } else if (toggleHotkey === "custom" && effectiveToggleShortcut) {
          // Handle custom toggle shortcut
          const matches = matchesKeyCombo(
            e.data,
            {
              ctrl: isPressedCtrlKey,
              shift: isPressedShiftKey,
              alt: isPressedAltKey,
              meta: isPressedMetaKey,
            },
            effectiveToggleShortcut,
          )
          if (isDebugKeybinds() && matches) {
            logKeybinds(
              "Toggle voice dictation triggered: Custom hotkey",
              effectiveToggleShortcut,
            )
          }
          if (matches) {
            if (state.isToggleRecordingActive) {
              // Stop toggle recording
              state.isToggleRecordingActive = false
              getWindowRendererHandlers("panel")?.finishRecording.send()
            } else {
              // Start toggle recording
              state.isToggleRecordingActive = true
              showPanelWindowAndStartRecording()
            }
            return
          }
        }
      }

      // Handle recording shortcuts
      const effectiveRecordingShortcut = getEffectiveShortcut(
        config.shortcut,
        config.customShortcut,
      )

      if (config.shortcut === "ctrl-slash") {
        if (e.data.key === "Slash" && isPressedCtrlKey) {
          // Shift+Ctrl+/ = continue last conversation in agent mode
          if (isPressedShiftKey) {
            if (isDebugKeybinds()) {
              logKeybinds("Recording triggered: Shift+Ctrl+/ (continue conversation)")
            }
            startMcpRecordingWithLastConversation()
            return
          }
          if (isDebugKeybinds()) {
            logKeybinds("Recording triggered: Ctrl+/")
          }
          getWindowRendererHandlers("panel")?.startOrFinishRecording.send()
        }
      } else if (config.shortcut === "custom" && effectiveRecordingShortcut) {
        // Handle custom recording shortcut
        const matches = matchesKeyCombo(
          e.data,
          {
            ctrl: isPressedCtrlKey,
            shift: isPressedShiftKey,
            alt: isPressedAltKey,
            meta: isPressedMetaKey,
          },
          effectiveRecordingShortcut,
        )
        if (matches) {
          const customMode = config.customShortcutMode || "hold"

          if (customMode === "toggle") {
            // Toggle mode: press once to start, press again to stop
            if (isDebugKeybinds()) {
              logKeybinds(
                "Recording triggered: Custom hotkey (toggle mode)",
                effectiveRecordingShortcut,
              )
            }
            getWindowRendererHandlers("panel")?.startOrFinishRecording.send()
            return
          } else {
            // Hold mode: start timer on key press, start recording after a short delay
            if (isDebugKeybinds()) {
              logKeybinds(
                "Recording triggered: Custom hotkey (hold mode)",
                effectiveRecordingShortcut,
              )
            }

            if (hasRecentKeyPress()) {
              return
            }

            if (startCustomRecordingTimer) {
              return
            }

            startCustomRecordingTimer = setTimeout(() => {
              // Re-check if keys are still pressed
              const stillMatches = matchesKeyCombo(
                e.data,
                {
                  ctrl: isPressedCtrlKey,
                  shift: isPressedShiftKey,
                  alt: isPressedAltKey,
                  meta: isPressedMetaKey,
                },
                effectiveRecordingShortcut,
              )
              if (!stillMatches) return

              isHoldingCustomRecordingKey = true
              showPanelWindowAndStartRecording()
            }, HOLD_TO_RECORD_DELAY_MS)
            return
          }
        }
      }

      // Handle hold-ctrl mode (default behavior)
      if (config.shortcut !== "ctrl-slash" && config.shortcut !== "custom") {
        if (e.data.key === "ControlLeft" || e.data.key === "ControlRight") {
          if (hasRecentKeyPress()) {
            return
          }

          if (startRecordingTimer) {
            return
          }

          startRecordingTimer = setTimeout(async () => {
            // Guard: ensure Ctrl is still held and Alt is not held when timer fires
            if (!isPressedCtrlKey || isPressedAltKey) {
              return
            }
            // Shift+Ctrl = continue last conversation in agent mode
            if (isPressedShiftKey) {
              // Use isHoldingCtrlAltKey so the Ctrl release handler sends
              // finishMcpRecording (not finishRecording) for this MCP path.
              // Only set isHoldingCtrlAltKey if recording actually started
              // to prevent spurious finishMcpRecording calls if user releases key during async lookup
              const started = await startMcpRecordingWithLastConversation(() => isPressedCtrlKey)
              if (started) isHoldingCtrlAltKey = true
            } else {
              isHoldingCtrlKey = true
              showPanelWindowAndStartRecording()
            }
          }, HOLD_TO_RECORD_DELAY_MS)
        } else if (
          (e.data.key === "Alt" || e.data.key === "AltLeft" || e.data.key === "AltRight") &&
          isPressedCtrlKey &&
          (config.agentShortcut || config.mcpToolsShortcut) === "hold-ctrl-alt"
        ) {
          // Legacy path kept for clarity; unified by tryStartMcpHoldIfEligible()
          tryStartMcpHoldIfEligible()
          if (hasRecentKeyPress()) {
            return
          }

          if (startMcpRecordingTimer) {
            return
          }

          // Cancel the regular recording timer since we're starting agent mode
          cancelRecordingTimer()

          startMcpRecordingTimer = setTimeout(async () => {
            // Guard: ensure Ctrl+Alt are still held when timer fires
            if (!isPressedCtrlKey || !isPressedAltKey) {
              return
            }
            // Shift+Ctrl+Alt = continue last conversation
            if (isPressedShiftKey) {
              // Only set isHoldingCtrlAltKey if recording actually started
              // to prevent spurious finishMcpRecording calls if user releases key during async lookup
              const started = await startMcpRecordingWithLastConversation(() => isPressedCtrlKey && isPressedAltKey)
              if (started) isHoldingCtrlAltKey = true
            } else {
              isHoldingCtrlAltKey = true
              showPanelWindowAndStartMcpRecording(undefined, undefined, undefined, undefined, undefined, () => isPressedCtrlKey && isPressedAltKey)
            }
          }, HOLD_TO_RECORD_DELAY_MS)
        } else {
          keysPressed.set(e.data.key, e.time.secs_since_epoch)
          cancelRecordingTimer()
          cancelMcpRecordingTimer()
          cancelCustomRecordingTimer()
          cancelCustomMcpTimer()

          // when holding ctrl key, pressing any other key will stop recording
          if (isHoldingCtrlKey) {
            stopRecordingAndHidePanelWindow()
          }

          // when holding ctrl+alt key, pressing any other key will stop MCP recording
          if (isHoldingCtrlAltKey) {
            stopRecordingAndHidePanelWindow()
          }

          // when holding custom recording key, pressing any other key will stop recording
          if (isHoldingCustomRecordingKey) {
            stopRecordingAndHidePanelWindow()
          }

          // when holding custom MCP key, pressing any other key will stop recording
          if (isHoldingCustomMcpKey) {
            stopRecordingAndHidePanelWindow()
          }

          isHoldingCtrlKey = false
          isHoldingCtrlAltKey = false
          isHoldingCustomRecordingKey = false
          isHoldingCustomMcpKey = false
        }
      }
    } else if (e.event_type === "KeyRelease") {
      keysPressed.delete(e.data.key)

      if (e.data.key === "ControlLeft" || e.data.key === "ControlRight") {
        isPressedCtrlKey = false
        if (isDebugKeybinds()) {
          logKeybinds("Ctrl key released, isPressedCtrlKey =", isPressedCtrlKey)
        }
      }

      if (e.data.key === "ShiftLeft" || e.data.key === "ShiftRight") {
        isPressedShiftKey = false
        if (isDebugKeybinds()) {
          logKeybinds(
            "Shift key released, isPressedShiftKey =",
            isPressedShiftKey,
          )
        }
      }

      if (e.data.key === "Alt" || e.data.key === "AltLeft" || e.data.key === "AltRight") {
        isPressedAltKey = false
        isPressedCtrlAltKey = false
        if (isDebugKeybinds()) {
          logKeybinds(
            "Alt key released, isPressedAltKey =",
            isPressedAltKey,
            "isPressedCtrlAltKey =",
            isPressedCtrlAltKey,
          )
        }
      }

      if (e.data.key === "MetaLeft" || e.data.key === "MetaRight") {
        isPressedMetaKey = false
        if (isDebugKeybinds()) {
          logKeybinds("Meta key released, isPressedMetaKey =", isPressedMetaKey)
        }
      }

      const currentConfig = configStore.get()

      // Handle custom shortcut key releases for hold mode
      if (currentConfig.shortcut === "custom") {
        const customMode = currentConfig.customShortcutMode || "hold"
        if (customMode === "toggle") {
          // Toggle mode doesn't need key release handling
          return
        }
        // Hold mode: check if we should finish recording
        if (isHoldingCustomRecordingKey) {
          const effectiveRecordingShortcut = getEffectiveShortcut(
            currentConfig.shortcut,
            currentConfig.customShortcut,
          )
          if (effectiveRecordingShortcut) {
            // Check if the released key is part of the custom shortcut
            const stillMatches = matchesKeyCombo(
              e.data,
              {
                ctrl: isPressedCtrlKey,
                shift: isPressedShiftKey,
                alt: isPressedAltKey,
                meta: isPressedMetaKey,
              },
              effectiveRecordingShortcut,
            )
            if (!stillMatches) {
              // Key combo no longer matches, finish recording
              getWindowRendererHandlers("panel")?.finishRecording.send()
              isHoldingCustomRecordingKey = false
            }
          }
        }
        cancelCustomRecordingTimer()
      }

      // Handle custom agent shortcut key releases for hold mode
      if ((currentConfig.agentShortcut || currentConfig.mcpToolsShortcut) === "custom") {
        const customMode = currentConfig.customAgentShortcutMode || currentConfig.customMcpToolsShortcutMode || "hold"
        if (customMode === "hold" && isHoldingCustomMcpKey) {
          const effectiveMcpToolsShortcut = getEffectiveShortcut(
            currentConfig.agentShortcut || currentConfig.mcpToolsShortcut,
            currentConfig.customAgentShortcut || currentConfig.customMcpToolsShortcut,
          )
          if (effectiveMcpToolsShortcut) {
            // Check if the released key is part of the custom shortcut
            const stillMatches = matchesKeyCombo(
              e.data,
              {
                ctrl: isPressedCtrlKey,
                shift: isPressedShiftKey,
                alt: isPressedAltKey,
                meta: isPressedMetaKey,
              },
              effectiveMcpToolsShortcut,
            )
            if (!stillMatches) {
              // Key combo no longer matches, finish recording
              getWindowRendererHandlers("panel")?.finishMcpRecording.send()
              isHoldingCustomMcpKey = false
            }
          }
        }
        cancelCustomMcpTimer()
      }

      // Always handle MCP hold-ctrl-alt key releases, regardless of recording shortcut mode
      // This must happen before the toggle mode early return below
      cancelMcpRecordingTimer()

      if (e.data.key === "ControlLeft" || e.data.key === "ControlRight") {
        if (isHoldingCtrlAltKey) {
          const panelHandlers = getWindowRendererHandlers("panel")
          panelHandlers?.finishMcpRecording.send()
          isHoldingCtrlAltKey = false
        }
      }

      if (e.data.key === "Alt" || e.data.key === "AltLeft" || e.data.key === "AltRight") {
        if (isHoldingCtrlAltKey) {
          const panelHandlers = getWindowRendererHandlers("panel")
          panelHandlers?.finishMcpRecording.send()
          isHoldingCtrlAltKey = false
        }
      }

      // Skip built-in hold mode handling for toggle mode shortcuts
      // (only applies to regular recording, not agent mode which is handled above)
      if (
        (currentConfig.shortcut === "ctrl-slash") ||
        (currentConfig.shortcut === "custom" && currentConfig.customShortcutMode === "toggle")
      )
        return

      cancelRecordingTimer()

      // Finish regular hold-ctrl recording on Ctrl release
      if (e.data.key === "ControlLeft" || e.data.key === "ControlRight") {
        if (isHoldingCtrlKey) {
          getWindowRendererHandlers("panel")?.finishRecording.send()
        } else if (!state.isTextInputActive && !state.isRecordingMcpMode) {
          // Only close panel if we're not in text input mode and not in MCP recording mode
          // (MCP toggle mode should not close panel on key release)
          stopRecordingAndHidePanelWindow()
        }
        isHoldingCtrlKey = false
      }

      // Close panel on Alt release if not in text input mode (and not in agent mode, which is handled above)
      if (e.data.key === "Alt" || e.data.key === "AltLeft" || e.data.key === "AltRight") {
        if (!state.isTextInputActive && !state.isRecordingMcpMode) {
          // Only close panel if we're not in text input mode and not in MCP recording mode
          // (MCP toggle mode should not close panel on key release)
          stopRecordingAndHidePanelWindow()
        }
      }
    }
  }

  const child = spawn(rdevPath, ["listen"], {})
  keyboardListenerChild = child

  if (isDebugKeybinds()) {
    logKeybinds("Starting keyboard event listener with rdev path:", rdevPath)
  }

  child.stdout.on("data", (data) => {
    const events = parseEvents(data)
    for (const event of events) {
      handleEvent(event)
    }
  })

  // Cap the stderr buffer to 16KB to avoid unbounded memory growth
  const STDERR_BUFFER_MAX_SIZE = 16 * 1024
  let stderrBuffer = ""

  child.stderr?.on("data", (data) => {
    const output = data.toString()
    stderrBuffer += output
    // Keep only the last N bytes to prevent unbounded growth
    if (stderrBuffer.length > STDERR_BUFFER_MAX_SIZE) {
      stderrBuffer = stderrBuffer.slice(-STDERR_BUFFER_MAX_SIZE)
    }
    if (isDebugKeybinds()) {
      logKeybinds("Keyboard listener stderr:", output)
    }
  })

  child.on("error", (error) => {
    if (isDebugKeybinds()) {
      logKeybinds("Keyboard listener process error:", error)
    }
  })

  child.on("exit", (code, signal) => {
    if (keyboardListenerChild === child) {
      keyboardListenerChild = null
    }

    if (isDebugKeybinds()) {
      logKeybinds("Keyboard listener process exited:", { code, signal })
    }

    // On Linux, if the process exits with code 1 and mentions PermissionDenied,
    // show a helpful notification about the input group requirement
    if (process.platform === "linux" && code === 1) {
      if (stderrBuffer.includes("PermissionDenied") || stderrBuffer.includes("Permission denied")) {
        const { dialog } = require("electron")

        dialog.showMessageBox({
          type: "warning",
          title: "Global Hotkeys Permission Required",
          message: "To use global hotkeys on Linux (especially Wayland), you need to add your user to the 'input' group.",
          detail: "Run this command in a terminal:\n\nsudo usermod -aG input $USER\n\nThen log out and log back in for the change to take effect.\n\nThis is required because DotAgents needs to read keyboard events from /dev/input/ devices.",
          buttons: ["OK"],
          defaultId: 0,
          cancelId: 0,
          noLink: true,
        })

        // eslint-disable-next-line no-console
        console.error(
          "[DotAgents] Global hotkeys failed: Permission denied.\n" +
          "To fix this on Linux, add your user to the 'input' group:\n" +
          "  sudo usermod -aG input $USER\n" +
          "Then log out and log back in."
        )
      }
    }
  })
}

export async function stopListeningToKeyboardEvents(): Promise<void> {
  const child = keyboardListenerChild
  if (!child) {
    return
  }

  if (keyboardListenerStopState?.child === child) {
    await keyboardListenerStopState.promise
    return
  }

  if (child.exitCode !== null) {
    if (keyboardListenerChild === child) {
      keyboardListenerChild = null
    }
    if (keyboardListenerStopState?.child === child) {
      keyboardListenerStopState = null
    }
    return
  }

  const stopState = {
    child,
    promise: Promise.resolve(),
  }
  keyboardListenerStopState = stopState

  stopState.promise = new Promise<void>((resolve) => {
    let done = false
    let forceKillTimer: ReturnType<typeof setTimeout> | undefined
    let forceKillWaitTimer: ReturnType<typeof setTimeout> | undefined

    const finish = ({ clearChild = true }: { clearChild?: boolean } = {}) => {
      if (done) return
      done = true
      child.off("exit", onExit)
      child.off("error", onError)
      if (forceKillTimer !== undefined) {
        clearTimeout(forceKillTimer)
      }
      if (forceKillWaitTimer !== undefined) {
        clearTimeout(forceKillWaitTimer)
      }
      if (clearChild && keyboardListenerChild === child) {
        keyboardListenerChild = null
      }
      if (keyboardListenerStopState === stopState) {
        keyboardListenerStopState = null
      }
      resolve()
    }

    const onExit = () => {
      finish()
    }

    const onError = () => {
      finish()
    }

    const finishIfExited = () => {
      if (child.exitCode !== null) {
        finish()
        return true
      }
      return false
    }

    const resolveWithoutClearingChild = () => {
      if (isDebugKeybinds()) {
        logKeybinds("Keyboard listener did not confirm exit; preserving child reference to avoid duplicate listeners")
      }
      finish({ clearChild: false })
    }

    child.once("exit", onExit)
    child.once("error", onError)

    if (finishIfExited()) {
      return
    }

    forceKillTimer = setTimeout(() => {
      if (finishIfExited()) {
        return
      }

      try {
        const forceKillSent = child.kill("SIGKILL")
        if (!forceKillSent) {
          if (finishIfExited()) {
            return
          }
          resolveWithoutClearingChild()
          return
        }
      } catch {
        if (finishIfExited()) {
          return
        }
        resolveWithoutClearingChild()
        return
      }

      if (finishIfExited()) {
        return
      }

      forceKillWaitTimer = setTimeout(() => {
        if (finishIfExited()) {
          return
        }
        resolveWithoutClearingChild()
      }, KEYBOARD_LISTENER_FORCE_KILL_WAIT_MS)

      if (typeof forceKillWaitTimer.unref === "function") {
        forceKillWaitTimer.unref()
      }
    }, KEYBOARD_LISTENER_SHUTDOWN_TIMEOUT_MS)

    if (typeof forceKillTimer.unref === "function") {
      forceKillTimer.unref()
    }

    try {
      const terminateSent = child.kill("SIGTERM")
      if (!terminateSent) {
        if (finishIfExited()) {
          return
        }
        resolveWithoutClearingChild()
        return
      }
    } catch {
      if (finishIfExited()) {
        return
      }
      resolveWithoutClearingChild()
      return
    }

    finishIfExited()
  })

  await stopState.promise
}
