export function applyTextInput(
  key: string,
  currentString: string,
  cursorPos: number,
  maxLength: number,
): { newString: string; newCursorPos: number } {
  let newString = currentString;
  let newCursorPos = cursorPos;
  const lowerKey = key.toLowerCase();

  if (lowerKey === 'arrowleft') {
    newCursorPos = Math.max(0, cursorPos - 1);
  } else if (lowerKey === 'arrowright') {
    newCursorPos = Math.min(newString.length, cursorPos + 1);
  } else if (lowerKey === 'backspace') {
    if (cursorPos > 0) {
      newString = newString.slice(0, cursorPos - 1) + newString.slice(cursorPos);
      newCursorPos = cursorPos - 1;
    }
  } else if (lowerKey === 'delete') {
    if (cursorPos < newString.length) {
      newString = newString.slice(0, cursorPos) + newString.slice(cursorPos + 1);
    }
  } else if (lowerKey === 'home') {
    newCursorPos = 0;
  } else if (lowerKey === 'end') {
    newCursorPos = newString.length;
  } else if (key.length === 1 && newString.length < maxLength) {
    newString = newString.slice(0, cursorPos) + key + newString.slice(cursorPos);
    newCursorPos = cursorPos + 1;
  }

  return { newString, newCursorPos };
}

export function shouldReplaceCurrentText(
  code: string,
  key: string,
  replaceTextOnNextType: boolean,
): { replaceTextOnNextType: boolean; shouldReplace: boolean } {
  if (!replaceTextOnNextType) return { replaceTextOnNextType: false, shouldReplace: false };
  if (code === 'ArrowLeft' || code === 'ArrowRight' || code === 'Home' || code === 'End') {
    return { replaceTextOnNextType: false, shouldReplace: false };
  }
  if (code === 'Backspace' || code === 'Delete') {
    return { replaceTextOnNextType: false, shouldReplace: false };
  }
  if (key.length === 1) {
    return { replaceTextOnNextType: false, shouldReplace: true };
  }
  return { replaceTextOnNextType: true, shouldReplace: false };
}

export function normalizePastedText(raw: string): string {
  return raw
    .replace(/\r\n/g, '\n')
    .replace(/\r/g, '\n')
    .replace(/\n/g, ' ')
    .replace(/[\u0000-\u0008\u000B-\u001F\u007F]/g, '');
}

export function applyPastedText(
  raw: string,
  currentString: string,
  cursorPos: number,
  maxLength: number,
  replaceTextOnNextType: boolean,
): { handled: boolean; newString: string; newCursorPos: number; replaceTextOnNextType: boolean } {
  const text = normalizePastedText(raw);
  if (!text) {
    return { handled: true, newString: currentString, newCursorPos: cursorPos, replaceTextOnNextType };
  }
  if (replaceTextOnNextType) {
    const replacement = text.slice(0, maxLength);
    return {
      handled: true,
      newString: replacement,
      newCursorPos: replacement.length,
      replaceTextOnNextType: false,
    };
  }
  const available = Math.max(0, maxLength - currentString.length);
  if (available <= 0) {
    return { handled: true, newString: currentString, newCursorPos: cursorPos, replaceTextOnNextType: false };
  }
  const insert = text.slice(0, available);
  return {
    handled: true,
    newString: currentString.slice(0, cursorPos) + insert + currentString.slice(cursorPos),
    newCursorPos: cursorPos + insert.length,
    replaceTextOnNextType: false,
  };
}

export function mapTextInputKey(code: string, key: string): string {
  if (code === 'ArrowLeft') return 'arrowleft';
  if (code === 'ArrowRight') return 'arrowright';
  if (code === 'Backspace') return 'backspace';
  if (code === 'Delete') return 'delete';
  if (code === 'Home') return 'home';
  if (code === 'End') return 'end';
  return key;
}

/** Returns whether a character should be treated as part of a word for Ctrl+Arrow navigation. */
function isWordCharacter(ch: string): boolean {
  return /[A-Za-z0-9_'\u2019]/.test(ch);
}

export function moveCursorWordLeft(text: string, cursorPos: number): number {
  if (cursorPos <= 0) return 0;
  let pos = cursorPos - 1;
  while (pos > 0 && !isWordCharacter(text[pos])) pos -= 1;
  while (pos > 0 && isWordCharacter(text[pos - 1])) pos -= 1;
  return pos;
}

export function moveCursorWordRight(text: string, cursorPos: number): number {
  let pos = cursorPos;
  while (pos < text.length && isWordCharacter(text[pos])) pos += 1;
  while (pos < text.length && !isWordCharacter(text[pos])) pos += 1;
  return pos;
}

/** Returns the contiguous word under the current cursor index, or null when none. */
function wordAtCursor(text: string, cursorPos: number): string | null {
  if (cursorPos < 0 || cursorPos >= text.length || !isWordCharacter(text[cursorPos])) {
    return null;
  }
  let start = cursorPos;
  while (start > 0 && isWordCharacter(text[start - 1])) start -= 1;
  let end = cursorPos + 1;
  while (end < text.length && isWordCharacter(text[end])) end += 1;
  return text.slice(start, end);
}

export function describeCharacter(ch: string): string {
  if (ch === ' ') return 'space';
  if (ch === '\t') return 'tab';
  if (ch === '.') return 'period';
  if (ch === ',') return 'comma';
  if (ch === ':') return 'colon';
  if (ch === ';') return 'semicolon';
  if (ch === '!') return 'exclamation mark';
  if (ch === '?') return 'question mark';
  if (ch === "'") return 'apostrophe';
  if (ch === '"') return 'quote';
  if (ch === '/') return 'slash';
  if (ch === '\\') return 'backslash';
  if (ch === '-') return 'dash';
  if (ch === '_') return 'underscore';
  if (ch === '=') return 'equals';
  if (ch === '+') return 'plus';
  if (ch === '*') return 'asterisk';
  if (ch === '&') return 'ampersand';
  if (ch === '@') return 'at sign';
  if (ch === '#') return 'hash';
  if (ch === '%') return 'percent';
  if (ch === '$') return 'dollar sign';
  if (ch === '^') return 'caret';
  if (ch === '|') return 'pipe';
  if (ch === '~') return 'tilde';
  if (ch === '`') return 'backtick';
  if (ch === '(') return 'left parenthesis';
  if (ch === ')') return 'right parenthesis';
  if (ch === '[') return 'left bracket';
  if (ch === ']') return 'right bracket';
  if (ch === '{') return 'left brace';
  if (ch === '}') return 'right brace';
  if (ch === '<') return 'less than';
  if (ch === '>') return 'greater than';
  return ch;
}

export function describeCursorCharacter(text: string, cursorPos: number): string | null {
  if (cursorPos < 0 || cursorPos > text.length) return null;
  if (cursorPos === text.length) return 'space';
  return describeCharacter(text[cursorPos]);
}

export function describeCursorWordOrCharacter(text: string, cursorPos: number): string | null {
  if (cursorPos === text.length) return 'space';
  const word = wordAtCursor(text, cursorPos);
  if (word) return word;
  return describeCursorCharacter(text, cursorPos);
}

export function describeBackspaceDeletedCharacter(text: string, cursorPos: number): string | null {
  if (cursorPos <= 0 || cursorPos > text.length) return null;
  return describeCharacter(text[cursorPos - 1]);
}

export function describeDeleteDeletedCharacter(text: string, cursorPos: number): string | null {
  if (cursorPos < 0 || cursorPos >= text.length) return null;
  return describeCharacter(text[cursorPos]);
}
