<script lang="ts">
import { m } from '$lib/paraglide/messages.js';
import { clickOutside } from '$lib/shared/actions/clickOutside';
import {
	createDocumentTypeahead,
	formatDocumentOption,
	type DocumentOption
} from './documentTypeahead';

interface Props {
	alreadyAddedIds?: Set<string>;
	placeholder?: string;
	/** Set when a visible <label for> is wired externally — replaces the aria-label fallback. */
	inputId?: string;
	onSelect: (doc: DocumentOption) => void;
}

let {
	alreadyAddedIds = new Set(),
	placeholder = m.journey_add_document(),
	inputId = undefined,
	onSelect
}: Props = $props();

const uid = $props.id();
const listboxId = `doc-picker-listbox-${uid}`;
const resolvedInputId = $derived(inputId ?? `doc-picker-input-${uid}`);

const picker = createDocumentTypeahead();

let inputValue = $state('');

const activeOptionId = $derived(
	picker.isOpen && picker.activeIndex >= 0 && picker.results[picker.activeIndex]
		? `${listboxId}-option-${picker.activeIndex}`
		: undefined
);

function handleInput(e: Event) {
	const q = (e.currentTarget as HTMLInputElement).value;
	inputValue = q;
	picker.setActiveIndex(-1);
	if (q.trim().length >= 1) {
		picker.setQuery(q);
	} else {
		picker.close();
	}
}

function handleSelect(doc: DocumentOption) {
	if (alreadyAddedIds.has(doc.id!)) return;
	inputValue = '';
	picker.close();
	onSelect(doc);
}

function handleKeydown(e: KeyboardEvent) {
	if (!picker.isOpen) return;

	const results = picker.results;

	if (e.key === 'ArrowDown') {
		e.preventDefault();
		if (results.length > 0) {
			picker.setActiveIndex((picker.activeIndex + 1) % results.length);
		}
	} else if (e.key === 'ArrowUp') {
		e.preventDefault();
		if (results.length > 0) {
			picker.setActiveIndex((picker.activeIndex - 1 + results.length) % results.length);
		}
	} else if (e.key === 'Enter') {
		e.preventDefault();
		const active = results[picker.activeIndex];
		// handleSelect is a no-op for already-added (aria-disabled) options.
		if (active) handleSelect(active);
	} else if (e.key === 'Escape') {
		e.preventDefault();
		picker.close();
	}
}
</script>

<div use:clickOutside onclickoutside={() => picker.close()} class="relative">
	<input
		type="text"
		role="combobox"
		autocomplete="off"
		id={resolvedInputId}
		aria-label={inputId ? undefined : placeholder}
		aria-expanded={picker.isOpen}
		aria-controls={picker.isOpen && !picker.loading && !picker.error && picker.results.length > 0
			? listboxId
			: undefined}
		aria-autocomplete="list"
		aria-activedescendant={activeOptionId}
		placeholder={placeholder}
		value={inputValue}
		oninput={handleInput}
		onkeydown={handleKeydown}
		class="block w-full rounded border border-line bg-surface px-3 py-2 text-sm text-ink placeholder:text-ink-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
	/>

	{#if picker.isOpen}
		{#if picker.loading}
			<div
				class="ring-opacity-5 absolute z-50 mt-1 w-full rounded-md bg-surface py-1 text-sm shadow-lg ring-1 ring-black"
			>
				<p role="status" class="px-3 py-2 text-ink-2">{m.comp_multiselect_loading()}</p>
			</div>
		{:else if picker.error}
			<div
				class="ring-opacity-5 absolute z-50 mt-1 w-full rounded-md bg-surface py-1 text-sm shadow-lg ring-1 ring-black"
			>
				<p role="alert" class="px-3 py-2 text-danger">{m.comp_typeahead_error()}</p>
			</div>
		{:else if picker.results.length === 0}
			<div
				class="ring-opacity-5 absolute z-50 mt-1 w-full rounded-md bg-surface py-1 text-sm shadow-lg ring-1 ring-black"
			>
				<p role="status" class="px-3 py-2 text-ink-2">{m.comp_typeahead_no_results()}</p>
			</div>
		{:else}
			<ul
				id={listboxId}
				role="listbox"
				class="ring-opacity-5 absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md bg-surface py-1 text-sm shadow-lg ring-1 ring-black"
			>
				{#each picker.results as doc, i (doc.id)}
					{@const disabled = alreadyAddedIds.has(doc.id!)}
					<!-- svelte-ignore a11y_click_events_have_key_events -->
					<li
						id={`${listboxId}-option-${i}`}
						role="option"
						aria-selected={i === picker.activeIndex}
						aria-disabled={disabled}
						onclick={() => handleSelect(doc)}
						class={[
							'px-3 py-2 text-ink select-none',
							i === picker.activeIndex ? 'bg-muted' : '',
							disabled
								? 'cursor-default opacity-50'
								: 'cursor-pointer hover:bg-muted'
						].join(' ')}
					>
						{formatDocumentOption(doc)}
						{#if disabled}
							<span class="sr-only">{m.journey_already_added()}</span>
						{/if}
					</li>
				{/each}
			</ul>
		{/if}
	{/if}
</div>
