"use client";

import { useMemo, useRef, useState, type KeyboardEvent } from "react";
import { toast } from "sonner";
import { Plus, Pencil, Trash2, Tag, X, Search } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogFooter,
} from "@/components/ui/dialog";
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { EmptyState } from "@/components/shared/empty-state";
import { GROCERY_CATEGORIES, type GroceryCategory } from "@/lib/grocery-categories";

const CATEGORY_LABELS: Record<GroceryCategory, string> = {
  produce: "Produce",
  dairyEggs: "Dairy & Eggs",
  meatSeafood: "Meat & Seafood",
  bakery: "Bakery",
  frozen: "Frozen",
  pantry: "Pantry",
  spicesCondiments: "Spices & Condiments",
  beverages: "Beverages",
};
const UNCATEGORIZED = "__uncategorized__";
const ALL_CATEGORIES = "__all__";

type Ingredient = {
  id: string;
  name: string;
  aliases: string[];
  category: string | null;
};

// Same case/accent-insensitive intent as lib/ingredient-match.ts's
// normalize() — duplicated rather than imported, since that module pulls
// in the DB client (server-only) and can't be bundled for the browser.
function normalize(s: string): string {
  return s.trim().toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "");
}

function categoryLabel(category: string | null): string {
  if (!category) return "Uncategorized";
  return CATEGORY_LABELS[category as GroceryCategory] ?? category;
}

function AliasTagInput({ aliases, onChange }: { aliases: string[]; onChange: (aliases: string[]) => void }) {
  const [input, setInput] = useState("");
  const inputRef = useRef<HTMLInputElement>(null);

  function addAlias(raw: string) {
    const alias = raw.trim();
    if (!alias || aliases.some((a) => normalize(a) === normalize(alias)) || aliases.length >= 50) return;
    onChange([...aliases, alias]);
    setInput("");
  }

  function removeAlias(alias: string) {
    onChange(aliases.filter((a) => a !== alias));
  }

  function handleKeyDown(e: KeyboardEvent<HTMLInputElement>) {
    if (e.key === "Enter" || e.key === ",") {
      e.preventDefault();
      addAlias(input);
    } else if (e.key === "Backspace" && !input && aliases.length > 0) {
      onChange(aliases.slice(0, -1));
    }
  }

  return (
    <div
      className="flex flex-wrap gap-1.5 min-h-9 rounded-lg border border-input bg-transparent px-2.5 py-1.5 cursor-text focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50"
      onClick={() => inputRef.current?.focus()}
    >
      {aliases.map((alias) => (
        <span key={alias} className="inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-xs bg-muted text-muted-foreground">
          {alias}
          <button type="button" onClick={(e) => { e.stopPropagation(); removeAlias(alias); }} className="hover:text-foreground transition-colors" aria-label={`Remove ${alias}`}>
            <X className="h-2.5 w-2.5" />
          </button>
        </span>
      ))}
      <input
        ref={inputRef}
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyDown={handleKeyDown}
        onBlur={() => { if (input.trim()) addAlias(input); }}
        placeholder={aliases.length === 0 ? "Type an alias, press Enter…" : ""}
        className="flex-1 min-w-[120px] bg-transparent text-sm outline-none placeholder:text-muted-foreground"
      />
    </div>
  );
}

function IngredientDialog({
  ingredient,
  open,
  onOpenChange,
  onSaved,
}: {
  ingredient: Ingredient | null;
  open: boolean;
  onOpenChange: (open: boolean) => void;
  onSaved: (ingredient: Ingredient) => void;
}) {
  const [name, setName] = useState(ingredient?.name ?? "");
  const [aliases, setAliases] = useState<string[]>(ingredient?.aliases ?? []);
  const [category, setCategory] = useState<string>(ingredient?.category ?? UNCATEGORIZED);
  const [saving, setSaving] = useState(false);
  const isEdit = !!ingredient;

  async function handleSave() {
    const trimmedName = name.trim();
    if (!trimmedName) return;
    setSaving(true);
    try {
      const res = await fetch(
        isEdit ? `/api/v1/admin/ingredients/${ingredient.id}` : "/api/v1/admin/ingredients",
        {
          method: isEdit ? "PUT" : "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ name: trimmedName, aliases, category: category === UNCATEGORIZED ? null : category }),
        }
      );
      if (!res.ok) {
        const data = await res.json().catch(() => null) as { error?: string } | null;
        toast.error(data?.error ?? "Failed to save");
        return;
      }
      toast.success(isEdit ? "Ingredient updated" : "Ingredient created");
      const id = isEdit ? ingredient.id : ((await res.json()) as { id: string }).id;
      onSaved({ id, name: trimmedName, aliases, category: category === UNCATEGORIZED ? null : category });
      onOpenChange(false);
    } finally {
      setSaving(false);
    }
  }

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="max-w-md">
        <DialogHeader>
          <DialogTitle>{isEdit ? "Edit ingredient" : "New ingredient"}</DialogTitle>
        </DialogHeader>
        <div className="space-y-3">
          <div className="space-y-1.5">
            <Label htmlFor="ingredient-name">Canonical name</Label>
            <Input id="ingredient-name" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. salt" />
          </div>
          <div className="space-y-1.5">
            <Label>Aliases</Label>
            <AliasTagInput aliases={aliases} onChange={setAliases} />
            <p className="text-xs text-muted-foreground">
              Matched case- and accent-insensitively against pantry item and recipe ingredient names.
            </p>
          </div>
          <div className="space-y-1.5">
            <Label>Category</Label>
            <Select value={category} onValueChange={(v) => setCategory(v ?? UNCATEGORIZED)}>
              <SelectTrigger className="w-full">
                <SelectValue>{(v: string) => categoryLabel(v === UNCATEGORIZED ? null : v)}</SelectValue>
              </SelectTrigger>
              <SelectContent>
                <SelectItem value={UNCATEGORIZED}>Uncategorized</SelectItem>
                {GROCERY_CATEGORIES.map((c) => (
                  <SelectItem key={c} value={c}>{CATEGORY_LABELS[c]}</SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
        </div>
        <DialogFooter>
          <Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={saving}>
            Cancel
          </Button>
          <Button type="button" onClick={() => { void handleSave(); }} disabled={saving || !name.trim()}>
            {saving ? "Saving…" : "Save"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

export function IngredientsManager({ initialIngredients }: { initialIngredients: Ingredient[] }) {
  const [ingredients, setIngredients] = useState<Ingredient[]>(initialIngredients);
  const [query, setQuery] = useState("");
  const [categoryFilter, setCategoryFilter] = useState<string>(ALL_CATEGORIES);
  const [dialogOpen, setDialogOpen] = useState(false);
  const [editingIngredient, setEditingIngredient] = useState<Ingredient | null>(null);
  const [confirmId, setConfirmId] = useState<string | null>(null);

  function openCreate() {
    setEditingIngredient(null);
    setDialogOpen(true);
  }

  function openEdit(ingredient: Ingredient) {
    setEditingIngredient(ingredient);
    setDialogOpen(true);
  }

  async function handleDelete(id: string) {
    const res = await fetch(`/api/v1/admin/ingredients/${id}`, { method: "DELETE" });
    if (res.ok) {
      setIngredients((prev) => prev.filter((i) => i.id !== id));
      toast.success("Ingredient deleted");
    } else {
      toast.error("Failed to delete");
    }
  }

  const categoryCounts = useMemo(() => {
    const counts = new Map<string, number>();
    for (const i of ingredients) {
      const key = i.category ?? UNCATEGORIZED;
      counts.set(key, (counts.get(key) ?? 0) + 1);
    }
    return counts;
  }, [ingredients]);

  const filtered = useMemo(() => {
    const q = normalize(query);
    return ingredients
      .filter((i) => categoryFilter === ALL_CATEGORIES || (i.category ?? UNCATEGORIZED) === categoryFilter)
      .filter((i) => {
        if (!q) return true;
        if (normalize(i.name).includes(q)) return true;
        return i.aliases.some((a) => normalize(a).includes(q));
      })
      .sort((a, b) => a.name.localeCompare(b.name));
  }, [ingredients, query, categoryFilter]);

  const pendingDelete = ingredients.find((i) => i.id === confirmId) ?? null;

  return (
    <div className="space-y-4 max-w-3xl">
      <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
        <div className="flex flex-1 gap-2">
          <div className="relative flex-1 max-w-sm">
            <Search className="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
            <Input
              value={query}
              onChange={(e) => setQuery(e.target.value)}
              placeholder="Search name or alias…"
              className="pl-8"
            />
          </div>
          <Select value={categoryFilter} onValueChange={(v) => setCategoryFilter(v ?? ALL_CATEGORIES)}>
            <SelectTrigger className="w-44 shrink-0">
              <SelectValue>{(v: string) => (v === ALL_CATEGORIES ? "All categories" : categoryLabel(v === UNCATEGORIZED ? null : v))}</SelectValue>
            </SelectTrigger>
            <SelectContent>
              <SelectItem value={ALL_CATEGORIES}>All categories ({ingredients.length})</SelectItem>
              <SelectItem value={UNCATEGORIZED}>Uncategorized ({categoryCounts.get(UNCATEGORIZED) ?? 0})</SelectItem>
              {GROCERY_CATEGORIES.map((c) => (
                <SelectItem key={c} value={c}>{CATEGORY_LABELS[c]} ({categoryCounts.get(c) ?? 0})</SelectItem>
              ))}
            </SelectContent>
          </Select>
        </div>
        <Button size="sm" onClick={openCreate} className="shrink-0">
          <Plus className="h-4 w-4" /> New ingredient
        </Button>
      </div>

      <p className="text-xs text-muted-foreground">
        {filtered.length === ingredients.length
          ? `${ingredients.length} ingredient${ingredients.length === 1 ? "" : "s"}`
          : `${filtered.length} of ${ingredients.length} ingredients`}
      </p>

      {ingredients.length === 0 ? (
        <EmptyState icon={Tag} title="No ingredients yet" description="Add a canonical ingredient and its common name variants." compact />
      ) : filtered.length === 0 ? (
        <p className="text-sm text-muted-foreground py-8 text-center">No ingredients match your search.</p>
      ) : (
        <div className="rounded-xl border divide-y">
          {filtered.map((ingredient) => (
            <div key={ingredient.id} className="flex items-center gap-3 px-4 py-3">
              <div className="flex-1 min-w-0">
                <div className="flex items-center gap-2 flex-wrap">
                  <span className="font-medium text-sm">{ingredient.name}</span>
                  <Badge variant="secondary" className="text-[10px] px-1.5 py-0">{categoryLabel(ingredient.category)}</Badge>
                </div>
                {ingredient.aliases.length > 0 && (
                  <div className="flex flex-wrap gap-1 mt-1">
                    {ingredient.aliases.map((alias) => (
                      <span key={alias} className="text-[11px] text-muted-foreground bg-muted rounded px-1.5 py-0.5">{alias}</span>
                    ))}
                  </div>
                )}
              </div>
              <button onClick={() => openEdit(ingredient)} aria-label="Edit" className="text-muted-foreground hover:text-foreground transition-colors shrink-0">
                <Pencil className="h-4 w-4" />
              </button>
              <button onClick={() => setConfirmId(ingredient.id)} aria-label="Delete" className="text-muted-foreground hover:text-destructive transition-colors shrink-0">
                <Trash2 className="h-4 w-4" />
              </button>
            </div>
          ))}
        </div>
      )}

      <IngredientDialog
        ingredient={editingIngredient}
        open={dialogOpen}
        onOpenChange={setDialogOpen}
        onSaved={(saved) => {
          setIngredients((prev) => {
            const exists = prev.some((i) => i.id === saved.id);
            const next = exists ? prev.map((i) => (i.id === saved.id ? saved : i)) : [...prev, saved];
            return next.sort((a, b) => a.name.localeCompare(b.name));
          });
        }}
      />

      <AlertDialog open={confirmId !== null} onOpenChange={(open) => !open && setConfirmId(null)}>
        <AlertDialogContent>
          <AlertDialogHeader>
            <AlertDialogTitle>Delete this ingredient?</AlertDialogTitle>
            <AlertDialogDescription>
              {pendingDelete ? `"${pendingDelete.name}" and its aliases will no longer be recognized as a match. Pantry items and recipe ingredients already linked to it just lose that link — nothing is deleted.` : ""}
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel>Cancel</AlertDialogCancel>
            <AlertDialogAction
              onClick={() => {
                if (confirmId) void handleDelete(confirmId);
                setConfirmId(null);
              }}
              className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
            >
              Delete
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
    </div>
  );
}
