<script lang="ts">
	let {
		id,
		value = $bindable(),
		suggestionRequired = false,
		emptyAllowed = false,
		searchSuggestionFunc = () => Promise.resolve([]),
		invalidMessage,
		size = 'md',
		label,
		required = false,
		onsubmit
	}: {
		id?: string;
		value: string;
		suggestionRequired?: boolean;
		emptyAllowed?: boolean;
		searchSuggestionFunc?: (input: string) => Promise<{ name: string; value: string }[]>;
		invalidMessage?: string;
		size?: 'xs' | 'sm' | 'md' | 'lg';
		label?: string;
		required?: boolean;
		onsubmit?: (event: Event & { input: string; value: string }) => void;
	} = $props();

	let elemValue = $state(value);
	let searchSuggestions: { name: string; value: string }[] = $state([]);
</script>

<div class="relative">
	<div>
		{#if label}
			<label class="label" for={id}>
				<span class="label-text">
					{label}
					{#if required}
						<span class="text-red-700">*</span>
					{/if}
				</span>
			</label>
		{/if}
		<input
			type="search"
			autocomplete="off"
			class="input input-bordered w-full"
			class:input-xs={size === 'xs'}
			class:input-sm={size === 'sm'}
			class:input-md={size === 'md'}
			class:input-lg={size === 'lg'}
			{id}
			{required}
			bind:value={elemValue}
			oninput={async (e: Event & { currentTarget: EventTarget & HTMLInputElement }) => {
				searchSuggestions = await searchSuggestionFunc(elemValue);
				const searchSuggestion = searchSuggestions.find((v) => v.name === elemValue);
				if (searchSuggestion !== undefined) {
					elemValue = searchSuggestion.name;
					value = searchSuggestion.value;
					searchSuggestions = [];
					(e.currentTarget || e.target).setCustomValidity('');
					onsubmit?.(Object.assign(e, { input: elemValue, value: value }));
				} else if (elemValue === '' && emptyAllowed) {
					onsubmit?.(Object.assign(e, { input: '', value: '' }));
				} else {
					value = '';
				}
			}}
			oninvalid={(e: Event & { currentTarget: EventTarget & HTMLInputElement }) => {
				if (invalidMessage) e.currentTarget.setCustomValidity(invalidMessage);
			}}
			onfocus={() => searchSuggestionFunc(elemValue).then((v) => (searchSuggestions = v))}
			pattern={suggestionRequired
				? `${value ? elemValue : 'a^' + (emptyAllowed ? '|$^' : '')}`
				: null}
		/>
	</div>

	{#if elemValue && searchSuggestions.length !== 0}
		<ul class="absolute bg-base-200 w-full z-20 menu menu-sm rounded-box">
			{#each searchSuggestions as searchSuggestion}
				<li class="w-full text-left">
					<button
						class="block w-full overflow-hidden text-ellipsis whitespace-nowrap"
						title="{searchSuggestion.name} ({searchSuggestion.value})"
						onclick={(e) => {
							elemValue = searchSuggestion.name;
							value = searchSuggestion.value;
							searchSuggestions = [];
							onsubmit?.(Object.assign(e, { input: elemValue, value: value }));
						}}>{searchSuggestion.name}</button
					>
				</li>
			{/each}
		</ul>
	{/if}
</div>

<!-- close the search suggestions box when clicking outside -->
{#if elemValue && searchSuggestions.length !== 0}
	<button
		aria-label=" "
		class="absolute top-0 left-0 z-10 w-full h-full cursor-default"
		onclick={() => (searchSuggestions = [])}
	></button>
{/if}