import { getEditorKeybindings } from '../keybindings.js';
import { KillRing } from '../kill-ring.js';
import { CURSOR_MARKER } from '../tui.js';
import { UndoStack } from '../undo-stack.js';
import { getSegmenter, isPunctuationChar, isWhitespaceChar, visibleWidth } from '../utils.js';
const segmenter = getSegmenter();
// Kitty CSI-u sequences for printable keys, including optional shifted/base codepoints.
const KITTY_CSI_U_REGEX = /^\x1b\[(\d+)(?::(\d*))?(?::(\d+))?(?:;(\d+))?(?::(\d+))?u$/;
const KITTY_MOD_SHIFT = 1;
const KITTY_MOD_ALT = 2;
const KITTY_MOD_CTRL = 4;
// Decode a printable CSI-u sequence, preferring the shifted key when present.
function decodeKittyPrintable(data) {
  const match = data.match(KITTY_CSI_U_REGEX);
  if (!match) return undefined;
  const codepoint = Number.parseInt(match[1] ?? '', 10);
  if (!Number.isFinite(codepoint)) return undefined;
  const shiftedKey = match[2] && match[2].length > 0 ? Number.parseInt(match[2], 10) : undefined;
  const modValue = match[4] ? Number.parseInt(match[4], 10) : 1;
  const modifier = Number.isFinite(modValue) ? modValue - 1 : 0;
  if (modifier & (KITTY_MOD_ALT | KITTY_MOD_CTRL)) return undefined;
  let effectiveCodepoint = codepoint;
  if (modifier & KITTY_MOD_SHIFT && typeof shiftedKey === 'number') {
    effectiveCodepoint = shiftedKey;
  }
  if (!Number.isFinite(effectiveCodepoint) || effectiveCodepoint < 32) return undefined;
  try {
    return String.fromCodePoint(effectiveCodepoint);
  } catch {
    return undefined;
  }
}
/**
 * Input component - single-line text input with horizontal scrolling
 */
export class Input {
  value = '';
  cursor = 0; // Cursor position in the value
  onSubmit;
  onEscape;
  /** Focusable interface - set by TUI when focus changes */
  focused = false;
  // Bracketed paste mode buffering
  pasteBuffer = '';
  isInPaste = false;
  // Kill ring for Emacs-style kill/yank operations
  killRing = new KillRing();
  lastAction = null;
  // Undo support
  undoStack = new UndoStack();
  getValue() {
    return this.value;
  }
  setValue(value) {
    this.value = value;
    this.cursor = Math.min(this.cursor, value.length);
  }
  handleInput(data) {
    // Handle bracketed paste mode
    // Start of paste: \x1b[200~
    // End of paste: \x1b[201~
    // Check if we're starting a bracketed paste
    if (data.includes('\x1b[200~')) {
      this.isInPaste = true;
      this.pasteBuffer = '';
      data = data.replace('\x1b[200~', '');
    }
    // If we're in a paste, buffer the data
    if (this.isInPaste) {
      // Check if this chunk contains the end marker
      this.pasteBuffer += data;
      const endIndex = this.pasteBuffer.indexOf('\x1b[201~');
      if (endIndex !== -1) {
        // Extract the pasted content
        const pasteContent = this.pasteBuffer.substring(0, endIndex);
        // Process the complete paste
        this.handlePaste(pasteContent);
        // Reset paste state
        this.isInPaste = false;
        // Handle any remaining input after the paste marker
        const remaining = this.pasteBuffer.substring(endIndex + 6); // 6 = length of \x1b[201~
        this.pasteBuffer = '';
        if (remaining) {
          this.handleInput(remaining);
        }
      }
      return;
    }
    const kb = getEditorKeybindings();
    // Escape/Cancel
    if (kb.matches(data, 'selectCancel')) {
      if (this.onEscape) this.onEscape();
      return;
    }
    // Undo
    if (kb.matches(data, 'undo')) {
      this.undo();
      return;
    }
    // Submit
    if (kb.matches(data, 'submit') || data === '\n') {
      if (this.onSubmit) this.onSubmit(this.value);
      return;
    }
    // Deletion
    if (kb.matches(data, 'deleteCharBackward')) {
      this.handleBackspace();
      return;
    }
    if (kb.matches(data, 'deleteCharForward')) {
      this.handleForwardDelete();
      return;
    }
    if (kb.matches(data, 'deleteWordBackward')) {
      this.deleteWordBackwards();
      return;
    }
    if (kb.matches(data, 'deleteWordForward')) {
      this.deleteWordForward();
      return;
    }
    if (kb.matches(data, 'deleteToLineStart')) {
      this.deleteToLineStart();
      return;
    }
    if (kb.matches(data, 'deleteToLineEnd')) {
      this.deleteToLineEnd();
      return;
    }
    // Kill ring actions
    if (kb.matches(data, 'yank')) {
      this.yank();
      return;
    }
    if (kb.matches(data, 'yankPop')) {
      this.yankPop();
      return;
    }
    // Cursor movement
    if (kb.matches(data, 'cursorLeft')) {
      this.lastAction = null;
      if (this.cursor > 0) {
        const beforeCursor = this.value.slice(0, this.cursor);
        const graphemes = [...segmenter.segment(beforeCursor)];
        const lastGrapheme = graphemes[graphemes.length - 1];
        this.cursor -= lastGrapheme ? lastGrapheme.segment.length : 1;
      }
      return;
    }
    if (kb.matches(data, 'cursorRight')) {
      this.lastAction = null;
      if (this.cursor < this.value.length) {
        const afterCursor = this.value.slice(this.cursor);
        const graphemes = [...segmenter.segment(afterCursor)];
        const firstGrapheme = graphemes[0];
        this.cursor += firstGrapheme ? firstGrapheme.segment.length : 1;
      }
      return;
    }
    if (kb.matches(data, 'cursorLineStart')) {
      this.lastAction = null;
      this.cursor = 0;
      return;
    }
    if (kb.matches(data, 'cursorLineEnd')) {
      this.lastAction = null;
      this.cursor = this.value.length;
      return;
    }
    if (kb.matches(data, 'cursorWordLeft')) {
      this.moveWordBackwards();
      return;
    }
    if (kb.matches(data, 'cursorWordRight')) {
      this.moveWordForwards();
      return;
    }
    // Kitty CSI-u sequences for printable keys
    const kittyPrintable = decodeKittyPrintable(data);
    if (kittyPrintable !== undefined) {
      this.insertCharacter(kittyPrintable);
      return;
    }
    // Regular character input - accept printable characters including Unicode,
    // but reject control characters (C0: 0x00-0x1F, DEL: 0x7F, C1: 0x80-0x9F)
    const hasControlChars = [...data].some((ch) => {
      const code = ch.charCodeAt(0);
      return code < 32 || code === 0x7f || (code >= 0x80 && code <= 0x9f);
    });
    if (!hasControlChars) {
      this.insertCharacter(data);
    }
  }
  insertCharacter(char) {
    // Undo coalescing: consecutive word chars coalesce into one undo unit
    if (isWhitespaceChar(char) || this.lastAction !== 'type-word') {
      this.pushUndo();
    }
    this.lastAction = 'type-word';
    this.value = this.value.slice(0, this.cursor) + char + this.value.slice(this.cursor);
    this.cursor += char.length;
  }
  handleBackspace() {
    this.lastAction = null;
    if (this.cursor > 0) {
      this.pushUndo();
      const beforeCursor = this.value.slice(0, this.cursor);
      const graphemes = [...segmenter.segment(beforeCursor)];
      const lastGrapheme = graphemes[graphemes.length - 1];
      const graphemeLength = lastGrapheme ? lastGrapheme.segment.length : 1;
      this.value =
        this.value.slice(0, this.cursor - graphemeLength) + this.value.slice(this.cursor);
      this.cursor -= graphemeLength;
    }
  }
  handleForwardDelete() {
    this.lastAction = null;
    if (this.cursor < this.value.length) {
      this.pushUndo();
      const afterCursor = this.value.slice(this.cursor);
      const graphemes = [...segmenter.segment(afterCursor)];
      const firstGrapheme = graphemes[0];
      const graphemeLength = firstGrapheme ? firstGrapheme.segment.length : 1;
      this.value =
        this.value.slice(0, this.cursor) + this.value.slice(this.cursor + graphemeLength);
    }
  }
  deleteToLineStart() {
    if (this.cursor === 0) return;
    this.pushUndo();
    const deletedText = this.value.slice(0, this.cursor);
    this.killRing.push(deletedText, { prepend: true, accumulate: this.lastAction === 'kill' });
    this.lastAction = 'kill';
    this.value = this.value.slice(this.cursor);
    this.cursor = 0;
  }
  deleteToLineEnd() {
    if (this.cursor >= this.value.length) return;
    this.pushUndo();
    const deletedText = this.value.slice(this.cursor);
    this.killRing.push(deletedText, { prepend: false, accumulate: this.lastAction === 'kill' });
    this.lastAction = 'kill';
    this.value = this.value.slice(0, this.cursor);
  }
  deleteWordBackwards() {
    if (this.cursor === 0) return;
    // Save lastAction before cursor movement (moveWordBackwards resets it)
    const wasKill = this.lastAction === 'kill';
    this.pushUndo();
    const oldCursor = this.cursor;
    this.moveWordBackwards();
    const deleteFrom = this.cursor;
    this.cursor = oldCursor;
    const deletedText = this.value.slice(deleteFrom, this.cursor);
    this.killRing.push(deletedText, { prepend: true, accumulate: wasKill });
    this.lastAction = 'kill';
    this.value = this.value.slice(0, deleteFrom) + this.value.slice(this.cursor);
    this.cursor = deleteFrom;
  }
  deleteWordForward() {
    if (this.cursor >= this.value.length) return;
    // Save lastAction before cursor movement (moveWordForwards resets it)
    const wasKill = this.lastAction === 'kill';
    this.pushUndo();
    const oldCursor = this.cursor;
    this.moveWordForwards();
    const deleteTo = this.cursor;
    this.cursor = oldCursor;
    const deletedText = this.value.slice(this.cursor, deleteTo);
    this.killRing.push(deletedText, { prepend: false, accumulate: wasKill });
    this.lastAction = 'kill';
    this.value = this.value.slice(0, this.cursor) + this.value.slice(deleteTo);
  }
  yank() {
    const text = this.killRing.peek();
    if (!text) return;
    this.pushUndo();
    this.value = this.value.slice(0, this.cursor) + text + this.value.slice(this.cursor);
    this.cursor += text.length;
    this.lastAction = 'yank';
  }
  yankPop() {
    if (this.lastAction !== 'yank' || this.killRing.length <= 1) return;
    this.pushUndo();
    // Delete the previously yanked text (still at end of ring before rotation)
    const prevText = this.killRing.peek() || '';
    this.value = this.value.slice(0, this.cursor - prevText.length) + this.value.slice(this.cursor);
    this.cursor -= prevText.length;
    // Rotate and insert new entry
    this.killRing.rotate();
    const text = this.killRing.peek() || '';
    this.value = this.value.slice(0, this.cursor) + text + this.value.slice(this.cursor);
    this.cursor += text.length;
    this.lastAction = 'yank';
  }
  pushUndo() {
    this.undoStack.push({ value: this.value, cursor: this.cursor });
  }
  undo() {
    const snapshot = this.undoStack.pop();
    if (!snapshot) return;
    this.value = snapshot.value;
    this.cursor = snapshot.cursor;
    this.lastAction = null;
  }
  moveWordBackwards() {
    if (this.cursor === 0) {
      return;
    }
    this.lastAction = null;
    const textBeforeCursor = this.value.slice(0, this.cursor);
    const graphemes = [...segmenter.segment(textBeforeCursor)];
    // Skip trailing whitespace
    while (
      graphemes.length > 0 &&
      isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || '')
    ) {
      this.cursor -= graphemes.pop()?.segment.length || 0;
    }
    if (graphemes.length > 0) {
      const lastGrapheme = graphemes[graphemes.length - 1]?.segment || '';
      if (isPunctuationChar(lastGrapheme)) {
        // Skip punctuation run
        while (
          graphemes.length > 0 &&
          isPunctuationChar(graphemes[graphemes.length - 1]?.segment || '')
        ) {
          this.cursor -= graphemes.pop()?.segment.length || 0;
        }
      } else {
        // Skip word run
        while (
          graphemes.length > 0 &&
          !isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || '') &&
          !isPunctuationChar(graphemes[graphemes.length - 1]?.segment || '')
        ) {
          this.cursor -= graphemes.pop()?.segment.length || 0;
        }
      }
    }
  }
  moveWordForwards() {
    if (this.cursor >= this.value.length) {
      return;
    }
    this.lastAction = null;
    const textAfterCursor = this.value.slice(this.cursor);
    const segments = segmenter.segment(textAfterCursor);
    const iterator = segments[Symbol.iterator]();
    let next = iterator.next();
    // Skip leading whitespace
    while (!next.done && isWhitespaceChar(next.value.segment)) {
      this.cursor += next.value.segment.length;
      next = iterator.next();
    }
    if (!next.done) {
      const firstGrapheme = next.value.segment;
      if (isPunctuationChar(firstGrapheme)) {
        // Skip punctuation run
        while (!next.done && isPunctuationChar(next.value.segment)) {
          this.cursor += next.value.segment.length;
          next = iterator.next();
        }
      } else {
        // Skip word run
        while (
          !next.done &&
          !isWhitespaceChar(next.value.segment) &&
          !isPunctuationChar(next.value.segment)
        ) {
          this.cursor += next.value.segment.length;
          next = iterator.next();
        }
      }
    }
  }
  handlePaste(pastedText) {
    this.lastAction = null;
    this.pushUndo();
    // Clean the pasted text - remove newlines and carriage returns
    const cleanText = pastedText.replace(/\r\n/g, '').replace(/\r/g, '').replace(/\n/g, '');
    // Insert at cursor position
    this.value = this.value.slice(0, this.cursor) + cleanText + this.value.slice(this.cursor);
    this.cursor += cleanText.length;
  }
  invalidate() {
    // No cached state to invalidate currently
  }
  render(width) {
    // Calculate visible window
    const prompt = '> ';
    const availableWidth = width - prompt.length;
    if (availableWidth <= 0) {
      return [prompt];
    }
    let visibleText = '';
    let cursorDisplay = this.cursor;
    if (this.value.length < availableWidth) {
      // Everything fits (leave room for cursor at end)
      visibleText = this.value;
    } else {
      // Need horizontal scrolling
      // Reserve one character for cursor if it's at the end
      const scrollWidth = this.cursor === this.value.length ? availableWidth - 1 : availableWidth;
      const halfWidth = Math.floor(scrollWidth / 2);
      const findValidStart = (start) => {
        while (start < this.value.length) {
          const charCode = this.value.charCodeAt(start);
          // this is low surrogate, not a valid start
          if (charCode >= 0xdc00 && charCode < 0xe000) {
            start++;
            continue;
          }
          break;
        }
        return start;
      };
      const findValidEnd = (end) => {
        while (end > 0) {
          const charCode = this.value.charCodeAt(end - 1);
          // this is high surrogate, might be split.
          if (charCode >= 0xd800 && charCode < 0xdc00) {
            end--;
            continue;
          }
          break;
        }
        return end;
      };
      if (this.cursor < halfWidth) {
        // Cursor near start
        visibleText = this.value.slice(0, findValidEnd(scrollWidth));
        cursorDisplay = this.cursor;
      } else if (this.cursor > this.value.length - halfWidth) {
        // Cursor near end
        const start = findValidStart(this.value.length - scrollWidth);
        visibleText = this.value.slice(start);
        cursorDisplay = this.cursor - start;
      } else {
        // Cursor in middle
        const start = findValidStart(this.cursor - halfWidth);
        visibleText = this.value.slice(start, findValidEnd(start + scrollWidth));
        cursorDisplay = halfWidth;
      }
    }
    // Build line with fake cursor
    // Insert cursor character at cursor position
    const graphemes = [...segmenter.segment(visibleText.slice(cursorDisplay))];
    const cursorGrapheme = graphemes[0];
    const beforeCursor = visibleText.slice(0, cursorDisplay);
    const atCursor = cursorGrapheme?.segment ?? ' '; // Character at cursor, or space if at end
    const afterCursor = visibleText.slice(cursorDisplay + atCursor.length);
    // Hardware cursor marker (zero-width, emitted before fake cursor for IME positioning)
    const marker = this.focused ? CURSOR_MARKER : '';
    // Use inverse video to show cursor
    const cursorChar = `\x1b[7m${atCursor}\x1b[27m`; // ESC[7m = reverse video, ESC[27m = normal
    const textWithCursor = beforeCursor + marker + cursorChar + afterCursor;
    // Calculate visual width
    const visualLength = visibleWidth(textWithCursor);
    const padding = ' '.repeat(Math.max(0, availableWidth - visualLength));
    const line = prompt + textWithCursor + padding;
    return [line];
  }
}
//# sourceMappingURL=input.js.map
