<script lang="ts">
	import { getSegmentsUpToChar } from "./utils";
	import { user } from "$lib/config";
	import TuiLine from "./TuiLine.svelte";
	import type { DisplayedLine, TerminalLine } from "./types";
	import "$lib/assets/css/tui-body.css";

	interface Props {
		displayedLines: DisplayedLine[];
		currentLineIndex?: number;
		isTyping?: boolean;
		selectedIndex?: number;
		ref?: HTMLDivElement | undefined;
		onButtonClick: (idx: number, line?: TerminalLine) => void;
		onHoverButton: (idx: number) => void;
		onLinkClick: (idx: number) => void;
		terminalSettings: { showCursor: boolean };
		interactiveInput?: boolean;
		onCommand?: (command: string) => void;
	}

	let {
		displayedLines = [],
		currentLineIndex = 0,
		isTyping = false,
		selectedIndex = -1,
		ref = $bindable(undefined),
		onButtonClick,
		onHoverButton,
		onLinkClick,
		terminalSettings,
		interactiveInput = false,
		onCommand,
	}: Props = $props();

	let inputEl = $state<HTMLInputElement>();
	let commandValue = $state("");
	let history = $state<string[]>([]);
	let historyIndex = $state(-1);
	let inputFocused = $state(false);
	let caretPos = $state(0);

	const beforeText = $derived(commandValue.slice(0, caretPos));
	const cursorChar = $derived(commandValue.slice(caretPos, caretPos + 1) || " ");
	const afterRest = $derived(commandValue.slice(caretPos + 1));
	const afterFull = $derived(commandValue.slice(caretPos));

	function syncCaret() {
		caretPos = inputEl?.selectionStart ?? commandValue.length;
	}

	function recall(value: string) {
		commandValue = value;
		caretPos = value.length;
	}

	function submitCommand() {
		const value = commandValue;
		commandValue = "";
		caretPos = 0;
		if (value.trim()) history = [...history, value];
		historyIndex = -1;
		onCommand?.(value);
		queueMicrotask(() => inputEl?.focus());
	}

	function handleInputKeydown(event: KeyboardEvent) {
		if (event.key === "Enter") {
			event.preventDefault();
			event.stopPropagation();
			submitCommand();
		} else if (event.key === "ArrowUp") {
			event.preventDefault();
			event.stopPropagation();
			if (history.length === 0) return;
			const nextIndex =
				historyIndex === -1
					? history.length - 1
					: Math.max(0, historyIndex - 1);
			historyIndex = nextIndex;
			recall(history[nextIndex]);
		} else if (event.key === "ArrowDown") {
			event.preventDefault();
			event.stopPropagation();
			if (historyIndex === -1) return;
			const nextIndex = historyIndex + 1;
			if (nextIndex >= history.length) {
				historyIndex = -1;
				recall("");
			} else {
				historyIndex = nextIndex;
				recall(history[nextIndex]);
			}
		} else {
			event.stopPropagation();
		}
	}

	function focusCommandInput(event: MouseEvent) {
		if (!interactiveInput) return;
		const target = event.target as HTMLElement;
		if (target.closest("a, button, input, textarea, .tui-button, .tui-link"))
			return;
		inputEl?.focus();
	}

	// Group consecutive inline items together
	type ProcessedGroup =
		| { kind: "single"; index: number; displayed: DisplayedLine }
		| {
				kind: "inline";
				items: Array<{ index: number; displayed: DisplayedLine }>;
		  };

	const processedGroups = $derived.by(() => {
		const groups: ProcessedGroup[] = [];
		let i = 0;

		while (i < displayedLines.length) {
			const displayed = displayedLines[i];
			const isInline =
				displayed.parsed.line.inline ||
				displayed.parsed.line.display === "inline";

			if (isInline) {
				const inlineItems: Array<{
					index: number;
					displayed: DisplayedLine;
				}> = [];
				// Collect consecutive inline items
				while (i < displayedLines.length) {
					const nextLine = displayedLines[i].parsed.line;
					const nextIsInline =
						nextLine.inline || nextLine.display === "inline";

					if (!nextIsInline) break;

					inlineItems.push({
						index: i,
						displayed: displayedLines[i],
					});
					i++;
				}
				groups.push({ kind: "inline", items: inlineItems });
			} else {
				groups.push({ kind: "single", index: i, displayed });
				i++;
			}
		}
		return groups;
	});
</script>

<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
<div class="tui-body" bind:this={ref} onclick={focusCommandInput}>
	{#each processedGroups as group, gi (gi)}
		{#if group.kind === "inline"}
			<div class="tui-inline-group">
				{#each group.items as item (item.index)}
					<TuiLine
						line={item.displayed.parsed.line}
						index={item.index}
						segments={getSegmentsUpToChar(
							item.displayed.parsed.segments,
							item.displayed.charIndex,
						)}
						complete={item.displayed.complete}
						showImage={item.displayed.showImage}
						{selectedIndex}
						inline={true}
						showCursor={terminalSettings.showCursor &&
							item.index === currentLineIndex &&
							!item.displayed.complete &&
							isTyping &&
							item.displayed.parsed.line.type !== "image"}
						{onButtonClick}
						{onHoverButton}
						{onLinkClick}
					/>
				{/each}
			</div>
		{:else}
			<TuiLine
				line={group.displayed.parsed.line}
				index={group.index}
				segments={getSegmentsUpToChar(
					group.displayed.parsed.segments,
					group.displayed.charIndex,
				)}
				complete={group.displayed.complete}
				showImage={group.displayed.showImage}
				{selectedIndex}
				inline={false}
				showCursor={terminalSettings.showCursor &&
					group.index === currentLineIndex &&
					!group.displayed.complete &&
					isTyping &&
					group.displayed.parsed.line.type !== "image"}
				{onButtonClick}
				{onHoverButton}
				{onLinkClick}
			/>
		{/if}
	{/each}

	{#if interactiveInput}
		{#if !isTyping}
			<div class="tui-line prompt tui-command-line">
				<span class="prompt">
					<span class="user">{user.username}</span><span class="at"
						>@</span
					><span class="host">{user.hostname}</span>
					<span class="separator">:</span><span class="path">~</span><span
						class="symbol">$</span
					>
				</span>
				<span class="tui-input-wrap">
					<input
						class="tui-command-input"
						bind:this={inputEl}
						bind:value={commandValue}
						onkeydown={handleInputKeydown}
						onkeyup={syncCaret}
						oninput={syncCaret}
						onclick={syncCaret}
						onfocus={() => {
							inputFocused = true;
							syncCaret();
						}}
						onblur={() => (inputFocused = false)}
						spellcheck="false"
						autocomplete="off"
						autocapitalize="off"
						autocorrect="off"
						aria-label="Terminal command input"
					/><span class="tui-input-mirror" aria-hidden="true">{beforeText}{#if inputFocused}<span class="cursor-block blink">{cursorChar}</span>{afterRest}{:else}{afterFull}{/if}</span>
				</span>
			</div>
		{/if}
	{:else if terminalSettings.showCursor && !isTyping && displayedLines.length > 0}
		<div class="tui-line prompt">
			<span class="prompt">
				<span class="user">{user.username}</span><span class="at"
					>@</span
				><span class="host">{user.hostname}</span>
				<span class="separator">:</span><span class="path">~</span><span
					class="symbol">$</span
				>
			</span>
			<span class="cursor blink"></span>
		</div>
	{/if}
</div>

<style>
	.tui-command-line {
		display: flex;
		align-items: center;
	}

	.tui-input-wrap {
		position: relative;
		flex: 1;
		min-width: 0;
		margin-left: 0.4rem;
		display: flex;
		align-items: center;
	}

	.tui-command-input {
		width: 100%;
		margin: 0;
		padding: 0;
		background: transparent;
		border: none;
		outline: none;
		box-shadow: none;
		color: transparent;
		font: inherit;
		line-height: inherit;
		caret-color: transparent;
	}

	.tui-command-input:focus,
	.tui-command-input:focus-visible {
		border: none;
		outline: none;
		box-shadow: none;
	}

	.tui-input-mirror {
		position: absolute;
		inset: 0;
		display: flex;
		align-items: center;
		white-space: pre;
		overflow: hidden;
		pointer-events: none;
		color: var(--terminal-text);
	}

	.cursor-block {
		display: inline-block;
		height: 1.2em;
		line-height: 1.2em;
		color: var(--terminal-bg);
		background: var(--terminal-accent, var(--terminal-primary));
	}

	.cursor-block.blink {
		animation: cursorBlink 1s step-end infinite;
	}
</style>
