<script lang="ts">
  import type { ThroughputProduct } from '$lib/types';
  import { Search, X, Check } from 'lucide-svelte';

  let {
    products = [],
    productId = $bindable(''),
    disabled = false,
    inputId = 'throughput-product'
  }: {
    products?: ThroughputProduct[];
    productId?: string;
    disabled?: boolean;
    inputId?: string;
  } = $props();

  let clientName = $state('');
  let query = $state('');
  let open = $state(false);
  let highlighted = $state(-1);
  let focused = $state(false);
  let root = $state<HTMLDivElement | null>(null);

  function label(product: ThroughputProduct): string {
    return product.item_id ? `${product.name} · ${product.item_id}` : product.name;
  }

  const clients = $derived(
    Array.from(
      new Set(
        products
          .map((p) => (p.client_name ?? '').trim())
          .filter((name) => name.length > 0)
      )
    ).sort((a, b) => a.localeCompare(b))
  );

  const selected = $derived(
    productId ? products.find((p) => String(p.id) === productId) ?? null : null
  );

  // Products narrowed by the chosen client, then by the search text. Item id and
  // name are both searchable so operators can type either.
  const filtered = $derived.by(() => {
    const q = query.trim().toLowerCase();
    return products.filter((product) => {
      if (clientName && (product.client_name ?? '') !== clientName) return false;
      if (!q) return true;
      return (
        product.name.toLowerCase().includes(q) ||
        (product.item_id ?? '').toLowerCase().includes(q)
      );
    });
  });

  // Keep the text box in sync when the selection is cleared from outside (for
  // example after "Save and add another" resets the form).
  $effect(() => {
    if (!productId && !focused) {
      query = '';
    }
  });

  // Reflect a selection set from outside (e.g. when an entry is loaded into the
  // composer to be edited) so the search box shows the chosen product, not blank.
  $effect(() => {
    if (productId && !focused) {
      const match = products.find((p) => String(p.id) === productId);
      if (match) query = label(match);
    }
  });

  // If the active client no longer contains the selected product, drop it.
  $effect(() => {
    if (selected && clientName && (selected.client_name ?? '') !== clientName) {
      productId = '';
      query = '';
    }
  });

  function choose(product: ThroughputProduct) {
    productId = String(product.id);
    query = label(product);
    open = false;
    highlighted = -1;
  }

  function clear() {
    productId = '';
    query = '';
    open = false;
    highlighted = -1;
  }

  function onInput(event: Event) {
    query = (event.target as HTMLInputElement).value;
    productId = '';
    open = true;
    highlighted = filtered.length ? 0 : -1;
  }

  function onKeydown(event: KeyboardEvent) {
    if (event.key === 'ArrowDown') {
      event.preventDefault();
      open = true;
      highlighted = Math.min(highlighted + 1, filtered.length - 1);
    } else if (event.key === 'ArrowUp') {
      event.preventDefault();
      highlighted = Math.max(highlighted - 1, 0);
    } else if (event.key === 'Enter') {
      if (open && highlighted >= 0 && highlighted < filtered.length) {
        event.preventDefault();
        choose(filtered[highlighted]);
      }
    } else if (event.key === 'Escape') {
      open = false;
      highlighted = -1;
    }
  }

  function onFocusOut(event: FocusEvent) {
    if (root && event.relatedTarget instanceof Node && root.contains(event.relatedTarget)) {
      return;
    }
    focused = false;
    open = false;
    highlighted = -1;
  }
</script>

<div class="picker" bind:this={root} onfocusin={() => (focused = true)} onfocusout={onFocusOut}>
  <select
    id={`${inputId}-client`}
    class="client-select"
    bind:value={clientName}
    aria-label="Filter by client"
    {disabled}
  >
    <option value="">All clients</option>
    {#each clients as client (client)}
      <option value={client}>{client}</option>
    {/each}
  </select>

  <div class="combo" role="combobox" aria-expanded={open} aria-haspopup="listbox" aria-controls={`${inputId}-list`}>
    <span class="combo-icon" aria-hidden="true"><Search size={16} strokeWidth={2.2} /></span>
    <input
      id={inputId}
      class="combo-input"
      type="text"
      autocomplete="off"
      placeholder="Search product or item id…"
      value={query}
      {disabled}
      aria-autocomplete="list"
      oninput={onInput}
      onfocus={() => (open = true)}
      onkeydown={onKeydown}
    />
    {#if productId}
      <button type="button" class="combo-clear" onclick={clear} aria-label="Clear product">
        <X size={15} strokeWidth={2.4} />
      </button>
    {/if}

    {#if open && !disabled}
      <ul class="options" id={`${inputId}-list`} role="listbox">
        {#if filtered.length === 0}
          <li class="option empty">No products match.</li>
        {:else}
          {#each filtered.slice(0, 50) as product, i (product.id)}
            <li
              class="option"
              class:highlighted={i === highlighted}
              class:selected={String(product.id) === productId}
              role="option"
              aria-selected={String(product.id) === productId}
              onmousedown={(e) => {
                e.preventDefault();
                choose(product);
              }}
              onmouseenter={() => (highlighted = i)}
            >
              <span class="option-name">{product.name}</span>
              <span class="option-meta">
                {#if product.client_name}<span class="option-client">{product.client_name}</span>{/if}
                {#if product.item_id}<span class="option-item">#{product.item_id}</span>{/if}
              </span>
              {#if String(product.id) === productId}
                <span class="option-check" aria-hidden="true"><Check size={15} strokeWidth={2.6} /></span>
              {/if}
            </li>
          {/each}
          {#if filtered.length > 50}
            <li class="option more">
              Showing first 50 of {filtered.length} — keep typing to narrow.
            </li>
          {/if}
        {/if}
      </ul>
    {/if}
  </div>
</div>

<style>
  /* Client filter and product search sit side by side so the product is never
     stacked underneath the client. They wrap to two rows only when the cell is
     too narrow to keep both readable. */
  .picker {
    display: flex;
    flex-direction: row;
    align-items: stretch;
    gap: 0.45rem;
    min-width: 0;
  }
  .client-select {
    flex: 0 1 8.5rem;
    min-width: 6rem;
    min-height: 48px;
    padding: 0.4rem 0.55rem;
    border: 1px solid var(--color-border, #d1d5db);
    border-radius: 0.55rem;
    font: inherit;
    background: var(--color-bg-surface, #fff);
    color: var(--color-text-primary, #111827);
  }
  .combo {
    position: relative;
    display: flex;
    align-items: center;
    flex: 1 1 auto;
    min-width: 0;
  }
  @media (max-width: 560px) {
    .picker {
      flex-wrap: wrap;
    }
    .client-select {
      flex: 1 1 100%;
    }
  }
  .combo-icon {
    position: absolute;
    left: 0.6rem;
    display: inline-flex;
    color: var(--color-text-muted, #6b7280);
    pointer-events: none;
  }
  .combo-input {
    width: 100%;
    min-height: 46px;
    padding: 0.55rem 2rem 0.55rem 2rem;
    border: 1px solid var(--color-border, #d1d5db);
    border-radius: 0.55rem;
    font: inherit;
    background: var(--color-bg-surface, #fff);
    color: var(--color-text-primary, #111827);
  }
  .combo-input:focus-visible {
    outline: 3px solid var(--color-brand, #157f3a);
    outline-offset: 1px;
    border-color: var(--color-brand, #157f3a);
  }
  .combo-clear {
    position: absolute;
    right: 0.45rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--color-text-muted, #6b7280);
    cursor: pointer;
  }
  .combo-clear:hover {
    background: var(--color-bg-app, #f3f4f6);
    color: var(--color-text-primary, #111827);
  }
  .options {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 200;
    margin: 0;
    padding: 0.25rem;
    list-style: none;
    max-height: 18rem;
    overflow-y: auto;
    background: var(--color-bg-surface, #fff);
    border: 1px solid var(--color-border, #d1d5db);
    border-radius: 0.6rem;
    box-shadow: 0 12px 32px rgba(15, 23, 42, 0.16);
  }
  .option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.6rem;
    border-radius: 0.45rem;
    font-size: 0.95rem;
    color: var(--color-text-primary, #111827);
    cursor: pointer;
  }
  .option.highlighted {
    background: var(--color-brand-tint, #e8f5ec);
  }
  .option.selected {
    font-weight: 650;
  }
  .option.empty,
  .option.more {
    color: var(--color-text-muted, #6b7280);
    cursor: default;
    font-size: 0.88rem;
  }
  .option-name {
    flex: 1 1 auto;
    min-width: 0;
  }
  .option-meta {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    flex-shrink: 0;
  }
  .option-client {
    font-size: 0.78rem;
    color: var(--color-text-secondary, #4b5563);
    background: var(--color-bg-app, #f3f4f6);
    padding: 0.1rem 0.4rem;
    border-radius: 999px;
  }
  .option-item {
    font-size: 0.8rem;
    color: var(--color-text-muted, #6b7280);
    font-variant-numeric: tabular-nums;
  }
  .option-check {
    color: var(--color-brand, #157f3a);
    flex-shrink: 0;
  }
</style>
