<script lang="ts">
	import Icon from "@iconify/svelte";
	import { getButtonStyle, parseColorText, getSegmentStyle } from "./utils";
	import { themeColors } from "$lib/stores/theme";
	import type { SelectLine } from "./types";
	import "$lib/assets/css/tui-select.css";

	interface Props {
		line: SelectLine;
		inline?: boolean;
		value?: string;
		onchange?: (value: string) => void;
		onfocus?: () => void;
		onblur?: () => void;
	}

	let {
		line,
		inline = false,
		value = $bindable(""),
		onchange,
		onfocus,
		onblur,
	}: Props = $props();

	const labelSegments = $derived(
		line.content ? parseColorText(line.content, $themeColors.colorMap) : [],
	);
	const options = $derived(line.selectOptions || []);
	const placeholder = $derived(
		line.inputPlaceholder || "Select an option...",
	);
	const isDisabled = $derived(line.inputDisabled || false);
	const hasError = $derived(line.inputError);
	const errorMessage = $derived(line.inputErrorMessage || "");
	const searchable = $derived(line.selectSearchable || false);

	let isOpen = $state(false);
	let searchQuery = $state("");
	let highlightedIndex = $state(0);
	let selectRef: HTMLDivElement | undefined = $state();

	const filteredOptions = $derived(
		searchable && searchQuery
			? options.filter(
					(opt) =>
						opt.label
							.toLowerCase()
							.includes(searchQuery.toLowerCase()) ||
						opt.value
							.toLowerCase()
							.includes(searchQuery.toLowerCase()),
				)
			: options,
	);

	const selectedOption = $derived(options.find((opt) => opt.value === value));
	const displayValue = $derived(selectedOption?.label || "");

	function handleToggle() {
		if (isDisabled) return;
		isOpen = !isOpen;
		if (isOpen) {
			highlightedIndex = 0;
			searchQuery = "";
			onfocus?.();
		} else {
			onblur?.();
		}
	}

	function handleSelect(optionValue: string) {
		if (isDisabled) return;
		value = optionValue;
		isOpen = false;
		searchQuery = "";
		onchange?.(value);
		onblur?.();
	}

	function handleKeydown(e: KeyboardEvent) {
		if (isDisabled) return;

		switch (e.key) {
			case "Enter":
			case " ":
				e.preventDefault();
				if (isOpen && filteredOptions[highlightedIndex]) {
					handleSelect(filteredOptions[highlightedIndex].value);
				} else {
					handleToggle();
				}
				break;
			case "ArrowDown":
				e.preventDefault();
				if (!isOpen) {
					isOpen = true;
				} else {
					highlightedIndex = Math.min(
						highlightedIndex + 1,
						filteredOptions.length - 1,
					);
				}
				break;
			case "ArrowUp":
				e.preventDefault();
				if (isOpen) {
					highlightedIndex = Math.max(highlightedIndex - 1, 0);
				}
				break;
			case "Escape":
				isOpen = false;
				searchQuery = "";
				break;
			case "Home":
				if (isOpen) {
					e.preventDefault();
					highlightedIndex = 0;
				}
				break;
			case "End":
				if (isOpen) {
					e.preventDefault();
					highlightedIndex = filteredOptions.length - 1;
				}
				break;
		}
	}

	function handleClickOutside(e: MouseEvent) {
		if (selectRef && !selectRef.contains(e.target as Node)) {
			isOpen = false;
			searchQuery = "";
		}
	}

	function handleSearchInput(e: Event) {
		searchQuery = (e.target as HTMLInputElement).value;
		highlightedIndex = 0;
	}
</script>

<svelte:window onclick={handleClickOutside} />

<div
	class="tui-select"
	class:inline
	class:open={isOpen}
	class:error={hasError}
	class:disabled={isDisabled}
	style="--select-color: {getButtonStyle(line.style)}"
	bind:this={selectRef}
>
	{#if line.content}
		<label class="select-label">
			{#if line.icon}
				<Icon icon={line.icon} width="14" class="label-icon" />
			{/if}
			{#each labelSegments as segment}
				{#if segment.icon}
					<Icon icon={segment.icon} width={segment.iconSize ?? 14} class="inline-icon" />
				{:else if getSegmentStyle(segment)}
					<span style={getSegmentStyle(segment)}>{segment.text}</span>
				{:else}
					{segment.text}
				{/if}
			{/each}
		</label>
	{/if}

	<div
		class="select-trigger"
		role="combobox"
		aria-expanded={isOpen}
		aria-controls="select-listbox"
		aria-haspopup="listbox"
		aria-disabled={isDisabled}
		tabindex={isDisabled ? -1 : 0}
		onclick={handleToggle}
		onkeydown={handleKeydown}
	>
		<span class="select-prompt">❯</span>
		<span class="select-value" class:placeholder={!selectedOption}>
			{displayValue || placeholder}
		</span>
		<span class="select-arrow">{isOpen ? "▲" : "▼"}</span>
	</div>

	{#if isOpen}
		<div class="select-dropdown" role="listbox" id="select-listbox">
			{#if searchable}
				<div class="select-search">
					<Icon icon="mdi:magnify" width="14" />
					<input
						type="text"
						placeholder="Search..."
						value={searchQuery}
						oninput={handleSearchInput}
						onclick={(e) => e.stopPropagation()}
					/>
				</div>
			{/if}
			<div class="select-options">
				{#each filteredOptions as option, i}
					{@const optionSegments = parseColorText(option.label, $themeColors.colorMap)}
					<div
						class="select-option"
						class:selected={value === option.value}
						class:highlighted={i === highlightedIndex}
						class:option-disabled={option.disabled}
						role="option"
						aria-selected={value === option.value}
						aria-disabled={option.disabled}
						tabindex={option.disabled ? -1 : 0}
						onclick={() =>
							!option.disabled && handleSelect(option.value)}
						onkeydown={(e) =>
							e.key === "Enter" &&
							!option.disabled &&
							handleSelect(option.value)}
						onmouseenter={() => (highlightedIndex = i)}
					>
						{#if option.icon}
							<Icon
								icon={option.icon}
								width="14"
								class="option-icon"
							/>
						{/if}
						<span class="option-label">
							{#each optionSegments as segment}
								{#if segment.icon}
									<Icon
										icon={segment.icon}
										width={segment.iconSize ?? 14}
										class="inline-icon"
									/>
								{:else if getSegmentStyle(segment)}
									<span style={getSegmentStyle(segment)}
										>{segment.text}</span
									>
								{:else}
									{segment.text}
								{/if}
							{/each}
						</span>
						{#if value === option.value}
							<Icon
								icon="mdi:check"
								width="14"
								class="check-icon"
							/>
						{/if}
					</div>
				{:else}
					<div class="select-empty">No options found</div>
				{/each}
			</div>
		</div>
	{/if}

	{#if hasError && errorMessage}
		<div class="select-error">
			<Icon icon="mdi:alert-circle" width="12" />
			<span>{errorMessage}</span>
		</div>
	{/if}
</div>

<style>
	.tui-select {
		position: relative;
		margin: 0.5rem 0;
		font-size: 0.9rem;
	}

	.tui-select.inline {
		display: inline-flex;
		flex-direction: column;
		margin: 0 0.5rem 0 0;
	}

	.select-label {
		display: flex;
		align-items: center;
		gap: 0.35rem;
		margin-bottom: 0.35rem;
		color: var(--terminal-muted);
		font-size: 0.85rem;
	}

	:global(.label-icon) {
		color: var(--select-color);
	}

	.select-trigger {
		display: flex;
		align-items: center;
		gap: 0.35rem;
		padding: 0.5rem 0.75rem;
		background: color-mix(in srgb, var(--terminal-bg) 80%, black);
		border: 1px solid var(--terminal-muted);
		border-radius: 4px;
		cursor: pointer;
		transition: all 0.15s ease;
	}

	.tui-select.open .select-trigger,
	.select-trigger:focus-visible {
		border-color: var(--select-color);
		box-shadow: 0 0 0 1px
			color-mix(in srgb, var(--select-color) 30%, transparent);
	}

	.tui-select.error .select-trigger {
		border-color: #f38ba8;
	}

	.tui-select.disabled .select-trigger {
		opacity: 0.5;
		cursor: not-allowed;
	}

	.select-prompt {
		color: var(--select-color);
		font-weight: bold;
	}

	.select-value {
		flex: 1;
		color: var(--terminal-text);
	}

	.select-value.placeholder {
		color: var(--terminal-muted);
		opacity: 0.6;
	}

	.select-arrow {
		color: var(--terminal-muted);
		font-size: 0.7rem;
	}

	.select-dropdown {
		position: absolute;
		top: 100%;
		left: 0;
		right: 0;
		margin-top: 0.25rem;
		background: var(--terminal-bg);
		border: 1px solid var(--select-color);
		border-radius: 4px;
		box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
		z-index: 100;
		overflow: hidden;
	}

	.select-search {
		display: flex;
		align-items: center;
		gap: 0.35rem;
		padding: 0.5rem 0.75rem;
		border-bottom: 1px solid var(--terminal-muted);
	}

	.select-search input {
		flex: 1;
		background: transparent;
		border: none;
		color: var(--terminal-text);
		font-family: inherit;
		font-size: inherit;
		outline: none;
	}

	.select-search input::placeholder {
		color: var(--terminal-muted);
	}

	.select-options {
		max-height: 200px;
		overflow-y: auto;
	}

	.select-option {
		display: flex;
		align-items: center;
		gap: 0.5rem;
		padding: 0.5rem 0.75rem;
		cursor: pointer;
		transition: background 0.1s ease;
	}

	.select-option.highlighted {
		background: color-mix(in srgb, var(--select-color) 15%, transparent);
	}

	.select-option.selected {
		color: var(--select-color);
	}

	.select-option.option-disabled {
		opacity: 0.5;
		cursor: not-allowed;
	}

	:global(.option-icon) {
		color: var(--select-color);
	}

	.option-label {
		flex: 1;
	}

	:global(.check-icon) {
		color: var(--select-color);
	}

	.select-empty {
		padding: 0.75rem;
		text-align: center;
		color: var(--terminal-muted);
		font-style: italic;
	}

	.select-error {
		display: flex;
		align-items: center;
		gap: 0.35rem;
		margin-top: 0.35rem;
		color: #f38ba8;
		font-size: 0.8rem;
	}
</style>
