"use client";

import { useMemo, useRef, useState } from "react";
import { Command as CommandPrimitive } from "cmdk";
import { AlertCircle, Building2, Factory, Star, UserRound, X } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { CommandEmpty, CommandGroup, CommandItem, CommandList } from "@/components/ui/command";
import { Skeleton } from "@/components/ui/skeleton";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { addressProblem, isValidEmail, parseAddressList, PARTY_LABELS } from "./mail-utils";
import { useRecipientSearch } from "./useRecipientSearch";

const KIND_GROUPS = [
  { kind: "buyer_contact", label: "Buyer contacts" },
  { kind: "factory_contact", label: "Factory contacts" },
  { kind: "team", label: "Team" },
  { kind: "recent", label: "Recent" },
];

const PARTY_ICONS = { buyer: Building2, factory: Factory };

/**
 * To / Cc / Bcc field: address chips plus contact autocomplete (buyer and factory contacts of the record first).
 * value: [{ email, name?, party? }]. Enter, comma, semicolon or Tab adds what was typed; pasting a list splits it;
 * Backspace on an empty input removes the last chip. Invalid or sample addresses show as red chips with the reason.
 */
export function RecipientInput({ id, value = [], onChange, onBlur, record, placeholder = "Type a name or email", invalid = false, "aria-label": ariaLabel, "aria-describedby": describedBy }) {
  const [draft, setDraft] = useState("");
  const [open, setOpen] = useState(false);
  const inputRef = useRef(null);
  const { results, isLoading } = useRecipientSearch(draft, record, open);
  const taken = useMemo(() => new Set(value.map((item) => item.email.toLowerCase())), [value]);
  const suggestions = results.filter((item) => !taken.has(item.email.toLowerCase()));
  const typed = draft.trim();
  const offerTyped = typed && isValidEmail(typed) && !suggestions.some((item) => item.email.toLowerCase() === typed.toLowerCase()) && !taken.has(typed.toLowerCase());

  const add = (items) => {
    const next = [...value];
    items.forEach((item) => {
      const email = item.email.trim();
      if (!email || next.some((existing) => existing.email.toLowerCase() === email.toLowerCase())) return;
      next.push({ email, name: item.name || null, party: item.party || null });
    });
    onChange?.(next);
    setDraft("");
  };
  const commitDraft = () => { if (typed) add(parseAddressList(typed)); };
  const remove = (email) => onChange?.(value.filter((item) => item.email !== email));

  const onKeyDown = (event) => {
    // Ctrl/Cmd+Enter sends the email (compose sheet). It must never pick the highlighted suggestion on the way:
    // keep what was typed, stop the list from selecting, and let the event bubble to the sheet.
    if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
      event.preventDefault();
      commitDraft();
      return;
    }
    if ((event.key === "," || event.key === ";") || (event.key === "Tab" && typed)) {
      if (!typed) return;
      event.preventDefault();
      commitDraft();
      return;
    }
    // With no suggestion highlighted (nothing typed matches), Enter adds the typed text.
    if (event.key === "Enter" && typed && !suggestions.length && !offerTyped) {
      event.preventDefault();
      commitDraft();
      return;
    }
    if (event.key === "Backspace" && !draft && value.length) remove(value[value.length - 1].email);
    if (event.key === "Escape" && showList) { event.preventDefault(); event.stopPropagation(); setOpen(false); }
  };

  const grouped = KIND_GROUPS.map((group) => ({ ...group, items: suggestions.filter((item) => item.kind === group.kind) })).filter((group) => group.items.length);
  const other = suggestions.filter((item) => !KIND_GROUPS.some((group) => group.kind === item.kind));
  if (other.length) grouped.push({ kind: "other", label: "Suggestions", items: other });

  // The list renders inline under the field (not in a portal), so focus never leaves the input while typing
  // inside the compose sheet's focus trap. Mouse-down on the list keeps focus in the input.
  const showList = open && (typed || suggestions.length > 0 || isLoading);

  return <CommandPrimitive id={id} shouldFilter={false} loop label={ariaLabel} className="relative" data-recipient-open={showList ? "" : undefined}>
    <div
      className={cn(
        "flex min-h-9 w-full flex-wrap items-center gap-1 rounded-md border border-input bg-transparent px-1.5 py-1 shadow-xs transition-[color,box-shadow] focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 dark:bg-input/30",
        invalid && "border-destructive ring-destructive/20",
      )}
      onClick={() => inputRef.current?.focus()}
    >
      {value.map((item) => <RecipientChip key={item.email} item={item} onRemove={() => remove(item.email)} />)}
      <CommandPrimitive.Input
        ref={inputRef}
        value={draft}
        onValueChange={(next) => { setDraft(next); setOpen(true); }}
        onFocus={() => setOpen(true)}
        onBlur={() => {
          // Leaving the field keeps what was typed as a chip.
          commitDraft();
          setOpen(false);
          onBlur?.();
        }}
        onKeyDown={onKeyDown}
        onPaste={(event) => {
          const text = event.clipboardData.getData("text");
          if (!/[,;\n]/.test(text)) return;
          event.preventDefault();
          add(parseAddressList(`${draft}${text}`));
        }}
        placeholder={value.length ? "" : placeholder}
        aria-label={ariaLabel}
        aria-describedby={describedBy}
        aria-invalid={invalid || undefined}
        autoComplete="off"
        className="h-7 min-w-24 flex-1 bg-transparent px-1 text-sm outline-none placeholder:text-muted-foreground"
      />
    </div>
    <div
      hidden={!showList}
      className="absolute inset-x-0 top-full z-50 mt-1 min-w-64 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md"
      onMouseDown={(event) => event.preventDefault()}
    >
      <CommandList className="max-h-72">
        {isLoading && !suggestions.length && !offerTyped
          ? <div className="grid gap-2 p-2" aria-busy="true">{[0, 1, 2].map((index) => <Skeleton key={index} className="h-9" />)}</div>
          : <CommandEmpty className="px-3 py-4 text-sm text-muted-foreground">{typed ? "No saved contact matches. Type the full address and press Enter." : "No saved contacts yet. Type an email address."}</CommandEmpty>}
        {offerTyped && <CommandGroup>
          <CommandItem value={`typed:${typed}`} onSelect={commitDraft}>Use <strong className="font-medium">{typed}</strong></CommandItem>
        </CommandGroup>}
        {grouped.map((group) => <CommandGroup key={group.kind} heading={group.label}>
          {group.items.map((item) => <CommandItem key={item.email} value={item.email} onSelect={() => add([item])} className="items-start gap-2">
            <SuggestionIcon item={item} />
            <span className="grid min-w-0 flex-1">
              <span className="flex min-w-0 items-center gap-1.5">
                <span className="truncate font-medium">{item.name || item.email}</span>
                {item.isPrimary && <Badge variant="secondary" className="h-4 gap-0.5 px-1 text-xs font-normal"><Star className="size-2.5!" aria-hidden="true" />Primary</Badge>}
              </span>
              <span className="truncate text-xs text-muted-foreground">{[item.name ? item.email : null, item.designation, item.party?.name].filter(Boolean).join(" · ")}</span>
            </span>
          </CommandItem>)}
        </CommandGroup>)}
      </CommandList>
    </div>
  </CommandPrimitive>;
}

function SuggestionIcon({ item }) {
  const Icon = PARTY_ICONS[item.party?.type] || UserRound;
  return <span className="mt-0.5 grid size-6 shrink-0 place-items-center rounded-full bg-muted text-muted-foreground"><Icon className="size-3.5" aria-hidden="true" /></span>;
}

function RecipientChip({ item, onRemove }) {
  const problem = addressProblem(item.email);
  const label = item.name || item.email;
  const chip = <Badge
    variant={problem ? "danger" : "secondary"}
    className="h-7 max-w-full gap-1 rounded-md pr-0.5 pl-2 text-sm font-normal"
  >
    {problem && <AlertCircle className="size-3.5!" aria-hidden="true" />}
    <span className="truncate">{label}</span>
    {item.party?.type && !problem && <span className="shrink-0 text-xs text-muted-foreground">· {item.party.name || PARTY_LABELS[item.party.type] || item.party.code}</span>}
    <button type="button" className="grid size-5 shrink-0 place-items-center rounded-sm hover:bg-foreground/10" aria-label={`Remove ${item.email}`} onClick={(event) => { event.stopPropagation(); onRemove(); }}><X className="size-3" /></button>
  </Badge>;

  return <Tooltip>
    <TooltipTrigger asChild><span className="inline-flex max-w-full">{chip}</span></TooltipTrigger>
    <TooltipContent>{problem || (item.name ? `${item.name} <${item.email}>` : item.email)}</TooltipContent>
  </Tooltip>;
}
