import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
import { Receipt, Stethoscope, Activity as ActivityIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import type { SearchResult, SearchResultsGrouped } from "@/hooks/useSearch";

/**
 * Result row — one entity hit, clickable / Enter-able.
 *
 * The "selected" state is owned by the parent (the SearchBar wires
 * keyboard nav) — we just consume it via the `selected` boolean so
 * the row can paint its accent. We also expose `data-result-index`
 * so the parent's index-based keyboard nav can scroll the matching
 * row into view via `scrollIntoView`.
 */
type ResultRowProps = {
  result: SearchResult;
  selected: boolean;
  index: number;
  onSelect: (result: SearchResult) => void;
  onHover: (index: number) => void;
};

const ICON_BY_KIND = {
  claim: Receipt,
  remittance: Stethoscope,
  activity: ActivityIcon,
} as const;

function ResultRow({
  result,
  selected,
  index,
  onSelect,
  onHover,
}: ResultRowProps) {
  const Icon = ICON_BY_KIND[result.kind];
  return (
    <button
      type="button"
      // `data-result-index` lets the parent keyboard handler call
      // `el.scrollIntoView()` after a nav move — see SearchBar.
      data-result-index={index}
      data-result-kind={result.kind}
      data-result-id={result.id}
      data-selected={selected || undefined}
      // Row is keyboard-focusable so screen readers and keyboard-only
      // users see something focusable moving under ↑/↓. We manage the
      // focused-row state ourselves (via the `selected` prop) rather
      // than relying on the browser's focus ring because the input
      // stays focused while the palette is open.
      aria-selected={selected}
      role="option"
      onMouseDown={(e) => {
        // `mousedown` rather than `click` so the input doesn't blur
        // before the navigation fires — otherwise the user sees a
        // frame of "input lost focus" before the dialog closes.
        e.preventDefault();
        onSelect(result);
      }}
      onMouseEnter={() => onHover(index)}
      className={cn(
        "group w-full flex items-center gap-3 px-3 py-2 rounded-md text-left transition-colors",
        "focus:outline-none",
        selected
          ? "bg-accent/15 ring-1 ring-inset ring-accent/50"
          : "hover:bg-muted/40"
      )}
    >
      <span
        className={cn(
          "flex h-6 w-6 shrink-0 items-center justify-center rounded-md transition-colors",
          selected
            ? "bg-accent/25 text-accent"
            : "bg-muted text-muted-foreground group-hover:bg-muted/70"
        )}
        aria-hidden
      >
        <Icon className="h-3.5 w-3.5" strokeWidth={1.75} />
      </span>
      <span className="min-w-0 flex-1">
        <span className="block text-[13px] font-medium truncate text-foreground">
          {result.title}
        </span>
        <span className="block text-[11px] mono text-muted-foreground truncate">
          {result.subtitle}
        </span>
      </span>
      <span
        className={cn(
          "shrink-0 px-1.5 py-0.5 rounded text-[10px] mono uppercase tracking-[0.14em] font-medium",
          "bg-muted/70 text-muted-foreground"
        )}
      >
        {result.badge}
      </span>
    </button>
  );
}

/** Section header — small caps label above each group. */
function GroupHeader({ label }: { label: string }) {
  return (
    <div className="px-4 pt-3 pb-1.5 flex items-center gap-2 text-[10.5px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
      <span aria-hidden className="inline-block h-px w-3 bg-border" />
      {label}
    </div>
  );
}

/** Render the "no matches" state. */
function EmptyState({ query }: { query: string }) {
  return (
    <div className="px-4 py-12 text-center text-muted-foreground">
      <div className="mono text-[12px]">
        {query ? (
          <>
            No matches for{" "}
            <span className="text-foreground font-semibold">&ldquo;{query}&rdquo;</span>
          </>
        ) : (
          "Type to search across claims, remittances, and activity."
        )}
      </div>
    </div>
  );
}

/**
 * Public ref API — the parent (SearchBar) drives navigation from
 * outside the list, so we expose an imperative handle with
 * `focusIndex(i)`. The implementation scrolls the matching row into
 * view; we don't move the actual DOM focus (the input owns that),
 * but we paint the row as selected via the parent's state.
 */
export interface SearchResultsHandle {
  focusIndex: (index: number) => void;
}

export interface SearchResultsProps {
  grouped: SearchResultsGrouped;
  selectedIndex: number;
  onSelect: (result: SearchResult) => void;
  onHoverIndex: (index: number) => void;
}

export const SearchResults = forwardRef<SearchResultsHandle, SearchResultsProps>(
  function SearchResults(
    { grouped, selectedIndex, onSelect, onHoverIndex },
    ref
  ) {
    const listRef = useRef<HTMLDivElement>(null);

    /**
     * Scroll the row at `index` into view when it changes. We do this
     * imperatively (rather than relying on the browser's native focus
     * scroll) because the input owns focus during keyboard nav — moving
     * DOM focus would close the virtual keyboard on mobile and break
     * the "input stays focused" invariant.
     */
    useImperativeHandle(ref, () => ({
      focusIndex: (index: number) => {
        const list = listRef.current;
        if (!list) return;
        const el = list.querySelector<HTMLElement>(
          `[data-result-index="${index}"]`
        );
        if (!el) return;
        el.scrollIntoView({ block: "nearest" });
      },
    }), []);

    const total = grouped.total;
    const isEmpty = total === 0;

    // Track the running flat-index so `data-result-index` is stable
    // and matches what the parent passes in `selectedIndex`.
    let runningIndex = 0;
    const claimNodes = grouped.claims.map((r) => {
      const idx = runningIndex++;
      return (
        <ResultRow
          key={`claim-${r.id}-${idx}`}
          result={r}
          index={idx}
          selected={idx === selectedIndex}
          onSelect={onSelect}
          onHover={onHoverIndex}
        />
      );
    });
    const remitNodes = grouped.remittances.map((r) => {
      const idx = runningIndex++;
      return (
        <ResultRow
          key={`remit-${r.id}-${idx}`}
          result={r}
          index={idx}
          selected={idx === selectedIndex}
          onSelect={onSelect}
          onHover={onHoverIndex}
        />
      );
    });
    const activityNodes = grouped.activity.map((r) => {
      const idx = runningIndex++;
      return (
        <ResultRow
          key={`activity-${r.id}-${idx}`}
          result={r}
          index={idx}
          selected={idx === selectedIndex}
          onSelect={onSelect}
          onHover={onHoverIndex}
        />
      );
    });

    // Keep the selected row visible any time the parent updates
    // `selectedIndex` (e.g. via ↑/↓). The `useImperativeHandle` above
    // does the same thing on demand; this effect picks up the cases
    // where the parent doesn't call into us explicitly.
    useEffect(() => {
      const list = listRef.current;
      if (!list) return;
      const el = list.querySelector<HTMLElement>(
        `[data-result-index="${selectedIndex}"]`
      );
      if (!el) return;
      el.scrollIntoView({ block: "nearest" });
    }, [selectedIndex]);

    return (
      <div
        ref={listRef}
        // `role="listbox"` for the whole results container; each row
        // is `role="option"`. This is what a screen reader expects
        // for a "combobox with listbox popup" pattern.
        role="listbox"
        aria-label="Search results"
        className="max-h-[60vh] overflow-y-auto py-1"
      >
        {isEmpty ? (
          <EmptyState query={grouped.query} />
        ) : (
          <>
            {claimNodes.length > 0 && (
              <>
                <GroupHeader label={`Claims · ${claimNodes.length}`} />
                <div className="space-y-0.5 px-1">{claimNodes}</div>
              </>
            )}
            {remitNodes.length > 0 && (
              <>
                <GroupHeader label={`Remittances · ${remitNodes.length}`} />
                <div className="space-y-0.5 px-1">{remitNodes}</div>
              </>
            )}
            {activityNodes.length > 0 && (
              <>
                <GroupHeader label={`Activity · ${activityNodes.length}`} />
                <div className="space-y-0.5 px-1 pb-1">{activityNodes}</div>
              </>
            )}
          </>
        )}
      </div>
    );
  }
);