"use client";

import * as React from "react";
import { Clock, Loader2, Mail, UserCheck, UserPlus } from "lucide-react";

import { Input } from "@/components/ui/input";
import { api } from "@/lib/trpc";
import { cn } from "@/lib/utils";
import type { InviteSuggestion } from "@tasks/shared";

/**
 * Standalone combobox for the invite recipient field. Fully controlled —
 * the parent owns the raw email string via `value`/`onChange` and may
 * react to a non-`new_email` pick (existing member, pending invite,
 * known user from a sibling workspace) via `onSelectSuggestion`.
 *
 * Behavior:
 *   - Debounces the query 200ms before hitting `invites.suggestRecipient`.
 *   - Shows nothing for queries shorter than 2 characters.
 *   - Distinct row styling per `kind` so the inviter can tell at a glance
 *     whether they're about to send a fresh invite vs. re-discover someone
 *     who's already in the workspace.
 *   - Keyboard: ArrowUp/Down to highlight, Enter to pick, Esc to close.
 *   - For a `new_email` pick we just fill the input via `onChange` and let
 *     the parent submit the form. For every other kind we also call
 *     `onSelectSuggestion` so the parent can e.g. close the dialog and
 *     scroll to the matching member row.
 */
export interface InviteRecipientComboboxProps {
  workspaceSlug: string;
  value: string;
  onChange: (email: string) => void;
  /**
   * Called when the user picks an existing-member / pending-invite /
   * known-user row. NOT called for `new_email`; for that kind, only
   * `onChange(suggestion.email)` runs.
   */
  onSelectSuggestion?: (suggestion: InviteSuggestion) => void;
  placeholder?: string;
  disabled?: boolean;
  /** Optional id so an external `<label htmlFor>` can target the input. */
  inputId?: string;
}

function useDebounced<T>(value: T, delayMs: number): T {
  const [debounced, setDebounced] = React.useState(value);
  React.useEffect(() => {
    const id = window.setTimeout(() => setDebounced(value), delayMs);
    return () => window.clearTimeout(id);
  }, [value, delayMs]);
  return debounced;
}

function formatExpires(expiresAt: Date): string {
  const ms = expiresAt.getTime() - Date.now();
  if (ms <= 0) return "expired";
  const minutes = Math.round(ms / (1000 * 60));
  if (minutes < 60) return minutes <= 1 ? "in 1 minute" : `in ${minutes} minutes`;
  const hours = Math.round(minutes / 60);
  if (hours < 24) return hours === 1 ? "in 1 hour" : `in ${hours} hours`;
  const days = Math.round(hours / 24);
  return days === 1 ? "in 1 day" : `in ${days} days`;
}

function suggestionKey(s: InviteSuggestion): string {
  switch (s.kind) {
    case "member":
      return `member:${s.userId}`;
    case "pending_invite":
      return `invite:${s.inviteId}`;
    case "known_user":
      return `known:${s.userId}`;
    case "new_email":
      return `new:${s.email}`;
  }
}

export function InviteRecipientCombobox({
  workspaceSlug,
  value,
  onChange,
  onSelectSuggestion,
  placeholder = "alex@example.com",
  disabled,
  inputId,
}: InviteRecipientComboboxProps) {
  const [open, setOpen] = React.useState(false);
  const [activeIdx, setActiveIdx] = React.useState(0);
  const listboxId = React.useId();
  const inputRef = React.useRef<HTMLInputElement>(null);
  const containerRef = React.useRef<HTMLDivElement>(null);

  const debouncedQuery = useDebounced(value.trim(), 200);
  const enabled = debouncedQuery.length >= 2;

  const suggestQuery = api.invites.suggestRecipient.useQuery(
    { workspace: workspaceSlug, query: debouncedQuery },
    { enabled, staleTime: 10_000 },
  );

  const suggestions = React.useMemo<InviteSuggestion[]>(
    () => (enabled ? (suggestQuery.data ?? []) : []),
    [enabled, suggestQuery.data],
  );

  React.useEffect(() => {
    setActiveIdx(0);
  }, [suggestions]);

  React.useEffect(() => {
    if (!enabled) setOpen(false);
  }, [enabled]);

  // Close when focus leaves the entire combobox (input + popup). Defer to
  // the next tick so a click inside the popup registers before we close.
  React.useEffect(() => {
    if (!open) return;
    const handleClick = (event: MouseEvent) => {
      const target = event.target as Node | null;
      if (!target) return;
      if (containerRef.current && !containerRef.current.contains(target)) {
        setOpen(false);
      }
    };
    document.addEventListener("mousedown", handleClick);
    return () => document.removeEventListener("mousedown", handleClick);
  }, [open]);

  const handleSelect = (suggestion: InviteSuggestion) => {
    onChange(suggestion.email);
    if (suggestion.kind !== "new_email") {
      onSelectSuggestion?.(suggestion);
    }
    setOpen(false);
    inputRef.current?.focus();
  };

  const popupOpen = open && enabled && (suggestQuery.isFetching || suggestions.length > 0);

  const onKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {
    if (event.key === "ArrowDown") {
      if (suggestions.length === 0) return;
      event.preventDefault();
      setOpen(true);
      setActiveIdx((i) => (i + 1) % suggestions.length);
    } else if (event.key === "ArrowUp") {
      if (suggestions.length === 0) return;
      event.preventDefault();
      setOpen(true);
      setActiveIdx((i) => (i - 1 + suggestions.length) % suggestions.length);
    } else if (event.key === "Enter") {
      if (!popupOpen || suggestions.length === 0) return;
      event.preventDefault();
      const pick = suggestions[activeIdx];
      if (pick) handleSelect(pick);
    } else if (event.key === "Escape") {
      if (popupOpen) {
        event.preventDefault();
        setOpen(false);
      }
    }
  };

  const activeId = popupOpen && suggestions[activeIdx]
    ? `${listboxId}-${suggestionKey(suggestions[activeIdx])}`
    : undefined;

  return (
    <div ref={containerRef} className="relative">
      <Input
        ref={inputRef}
        id={inputId}
        type="email"
        autoComplete="off"
        spellCheck={false}
        placeholder={placeholder}
        value={value}
        disabled={disabled}
        onChange={(event) => {
          onChange(event.target.value);
          setOpen(true);
        }}
        onFocus={() => {
          if (enabled) setOpen(true);
        }}
        onKeyDown={onKeyDown}
        role="combobox"
        aria-expanded={popupOpen}
        aria-controls={listboxId}
        aria-autocomplete="list"
        aria-activedescendant={activeId}
      />
      {popupOpen ? (
        <div
          className={cn(
            "absolute left-0 right-0 top-full z-50 mt-2 rounded-lg",
            "border border-border bg-popover p-1 shadow-lg outline-none",
          )}
        >
          <ul
            id={listboxId}
            role="listbox"
            aria-label="Invite recipient suggestions"
            className="max-h-72 overflow-y-auto"
          >
            {suggestQuery.isFetching && suggestions.length === 0 ? (
              <li className="flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground">
                <Loader2 className="size-3 animate-spin" aria-hidden />
                Searching…
              </li>
            ) : null}
            {suggestions.map((suggestion, idx) => (
              <SuggestionRow
                key={suggestionKey(suggestion)}
                id={`${listboxId}-${suggestionKey(suggestion)}`}
                suggestion={suggestion}
                active={idx === activeIdx}
                onMouseEnter={() => setActiveIdx(idx)}
                onClick={() => handleSelect(suggestion)}
              />
            ))}
          </ul>
        </div>
      ) : null}
    </div>
  );
}

interface SuggestionRowProps {
  id: string;
  suggestion: InviteSuggestion;
  active: boolean;
  onMouseEnter: () => void;
  onClick: () => void;
}

function SuggestionRow({
  id,
  suggestion,
  active,
  onMouseEnter,
  onClick,
}: SuggestionRowProps) {
  const buttonBase = cn(
    "flex w-full items-center gap-3 rounded-md px-2 py-2 text-left text-xs transition-colors",
    "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
    active && "bg-accent",
  );

  switch (suggestion.kind) {
    case "member":
      return (
        <li id={id} role="option" aria-selected={active}>
          <button
            type="button"
            // Prevent input from losing focus on mousedown so the click
            // still fires before the outside-click handler closes the popup.
            onMouseDown={(event) => event.preventDefault()}
            onMouseEnter={onMouseEnter}
            onClick={onClick}
            className={cn(buttonBase, "opacity-70 hover:opacity-100")}
          >
            <UserCheck className="size-4 shrink-0 text-muted-foreground" aria-hidden />
            <div className="min-w-0 flex-1">
              <div className="truncate font-medium text-foreground">
                {suggestion.name ?? suggestion.email}
              </div>
              <div className="truncate text-[11px] text-muted-foreground">
                Already a member · click to focus their row
              </div>
            </div>
            <span className="shrink-0 text-[10px] uppercase tracking-wide text-muted-foreground">
              {suggestion.role}
            </span>
          </button>
        </li>
      );
    case "pending_invite":
      return (
        <li id={id} role="option" aria-selected={active}>
          <button
            type="button"
            onMouseDown={(event) => event.preventDefault()}
            onMouseEnter={onMouseEnter}
            onClick={onClick}
            className={buttonBase}
          >
            <Clock className="size-4 shrink-0 text-amber-600" aria-hidden />
            <div className="min-w-0 flex-1">
              <div className="truncate font-medium text-foreground">
                {suggestion.email}
              </div>
              <div className="truncate text-[11px] text-muted-foreground">
                Invite already sent · expires {formatExpires(suggestion.expiresAt)}
              </div>
            </div>
            <span className="shrink-0 text-[10px] uppercase tracking-wide text-muted-foreground">
              {suggestion.role}
            </span>
          </button>
        </li>
      );
    case "known_user":
      return (
        <li id={id} role="option" aria-selected={active}>
          <button
            type="button"
            onMouseDown={(event) => event.preventDefault()}
            onMouseEnter={onMouseEnter}
            onClick={onClick}
            className={buttonBase}
          >
            <UserPlus className="size-4 shrink-0 text-emerald-600" aria-hidden />
            <div className="min-w-0 flex-1">
              <div className="truncate font-medium text-foreground">
                {suggestion.name ?? suggestion.email}
              </div>
              <div className="truncate text-[11px] text-muted-foreground">
                Invite as member · in {suggestion.sharedWorkspaceCount} of your
                {" "}workspace{suggestion.sharedWorkspaceCount === 1 ? "" : "s"}
              </div>
            </div>
          </button>
        </li>
      );
    case "new_email":
      return (
        <li id={id} role="option" aria-selected={active}>
          <button
            type="button"
            onMouseDown={(event) => event.preventDefault()}
            onMouseEnter={onMouseEnter}
            onClick={onClick}
            className={buttonBase}
          >
            <Mail className="size-4 shrink-0 text-primary" aria-hidden />
            <div className="min-w-0 flex-1">
              <div className="truncate font-medium text-foreground">
                Send invite to {suggestion.email}
              </div>
              <div className="truncate text-[11px] text-muted-foreground">
                No existing account · a fresh invite link will be created
              </div>
            </div>
          </button>
        </li>
      );
  }
}
