<script lang="ts">
import { untrack } from 'svelte';
import type { components } from '$lib/generated/api';
import { m } from '$lib/paraglide/messages.js';
import { clickOutside } from '$lib/shared/actions/clickOutside';
import { createTypeahead } from '$lib/shared/hooks/useTypeahead.svelte';

type Tag = components['schemas']['Tag'];

interface FlatTagRef {
	id: string;
	name: string;
	parentId?: string;
}

interface Props {
	name: string;
	value?: string;
	excludeIds?: string[];
	initialName?: string;
	allTags?: FlatTagRef[];
	placeholder?: string;
}

let {
	name,
	value = $bindable(''),
	excludeIds = [],
	initialName = '',
	allTags = [],
	placeholder = m.admin_tag_parent_placeholder()
}: Props = $props();

// displayName must be both prop-derived AND locally writable (user typing), so $state +
// $effect is the correct pattern here — writable $derived is read-only and won't work.
// eslint-disable-next-line svelte/prefer-writable-derived
let displayName = $state(initialName);

$effect(() => {
	displayName = initialName;
});

// Uses fetch directly (not the typed api client) because this component runs in the browser
// where the typed api client is not available, and the tags endpoint needs no auth cookie.
const typeahead = createTypeahead<Tag>({
	fetchUrl: async (q) => {
		const res = await fetch(`/api/tags?query=${encodeURIComponent(q)}`);
		return res.ok ? await res.json() : [];
	},
	debounceMs: 300
});

const filteredResults = $derived(typeahead.results.filter((t) => !excludeIds.includes(t.id)));

function handleInput() {
	const term = untrack(() => displayName);
	typeahead.setQuery(term);
	// Reset active index whenever results are re-fetched
	typeahead.setActiveIndex(-1);
}

function selectTag(tag: Tag) {
	value = tag.id;
	displayName = tag.name;
	typeahead.close();
}

function clearSelection() {
	value = '';
	displayName = '';
	typeahead.close();
}

function handleKeydown(e: KeyboardEvent) {
	if (!typeahead.isOpen) return;
	const len = filteredResults.length;
	if (len === 0) return;

	if (e.key === 'ArrowDown') {
		e.preventDefault();
		typeahead.setActiveIndex((typeahead.activeIndex + 1) % len);
	} else if (e.key === 'ArrowUp') {
		e.preventDefault();
		typeahead.setActiveIndex((typeahead.activeIndex - 1 + len) % len);
	} else if (e.key === 'Enter' && typeahead.activeIndex >= 0) {
		e.preventDefault();
		selectTag(filteredResults[typeahead.activeIndex]);
	} else if (e.key === 'Escape') {
		typeahead.close();
	}
}
</script>

<div class="relative" use:clickOutside onclickoutside={() => typeahead.close()}>
	<input type="hidden" name={name} bind:value={value} />

	<div class="relative">
		<input
			type="text"
			id="{name}-search"
			autocomplete="off"
			role="combobox"
			aria-expanded={typeahead.isOpen}
			aria-controls="{name}-listbox"
			aria-autocomplete="list"
			aria-activedescendant={typeahead.activeIndex >= 0
				? `${name}-option-${typeahead.activeIndex}`
				: undefined}
			bind:value={displayName}
			oninput={handleInput}
			onkeydown={handleKeydown}
			placeholder={placeholder}
			class="mt-1 block w-full rounded-md border border-line bg-surface p-2 pr-8 text-ink shadow-sm placeholder:text-ink-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
		/>

		{#if value}
			<button
				type="button"
				onclick={clearSelection}
				aria-label="Auswahl entfernen"
				class="absolute top-1/2 right-2 -translate-y-1/2 rounded-sm text-ink-3 hover:text-ink focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:outline-none"
			>
				<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
					<path
						stroke-linecap="round"
						stroke-linejoin="round"
						stroke-width="2"
						d="M6 18L18 6M6 6l12 12"
					/>
				</svg>
			</button>
		{/if}
	</div>

	{#if typeahead.isOpen && filteredResults.length > 0}
		<div
			id="{name}-listbox"
			role="listbox"
			class="ring-opacity-5 absolute top-full left-0 z-50 mt-1 max-h-60 w-full overflow-auto rounded-md bg-surface py-1 text-base shadow-lg ring-1 ring-black focus:outline-none sm:text-sm"
		>
			{#each filteredResults as tag, i (tag.id)}
				<div
					id="{name}-option-{i}"
					role="option"
					tabindex="-1"
					aria-selected={i === typeahead.activeIndex}
					class="relative cursor-pointer py-2 pr-9 pl-3 text-ink select-none hover:bg-accent-bg {i === typeahead.activeIndex ? 'bg-accent-bg' : ''}"
					onclick={() => selectTag(tag)}
					onkeydown={(e) => e.key === 'Enter' && selectTag(tag)}
				>
					<span class="block truncate font-medium">{tag.name}</span>
					{#if tag.parentId}
						{@const parentName = allTags.find((t) => t.id === tag.parentId)?.name ?? tag.parentId}
						<span class="block truncate text-xs text-ink-3">{parentName}</span>
					{/if}
				</div>
			{/each}
		</div>
	{/if}
</div>
