<script>
  /**
   * Searchable combobox input.
   * @type {{ id?: string, value: string, options: string[], placeholder?: string, required?: boolean, onchange?: (v: string) => void }}
   */
  let { id, value = $bindable(), options, placeholder = '', required = false, onselect, onblurcommit } = $props();

  let query = $state(value);
  let open = $state(false);
  let focused = $state(-1);

  let filtered = $derived(
    query.trim() === ''
      ? options
      : options.filter(o => o.toLowerCase().includes(query.toLowerCase()))
  );

  function select(opt) {
    query = opt;
    value = opt;
    open = false;
    focused = -1;
    onselect?.(opt);
  }

  function onInput(e) {
    query = e.currentTarget.value;
    value = query;
    open = true;
    focused = -1;
  }

  function onKeydown(e) {
    if (!open) { if (e.key === 'ArrowDown') { open = true; } return; }
    if (e.key === 'ArrowDown') { e.preventDefault(); focused = Math.min(focused + 1, filtered.length - 1); }
    else if (e.key === 'ArrowUp') { e.preventDefault(); focused = Math.max(focused - 1, 0); }
    else if (e.key === 'Enter') { e.preventDefault(); if (focused >= 0) { select(filtered[focused]); } else if (query.trim()) { select(query.trim()); } }
    else if (e.key === 'Escape') { open = false; focused = -1; }
  }

  function onBlur() {
    setTimeout(() => {
      open = false;
      focused = -1;
      if (onblurcommit && query.trim()) onblurcommit(query.trim());
    }, 150);
  }

  // Keep query in sync if value is changed externally
  $effect(() => { query = value; });
</script>

<div class="combo">
  <input
    {id}
    {required}
    {placeholder}
    class="combo-input"
    type="text"
    autocomplete="off"
    value={query}
    oninput={onInput}
    onkeydown={onKeydown}
    onfocus={() => open = true}
    onblur={onBlur}
  />
  {#if open && filtered.length > 0}
    <ul class="dropdown" role="listbox">
      {#each filtered as opt, i}
        <li
          class="option"
          class:highlighted={i === focused}
          role="option"
          aria-selected={opt === value}
          onmousedown={() => select(opt)}
        >{opt}</li>
      {/each}
    </ul>
  {/if}
</div>

<style>
  .combo {
    position: relative;
    width: 100%;
  }

  .combo-input {
    font-family: var(--sans);
    font-size: 14px;
    font-weight: 300;
    color: var(--text-h);
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 8px 12px;
    outline: none;
    transition: border-color 0.15s;
    width: 100%;
  }
  .combo-input:focus { border-color: var(--accent-border); }

  .dropdown {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.1);
    list-style: none;
    max-height: 200px;
    overflow-y: auto;
    z-index: 50;
    padding: 4px;
  }

  .option {
    font-size: 13px;
    font-weight: 300;
    color: var(--text);
    padding: 7px 10px;
    border-radius: 6px;
    cursor: pointer;
    transition: background 0.1s, color 0.1s;
  }
  .option:hover, .option.highlighted {
    background: var(--accent-bg);
    color: var(--accent);
  }
</style>
