/**
 * Keyboard input handling for terminal applications.
 *
 * Supports both legacy terminal sequences and Kitty keyboard protocol.
 * See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
 * Reference: https://github.com/sst/opentui/blob/7da92b4088aebfe27b9f691c04163a48821e49fd/packages/core/src/lib/parse.keypress.ts
 *
 * Symbol keys are also supported, however some ctrl+symbol combos
 * overlap with ASCII codes, e.g. ctrl+[ = ESC.
 * See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/#legacy-ctrl-mapping-of-ascii-keys
 * Those can still be * used for ctrl+shift combos
 *
 * API:
 * - matchesKey(data, keyId) - Check if input matches a key identifier
 * - parseKey(data) - Parse input and return the key identifier
 * - Key - Helper object for creating typed key identifiers
 * - setKittyProtocolActive(active) - Set global Kitty protocol state
 * - isKittyProtocolActive() - Query global Kitty protocol state
 */
// =============================================================================
// Global Kitty Protocol State
// =============================================================================
let _kittyProtocolActive = false;
/**
 * Set the global Kitty keyboard protocol state.
 * Called by ProcessTerminal after detecting protocol support.
 */
export function setKittyProtocolActive(active) {
  _kittyProtocolActive = active;
}
/**
 * Query whether Kitty keyboard protocol is currently active.
 */
export function isKittyProtocolActive() {
  return _kittyProtocolActive;
}
/**
 * Helper object for creating typed key identifiers with autocomplete.
 *
 * Usage:
 * - Key.escape, Key.enter, Key.tab, etc. for special keys
 * - Key.backtick, Key.comma, Key.period, etc. for symbol keys
 * - Key.ctrl("c"), Key.alt("x") for single modifier
 * - Key.ctrlShift("p"), Key.ctrlAlt("x") for combined modifiers
 */
export const Key = {
  // Special keys
  escape: 'escape',
  esc: 'esc',
  enter: 'enter',
  return: 'return',
  tab: 'tab',
  space: 'space',
  backspace: 'backspace',
  delete: 'delete',
  insert: 'insert',
  clear: 'clear',
  home: 'home',
  end: 'end',
  pageUp: 'pageUp',
  pageDown: 'pageDown',
  up: 'up',
  down: 'down',
  left: 'left',
  right: 'right',
  f1: 'f1',
  f2: 'f2',
  f3: 'f3',
  f4: 'f4',
  f5: 'f5',
  f6: 'f6',
  f7: 'f7',
  f8: 'f8',
  f9: 'f9',
  f10: 'f10',
  f11: 'f11',
  f12: 'f12',
  // Symbol keys
  backtick: '`',
  hyphen: '-',
  equals: '=',
  leftbracket: '[',
  rightbracket: ']',
  backslash: '\\',
  semicolon: ';',
  quote: "'",
  comma: ',',
  period: '.',
  slash: '/',
  exclamation: '!',
  at: '@',
  hash: '#',
  dollar: '$',
  percent: '%',
  caret: '^',
  ampersand: '&',
  asterisk: '*',
  leftparen: '(',
  rightparen: ')',
  underscore: '_',
  plus: '+',
  pipe: '|',
  tilde: '~',
  leftbrace: '{',
  rightbrace: '}',
  colon: ':',
  lessthan: '<',
  greaterthan: '>',
  question: '?',
  // Single modifiers
  ctrl: (key) => `ctrl+${key}`,
  shift: (key) => `shift+${key}`,
  alt: (key) => `alt+${key}`,
  // Combined modifiers
  ctrlShift: (key) => `ctrl+shift+${key}`,
  shiftCtrl: (key) => `shift+ctrl+${key}`,
  ctrlAlt: (key) => `ctrl+alt+${key}`,
  altCtrl: (key) => `alt+ctrl+${key}`,
  shiftAlt: (key) => `shift+alt+${key}`,
  altShift: (key) => `alt+shift+${key}`,
  // Triple modifiers
  ctrlShiftAlt: (key) => `ctrl+shift+alt+${key}`
};
// =============================================================================
// Constants
// =============================================================================
const SYMBOL_KEYS = new Set([
  '`',
  '-',
  '=',
  '[',
  ']',
  '\\',
  ';',
  "'",
  ',',
  '.',
  '/',
  '!',
  '@',
  '#',
  '$',
  '%',
  '^',
  '&',
  '*',
  '(',
  ')',
  '_',
  '+',
  '|',
  '~',
  '{',
  '}',
  ':',
  '<',
  '>',
  '?'
]);
const MODIFIERS = {
  shift: 1,
  alt: 2,
  ctrl: 4
};
const LOCK_MASK = 64 + 128; // Caps Lock + Num Lock
const CODEPOINTS = {
  escape: 27,
  tab: 9,
  enter: 13,
  space: 32,
  backspace: 127,
  kpEnter: 57414 // Numpad Enter (Kitty protocol)
};
const ARROW_CODEPOINTS = {
  up: -1,
  down: -2,
  right: -3,
  left: -4
};
const FUNCTIONAL_CODEPOINTS = {
  delete: -10,
  insert: -11,
  pageUp: -12,
  pageDown: -13,
  home: -14,
  end: -15
};
const LEGACY_KEY_SEQUENCES = {
  up: ['\x1b[A', '\x1bOA'],
  down: ['\x1b[B', '\x1bOB'],
  right: ['\x1b[C', '\x1bOC'],
  left: ['\x1b[D', '\x1bOD'],
  home: ['\x1b[H', '\x1bOH', '\x1b[1~', '\x1b[7~'],
  end: ['\x1b[F', '\x1bOF', '\x1b[4~', '\x1b[8~'],
  insert: ['\x1b[2~'],
  delete: ['\x1b[3~'],
  pageUp: ['\x1b[5~', '\x1b[[5~'],
  pageDown: ['\x1b[6~', '\x1b[[6~'],
  clear: ['\x1b[E', '\x1bOE'],
  f1: ['\x1bOP', '\x1b[11~', '\x1b[[A'],
  f2: ['\x1bOQ', '\x1b[12~', '\x1b[[B'],
  f3: ['\x1bOR', '\x1b[13~', '\x1b[[C'],
  f4: ['\x1bOS', '\x1b[14~', '\x1b[[D'],
  f5: ['\x1b[15~', '\x1b[[E'],
  f6: ['\x1b[17~'],
  f7: ['\x1b[18~'],
  f8: ['\x1b[19~'],
  f9: ['\x1b[20~'],
  f10: ['\x1b[21~'],
  f11: ['\x1b[23~'],
  f12: ['\x1b[24~']
};
const LEGACY_SHIFT_SEQUENCES = {
  up: ['\x1b[a'],
  down: ['\x1b[b'],
  right: ['\x1b[c'],
  left: ['\x1b[d'],
  clear: ['\x1b[e'],
  insert: ['\x1b[2$'],
  delete: ['\x1b[3$'],
  pageUp: ['\x1b[5$'],
  pageDown: ['\x1b[6$'],
  home: ['\x1b[7$'],
  end: ['\x1b[8$']
};
const LEGACY_CTRL_SEQUENCES = {
  up: ['\x1bOa'],
  down: ['\x1bOb'],
  right: ['\x1bOc'],
  left: ['\x1bOd'],
  clear: ['\x1bOe'],
  insert: ['\x1b[2^'],
  delete: ['\x1b[3^'],
  pageUp: ['\x1b[5^'],
  pageDown: ['\x1b[6^'],
  home: ['\x1b[7^'],
  end: ['\x1b[8^']
};
const LEGACY_SEQUENCE_KEY_IDS = {
  '\x1bOA': 'up',
  '\x1bOB': 'down',
  '\x1bOC': 'right',
  '\x1bOD': 'left',
  '\x1bOH': 'home',
  '\x1bOF': 'end',
  '\x1b[E': 'clear',
  '\x1bOE': 'clear',
  '\x1bOe': 'ctrl+clear',
  '\x1b[e': 'shift+clear',
  '\x1b[2~': 'insert',
  '\x1b[2$': 'shift+insert',
  '\x1b[2^': 'ctrl+insert',
  '\x1b[3$': 'shift+delete',
  '\x1b[3^': 'ctrl+delete',
  '\x1b[[5~': 'pageUp',
  '\x1b[[6~': 'pageDown',
  '\x1b[a': 'shift+up',
  '\x1b[b': 'shift+down',
  '\x1b[c': 'shift+right',
  '\x1b[d': 'shift+left',
  '\x1bOa': 'ctrl+up',
  '\x1bOb': 'ctrl+down',
  '\x1bOc': 'ctrl+right',
  '\x1bOd': 'ctrl+left',
  '\x1b[5$': 'shift+pageUp',
  '\x1b[6$': 'shift+pageDown',
  '\x1b[7$': 'shift+home',
  '\x1b[8$': 'shift+end',
  '\x1b[5^': 'ctrl+pageUp',
  '\x1b[6^': 'ctrl+pageDown',
  '\x1b[7^': 'ctrl+home',
  '\x1b[8^': 'ctrl+end',
  '\x1bOP': 'f1',
  '\x1bOQ': 'f2',
  '\x1bOR': 'f3',
  '\x1bOS': 'f4',
  '\x1b[11~': 'f1',
  '\x1b[12~': 'f2',
  '\x1b[13~': 'f3',
  '\x1b[14~': 'f4',
  '\x1b[[A': 'f1',
  '\x1b[[B': 'f2',
  '\x1b[[C': 'f3',
  '\x1b[[D': 'f4',
  '\x1b[[E': 'f5',
  '\x1b[15~': 'f5',
  '\x1b[17~': 'f6',
  '\x1b[18~': 'f7',
  '\x1b[19~': 'f8',
  '\x1b[20~': 'f9',
  '\x1b[21~': 'f10',
  '\x1b[23~': 'f11',
  '\x1b[24~': 'f12',
  '\x1bb': 'alt+left',
  '\x1bf': 'alt+right',
  '\x1bp': 'alt+up',
  '\x1bn': 'alt+down'
};
const MOUSE_SCROLL_MASK = 0x43;
const MOUSE_SCROLL_UP = 0x40;
const MOUSE_SCROLL_DOWN = 0x41;
const matchesLegacySequence = (data, sequences) => sequences.includes(data);
const matchesLegacyModifierSequence = (data, key, modifier) => {
  if (modifier === MODIFIERS.shift) {
    return matchesLegacySequence(data, LEGACY_SHIFT_SEQUENCES[key]);
  }
  if (modifier === MODIFIERS.ctrl) {
    return matchesLegacySequence(data, LEGACY_CTRL_SEQUENCES[key]);
  }
  return false;
};
// Store the last parsed event type for isKeyRelease() to query
let _lastEventType = 'press';
/**
 * Check if the last parsed key event was a key release.
 * Only meaningful when Kitty keyboard protocol with flag 2 is active.
 */
export function isKeyRelease(data) {
  // Don't treat bracketed paste content as key release, even if it contains
  // patterns like ":3F" (e.g., bluetooth MAC addresses like "90:62:3F:A5").
  // Terminal.ts re-wraps paste content with bracketed paste markers before
  // passing to TUI, so pasted data will always contain \x1b[200~.
  if (data.includes('\x1b[200~')) {
    return false;
  }
  // Quick check: release events with flag 2 contain ":3"
  // Format: \x1b[<codepoint>;<modifier>:3u
  if (
    data.includes(':3u') ||
    data.includes(':3~') ||
    data.includes(':3A') ||
    data.includes(':3B') ||
    data.includes(':3C') ||
    data.includes(':3D') ||
    data.includes(':3H') ||
    data.includes(':3F')
  ) {
    return true;
  }
  return false;
}
/**
 * Check if the last parsed key event was a key repeat.
 * Only meaningful when Kitty keyboard protocol with flag 2 is active.
 */
export function isKeyRepeat(data) {
  // Don't treat bracketed paste content as key repeat, even if it contains
  // patterns like ":2F". See isKeyRelease() for details.
  if (data.includes('\x1b[200~')) {
    return false;
  }
  if (
    data.includes(':2u') ||
    data.includes(':2~') ||
    data.includes(':2A') ||
    data.includes(':2B') ||
    data.includes(':2C') ||
    data.includes(':2D') ||
    data.includes(':2H') ||
    data.includes(':2F')
  ) {
    return true;
  }
  return false;
}
function parseEventType(eventTypeStr) {
  if (!eventTypeStr) return 'press';
  const eventType = parseInt(eventTypeStr, 10);
  if (eventType === 2) return 'repeat';
  if (eventType === 3) return 'release';
  return 'press';
}
function parseKittySequence(data) {
  // CSI u format with alternate keys (flag 4):
  // \x1b[<codepoint>u
  // \x1b[<codepoint>;<mod>u
  // \x1b[<codepoint>;<mod>:<event>u
  // \x1b[<codepoint>:<shifted>;<mod>u
  // \x1b[<codepoint>:<shifted>:<base>;<mod>u
  // \x1b[<codepoint>::<base>;<mod>u (no shifted key, only base)
  //
  // With flag 2, event type is appended after modifier colon: 1=press, 2=repeat, 3=release
  // With flag 4, alternate keys are appended after codepoint with colons
  const csiUMatch = data.match(/^\x1b\[(\d+)(?::(\d*))?(?::(\d+))?(?:;(\d+))?(?::(\d+))?u$/);
  if (csiUMatch) {
    const codepoint = parseInt(csiUMatch[1], 10);
    const shiftedKey =
      csiUMatch[2] && csiUMatch[2].length > 0 ? parseInt(csiUMatch[2], 10) : undefined;
    const baseLayoutKey = csiUMatch[3] ? parseInt(csiUMatch[3], 10) : undefined;
    const modValue = csiUMatch[4] ? parseInt(csiUMatch[4], 10) : 1;
    const eventType = parseEventType(csiUMatch[5]);
    _lastEventType = eventType;
    return { codepoint, shiftedKey, baseLayoutKey, modifier: modValue - 1, eventType };
  }
  // Arrow keys with modifier: \x1b[1;<mod>A/B/C/D or \x1b[1;<mod>:<event>A/B/C/D
  const arrowMatch = data.match(/^\x1b\[1;(\d+)(?::(\d+))?([ABCD])$/);
  if (arrowMatch) {
    const modValue = parseInt(arrowMatch[1], 10);
    const eventType = parseEventType(arrowMatch[2]);
    const arrowCodes = { A: -1, B: -2, C: -3, D: -4 };
    _lastEventType = eventType;
    return { codepoint: arrowCodes[arrowMatch[3]], modifier: modValue - 1, eventType };
  }
  // Functional keys: \x1b[<num>~ or \x1b[<num>;<mod>~ or \x1b[<num>;<mod>:<event>~
  const funcMatch = data.match(/^\x1b\[(\d+)(?:;(\d+))?(?::(\d+))?~$/);
  if (funcMatch) {
    const keyNum = parseInt(funcMatch[1], 10);
    const modValue = funcMatch[2] ? parseInt(funcMatch[2], 10) : 1;
    const eventType = parseEventType(funcMatch[3]);
    const funcCodes = {
      2: FUNCTIONAL_CODEPOINTS.insert,
      3: FUNCTIONAL_CODEPOINTS.delete,
      5: FUNCTIONAL_CODEPOINTS.pageUp,
      6: FUNCTIONAL_CODEPOINTS.pageDown,
      7: FUNCTIONAL_CODEPOINTS.home,
      8: FUNCTIONAL_CODEPOINTS.end
    };
    const codepoint = funcCodes[keyNum];
    if (codepoint !== undefined) {
      _lastEventType = eventType;
      return { codepoint, modifier: modValue - 1, eventType };
    }
  }
  // Home/End with modifier: \x1b[1;<mod>H/F or \x1b[1;<mod>:<event>H/F
  const homeEndMatch = data.match(/^\x1b\[1;(\d+)(?::(\d+))?([HF])$/);
  if (homeEndMatch) {
    const modValue = parseInt(homeEndMatch[1], 10);
    const eventType = parseEventType(homeEndMatch[2]);
    const codepoint =
      homeEndMatch[3] === 'H' ? FUNCTIONAL_CODEPOINTS.home : FUNCTIONAL_CODEPOINTS.end;
    _lastEventType = eventType;
    return { codepoint, modifier: modValue - 1, eventType };
  }
  return null;
}
function matchesKittySequence(data, expectedCodepoint, expectedModifier) {
  const parsed = parseKittySequence(data);
  if (!parsed) return false;
  const actualMod = parsed.modifier & ~LOCK_MASK;
  const expectedMod = expectedModifier & ~LOCK_MASK;
  // Check if modifiers match
  if (actualMod !== expectedMod) return false;
  // Primary match: codepoint matches directly
  if (parsed.codepoint === expectedCodepoint) return true;
  // Alternate match: use base layout key for non-Latin keyboard layouts.
  // This allows Ctrl+С (Cyrillic) to match Ctrl+c (Latin) when terminal reports
  // the base layout key (the key in standard PC-101 layout).
  //
  // Only fall back to base layout key when the codepoint is NOT already a
  // recognized Latin letter (a-z) or symbol (e.g., /, -, [, ;, etc.).
  // When the codepoint is a recognized key, it is authoritative regardless
  // of physical key position. This prevents remapped layouts (Dvorak, Colemak,
  // xremap, etc.) from causing false matches: both letters and symbols move
  // to different physical positions, so Ctrl+K could falsely match Ctrl+V
  // (letter remapping) and Ctrl+/ could falsely match Ctrl+[ (symbol remapping)
  // if the base layout key were always considered.
  if (parsed.baseLayoutKey !== undefined && parsed.baseLayoutKey === expectedCodepoint) {
    const cp = parsed.codepoint;
    const isLatinLetter = cp >= 97 && cp <= 122; // a-z
    const isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(cp));
    if (!isLatinLetter && !isKnownSymbol) return true;
  }
  return false;
}
/**
 * Match xterm modifyOtherKeys format: CSI 27 ; modifiers ; keycode ~
 * This is used by terminals when Kitty protocol is not enabled.
 * Modifier values are 1-indexed: 2=shift, 3=alt, 5=ctrl, etc.
 */
function matchesModifyOtherKeys(data, expectedKeycode, expectedModifier) {
  const match = data.match(/^\x1b\[27;(\d+);(\d+)~$/);
  if (!match) return false;
  const modValue = parseInt(match[1], 10);
  const keycode = parseInt(match[2], 10);
  // Convert from 1-indexed xterm format to our 0-indexed format
  const actualMod = modValue - 1;
  return keycode === expectedKeycode && actualMod === expectedModifier;
}

function parseMouseButton(button) {
  const normalized = button & MOUSE_SCROLL_MASK;
  if (normalized === MOUSE_SCROLL_UP) {
    return { kind: 'wheel', direction: 'up' };
  }
  if (normalized === MOUSE_SCROLL_DOWN) {
    return { kind: 'wheel', direction: 'down' };
  }
  return { kind: 'button' };
}

function parseSgrMouseEvent(data) {
  const match = data.match(/^\x1b\[<(\d+);(\d+);(\d+)([mM])$/u);
  if (!match) return undefined;

  const button = Number.parseInt(match[1] ?? '', 10);
  const col = Number.parseInt(match[2] ?? '', 10);
  const row = Number.parseInt(match[3] ?? '', 10);
  if (!Number.isFinite(button) || !Number.isFinite(col) || !Number.isFinite(row)) {
    return undefined;
  }

  const parsedButton = parseMouseButton(button);
  return {
    protocol: 'sgr',
    button,
    col,
    row,
    shift: (button & 0x04) !== 0,
    alt: (button & 0x08) !== 0,
    ctrl: (button & 0x10) !== 0,
    action: match[4] === 'm' ? 'release' : 'press',
    ...parsedButton
  };
}

function parseLegacyMouseEvent(data) {
  const match = data.match(/^\x1b\[M([\s\S])([\s\S])([\s\S])$/u);
  if (!match) return undefined;

  const button = (match[1]?.codePointAt(0) ?? 32) - 32;
  const col = (match[2]?.codePointAt(0) ?? 32) - 32;
  const row = (match[3]?.codePointAt(0) ?? 32) - 32;
  if (button < 0 || col < 0 || row < 0) {
    return undefined;
  }

  const parsedButton = parseMouseButton(button);
  return {
    protocol: 'x10',
    button,
    col,
    row,
    shift: (button & 0x04) !== 0,
    alt: (button & 0x08) !== 0,
    ctrl: (button & 0x10) !== 0,
    action: 'press',
    ...parsedButton
  };
}

export function parseMouseEvent(data) {
  return parseSgrMouseEvent(data) ?? parseLegacyMouseEvent(data);
}

export function parseMouseWheelDelta(data, linesPerTick = 1) {
  const event = parseMouseEvent(data);
  if (!event || event.kind !== 'wheel') {
    return 0;
  }

  const lines = Math.max(1, Math.floor(linesPerTick));
  return event.direction === 'up' ? -lines : lines;
}
// =============================================================================
// Generic Key Matching
// =============================================================================
/**
 * Get the control character for a key.
 * Uses the universal formula: code & 0x1f (mask to lower 5 bits)
 *
 * Works for:
 * - Letters a-z → 1-26
 * - Symbols [\]_ → 27, 28, 29, 31
 * - Also maps - to same as _ (same physical key on US keyboards)
 */
function rawCtrlChar(key) {
  const char = key.toLowerCase();
  const code = char.charCodeAt(0);
  if (
    (code >= 97 && code <= 122) ||
    char === '[' ||
    char === '\\' ||
    char === ']' ||
    char === '_'
  ) {
    return String.fromCharCode(code & 0x1f);
  }
  // Handle - as _ (same physical key on US keyboards)
  if (char === '-') {
    return String.fromCharCode(31); // Same as Ctrl+_
  }
  return null;
}
function parseKeyId(keyId) {
  const parts = keyId.toLowerCase().split('+');
  const key = parts[parts.length - 1];
  if (!key) return null;
  return {
    key,
    ctrl: parts.includes('ctrl'),
    shift: parts.includes('shift'),
    alt: parts.includes('alt')
  };
}
/**
 * Match input data against a key identifier string.
 *
 * Supported key identifiers:
 * - Single keys: "escape", "tab", "enter", "backspace", "delete", "home", "end", "space"
 * - Arrow keys: "up", "down", "left", "right"
 * - Ctrl combinations: "ctrl+c", "ctrl+z", etc.
 * - Shift combinations: "shift+tab", "shift+enter"
 * - Alt combinations: "alt+enter", "alt+backspace"
 * - Combined modifiers: "shift+ctrl+p", "ctrl+alt+x"
 *
 * Use the Key helper for autocomplete: Key.ctrl("c"), Key.escape, Key.ctrlShift("p")
 *
 * @param data - Raw input data from terminal
 * @param keyId - Key identifier (e.g., "ctrl+c", "escape", Key.ctrl("c"))
 */
export function matchesKey(data, keyId) {
  const parsed = parseKeyId(keyId);
  if (!parsed) return false;
  const { key, ctrl, shift, alt } = parsed;
  let modifier = 0;
  if (shift) modifier |= MODIFIERS.shift;
  if (alt) modifier |= MODIFIERS.alt;
  if (ctrl) modifier |= MODIFIERS.ctrl;
  switch (key) {
    case 'escape':
    case 'esc':
      if (modifier !== 0) return false;
      return data === '\x1b' || matchesKittySequence(data, CODEPOINTS.escape, 0);
    case 'space':
      if (!_kittyProtocolActive) {
        if (ctrl && !alt && !shift && data === '\x00') {
          return true;
        }
        if (alt && !ctrl && !shift && data === '\x1b ') {
          return true;
        }
      }
      if (modifier === 0) {
        return data === ' ' || matchesKittySequence(data, CODEPOINTS.space, 0);
      }
      return matchesKittySequence(data, CODEPOINTS.space, modifier);
    case 'tab':
      if (shift && !ctrl && !alt) {
        return data === '\x1b[Z' || matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift);
      }
      if (modifier === 0) {
        return data === '\t' || matchesKittySequence(data, CODEPOINTS.tab, 0);
      }
      return matchesKittySequence(data, CODEPOINTS.tab, modifier);
    case 'enter':
    case 'return':
      if (shift && !ctrl && !alt) {
        // CSI u sequences (standard Kitty protocol)
        if (
          matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.shift) ||
          matchesKittySequence(data, CODEPOINTS.kpEnter, MODIFIERS.shift)
        ) {
          return true;
        }
        // xterm modifyOtherKeys format (fallback when Kitty protocol not enabled)
        if (matchesModifyOtherKeys(data, CODEPOINTS.enter, MODIFIERS.shift)) {
          return true;
        }
        // When Kitty protocol is active, legacy sequences are custom terminal mappings
        // \x1b\r = Kitty's "map shift+enter send_text all \e\r"
        // \n = Ghostty's "keybind = shift+enter=text:\n"
        if (_kittyProtocolActive) {
          return data === '\x1b\r' || data === '\n';
        }
        return false;
      }
      if (alt && !ctrl && !shift) {
        // CSI u sequences (standard Kitty protocol)
        if (
          matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt) ||
          matchesKittySequence(data, CODEPOINTS.kpEnter, MODIFIERS.alt)
        ) {
          return true;
        }
        // xterm modifyOtherKeys format (fallback when Kitty protocol not enabled)
        if (matchesModifyOtherKeys(data, CODEPOINTS.enter, MODIFIERS.alt)) {
          return true;
        }
        // \x1b\r is alt+enter only in legacy mode (no Kitty protocol)
        // When Kitty protocol is active, alt+enter comes as CSI u sequence
        if (!_kittyProtocolActive) {
          return data === '\x1b\r';
        }
        return false;
      }
      if (modifier === 0) {
        return (
          data === '\r' ||
          (!_kittyProtocolActive && data === '\n') ||
          data === '\x1bOM' || // SS3 M (numpad enter in some terminals)
          matchesKittySequence(data, CODEPOINTS.enter, 0) ||
          matchesKittySequence(data, CODEPOINTS.kpEnter, 0)
        );
      }
      return (
        matchesKittySequence(data, CODEPOINTS.enter, modifier) ||
        matchesKittySequence(data, CODEPOINTS.kpEnter, modifier)
      );
    case 'backspace':
      if (alt && !ctrl && !shift) {
        if (data === '\x1b\x7f' || data === '\x1b\b') {
          return true;
        }
        return matchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.alt);
      }
      if (modifier === 0) {
        return (
          data === '\x7f' || data === '\x08' || matchesKittySequence(data, CODEPOINTS.backspace, 0)
        );
      }
      return matchesKittySequence(data, CODEPOINTS.backspace, modifier);
    case 'insert':
      if (modifier === 0) {
        return (
          matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.insert) ||
          matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.insert, 0)
        );
      }
      if (matchesLegacyModifierSequence(data, 'insert', modifier)) {
        return true;
      }
      return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.insert, modifier);
    case 'delete':
      if (modifier === 0) {
        return (
          matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.delete) ||
          matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, 0)
        );
      }
      if (matchesLegacyModifierSequence(data, 'delete', modifier)) {
        return true;
      }
      return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, modifier);
    case 'clear':
      if (modifier === 0) {
        return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.clear);
      }
      return matchesLegacyModifierSequence(data, 'clear', modifier);
    case 'home':
      if (modifier === 0) {
        return (
          matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.home) ||
          matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, 0)
        );
      }
      if (matchesLegacyModifierSequence(data, 'home', modifier)) {
        return true;
      }
      return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, modifier);
    case 'end':
      if (modifier === 0) {
        return (
          matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.end) ||
          matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.end, 0)
        );
      }
      if (matchesLegacyModifierSequence(data, 'end', modifier)) {
        return true;
      }
      return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.end, modifier);
    case 'pageup':
      if (modifier === 0) {
        return (
          matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.pageUp) ||
          matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageUp, 0)
        );
      }
      if (matchesLegacyModifierSequence(data, 'pageUp', modifier)) {
        return true;
      }
      return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageUp, modifier);
    case 'pagedown':
      if (modifier === 0) {
        return (
          matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.pageDown) ||
          matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageDown, 0)
        );
      }
      if (matchesLegacyModifierSequence(data, 'pageDown', modifier)) {
        return true;
      }
      return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageDown, modifier);
    case 'up':
      if (alt && !ctrl && !shift) {
        return data === '\x1bp' || matchesKittySequence(data, ARROW_CODEPOINTS.up, MODIFIERS.alt);
      }
      if (modifier === 0) {
        return (
          matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.up) ||
          matchesKittySequence(data, ARROW_CODEPOINTS.up, 0)
        );
      }
      if (matchesLegacyModifierSequence(data, 'up', modifier)) {
        return true;
      }
      return matchesKittySequence(data, ARROW_CODEPOINTS.up, modifier);
    case 'down':
      if (alt && !ctrl && !shift) {
        return data === '\x1bn' || matchesKittySequence(data, ARROW_CODEPOINTS.down, MODIFIERS.alt);
      }
      if (modifier === 0) {
        return (
          matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.down) ||
          matchesKittySequence(data, ARROW_CODEPOINTS.down, 0)
        );
      }
      if (matchesLegacyModifierSequence(data, 'down', modifier)) {
        return true;
      }
      return matchesKittySequence(data, ARROW_CODEPOINTS.down, modifier);
    case 'left':
      if (alt && !ctrl && !shift) {
        return (
          data === '\x1b[1;3D' ||
          (!_kittyProtocolActive && data === '\x1bB') ||
          data === '\x1bb' ||
          matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.alt)
        );
      }
      if (ctrl && !alt && !shift) {
        return (
          data === '\x1b[1;5D' ||
          matchesLegacyModifierSequence(data, 'left', MODIFIERS.ctrl) ||
          matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.ctrl)
        );
      }
      if (modifier === 0) {
        return (
          matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.left) ||
          matchesKittySequence(data, ARROW_CODEPOINTS.left, 0)
        );
      }
      if (matchesLegacyModifierSequence(data, 'left', modifier)) {
        return true;
      }
      return matchesKittySequence(data, ARROW_CODEPOINTS.left, modifier);
    case 'right':
      if (alt && !ctrl && !shift) {
        return (
          data === '\x1b[1;3C' ||
          (!_kittyProtocolActive && data === '\x1bF') ||
          data === '\x1bf' ||
          matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.alt)
        );
      }
      if (ctrl && !alt && !shift) {
        return (
          data === '\x1b[1;5C' ||
          matchesLegacyModifierSequence(data, 'right', MODIFIERS.ctrl) ||
          matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.ctrl)
        );
      }
      if (modifier === 0) {
        return (
          matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.right) ||
          matchesKittySequence(data, ARROW_CODEPOINTS.right, 0)
        );
      }
      if (matchesLegacyModifierSequence(data, 'right', modifier)) {
        return true;
      }
      return matchesKittySequence(data, ARROW_CODEPOINTS.right, modifier);
    case 'f1':
    case 'f2':
    case 'f3':
    case 'f4':
    case 'f5':
    case 'f6':
    case 'f7':
    case 'f8':
    case 'f9':
    case 'f10':
    case 'f11':
    case 'f12': {
      if (modifier !== 0) {
        return false;
      }
      const functionKey = key;
      return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES[functionKey]);
    }
  }
  // Handle single letter keys (a-z) and some symbols
  if (key.length === 1 && ((key >= 'a' && key <= 'z') || SYMBOL_KEYS.has(key))) {
    const codepoint = key.charCodeAt(0);
    const rawCtrl = rawCtrlChar(key);
    if (ctrl && alt && !shift && !_kittyProtocolActive && rawCtrl) {
      // Legacy: ctrl+alt+key is ESC followed by the control character
      return data === `\x1b${rawCtrl}`;
    }
    if (alt && !ctrl && !shift && !_kittyProtocolActive && key >= 'a' && key <= 'z') {
      // Legacy: alt+letter is ESC followed by the letter
      if (data === `\x1b${key}`) return true;
    }
    if (ctrl && !shift && !alt) {
      // Legacy: ctrl+key sends the control character
      if (rawCtrl && data === rawCtrl) return true;
      return matchesKittySequence(data, codepoint, MODIFIERS.ctrl);
    }
    if (ctrl && shift && !alt) {
      return matchesKittySequence(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl);
    }
    if (shift && !ctrl && !alt) {
      // Legacy: shift+letter produces uppercase
      if (data === key.toUpperCase()) return true;
      return matchesKittySequence(data, codepoint, MODIFIERS.shift);
    }
    if (modifier !== 0) {
      return matchesKittySequence(data, codepoint, modifier);
    }
    // Check both raw char and Kitty sequence (needed for release events)
    return data === key || matchesKittySequence(data, codepoint, 0);
  }
  return false;
}
/**
 * Parse input data and return the key identifier if recognized.
 *
 * @param data - Raw input data from terminal
 * @returns Key identifier string (e.g., "ctrl+c") or undefined
 */
export function parseKey(data) {
  const kitty = parseKittySequence(data);
  if (kitty) {
    const { codepoint, baseLayoutKey, modifier } = kitty;
    const mods = [];
    const effectiveMod = modifier & ~LOCK_MASK;
    if (effectiveMod & MODIFIERS.shift) mods.push('shift');
    if (effectiveMod & MODIFIERS.ctrl) mods.push('ctrl');
    if (effectiveMod & MODIFIERS.alt) mods.push('alt');
    // Use base layout key only when codepoint is not a recognized Latin
    // letter (a-z) or symbol (/, -, [, ;, etc.). For those, the codepoint
    // is authoritative regardless of physical key position. This prevents
    // remapped layouts (Dvorak, Colemak, xremap, etc.) from reporting the
    // wrong key name based on the QWERTY physical position.
    const isLatinLetter = codepoint >= 97 && codepoint <= 122; // a-z
    const isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(codepoint));
    const effectiveCodepoint =
      isLatinLetter || isKnownSymbol ? codepoint : (baseLayoutKey ?? codepoint);
    let keyName;
    if (effectiveCodepoint === CODEPOINTS.escape) keyName = 'escape';
    else if (effectiveCodepoint === CODEPOINTS.tab) keyName = 'tab';
    else if (effectiveCodepoint === CODEPOINTS.enter || effectiveCodepoint === CODEPOINTS.kpEnter)
      keyName = 'enter';
    else if (effectiveCodepoint === CODEPOINTS.space) keyName = 'space';
    else if (effectiveCodepoint === CODEPOINTS.backspace) keyName = 'backspace';
    else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.delete) keyName = 'delete';
    else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.insert) keyName = 'insert';
    else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.home) keyName = 'home';
    else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.end) keyName = 'end';
    else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageUp) keyName = 'pageUp';
    else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageDown) keyName = 'pageDown';
    else if (effectiveCodepoint === ARROW_CODEPOINTS.up) keyName = 'up';
    else if (effectiveCodepoint === ARROW_CODEPOINTS.down) keyName = 'down';
    else if (effectiveCodepoint === ARROW_CODEPOINTS.left) keyName = 'left';
    else if (effectiveCodepoint === ARROW_CODEPOINTS.right) keyName = 'right';
    else if (effectiveCodepoint >= 97 && effectiveCodepoint <= 122)
      keyName = String.fromCharCode(effectiveCodepoint);
    else if (SYMBOL_KEYS.has(String.fromCharCode(effectiveCodepoint)))
      keyName = String.fromCharCode(effectiveCodepoint);
    if (keyName) {
      return mods.length > 0 ? `${mods.join('+')}+${keyName}` : keyName;
    }
  }
  // Mode-aware legacy sequences
  // When Kitty protocol is active, ambiguous sequences are interpreted as custom terminal mappings:
  // - \x1b\r = shift+enter (Kitty mapping), not alt+enter
  // - \n = shift+enter (Ghostty mapping)
  if (_kittyProtocolActive) {
    if (data === '\x1b\r' || data === '\n') return 'shift+enter';
  }
  const legacySequenceKeyId = LEGACY_SEQUENCE_KEY_IDS[data];
  if (legacySequenceKeyId) return legacySequenceKeyId;
  // Legacy sequences (used when Kitty protocol is not active, or for unambiguous sequences)
  if (data === '\x1b') return 'escape';
  if (data === '\x1c') return 'ctrl+\\';
  if (data === '\x1d') return 'ctrl+]';
  if (data === '\x1f') return 'ctrl+-';
  if (data === '\x1b\x1b') return 'ctrl+alt+[';
  if (data === '\x1b\x1c') return 'ctrl+alt+\\';
  if (data === '\x1b\x1d') return 'ctrl+alt+]';
  if (data === '\x1b\x1f') return 'ctrl+alt+-';
  if (data === '\t') return 'tab';
  if (data === '\r' || (!_kittyProtocolActive && data === '\n') || data === '\x1bOM')
    return 'enter';
  if (data === '\x00') return 'ctrl+space';
  if (data === ' ') return 'space';
  if (data === '\x7f' || data === '\x08') return 'backspace';
  if (data === '\x1b[Z') return 'shift+tab';
  if (!_kittyProtocolActive && data === '\x1b\r') return 'alt+enter';
  if (!_kittyProtocolActive && data === '\x1b ') return 'alt+space';
  if (data === '\x1b\x7f' || data === '\x1b\b') return 'alt+backspace';
  if (!_kittyProtocolActive && data === '\x1bB') return 'alt+left';
  if (!_kittyProtocolActive && data === '\x1bF') return 'alt+right';
  if (!_kittyProtocolActive && data.length === 2 && data[0] === '\x1b') {
    const code = data.charCodeAt(1);
    if (code >= 1 && code <= 26) {
      return `ctrl+alt+${String.fromCharCode(code + 96)}`;
    }
    // Legacy alt+letter (ESC followed by letter a-z)
    if (code >= 97 && code <= 122) {
      return `alt+${String.fromCharCode(code)}`;
    }
  }
  if (data === '\x1b[A') return 'up';
  if (data === '\x1b[B') return 'down';
  if (data === '\x1b[C') return 'right';
  if (data === '\x1b[D') return 'left';
  if (data === '\x1b[H' || data === '\x1bOH') return 'home';
  if (data === '\x1b[F' || data === '\x1bOF') return 'end';
  if (data === '\x1b[3~') return 'delete';
  if (data === '\x1b[5~') return 'pageUp';
  if (data === '\x1b[6~') return 'pageDown';
  // Raw Ctrl+letter
  if (data.length === 1) {
    const code = data.charCodeAt(0);
    if (code >= 1 && code <= 26) {
      return `ctrl+${String.fromCharCode(code + 96)}`;
    }
    if (code >= 32 && code <= 126) {
      return data;
    }
  }
  return undefined;
}
//# sourceMappingURL=keys.js.map
