<script lang="ts">
  import { ChevronDown } from "lucide-svelte";

  interface DropdownItem {
    value: string;
    label: string;
    group?: string;
  }

  interface Props {
    items: DropdownItem[];
    selected: string;
    placeholder?: string;
    onSelect: (value: string) => void;
    groupBy?: boolean;
    disabled?: boolean;
  }

  let {
    items,
    selected,
    placeholder = "Select...",
    onSelect,
    groupBy = false,
    disabled = false,
  }: Props = $props();

  let open = $state(false);
  let focusIndex = $state(-1);
  let flipUp = $state(false);
  let menuRef = $state<HTMLDivElement | null>(null);
  let triggerRef = $state<HTMLButtonElement | null>(null);

  let selectedLabel = $derived(
    items.find((i) => i.value === selected)?.label ?? placeholder,
  );

  // Group items by group field
  let groups = $derived.by<string[]>(() => {
    if (!groupBy) return [];
    const seen = new Set<string>();
    for (const item of items) {
      if (item.group) seen.add(item.group);
    }
    return [...seen];
  });

  let flatItems = $derived(items);

  function computeFlip() {
    if (!triggerRef) return;
    const rect = triggerRef.getBoundingClientRect();
    const spaceBelow = window.innerHeight - rect.bottom;
    flipUp = spaceBelow < 200 && rect.top > spaceBelow;
  }

  function toggle() {
    if (disabled) return;
    open = !open;
    if (open) {
      focusIndex = flatItems.findIndex((i) => i.value === selected);
      computeFlip();
    }
  }

  function close() {
    open = false;
    focusIndex = -1;
  }

  function select(value: string) {
    onSelect(value);
    close();
    triggerRef?.focus();
  }

  function handleKeydown(e: KeyboardEvent) {
    if (!open) {
      if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
        e.preventDefault();
        toggle();
      }
      return;
    }

    switch (e.key) {
      case "ArrowDown":
        e.preventDefault();
        focusIndex = Math.min(focusIndex + 1, flatItems.length - 1);
        break;
      case "ArrowUp":
        e.preventDefault();
        focusIndex = Math.max(focusIndex - 1, 0);
        break;
      case "Enter":
      case " ":
        e.preventDefault();
        if (focusIndex >= 0 && focusIndex < flatItems.length) {
          select(flatItems[focusIndex].value);
        }
        break;
      case "Escape":
        e.preventDefault();
        close();
        triggerRef?.focus();
        break;
    }
  }

  function handleBackdropClick() {
    close();
  }
</script>

<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="dd-root" onkeydown={handleKeydown}>
  <button
    class="dd-trigger"
    class:open
    class:disabled
    bind:this={triggerRef}
    onclick={toggle}
    aria-haspopup="listbox"
    aria-expanded={open}
    {disabled}
  >
    <span class="dd-trigger-label">{selectedLabel}</span>
    <ChevronDown size={14} class="dd-chev {open ? 'dd-chev-open' : ''}" />
  </button>

  <!-- svelte-ignore a11y_no_static_element_interactions a11y-no-static-element-interactions -->
  <div
    class="dd-backdrop"
    style:display={open ? "block" : "none"}
    onclick={handleBackdropClick}
    onkeydown={(e) => e.key === "Escape" && close()}
  ></div>

  <div
    class="dd-menu"
    class:flip-up={flipUp}
    style:display={open ? "flex" : "none"}
    bind:this={menuRef}
    role="listbox"
  >
    {#if groupBy && groups.length > 0}
      {#each groups as group}
        <div class="dd-group-header">{group}</div>
        {#each flatItems.filter((i) => i.group === group) as item}
          {@const globalIdx = flatItems.indexOf(item)}
          <button
            class="dd-option"
            class:selected={item.value === selected}
            class:focused={globalIdx === focusIndex}
            role="option"
            aria-selected={item.value === selected}
            onclick={() => select(item.value)}>{item.label}</button
          >
        {/each}
      {/each}
      <!-- Ungrouped items -->
      {#each flatItems.filter((i) => !i.group) as item}
        {@const globalIdx = flatItems.indexOf(item)}
        <button
          class="dd-option"
          class:selected={item.value === selected}
          class:focused={globalIdx === focusIndex}
          role="option"
          aria-selected={item.value === selected}
          onclick={() => select(item.value)}>{item.label}</button
        >
      {/each}
    {:else}
      {#each flatItems as item, idx}
        <button
          class="dd-option"
          class:selected={item.value === selected}
          class:focused={idx === focusIndex}
          role="option"
          aria-selected={item.value === selected}
          onclick={() => select(item.value)}>{item.label}</button
        >
      {/each}
    {/if}
  </div>
</div>

<style>
  .dd-root {
    position: relative;
    width: 100%;
  }

  .dd-trigger {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.375rem;
    padding: 0.375rem 0.5rem;
    background: var(--ctp-surface0);
    border: 1px solid var(--ctp-surface1);
    border-radius: 0.25rem;
    color: var(--ctp-text);
    font-size: 0.8125rem;
    font-family: var(--ui-font-family);
    cursor: pointer;
    text-align: left;
    transition: border-color 0.12s;
  }
  .dd-trigger:hover:not(.disabled) {
    border-color: var(--ctp-surface2);
  }
  .dd-trigger:focus-visible {
    outline: 2px solid var(--ctp-blue);
    outline-offset: -1px;
  }
  .dd-trigger.open {
    border-color: var(--ctp-blue);
  }
  .dd-trigger.disabled {
    opacity: 0.4;
    cursor: not-allowed;
  }

  .dd-trigger-label {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .dd-trigger :global(.dd-chev) {
    color: var(--ctp-overlay1);
    flex-shrink: 0;
    transition: transform 0.15s;
  }
  .dd-trigger :global(.dd-chev-open) {
    transform: rotate(180deg);
  }

  .dd-backdrop {
    position: fixed;
    inset: 0;
    z-index: 49;
    background: transparent;
  }

  .dd-menu {
    position: absolute;
    top: calc(100% + 0.25rem);
    left: 0;
    right: 0;
    z-index: 50;
    flex-direction: column;
    background: var(--ctp-mantle);
    border: 1px solid var(--ctp-surface1);
    border-radius: 0.25rem;
    padding: 0.25rem;
    max-height: 14rem;
    overflow-y: auto;
    box-shadow: 0 0.5rem 1rem
      color-mix(in srgb, var(--ctp-crust) 60%, transparent);
  }
  .dd-menu.flip-up {
    top: auto;
    bottom: calc(100% + 0.25rem);
    box-shadow: 0 -0.5rem 1rem color-mix(in srgb, var(--ctp-crust) 60%, transparent);
  }

  .dd-group-header {
    padding: 0.25rem 0.5rem 0.125rem;
    font-size: 0.625rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--ctp-overlay0);
    border-top: 1px solid var(--ctp-surface0);
  }
  .dd-group-header:first-child {
    border-top: none;
  }

  .dd-option {
    width: 100%;
    padding: 0.3rem 0.5rem;
    background: none;
    border: none;
    border-radius: 0.2rem;
    color: var(--ctp-subtext1);
    font-size: 0.8125rem;
    font-family: var(--ui-font-family);
    cursor: pointer;
    text-align: left;
    transition: background 0.08s;
  }
  .dd-option:hover,
  .dd-option.focused {
    background: var(--ctp-surface0);
    color: var(--ctp-text);
  }
  .dd-option:focus-visible {
    outline: 2px solid var(--ctp-blue);
    outline-offset: -1px;
  }
  .dd-option.selected {
    background: color-mix(in srgb, var(--ctp-blue) 15%, transparent);
    color: var(--ctp-blue);
    font-weight: 500;
  }
</style>
