<script lang="ts">
  import { onMount, tick } from 'svelte';

  import { pageMeta, type SearchItem } from '$lib/navigation/client-navigation';

  let {
    query = $bindable(''),
    open = $bindable(false),
    focusRequest = 0,
    filteredSearchItems,
    hiddenResultCount,
    label = 'Search the workspace',
    placeholder = 'Search products, mixes, sessions, and pages...',
    className = '',
    showShortcut = true,
    onRunSearchItem
  }: {
    query?: string;
    open?: boolean;
    focusRequest?: number;
    filteredSearchItems: SearchItem[];
    hiddenResultCount: number;
    label?: string;
    placeholder?: string;
    className?: string;
    showShortcut?: boolean;
    onRunSearchItem: (item: SearchItem) => void | Promise<void>;
  } = $props();

  let root: HTMLDivElement | null = null;
  let input: HTMLInputElement | null = null;
  let highlightedIndex = $state(-1);
  const resultsId = `workspace-search-results-${Math.random().toString(36).slice(2)}`;

  const activeDescendant = $derived(
    highlightedIndex >= 0 ? `${resultsId}-${highlightedIndex}` : undefined
  );

  function openSearch() {
    open = true;
  }

  function closeSearch() {
    open = false;
    highlightedIndex = -1;
  }

  async function selectItem(item: SearchItem) {
    closeSearch();
    await onRunSearchItem(item);
  }

  function moveHighlight(direction: 1 | -1) {
    if (!filteredSearchItems.length) {
      highlightedIndex = -1;
      return;
    }

    open = true;

    if (highlightedIndex === -1) {
      highlightedIndex = direction === 1 ? 0 : filteredSearchItems.length - 1;
      return;
    }

    highlightedIndex = (highlightedIndex + direction + filteredSearchItems.length) % filteredSearchItems.length;
  }

  function handleKeydown(event: KeyboardEvent) {
    if (event.key === 'ArrowDown') {
      event.preventDefault();
      moveHighlight(1);
      return;
    }

    if (event.key === 'ArrowUp') {
      event.preventDefault();
      moveHighlight(-1);
      return;
    }

    if (event.key === 'Enter' && open) {
      const candidate =
        highlightedIndex >= 0 ? filteredSearchItems[highlightedIndex] : filteredSearchItems[0];

      if (candidate) {
        event.preventDefault();
        void selectItem(candidate);
      }
      return;
    }

    if (event.key === 'Escape') {
      event.preventDefault();
      closeSearch();
    }
  }

  $effect(() => {
    focusRequest;

    if (!open) {
      return;
    }

    tick().then(() => input?.focus());
  });

  $effect(() => {
    if (!open) {
      highlightedIndex = -1;
      return;
    }

    if (!filteredSearchItems.length) {
      highlightedIndex = -1;
      return;
    }

    if (highlightedIndex >= filteredSearchItems.length) {
      highlightedIndex = filteredSearchItems.length - 1;
    }
  });

  onMount(() => {
    const handlePointerDown = (event: MouseEvent) => {
      if (root?.contains(event.target as Node)) {
        return;
      }

      closeSearch();
    };

    window.addEventListener('mousedown', handlePointerDown);

    return () => {
      window.removeEventListener('mousedown', handlePointerDown);
    };
  });
</script>

<div bind:this={root} class={`workspace-search ${className}`.trim()}>
  <label class="search-box">
    <span class="search-icon" aria-hidden="true"></span>
    <input
      bind:this={input}
      bind:value={query}
      type="search"
      role="combobox"
      autocomplete="off"
      spellcheck="false"
      aria-label={label}
      aria-expanded={open}
      aria-controls={resultsId}
      aria-activedescendant={activeDescendant}
      aria-autocomplete="list"
      placeholder={placeholder}
      onfocus={openSearch}
      oninput={openSearch}
      onkeydown={handleKeydown}
    />
    {#if showShortcut}
      <kbd>/</kbd>
    {/if}
  </label>

  {#if open}
    <div class="search-results" id={resultsId} role="listbox">
      {#if filteredSearchItems.length}
        {#each filteredSearchItems as item, index (item.href + item.label)}
          {@const ResultIcon = pageMeta(item.href).icon}
          <button
            id={`${resultsId}-${index}`}
            class:active={index === highlightedIndex}
            class="search-result"
            type="button"
            role="option"
            aria-selected={index === highlightedIndex}
            onmouseenter={() => (highlightedIndex = index)}
            onclick={() => void selectItem(item)}
          >
            <span class="search-result-icon" aria-hidden="true">
              <ResultIcon size={18} strokeWidth={1.9} />
            </span>
            <div class="search-result-copy">
              <strong>{item.label}</strong>
              <span>{item.description}</span>
            </div>
            <small>{item.href}</small>
          </button>
        {/each}

        {#if hiddenResultCount > 0}
          <p class="search-more">
            {hiddenResultCount} more {hiddenResultCount === 1 ? 'match' : 'matches'}, keep typing to narrow.
          </p>
        {/if}
      {:else}
        <div class="search-empty">
          <strong>No results</strong>
          <span>Try searching for mixes, sessions, or pages.</span>
        </div>
      {/if}
    </div>
  {/if}
</div>

<style>
  .workspace-search {
    position: relative;
    width: 100%;
    min-width: 0;
  }

  .search-box {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 0.64rem;
    width: 100%;
    min-height: 2.75rem;
    padding: 0.72rem 0.82rem;
    border: 1px solid var(--line);
    border-radius: 0.82rem;
    background: color-mix(in srgb, var(--panel-soft) 60%, var(--color-bg-surface));
    transition: border-color 140ms ease, background-color 140ms ease, box-shadow 140ms ease;
  }

  .workspace-search:focus-within .search-box,
  .search-box:hover {
    border-color: color-mix(in srgb, var(--color-brand) 24%, var(--line));
    background: var(--color-bg-surface);
  }

  .workspace-search:focus-within .search-box {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-brand) 18%, transparent);
  }

  input {
    min-width: 0;
    border: none;
    outline: none;
    background: transparent;
    color: var(--color-text-primary);
    font: inherit;
  }

  input::placeholder {
    color: var(--color-text-muted);
  }

  .search-icon {
    position: relative;
    display: inline-block;
    width: 0.82rem;
    height: 0.82rem;
    border: 2px solid var(--color-text-muted);
    border-radius: 999px;
  }

  .search-icon::after {
    content: '';
    position: absolute;
    right: -0.28rem;
    bottom: -0.18rem;
    width: 0.42rem;
    height: 2px;
    border-radius: 999px;
    background: var(--color-text-muted);
    transform: rotate(45deg);
  }

  .search-results {
    position: absolute;
    top: calc(100% + 0.45rem);
    left: 0;
    right: 0;
    z-index: 35;
    display: grid;
    gap: 0.18rem;
    max-height: min(26rem, calc(100vh - 10rem));
    overflow: auto;
    padding: 0.46rem;
    border: 1px solid var(--color-border);
    border-radius: 1rem;
    background: var(--color-bg-surface);
    box-shadow: 0 18px 44px rgba(11, 18, 14, 0.14);
  }

  .search-result,
  .search-empty {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.8rem;
    padding: 0.7rem 0.8rem;
    border: none;
    border-radius: 0.82rem;
    background: transparent;
    text-align: left;
  }

  .search-result {
    cursor: pointer;
    transition: background-color 140ms ease;
  }

  .search-result:hover,
  .search-result.active {
    background: var(--panel-soft);
  }

  .search-result-icon {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 0.7rem;
    border: 1px solid color-mix(in srgb, var(--color-brand) 16%, var(--color-border));
    background: color-mix(in srgb, var(--color-brand-tint) 72%, var(--color-bg-surface));
    color: var(--color-brand);
  }

  /* Strip the inline-SVG baseline gap so the glyph is truly centred, not nudged
     down-and-left inside the badge. */
  .search-result-icon :global(svg) {
    display: block;
  }

  .search-result-copy {
    min-width: 0;
    /* Take the middle slot so the trailing href stays right-aligned. */
    flex: 1;
  }

  .search-result strong,
  .search-empty strong {
    display: block;
    font-size: 0.94rem;
    color: var(--color-text-primary);
  }

  .search-result span,
  .search-empty span,
  .search-result small,
  .search-more {
    color: var(--color-text-secondary);
  }

  .search-result span {
    display: block;
    margin-top: 0.18rem;
    font-size: 0.82rem;
  }

  .search-result small {
    flex-shrink: 0;
    font-size: 0.74rem;
  }

  .search-empty {
    justify-content: flex-start;
  }

  .search-more {
    margin: 0.12rem 0.28rem 0;
    padding: 0.48rem 0.52rem 0.1rem;
    border-top: 1px solid var(--line);
    font-size: 0.76rem;
  }

  kbd {
    padding: 0.1rem 0.42rem;
    border: 1px solid var(--line-strong);
    border-radius: 0.42rem;
    color: var(--muted);
    background: var(--color-bg-surface);
    font-size: 0.76rem;
  }

  @media (max-width: 720px) {
    .search-box {
      min-height: 2.55rem;
      padding: 0.66rem 0.74rem;
    }

    .search-results {
      max-height: min(22rem, calc(100vh - 8rem));
    }
  }
</style>
