<svelte:options
	customElement={{
		tag: 'eve-combobox'
	}}
/>

<script lang="ts">
	import { onMount } from 'svelte';
	import Icon from '@iconify/svelte';
	import LiquidGlass from '../LiquidGlass.svelte';

	let {
		items = [],
		value = $bindable(),
		placeholder = 'Search...',
		displayValue = value,
		maxResults = Number.POSITIVE_INFINITY,
		filterFunction = defaultFilter
	}: {
		items?: string[];
		value?: string;
		placeholder?: string;
		displayValue?: string;
		maxResults?: number;
		filterFunction?: (items: string[], search: string) => string[];
	} = $props();

	let search: string = $state(displayValue || '');
	let isOpen: boolean = $state(false);

	let comboboxRef: HTMLDivElement;
	let inputRef: HTMLInputElement;

	let dropdownElement: HTMLDivElement;
	let portalTarget: HTMLElement;
	let dropdownPosition = $state({ top: 0, left: 0, width: 0 });

	function defaultFilter(items: string[], search: string): string[] {
		return items.filter((item) => item.toLowerCase().includes(search.toLowerCase()));
	}

	onMount(() => {
		portalTarget = document.body;
		displayValue = value;

		const handleClickOutside = (event: MouseEvent) => {
			if (
				comboboxRef &&
				!comboboxRef.contains(event.target as Node) &&
				dropdownElement &&
				!dropdownElement.contains(event.target as Node)
			) {
				isOpen = false;
			}
		};

		const updatePosition = () => {
			if (inputRef && isOpen) {
				const rect = inputRef.getBoundingClientRect();
				dropdownPosition = {
					top: rect.bottom + window.scrollY + 4,
					left: rect.left + window.scrollX,
					width: rect.width
				};
			}
		};

		document.addEventListener('click', handleClickOutside);
		window.addEventListener('scroll', updatePosition, true);
		window.addEventListener('resize', updatePosition);

		return () => {
			document.removeEventListener('click', handleClickOutside);
			window.removeEventListener('scroll', updatePosition, true);
			window.removeEventListener('resize', updatePosition);
		};
	});

	const filtered = $derived(filterFunction(items, search).slice(0, maxResults));

	$effect(() => {
		if (isOpen && inputRef) {
			const rect = inputRef.getBoundingClientRect();
			dropdownPosition = {
				top: rect.bottom + window.scrollY + 4,
				left: rect.left + window.scrollX,
				width: rect.width
			};
		}
	});

	function selectItem(item: string) {
		value = item;
		search = displayValue;
		isOpen = false;
		inputRef?.blur();
		$host()?.dispatchEvent(new CustomEvent('select', { detail: item }));
	}

	function handleInputFocus() {
		isOpen = true;
	}

	function handleInputChange() {
		$host()?.dispatchEvent(new CustomEvent('change', { detail: search }));
	}

	function handleKeydown(event: KeyboardEvent) {
		if (event.key === 'Escape') {
			isOpen = false;
			inputRef?.blur();
		} else if (event.key === 'Enter' && filtered.length > 0) {
			selectItem(filtered[0]);
		} else if (event.key === 'ArrowDown') {
			event.preventDefault();
			isOpen = true;
		}
	}
</script>

<div class="relative" bind:this={comboboxRef}>
	<div class="relative">
		<input
			bind:this={inputRef}
			type="text"
			{placeholder}
			bind:value={search}
			onfocus={handleInputFocus}
			oninput={handleInputChange}
			onkeydown={handleKeydown}
			class="input input-bordered w-full pr-10 text-base-content"
			autocomplete="off"
			role="combobox"
			aria-expanded={isOpen}
			aria-haspopup="listbox"
		/>
		<button
			type="button"
			onclick={() => (isOpen = !isOpen)}
			class="absolute right-2 top-1/2 -translate-y-1/2 p-1 hover:bg-base-200 rounded"
			aria-label="Toggle dropdown"
		>
			<Icon
				icon={isOpen ? 'mdi:chevron-up' : 'mdi:chevron-down'}
				class="w-5 h-5 text-base-content/70"
			/>
		</button>
	</div>
</div>

{#if isOpen && portalTarget}
	<div
		bind:this={dropdownElement}
		class="fixed z-50 rounded-lg shadow-lg max-h-60 overflow-y-auto"
		style="top: {dropdownPosition.top}px; left: {dropdownPosition.left}px; width: {dropdownPosition.width}px;"
		role="listbox"
	>
		<LiquidGlass class="border border-base-300">
			{#if filtered.length > 0}
				{#each filtered as item}
					<button
						type="button"
						class="w-full px-4 py-2 text-left hover:bg-base-200 focus:bg-base-200 focus:outline-none text-base-content border-b border-base-200 last:border-b-0"
						onclick={() => selectItem(item)}
						role="option"
						aria-selected={value === item}
					>
						<slot name="item" {item}>
							{item}
						</slot>
					</button>
				{/each}
			{:else}
				<div class="p-4 text-center text-base-content/70">
					<slot name="empty">No results found</slot>
				</div>
			{/if}
		</LiquidGlass>
	</div>
{/if}
