<script lang="ts">
	import type { Character } from '$lib/data/character';
	import type { Snippet } from 'svelte';
	import type { ClassValue } from 'svelte/elements';
	import { stringsearch } from './stringsearch';

	interface Props {
		id: string;
		characters: Character[] | null;
		value: Character | undefined;
		class?: ClassValue | null;
		option?: Snippet<[Character]>;
		required?: boolean;
	}

	let { id, characters, value = $bindable(), class: className, option, required = false }: Props = $props();

	let popover: HTMLElement | undefined = $state();
	let optionsContainer: HTMLElement | undefined = $state();
	const expanded = $derived(!popover?.hidden);

	let search = $state('');

	const charas = $derived(characters ?? []);
	const searchedCharas = $derived(stringsearch(search, charas, ({name}) => name) || charas);

	$effect(() => {
		if (required && value == null && charas.length > 0) {
			value = charas[0];
		}
	});

	function setByElem(o: HTMLElement) {
		const j = parseInt(o.dataset.charaId ?? '');
		value = charas.find((c) => c.chara_id === j) ?? value;
		o.focus();
	}

	function onkeydown(evt: KeyboardEvent) {
		switch (evt.key) {
			case 'ArrowDown': {
				const opts = [...optionsContainer!.children] as HTMLElement[];
				const i = opts.findIndex((n) => parseInt(n.dataset.charaId ?? '') === value?.chara_id);
				const o = i != null && i >= 0 ? opts[Math.min(i+1, opts.length - 1)] : opts[0];
				setByElem(o);
				break;
			}
			case 'ArrowUp': {
				const opts = [...optionsContainer!.children] as HTMLElement[];
				const i = opts.findIndex((n) => parseInt(n.dataset.charaId ?? '') === value?.chara_id);
				const o = i != null && i > 0 ? opts[i-1] : opts[0];
				setByElem(o);
				break;
			}
			case 'Home': {
				search = '';
				setByElem(optionsContainer!.children[0] as HTMLElement);
				break;
			}
			case 'End': {
				search = '';
				setByElem(optionsContainer!.children[optionsContainer!.childElementCount - 1] as HTMLElement);
				break;
			}
			case ' ':
				popover!.showPopover();
				break;
			case 'Escape':
				// TODO(zeph): restore chara that was selected when the popup opened?
				// for now just hide
				popover!.hidePopover();
				break;
			case 'Enter':
				popover!.togglePopover();
				break;
			default:
				return;
		}
		evt.preventDefault();
	}
</script>

<div class={className} style={`anchor-name: --anchor-${id}`}>
	<span
		{id}
		class="block h-9 content-center hover:cursor-pointer bg-mist-300 dark:bg-mist-900"
		role="combobox"
		aria-expanded={expanded}
		aria-controls="charaOptions"
		aria-haspopup="listbox"
		onclick={() => popover?.togglePopover()}
		{onkeydown}
		tabindex="0"
	>{value?.name ?? ''}</span>
	<div
		class="absolute top-2 shadow-lg open:flex flex-col px-2 skill-tip"
		style={`position-anchor: --anchor-${id}; position-area: bottom;`}
		id="charaOptions"
		role="listbox"
		popover
		bind:this={popover}
	>
		<input class="my-2 border rounded-md min-h-8 pointer-coarse:min-h-12" placeholder=" Search" role="searchbox" bind:value={search} />
		<div class="max-h-72 overflow-y-scroll" bind:this={optionsContainer}>
			{#if !required}
				<div
					class="w-full h-8 hover:cursor-pointer hover:bg-mist-300 hover:dark:bg-mist-900 text-lg"
					role="option"
					aria-selected={value == undefined}
					tabindex="0"
					data-chara-id=""
					onmousedown={() => {value = undefined; search = ''; popover!.hidePopover()}}
					onfocus={() => value = undefined}
					{onkeydown}
				>
					<span class="italic text-sm">Reset</span>
				</div>
			{/if}
			{#each searchedCharas as c (c.chara_id)}
				<div
					class="w-full h-8 hover:cursor-pointer hover:bg-mist-300 hover:dark:bg-mist-900 text-lg"
					role="option"
					aria-selected={value?.chara_id === c.chara_id}
					tabindex="0"
					data-chara-id={c.chara_id}
					onmousedown={() => {value = c; popover!.hidePopover()}}
					onfocus={() => value = c}
					{onkeydown}
				>
					{#if option != null}
						{@render option(c)}
					{:else}
						<span>{c.name}</span>
					{/if}
				</div>
			{:else}
				<div class="w-full h-8 text-lg italic">No matches.</div>
			{/each}
		</div>
	</div>
</div>
