/**
 * Terminal key encoding utilities for translating named keys and modifiers
 * into terminal escape sequences.
 */

// Named key sequences (without modifiers)
const NAMED_KEYS: Record<string, string> = {
	// Arrow keys
	up: "\x1b[A",
	down: "\x1b[B",
	left: "\x1b[D",
	right: "\x1b[C",

	// Common keys
	enter: "\r",
	return: "\r",
	escape: "\x1b",
	esc: "\x1b",
	tab: "\t",
	space: " ",
	backspace: "\x7f",
	bspace: "\x7f", // tmux-style alias

	// Editing keys
	delete: "\x1b[3~",
	del: "\x1b[3~",
	dc: "\x1b[3~", // tmux-style alias
	insert: "\x1b[2~",
	ic: "\x1b[2~", // tmux-style alias

	// Navigation
	home: "\x1b[H",
	end: "\x1b[F",
	pageup: "\x1b[5~",
	pgup: "\x1b[5~",
	ppage: "\x1b[5~", // tmux-style alias
	pagedown: "\x1b[6~",
	pgdn: "\x1b[6~",
	npage: "\x1b[6~", // tmux-style alias

	// Shift+Tab (backtab)
	btab: "\x1b[Z",

	// Function keys
	f1: "\x1bOP",
	f2: "\x1bOQ",
	f3: "\x1bOR",
	f4: "\x1bOS",
	f5: "\x1b[15~",
	f6: "\x1b[17~",
	f7: "\x1b[18~",
	f8: "\x1b[19~",
	f9: "\x1b[20~",
	f10: "\x1b[21~",
	f11: "\x1b[23~",
	f12: "\x1b[24~",

	// Keypad keys (application mode)
	kp0: "\x1bOp",
	kp1: "\x1bOq",
	kp2: "\x1bOr",
	kp3: "\x1bOs",
	kp4: "\x1bOt",
	kp5: "\x1bOu",
	kp6: "\x1bOv",
	kp7: "\x1bOw",
	kp8: "\x1bOx",
	kp9: "\x1bOy",
	"kp/": "\x1bOo",
	"kp*": "\x1bOj",
	"kp-": "\x1bOm",
	"kp+": "\x1bOk",
	"kp.": "\x1bOn",
	kpenter: "\x1bOM",
};

// Ctrl+key combinations (ctrl+a through ctrl+z, plus some special)
const CTRL_KEYS: Record<string, string> = {};
for (let i = 0; i < 26; i++) {
	const char = String.fromCharCode(97 + i); // a-z
	CTRL_KEYS[`ctrl+${char}`] = String.fromCharCode(i + 1);
}
// Special ctrl combinations
CTRL_KEYS["ctrl+["] = "\x1b"; // Same as Escape
CTRL_KEYS["ctrl+\\"] = "\x1c";
CTRL_KEYS["ctrl+]"] = "\x1d";
CTRL_KEYS["ctrl+^"] = "\x1e";
CTRL_KEYS["ctrl+_"] = "\x1f";
CTRL_KEYS["ctrl+?"] = "\x7f"; // Same as Backspace

// Alt+key sends ESC followed by the key
function altKey(char: string): string {
	return `\x1b${char}`;
}

// Keys that support xterm modifier encoding (CSI sequences)
const MODIFIABLE_KEYS = new Set([
	"up", "down", "left", "right", "home", "end",
	"pageup", "pgup", "ppage", "pagedown", "pgdn", "npage",
	"insert", "ic", "delete", "del", "dc",
]);

// Calculate xterm modifier code: 1 + (shift?1:0) + (alt?2:0) + (ctrl?4:0)
function xtermModifier(shift: boolean, alt: boolean, ctrl: boolean): number {
	let mod = 1;
	if (shift) mod += 1;
	if (alt) mod += 2;
	if (ctrl) mod += 4;
	return mod;
}

// Apply xterm modifier to CSI sequence: ESC[A -> ESC[1;modA
function applyXtermModifier(sequence: string, modifier: number): string | null {
	// Arrow keys: ESC[A -> ESC[1;modA
	const arrowMatch = sequence.match(/^\x1b\[([A-D])$/);
	if (arrowMatch) {
		return `\x1b[1;${modifier}${arrowMatch[1]}`;
	}
	// Numbered sequences: ESC[5~ -> ESC[5;mod~
	const numMatch = sequence.match(/^\x1b\[(\d+)~$/);
	if (numMatch) {
		return `\x1b[${numMatch[1]};${modifier}~`;
	}
	// Home/End: ESC[H -> ESC[1;modH, ESC[F -> ESC[1;modF
	const hfMatch = sequence.match(/^\x1b\[([HF])$/);
	if (hfMatch) {
		return `\x1b[1;${modifier}${hfMatch[1]}`;
	}
	return null;
}

// Bracketed paste mode sequences
const BRACKETED_PASTE_START = "\x1b[200~";
const BRACKETED_PASTE_END = "\x1b[201~";

function encodePaste(text: string, bracketed = true): string {
	if (!bracketed) return text;
	return `${BRACKETED_PASTE_START}${text}${BRACKETED_PASTE_END}`;
}

/** Parse a key token and return the escape sequence */
function encodeKeyToken(token: string): string {
	const normalized = token.trim().toLowerCase();
	if (!normalized) return "";

	// Check for direct match in named keys
	if (NAMED_KEYS[normalized]) {
		return NAMED_KEYS[normalized];
	}

	// Check for ctrl+key
	if (CTRL_KEYS[normalized]) {
		return CTRL_KEYS[normalized];
	}

	// Parse modifier prefixes: ctrl+alt+shift+key, c-m-s-key, etc.
	let rest = normalized;
	let ctrl = false, alt = false, shift = false;

	// Support both "ctrl+alt+x" and "c-m-x" syntax
	while (rest.length > 2) {
		if (rest.startsWith("ctrl+") || rest.startsWith("ctrl-")) {
			ctrl = true;
			rest = rest.slice(5);
		} else if (rest.startsWith("alt+") || rest.startsWith("alt-")) {
			alt = true;
			rest = rest.slice(4);
		} else if (rest.startsWith("shift+") || rest.startsWith("shift-")) {
			shift = true;
			rest = rest.slice(6);
		} else if (rest.startsWith("c-")) {
			ctrl = true;
			rest = rest.slice(2);
		} else if (rest.startsWith("m-")) {
			alt = true;
			rest = rest.slice(2);
		} else if (rest.startsWith("s-")) {
			shift = true;
			rest = rest.slice(2);
		} else {
			break;
		}
	}

	// Handle shift+tab specially
	if (shift && rest === "tab") {
		return "\x1b[Z";
	}

	// Check if base key is a named key that supports modifiers
	const baseSeq = NAMED_KEYS[rest];
	if (baseSeq && MODIFIABLE_KEYS.has(rest) && (ctrl || alt || shift)) {
		const mod = xtermModifier(shift, alt, ctrl);
		if (mod > 1) {
			const modified = applyXtermModifier(baseSeq, mod);
			if (modified) return modified;
		}
	}

	// For single character with modifiers
	if (rest.length === 1) {
		let char = rest;
		if (shift && /[a-z]/.test(char)) {
			char = char.toUpperCase();
		}
		if (ctrl) {
			const ctrlChar = CTRL_KEYS[`ctrl+${char.toLowerCase()}`];
			if (ctrlChar) char = ctrlChar;
		}
		if (alt) {
			return altKey(char);
		}
		return char;
	}

	// Named key with alt modifier
	if (baseSeq && alt) {
		return `\x1b${baseSeq}`;
	}

	// Return base sequence if found
	if (baseSeq) {
		return baseSeq;
	}

	// Unknown key, return as literal
	return token;
}

/** Translate input specification to terminal escape sequences */
export function translateInput(input: string | { text?: string; keys?: string[]; paste?: string; hex?: string[] }): string {
	if (typeof input === "string") {
		return input;
	}

	let result = "";

	// Hex bytes (raw escape sequences)
	if (input.hex?.length) {
		for (const raw of input.hex) {
			const trimmed = raw.trim().toLowerCase();
			const normalized = trimmed.startsWith("0x") ? trimmed.slice(2) : trimmed;
			if (/^[0-9a-f]{1,2}$/.test(normalized)) {
				const value = Number.parseInt(normalized, 16);
				if (!Number.isNaN(value) && value >= 0 && value <= 0xff) {
					result += String.fromCharCode(value);
				}
			}
		}
	}

	// Literal text
	if (input.text) {
		result += input.text;
	}

	// Bracketed paste
	if (input.paste) {
		result += encodePaste(input.paste);
	}

	// Named keys with modifier support
	if (input.keys) {
		for (const key of input.keys) {
			result += encodeKeyToken(key);
		}
	}

	return result;
}
