import * as React from 'react';
import { clsx } from 'clsx';
import { Check, ChevronDown, X } from 'lucide-react';

export interface ComboboxOption<T extends string = string> {
  value: T;
  label: string;
  /** Optional secondary line. */
  description?: string;
  /** Disables selection but keeps the row visible. */
  disabled?: boolean;
}

export interface ComboboxProps<T extends string = string> {
  /** Available options. */
  options: ComboboxOption<T>[];
  /** Controlled value. `null` represents no selection. */
  value: T | null;
  /** Called when the user picks (or clears) a value. */
  onChange: (next: T | null) => void;
  /** Placeholder shown when nothing is selected. */
  placeholder?: string;
  /** Custom filter — defaults to case-insensitive label/value substring. */
  filter?: (option: ComboboxOption<T>, query: string) => boolean;
  /** Allow clearing the selection with an `x`. Default true. */
  clearable?: boolean;
  /** Disable the entire control. */
  disabled?: boolean;
  /** Accessible label. */
  ariaLabel?: string;
  /** Max popover height in px. Default 240. */
  maxListHeight?: number;
  className?: string;
}

const DEFAULT_FILTER = <T extends string>(opt: ComboboxOption<T>, q: string) => {
  if (!q) return true;
  const needle = q.toLowerCase();
  return (
    opt.label.toLowerCase().includes(needle) ||
    opt.value.toLowerCase().includes(needle)
  );
};

/**
 * `<Combobox>` — searchable select. Headless-ish: standard semantics
 * (`role="combobox"` + `aria-expanded` + roving `aria-activedescendant`)
 * with token-driven styling.
 *
 * Keyboard:
 *   - **↓ / ↑**  move highlight
 *   - **Enter**  pick the highlighted option
 *   - **Esc**    close
 *   - **Tab**    close + commit text query as no-op
 *
 * Closes the v3.0 audit gap where async-loading selects in
 * notes/fastgap/voice were each bespoke.
 */
export function Combobox<T extends string = string>({
  options,
  value,
  onChange,
  placeholder = 'Select…',
  filter = DEFAULT_FILTER,
  clearable = true,
  disabled = false,
  ariaLabel,
  maxListHeight = 240,
  className,
}: ComboboxProps<T>) {
  const [open, setOpen] = React.useState(false);
  const [query, setQuery] = React.useState('');
  const [highlight, setHighlight] = React.useState(0);
  const rootRef = React.useRef<HTMLDivElement>(null);
  const listId = React.useId();

  const filtered = React.useMemo(
    () => options.filter((opt) => filter(opt, query)),
    [options, query, filter],
  );

  // Reset highlight when the visible list changes shape.
  React.useEffect(() => {
    setHighlight(0);
  }, [query, open]);

  // Keep the highlighted option visible during arrow-key navigation so it
  // never scrolls out of the popover viewport (ROADMAP V4 IMP-1).
  React.useEffect(() => {
    if (!open) return;
    const el = document.getElementById(`${listId}-opt-${highlight}`);
    el?.scrollIntoView({ block: 'nearest' });
  }, [highlight, open, listId]);

  // Click-outside close.
  React.useEffect(() => {
    if (!open) return;
    const onDoc = (e: MouseEvent) => {
      if (!rootRef.current?.contains(e.target as Node)) setOpen(false);
    };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, [open]);

  const selectedOption = options.find((o) => o.value === value) ?? null;
  const displayText = open ? query : selectedOption?.label ?? '';

  const commit = (opt: ComboboxOption<T>) => {
    if (opt.disabled) return;
    onChange(opt.value);
    setQuery('');
    setOpen(false);
  };

  const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (disabled) return;
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      if (!open) setOpen(true);
      setHighlight((h) => Math.min(filtered.length - 1, h + 1));
      return;
    }
    if (e.key === 'ArrowUp') {
      e.preventDefault();
      setHighlight((h) => Math.max(0, h - 1));
      return;
    }
    if (e.key === 'Enter') {
      if (!open) {
        setOpen(true);
        return;
      }
      e.preventDefault();
      const opt = filtered[highlight];
      if (opt) commit(opt);
      return;
    }
    if (e.key === 'Escape') {
      setOpen(false);
      setQuery('');
      return;
    }
  };

  return (
    <div
      ref={rootRef}
      data-testid="bl-combobox"
      className={clsx('relative', className)}
    >
      <div
        className={clsx(
          'flex items-center gap-1 rounded-lg border bg-[var(--bl-surface-card,#fff)] px-2 transition',
          'border-[var(--bl-border,rgba(0,0,0,0.12))]',
          'focus-within:border-[var(--bl-accent,#6366f1)] focus-within:ring-2 focus-within:ring-[var(--bl-accent-muted,rgba(99,102,241,0.18))]',
          disabled && 'cursor-not-allowed opacity-60',
        )}
      >
        <input
          role="combobox"
          aria-expanded={open}
          aria-controls={listId}
          aria-autocomplete="list"
          aria-activedescendant={
            open && filtered[highlight] ? `${listId}-opt-${highlight}` : undefined
          }
          aria-label={ariaLabel ?? placeholder}
          type="text"
          value={displayText}
          onChange={(e) => {
            setQuery(e.target.value);
            if (!open) setOpen(true);
          }}
          onFocus={() => !disabled && setOpen(true)}
          onKeyDown={onKeyDown}
          placeholder={placeholder}
          disabled={disabled}
          className="h-9 flex-1 bg-transparent px-1 text-sm outline-none placeholder:text-[var(--bl-text-tertiary,#999)]"
        />
        {clearable && value !== null && !disabled && (
          <button
            type="button"
            aria-label="Clear selection"
            onClick={() => {
              onChange(null);
              setQuery('');
            }}
            className="grid place-items-center rounded p-1 text-[var(--bl-text-tertiary,#999)] hover:bg-[var(--bl-surface-muted,rgba(0,0,0,0.05))]"
          >
            <X className="h-3.5 w-3.5" aria-hidden="true" />
          </button>
        )}
        <ChevronDown
          className={clsx(
            'h-4 w-4 text-[var(--bl-text-tertiary,#999)] transition-transform',
            open && 'rotate-180',
          )}
          aria-hidden="true"
        />
      </div>
      {open && (
        <ul
          id={listId}
          role="listbox"
          data-testid="bl-combobox-list"
          style={{ maxHeight: maxListHeight }}
          className="absolute left-0 right-0 top-[calc(100%+4px)] z-50 overflow-auto rounded-lg border border-[var(--bl-border,rgba(0,0,0,0.12))] bg-[var(--bl-surface-card,#fff)] shadow-lg"
        >
          {filtered.length === 0 ? (
            <li className="px-3 py-2 text-sm text-[var(--bl-text-tertiary,#999)]">
              No matches
            </li>
          ) : (
            filtered.map((opt, i) => {
              const active = i === highlight;
              const selected = opt.value === value;
              return (
                <li
                  key={opt.value}
                  id={`${listId}-opt-${i}`}
                  role="option"
                  aria-selected={selected}
                  aria-disabled={opt.disabled}
                  onMouseEnter={() => setHighlight(i)}
                  onMouseDown={(e) => {
                    e.preventDefault(); // keep input focused
                    commit(opt);
                  }}
                  className={clsx(
                    'flex cursor-pointer items-start justify-between gap-2 px-3 py-2 text-sm',
                    active && 'bg-[var(--bl-surface-muted,rgba(0,0,0,0.05))]',
                    opt.disabled && 'cursor-not-allowed opacity-50',
                  )}
                >
                  <div className="flex flex-col">
                    <span>{opt.label}</span>
                    {opt.description && (
                      <span className="text-xs text-[var(--bl-text-tertiary,#999)]">
                        {opt.description}
                      </span>
                    )}
                  </div>
                  {selected && (
                    <Check
                      className="mt-0.5 h-4 w-4 shrink-0 text-[var(--bl-accent,#6366f1)]"
                      aria-hidden="true"
                    />
                  )}
                </li>
              );
            })
          )}
        </ul>
      )}
    </div>
  );
}

Combobox.displayName = 'Combobox';
