// @design-system: inputs/Combobox
// Registered at /design-system#combobox-input

'use client';

/**
 * Combobox — strict, searchable single-select built on Popover + cmdk.
 *
 * Typing filters options only; selection always emits a known option value.
 * Stored values missing from the current list render as a pinned top item.
 */

import { useId, useState } from 'react';
import { Command } from 'cmdk';
import { cn } from '@/lib/cn';
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/overlays/Popover';
import { Spinner } from '@/components/ui/feedback/Spinner';

export interface ComboboxOption {
  value: string;
  label: string;
}

export interface ComboboxProps {
  value: string | undefined;
  onValueChange: (value: string) => void;
  options: ComboboxOption[];
  placeholder?: string;
  searchPlaceholder?: string;
  emptyText?: string;
  disabled?: boolean;
  loading?: boolean;
  id?: string;
  className?: string;
  dir?: 'ltr' | 'rtl';
  pinnedValueHint?: string;
}

const ITEM_CLASS = cn(
  'flex cursor-pointer items-center gap-2 rounded-sm px-3 py-2 text-sm',
  'text-text-primary outline-none',
  'data-[selected=true]:bg-brand-primary-50 data-[selected=true]:text-brand-primary-700',
  'transition-colors motion-reduce:transition-none',
);

export function Combobox({
  value,
  onValueChange,
  options,
  placeholder,
  searchPlaceholder,
  emptyText,
  disabled = false,
  loading = false,
  id,
  className,
  dir = 'ltr',
  pinnedValueHint,
}: ComboboxProps) {
  const [open, setOpen] = useState(false);
  const [search, setSearch] = useState('');
  const listId = useId();

  const selectedOption = options.find((option) => option.value === value);
  const isPinnedValue = Boolean(value) && !selectedOption;
  const triggerLabel = selectedOption?.label ?? (value || undefined);
  const isTriggerDisabled = disabled || loading;

  function handleOpenChange(next: boolean) {
    setOpen(next);
    if (!next) setSearch('');
  }

  function handleSelect(nextValue: string) {
    onValueChange(nextValue);
    setOpen(false);
    setSearch('');
  }

  return (
    <Popover open={open} onOpenChange={handleOpenChange}>
      <PopoverTrigger asChild>
        <button
          type="button"
          id={id}
          role="combobox"
          aria-expanded={open}
          aria-haspopup="listbox"
          aria-controls={listId}
          disabled={isTriggerDisabled}
          className={cn(
            'flex w-full items-center justify-between',
            'border-border-default bg-surface-base rounded-md border',
            'py-2 ps-3 pe-3',
            'text-base text-neutral-900',
            'transition-colors motion-reduce:transition-none',
            'focus-visible:border-brand-primary-500 focus-visible:ring-brand-primary-500/40 focus-visible:ring-2 focus-visible:outline-none',
            'disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:text-neutral-400',
            className,
          )}
        >
          {loading ? (
            <span className="text-text-muted flex min-w-0 flex-1 items-center gap-2 text-start">
              <Spinner variant="dots" size="sm" />
              <span className="truncate">{placeholder}</span>
            </span>
          ) : (
            <span
              className={cn(
                'min-w-0 flex-1 truncate text-start',
                !triggerLabel && 'text-neutral-500',
              )}
            >
              {triggerLabel ?? placeholder}
            </span>
          )}
          <ChevronDownIcon className="shrink-0 text-neutral-500" />
        </button>
      </PopoverTrigger>

      <PopoverContent
        align="start"
        sideOffset={4}
        dir={dir}
        className="w-[var(--radix-popover-trigger-width)] p-0"
      >
        <Command label={searchPlaceholder} loop className="flex flex-col" shouldFilter>
          <div className="border-border-default flex items-center gap-2 border-b px-3 py-2">
            <Command.Input
              value={search}
              onValueChange={setSearch}
              placeholder={searchPlaceholder}
              className={cn(
                'text-text-primary flex-1 bg-transparent text-sm',
                'placeholder:text-text-muted',
                'focus-visible:ring-brand-primary-500 focus-visible:ring-2 focus-visible:outline-none',
              )}
            />
          </div>

          <Command.List
            id={listId}
            className={cn('max-h-60 overflow-y-auto overscroll-contain', 'p-1')}
          >
            <Command.Empty className="text-text-muted px-3 py-6 text-center text-sm">
              {emptyText}
            </Command.Empty>

            {isPinnedValue && value && (
              <Command.Item
                value={value}
                onSelect={() => handleSelect(value)}
                className={cn(ITEM_CLASS, 'font-semibold')}
              >
                <span className="min-w-0 truncate">{value}</span>
                {pinnedValueHint && (
                  <span className="text-text-muted shrink-0">{pinnedValueHint}</span>
                )}
              </Command.Item>
            )}

            {options.map((option) => (
              <Command.Item
                key={option.value}
                value={option.value}
                keywords={[option.label]}
                onSelect={() => handleSelect(option.value)}
                className={cn(ITEM_CLASS, option.value === value && 'font-semibold')}
              >
                <span className="min-w-0 truncate">{option.label}</span>
              </Command.Item>
            ))}
          </Command.List>
        </Command>
      </PopoverContent>
    </Popover>
  );
}

function ChevronDownIcon({ className }: { className?: string }) {
  return (
    <svg
      width="16"
      height="16"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      aria-hidden="true"
      className={className}
    >
      <path d="m6 9 6 6 6-6" />
    </svg>
  );
}
