"use client";

import { useCallback, useEffect, useMemo, useRef, useState } from "react";

import type { components } from "@/lib/api/schema";

type Person = components["schemas"]["PersonRead"];

/**
 * A type-to-pick person picker. Two modes:
 *   - client  (`people`):   filter a preloaded list in the browser.
 *   - server  (`onSearch`): query the backend (debounced) as you type — the
 *                           preferred mode for large trees, so the page doesn't
 *                           have to preload every person just to search.
 * Selecting one sets `value` (a person id) and fills the input with their name.
 */
export function PersonCombobox({
  people,
  onSearch,
  value,
  onChange,
  onCreate,
  placeholder = "Search for a person…",
  className,
}: {
  people?: Person[];
  onSearch?: (q: string) => Promise<Person[]>;
  value: string;
  onChange: (id: string) => void;
  /** When set, the dropdown offers a "Create '<typed name>'" action. */
  onCreate?: (name: string) => void;
  placeholder?: string;
  className?: string;
}) {
  const [query, setQuery] = useState("");
  const [open, setOpen] = useState(false);
  const [results, setResults] = useState<Person[]>([]);
  const [loading, setLoading] = useState(false);
  const wrapRef = useRef<HTMLDivElement>(null);
  // Names we've seen (from the list or search results), so a selected value
  // keeps displaying its name even in server mode.
  const known = useRef<Map<string, string>>(new Map());

  const remember = useCallback((ps: Person[] | undefined) => {
    for (const p of ps ?? []) known.current.set(p.id, p.primary_name ?? "Unnamed");
  }, []);
  useEffect(() => {
    remember(people);
  }, [people, remember]);

  const nameOf = useCallback((id: string) => known.current.get(id) ?? "", []);

  // Keep the input text in sync when the selection changes externally
  // (e.g. cleared to "" after a successful add).
  useEffect(() => {
    if (!value) setQuery("");
    else if (!open) setQuery(nameOf(value));
  }, [value, open, nameOf]);

  // Close on outside click.
  useEffect(() => {
    function onDoc(e: MouseEvent) {
      if (wrapRef.current && !wrapRef.current.contains(e.target as Node)) setOpen(false);
    }
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, []);

  // Server search, debounced. Stale responses are dropped via `cancelled`.
  useEffect(() => {
    if (!onSearch) return;
    const q = query.trim();
    if (!q) {
      setResults([]);
      setLoading(false);
      return;
    }
    setLoading(true);
    let cancelled = false;
    const t = setTimeout(async () => {
      try {
        const r = await onSearch(q);
        if (cancelled) return;
        remember(r);
        setResults(r);
      } finally {
        if (!cancelled) setLoading(false);
      }
    }, 160);
    return () => {
      cancelled = true;
      clearTimeout(t);
    };
  }, [query, onSearch, remember]);

  const matches = useMemo(() => {
    if (onSearch) return results.slice(0, 10);
    const q = query.trim().toLowerCase();
    const pool = q
      ? (people ?? []).filter((p) => (p.primary_name ?? "").toLowerCase().includes(q))
      : (people ?? []);
    return pool.slice(0, 10);
  }, [query, results, people, onSearch]);

  const base =
    "h-9 w-56 rounded-md border border-[var(--border)] bg-[var(--surface)] px-2 text-sm placeholder:text-[var(--muted)] focus-visible:border-bronze focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bronze/40";

  const showDropdown =
    open && (matches.length > 0 || loading || (onCreate && query.trim()));

  return (
    <div ref={wrapRef} className="relative">
      <input
        className={`${base} ${className ?? ""}`}
        value={query}
        placeholder={placeholder}
        onFocus={() => setOpen(true)}
        onChange={(e) => {
          setQuery(e.target.value);
          setOpen(true);
          if (value) onChange(""); // typing invalidates the prior pick
        }}
      />
      {showDropdown && (
        <ul className="absolute z-30 mt-1 max-h-64 w-72 overflow-auto rounded-lg border border-[var(--border)] bg-[var(--surface)] shadow-lg">
          {loading && matches.length === 0 && (
            <li className="px-3 py-2 text-sm text-[var(--muted)]">Searching…</li>
          )}
          {matches.map((p) => (
            <li key={p.id}>
              <button
                type="button"
                onClick={() => {
                  onChange(p.id);
                  setQuery(p.primary_name ?? "Unnamed");
                  setOpen(false);
                }}
                className={`block w-full px-3 py-2 text-left text-sm hover:bg-[var(--muted-bg,rgba(0,0,0,0.04))] ${
                  p.id === value ? "text-bronze" : ""
                }`}
              >
                {p.primary_name ?? "Unnamed"}
              </button>
            </li>
          ))}
          {onCreate && query.trim() && (
            <li className={matches.length > 0 ? "border-t border-[var(--border)]" : ""}>
              <button
                type="button"
                onClick={() => {
                  const name = query.trim();
                  setOpen(false);
                  onCreate(name);
                }}
                className="block w-full px-3 py-2 text-left text-sm text-bronze hover:bg-[var(--muted-bg,rgba(0,0,0,0.04))]"
              >
                + Create “{query.trim()}”
              </button>
            </li>
          )}
        </ul>
      )}
    </div>
  );
}
