<script lang="ts">
  // v0.2 Phase 5 primitive — searchable combobox over Bits UI Combobox.
  // Use cases in v0.2: HistoryPage FTS5 transcript search, ModelDownloader
  // model picker. Caller supplies `options` filtered against `inputValue`
  // upstream (so the combobox stays headless about filter semantics).
  import { Combobox } from "bits-ui";
  import { Check, ChevronsUpDown, X } from "lucide-svelte";

  type Option = { value: string; label: string; disabled?: boolean };

  interface Props {
    value: string;
    /**
     * Read-only initial / programmatic input string. Bits UI's
     * Combobox.Root treats `inputValue` as one-way controlled — to
     * react to typing, subscribe via `oninput` below and update
     * your filtered options upstream.
     */
    inputValue?: string;
    options: Option[];
    placeholder?: string;
    label?: string;
    disabled?: boolean;
    allowClear?: boolean;
    onValueChange?: (value: string) => void;
    /** Fired on every keystroke in the combobox input. */
    oninput?: (e: Event & { currentTarget: HTMLInputElement }) => void;
    onClear?: () => void;
  }

  let {
    value = $bindable(""),
    inputValue,
    options,
    placeholder = "Search…",
    label,
    disabled = false,
    allowClear = true,
    onValueChange,
    oninput,
    onClear,
  }: Props = $props();
</script>

<Combobox.Root
  type="single"
  bind:value
  inputValue={inputValue}
  {disabled}
  onValueChange={onValueChange}
>
  <div class="lumotia-combobox-shell relative">
    <Combobox.Input
      class="w-full bg-bg-input border border-border-subtle rounded-md pl-3 pr-16 py-2 text-[13px] text-text focus:outline-none focus:border-accent disabled:opacity-50"
      aria-label={label}
      {placeholder}
      {oninput}
    />
    <div class="absolute right-1.5 top-1/2 -translate-y-1/2 flex items-center gap-0.5">
      {#if allowClear && (inputValue || value)}
        <button
          type="button"
          class="size-6 rounded flex items-center justify-center text-text-tertiary hover:text-text hover:bg-hover"
          aria-label="Clear"
          onclick={() => { value = ""; onClear?.(); }}
        >
          <X size={12} />
        </button>
      {/if}
      <Combobox.Trigger
        class="size-6 rounded flex items-center justify-center text-text-tertiary hover:text-text hover:bg-hover"
        aria-label="Open options"
      >
        <ChevronsUpDown size={12} />
      </Combobox.Trigger>
    </div>
  </div>
  <Combobox.Portal>
    <Combobox.Content
      class="z-50 rounded-md bg-bg-card border border-border-subtle shadow-xl py-1 max-h-[280px] overflow-auto"
      sideOffset={4}
    >
      {#each options as opt (opt.value)}
        <Combobox.Item
          value={opt.value}
          label={opt.label}
          disabled={opt.disabled}
          class="px-3 py-1.5 text-[13px] text-text data-[highlighted]:bg-hover data-[disabled]:opacity-50 cursor-pointer flex items-center gap-2"
        >
          {#snippet children({ selected })}
            <span class="size-4 flex items-center justify-center shrink-0">
              {#if selected}<Check size={14} class="text-accent" />{/if}
            </span>
            <span class="truncate">{opt.label}</span>
          {/snippet}
        </Combobox.Item>
      {/each}
      {#if options.length === 0}
        <p class="px-3 py-2 text-[12px] text-text-tertiary italic">No matches</p>
      {/if}
    </Combobox.Content>
  </Combobox.Portal>
</Combobox.Root>
