<script lang="ts">
	import MdiIcon from './MdiIcon.svelte';
	import { t } from '$lib/i18n';
	import { portal } from '$lib/portal';

	export interface EntityItem {
		value: string | number;
		label: string;
		icon?: string;
		desc?: string;
		disabled?: boolean;
		disabledHint?: string;
	}

	let {
		items = [],
		value = $bindable(),
		placeholder = 'Select...',
		allowNone = false,
		noneLabel = '—',
		disabled = false,
		size = 'default',
		open = $bindable(false),
		showTrigger = true,
		onselect,
		onclose,
	}: {
		items: EntityItem[];
		value: string | number | null;
		placeholder?: string;
		allowNone?: boolean;
		noneLabel?: string;
		disabled?: boolean;
		size?: 'sm' | 'default';
		open?: boolean;
		showTrigger?: boolean;
		onselect?: (value: string | number | null) => void;
		onclose?: () => void;
	} = $props();

	let query = $state('');
	let highlightIdx = $state(0);
	let inputEl = $state<HTMLInputElement | undefined>();
	let listEl = $state<HTMLDivElement | undefined>();

	const selected = $derived(items.find(i => String(i.value) === String(value)));

	const filtered = $derived.by(() => {
		const q = query.toLowerCase().trim();
		let result: EntityItem[] = [];
		if (allowNone) {
			result.push({ value: '', label: noneLabel, icon: '' });
		}
		const matching = q
			? items.filter(i => i.label.toLowerCase().includes(q) || (i.desc || '').toLowerCase().includes(q))
			: items;
		return [...result, ...matching];
	});

	// Focus input whenever the palette transitions to open (covers both internal
	// trigger clicks and external programmatic opening via bind:open).
	let wasOpen = false;
	$effect(() => {
		if (open && !wasOpen) {
			query = '';
			highlightIdx = Math.max(0, filtered.findIndex(i => String(i.value) === String(value)));
			requestAnimationFrame(() => inputEl?.focus());
		}
		wasOpen = open;
	});

	function openPalette() {
		if (disabled) return;
		open = true;
	}

	// Called when the user dismisses the palette (overlay click or ESC).
	// Selection uses its own quiet-close path so onclose stays a true "cancel" signal.
	function closePalette() {
		open = false;
		query = '';
		onclose?.();
	}

	function selectItem(item: EntityItem) {
		if (item.disabled) return;
		value = item.value || null;
		onselect?.(value);
		open = false;
		query = '';
	}

	function handleKeydown(e: KeyboardEvent) {
		if (e.key === 'Escape') {
			closePalette();
			return;
		}
		if (e.key === 'ArrowDown') {
			e.preventDefault();
			highlightIdx = Math.min(highlightIdx + 1, filtered.length - 1);
			scrollToHighlight();
		} else if (e.key === 'ArrowUp') {
			e.preventDefault();
			highlightIdx = Math.max(highlightIdx - 1, 0);
			scrollToHighlight();
		} else if (e.key === 'Enter') {
			e.preventDefault();
			if (filtered[highlightIdx]) selectItem(filtered[highlightIdx]);
		} else if (e.key === 'Tab') {
			e.preventDefault();
		}
	}

	function scrollToHighlight() {
		requestAnimationFrame(() => {
			listEl?.querySelector('.ep-highlight')?.scrollIntoView({ block: 'nearest' });
		});
	}

	// Reset highlight when query changes
	$effect(() => {
		query; // track
		highlightIdx = 0;
	});
</script>

<!-- Trigger button (hidden when the parent drives `open` via bind:open) -->
{#if showTrigger}
	<button type="button" class="es-trigger" class:es-sm={size === 'sm'} onclick={openPalette}
		aria-expanded={open}
		aria-haspopup="listbox"
		style="opacity: {disabled ? 0.5 : 1}; cursor: {disabled ? 'default' : 'pointer'};">
		{#if selected}
			{#if selected.icon}
				<span class="es-trigger-icon"><MdiIcon name={selected.icon} size={16} /></span>
			{/if}
			<span class="es-trigger-label">{selected.label}</span>
		{:else}
			<span class="es-trigger-label es-trigger-none">{placeholder}</span>
		{/if}
		<span class="es-trigger-arrow"><MdiIcon name="mdiChevronDown" size={14} /></span>
	</button>
{/if}

<!-- Palette overlay — portalled to <body> to escape backdrop-filter ancestors -->
{#if open}
	<div use:portal class="es-portal-root">
		<div class="ep-overlay" onclick={closePalette} role="presentation"></div>

		<div class="ep-container">
			<div class="ep-search-row">
				<MdiIcon name="mdiMagnify" size={18} />
				<input
					bind:this={inputEl}
					bind:value={query}
					placeholder={selected ? selected.label : placeholder}
					class="ep-input"
					type="text"
					autocomplete="off"
					spellcheck="false"
					onkeydown={handleKeydown}
				/>
				<kbd class="ep-kbd">ESC</kbd>
			</div>

			<div class="ep-list" bind:this={listEl} role="listbox">
				{#if filtered.length === 0}
					<div class="ep-empty">{t('common.noMatches')}</div>
				{:else}
					{#each filtered as item, i}
						<button
							class="ep-item"
							class:ep-highlight={i === highlightIdx && !item.disabled}
							class:ep-current={String(item.value) === String(value)}
							class:ep-disabled={item.disabled}
							role="option"
							aria-selected={String(item.value) === String(value)}
							aria-disabled={item.disabled || undefined}
							onclick={() => selectItem(item)}
							onmouseenter={() => highlightIdx = i}
							type="button"
						>
							{#if item.icon}
								<span class="ep-item-icon"><MdiIcon name={item.icon} size={18} /></span>
							{/if}
							<span class="ep-item-label">{item.label}</span>
							{#if item.disabled && item.disabledHint}
								<span class="ep-item-hint">{item.disabledHint}</span>
							{:else if item.desc}
								<span class="ep-item-desc">{item.desc}</span>
							{/if}
						</button>
					{/each}
				{/if}
			</div>
		</div>
	</div>
{/if}

<style>
	/* Trigger button */
	.es-trigger {
		display: flex;
		align-items: center;
		gap: 0.5rem;
		width: 100%;
		padding: 0.5rem 0.75rem;
		border: 1px solid var(--color-rule-strong);
		border-radius: 0.625rem;
		font-size: 0.875rem;
		background: var(--color-input-bg);
		color: var(--color-foreground);
		transition: border-color 0.15s, background 0.15s;
		text-align: left;
		cursor: pointer;
		font-family: inherit;
	}
	.es-trigger.es-sm {
		padding: 0.3rem 0.55rem;
		font-size: 0.8rem;
		gap: 0.4rem;
	}
	.es-trigger:hover {
		background: var(--color-glass-strong);
		border-color: var(--color-rule-strong);
	}
	.es-trigger-icon {
		flex-shrink: 0;
		color: var(--color-primary);
	}
	.es-trigger-label {
		flex: 1;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}
	.es-trigger-none {
		color: var(--color-muted-foreground);
	}
	.es-trigger-arrow {
		flex-shrink: 0;
		color: var(--color-muted-foreground);
	}

	/* Portal root — escapes any backdrop-filter ancestor */
	.es-portal-root {
		position: fixed;
		inset: 0;
		z-index: 9998;
		pointer-events: none;
	}

	/* Overlay */
	.ep-overlay {
		position: absolute;
		inset: 0;
		pointer-events: auto;
		background: rgba(0, 0, 0, 0.55);
		backdrop-filter: blur(8px) saturate(120%);
		-webkit-backdrop-filter: blur(8px) saturate(120%);
	}

	/* Palette container — high opacity for legibility */
	.ep-container {
		pointer-events: auto;
		position: absolute;
		top: min(20vh, 120px);
		left: 50%;
		transform: translateX(-50%);
		z-index: 1;
		width: min(480px, 92vw);
		max-height: 60vh;
		background: var(--ep-solid-bg);
		border: 1px solid var(--color-rule-strong);
		border-radius: 16px;
		box-shadow: var(--shadow-card), 0 24px 48px -16px rgba(0, 0, 0, 0.55);
		display: flex;
		flex-direction: column;
		overflow: hidden;
		--ep-solid-bg: #131520;
	}
	:global([data-theme="light"]) .ep-container { --ep-solid-bg: #fafafe; }
	.ep-container::after {
		content: '';
		position: absolute; inset: 0;
		border-radius: inherit;
		pointer-events: none;
		background: linear-gradient(180deg, var(--color-highlight), transparent 30%);
		opacity: 0.4;
	}

	/* Search row */
	.ep-search-row {
		display: flex;
		align-items: center;
		gap: 0.6rem;
		padding: 0.85rem 1rem;
		border-bottom: 1px solid var(--color-border);
		color: var(--color-muted-foreground);
		position: relative;
		z-index: 1;
	}
	.ep-input {
		flex: 1;
		border: none;
		outline: none;
		background: transparent;
		font-size: 0.9rem;
		color: var(--color-foreground);
		padding: 0;
		font-family: inherit;
	}
	.ep-input::placeholder { color: var(--color-muted-foreground); }
	.ep-kbd {
		font-size: 0.62rem;
		font-family: var(--font-mono);
		padding: 0.2rem 0.45rem;
		border-radius: 6px;
		background: var(--color-glass-strong);
		color: var(--color-foreground);
		border: 1px solid var(--color-border);
	}

	/* List */
	.ep-list {
		overflow-y: auto;
		overscroll-behavior: contain;
		scrollbar-width: thin;
		padding: 0.35rem;
		position: relative;
		z-index: 1;
	}
	.ep-empty {
		padding: 1.25rem;
		text-align: center;
		color: var(--color-muted-foreground);
		font-size: 0.85rem;
	}

	/* Items */
	.ep-item {
		display: flex;
		align-items: center;
		gap: 0.65rem;
		width: 100%;
		padding: 0.55rem 0.75rem;
		border: 1px solid transparent;
		background: transparent;
		color: var(--color-foreground);
		font-size: 0.88rem;
		cursor: pointer;
		text-align: left;
		transition: background 0.12s, border-color 0.12s;
		border-radius: 10px;
		font-family: inherit;
	}
	.ep-item:hover, .ep-item.ep-highlight {
		background: rgba(255, 255, 255, 0.06);
		border-color: var(--color-rule-strong);
	}
	:global([data-theme="light"]) .ep-item:hover,
	:global([data-theme="light"]) .ep-item.ep-highlight {
		background: rgba(20, 15, 60, 0.05);
	}
	.ep-item.ep-disabled {
		opacity: 0.4;
		cursor: default;
	}
	.ep-item.ep-disabled:hover {
		background: transparent;
		border-color: transparent;
	}
	.ep-item.ep-current {
		background: linear-gradient(135deg,
			color-mix(in srgb, var(--color-primary) 14%, transparent),
			color-mix(in srgb, var(--color-orchid) 14%, transparent));
		border-color: color-mix(in srgb, var(--color-primary) 40%, var(--color-border));
		box-shadow: inset 0 1px 0 var(--color-highlight);
	}
	.ep-item-icon {
		flex-shrink: 0;
		display: flex;
		align-items: center;
		justify-content: center;
		color: var(--color-muted-foreground);
		width: 28px; height: 28px;
		border-radius: 8px;
		background: var(--color-glass-strong);
		border: 1px solid var(--color-border);
	}
	.ep-item.ep-current .ep-item-icon {
		color: var(--color-primary);
		background: var(--color-glass-elev);
	}
	.ep-item-label {
		flex: 1;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
		font-weight: 500;
	}
	.ep-item-desc {
		font-size: 0.7rem;
		font-family: var(--font-mono);
		color: var(--color-muted-foreground);
		padding: 0.12rem 0.5rem;
		border-radius: 9999px;
		background: var(--color-glass-strong);
		border: 1px solid var(--color-border);
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
		max-width: 40%;
	}
	.ep-item-hint {
		font-size: 0.7rem;
		font-style: italic;
		color: var(--color-muted-foreground);
		white-space: nowrap;
		margin-left: auto;
	}
</style>
