<script lang="ts">
  // types
  interface Props {
    id?: string;
    value?: string | null;
    label?: string;
    readonly?: boolean;
    required?: boolean;
    mustMatch?: boolean;

    requestSuggestions: (query: string, limit: number) => Promise<{ name: string; value: string }[]>;

    onSubmit?: (value: { name: string; value: string } | null) => void;
  }

  // html bindings
  let container: HTMLDivElement;

  // inputs
  let { id, value = $bindable(), label, readonly, required, mustMatch, requestSuggestions, onSubmit }: Props = $props();

  // states
  let inputValue = $derived(value);
  let suggestions = $state<{ name: string; value: string }[]>([]);
  let matched = $state(false);

  // callbacks
  async function onBodyMouseDown(e: MouseEvent) {
    if (!container.contains(e.target as Node)) suggestions = [];
  }

  async function onSearchInput() {
    if (readonly) return;

    suggestions = await requestSuggestions(inputValue ?? '', 5);

    let suggestion = suggestions.find((s) => s.name === inputValue);
    if (suggestion != null) {
      inputValue = value = suggestion.name;
      matched = true;
      onSubmit?.(suggestion);
    } else if (!mustMatch) {
      value = inputValue;
      matched = false;
    } else {
      value = null;
      matched = false;
      onSubmit?.(null);
    }
  }

  function onSuggestionClick(name: string) {
    const suggestion = suggestions.find((s) => s.name === name)!;

    inputValue = value = suggestion.name;
    suggestions = [];
    onSubmit?.(suggestion);
  }
</script>

<svelte:body onmousedown={onBodyMouseDown} />

<fieldset class="fieldset">
  <legend class="fieldset-legend">
    <span>
      {label}
      {#if required}
        <span class="text-red-700">*</span>
      {/if}
    </span>
  </legend>
  <div class="relative" bind:this={container}>
    <input
      {id}
      {readonly}
      type="search"
      autocomplete="off"
      class="input"
      bind:value={inputValue}
      oninput={() => onSearchInput()}
      onfocusin={() => onSearchInput()}
      pattern={mustMatch && matched ? `^(${suggestions.map((s) => s.name).join('|')})$` : undefined}
    />
    {#if suggestions.length > 0}
      <ul class="absolute bg-base-200 w-full z-20 menu menu-sm rounded-box">
        {#each suggestions as suggestion (suggestion)}
          <li class="w-full text-left">
            <button
              class="block w-full overflow-hidden text-ellipsis whitespace-nowrap"
              title={suggestion.name}
              onclick={() => onSuggestionClick(suggestion.name)}>{suggestion.name}</button
            >
          </li>
        {/each}
      </ul>
    {/if}
  </div>
  <p class="fieldset-label"></p>
</fieldset>
