import { useEffect, useRef, useState } from 'react';
import { Input } from './input';
import { cn } from '@/lib/utils';

// Searchable combobox: type to filter, click/Enter to pick. On blur it commits
// only an exact (case-insensitive) match — otherwise it reverts, so the field
// can't hold a typo'd value that isn't in the list.
export function Combobox({
  value, onChange, options, placeholder, className, allowFreeText = false, commitOnType = false,
}: {
  value: string;
  onChange: (v: string) => void;
  options: string[];
  placeholder?: string;
  className?: string;
  allowFreeText?: boolean;
  // Commit each keystroke to the parent immediately (not just on blur). Use for
  // fields read live by other actions — e.g. RST, so a CW macro sent without
  // leaving the field uses the value just typed.
  commitOnType?: boolean;
}) {
  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState('');
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    function onDoc(e: MouseEvent) {
      if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
    }
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, []);

  const filtered = open
    ? options.filter((o) => o.toLowerCase().includes(query.toLowerCase())).slice(0, 60)
    : [];

  function commit(v: string) {
    onChange(v);
    setQuery(v);
    setOpen(false);
  }

  function onBlur() {
    // Defer so a click on an option registers first.
    setTimeout(() => {
      setOpen(false);
      const trimmed = query.trim();
      const exact = options.find((o) => o.toLowerCase() === trimmed.toLowerCase());
      // Only fire onChange when the value actually changed — committing an
      // unchanged value on a plain tab-through would wrongly flag the field as
      // "user-edited" (e.g. RST, which then blocks the CW/SSB 599↔59 default).
      if (exact) { if (exact !== value) onChange(exact); setQuery(exact); }
      else if (allowFreeText) { if (trimmed !== value) onChange(trimmed); }
      else { setQuery(value); } // revert typo
    }, 120);
  }

  return (
    <div ref={ref} className={cn('relative', className)}>
      <Input
        value={open ? query : value}
        placeholder={placeholder}
        // Focus selects the text so a keystroke replaces it — but does NOT
        // open the list (so tabbing in doesn't pop the dropdown).
        onFocus={(e) => { setQuery(value); e.currentTarget.select(); }}
        onChange={(e) => { setQuery(e.target.value); setOpen(true); if (commitOnType) onChange(e.target.value); }}
        onBlur={onBlur}
        onKeyDown={(e) => {
          if ((e.key === 'ArrowDown' || e.key === 'Alt') && !open) { setOpen(true); }
          else if (e.key === 'Enter' && open && filtered.length > 0) { e.preventDefault(); commit(filtered[0]); }
          else if (e.key === 'Escape') { setQuery(value); setOpen(false); }
          // Tab: just let it move on; onBlur commits/closes. Options are
          // tabIndex=-1 so a single Tab leaves the field.
        }}
      />
      {open && filtered.length > 0 && (
        <div className="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border border-border bg-card shadow-lg text-xs">
          {filtered.map((o) => (
            <button
              key={o}
              type="button"
              tabIndex={-1}
              className="block w-full text-left px-2 py-1 hover:bg-accent/40"
              onMouseDown={(e) => { e.preventDefault(); commit(o); }}
            >
              {o}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}
