import React, { useState, useEffect, useCallback, useRef } from 'react';
import { Link } from 'react-router-dom';
import { toast } from 'sonner';
import {
  ArrowDown, ArrowUp, ChevronDown, GripVertical, Plus, Pencil, Trash2, ReceiptText, AlertCircle, RefreshCw, ShoppingCart,
} from 'lucide-react';
import { api } from '@/api';
import { Button, buttonVariants } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { InputDialog } from '@/components/ui/input-dialog';
import {
  AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle,
  AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction,
} from '@/components/ui/alert-dialog';
import {
  Tooltip, TooltipContent, TooltipProvider, TooltipTrigger,
} from '@/components/ui/tooltip';
import { cn, fmtDate, errMessage } from '@/lib/utils';
import { formatUSD, asDollars } from '@/lib/money';
import { moveInArray, movedItemId, reorderPayload } from '@/lib/reorder';

function fmt(v: number | null | undefined): string {
  return formatUSD(asDollars(Number(v) || 0));
}

interface CategoryBill {
  id: number;
  name: string;
  due_day?: number | null;
  expected_amount?: number;
  total_paid?: number;
  payment_count?: number;
  last_paid_date?: string | null;
  active?: number;
}

interface CatRow {
  id: number;
  name: string;
  active_bill_count?: number;
  inactive_bill_count?: number;
  payment_count?: number;
  bill_names?: string[];
  bills?: CategoryBill[];
  spending_enabled?: number | boolean;
  [key: string]: unknown;
}

interface CategoryMoveControls {
  enabled: boolean;
  moving: boolean;
  canMoveUp: boolean;
  canMoveDown: boolean;
  onMoveUp: (event: React.MouseEvent) => void;
  onMoveDown: (event: React.MouseEvent) => void;
}

interface CategoryDragProps {
  draggable: boolean;
  isDragging?: boolean;
  isDropTarget?: boolean;
  onDragStart?: React.DragEventHandler<HTMLElement>;
  onDragEnter?: React.DragEventHandler<HTMLElement>;
  onDragOver?: React.DragEventHandler<HTMLElement>;
  onDragEnd?: React.DragEventHandler<HTMLElement>;
  onDrop?: React.DragEventHandler<HTMLElement>;
}

function plural(count: number, label: string): string {
  return `${count} ${label}${count === 1 ? '' : 's'}`;
}

function billPreview(names: string[] = []): string {
  if (!names.length) return 'No bills in this category yet.';
  const visible = names.slice(0, 4).join(', ');
  const more = names.length > 4 ? `, +${names.length - 4} more` : '';
  return `${visible}${more}`;
}

function categoryBillCount(category: CatRow): number {
  return (category.active_bill_count || 0) + (category.inactive_bill_count || 0);
}

function Chip({ value, label, tone = 'muted', details }: {
  value: React.ReactNode;
  label: string;
  tone?: string;
  details?: string;
}) {
  const toneClass = ({
    active: 'border-primary/25 bg-primary/10 text-primary',
    muted: 'border-border bg-muted/55 text-muted-foreground',
    info: 'border-sky-500/25 bg-sky-500/10 text-sky-600 dark:text-sky-400',
  } as Record<string, string>)[tone];

  return (
    <Tooltip>
      <TooltipTrigger asChild>
        <span
          tabIndex={0}
          title={details || label}
          aria-label={details || label}
          className={cn(
            'inline-flex h-6 min-w-7 items-center justify-center rounded-full border px-2 text-[11px] font-semibold tabular-nums',
            'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
            toneClass,
          )}
        >
          {value}
        </span>
      </TooltipTrigger>
      <TooltipContent className="max-w-64 leading-relaxed">
        <p>{label}</p>
        {details && details !== label && <p className="opacity-85">{details}</p>}
      </TooltipContent>
    </Tooltip>
  );
}

function StatChips({ category }: { category: CatRow }) {
  const names = billPreview(category.bill_names);
  return (
    <div className="flex flex-wrap items-center gap-1.5">
      <Chip
        value={category.active_bill_count || 0}
        label={plural(category.active_bill_count || 0, 'active bill')}
        details={names}
        tone="active"
      />
      <Chip
        value={category.inactive_bill_count || 0}
        label={plural(category.inactive_bill_count || 0, 'inactive bill')}
        details={names}
      />
      <Chip
        value={category.payment_count || 0}
        label={plural(category.payment_count || 0, 'payment')}
        details={names}
        tone="info"
      />
    </div>
  );
}

function ChipLegend() {
  const items: [string, string][] = [
    ['Active', 'active'],
    ['Inactive', 'muted'],
    ['Payments', 'info'],
  ];

  return (
    <div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
      {items.map(([label, tone]) => (
        <span key={label} className="inline-flex items-center gap-1.5">
          <span className={cn(
            'h-2.5 w-2.5 rounded-full border',
            tone === 'active' && 'border-primary/30 bg-primary/45',
            tone === 'muted' && 'border-border bg-muted',
            tone === 'info' && 'border-sky-500/30 bg-sky-500/45',
          )} />
          {label}
        </span>
      ))}
    </div>
  );
}

function StatusPill({ active }: { active?: number }) {
  return (
    <span className={cn(
      'inline-flex rounded-full border px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide',
      active
        ? 'border-emerald-500/25 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
        : 'border-border bg-muted text-muted-foreground',
    )}>
      {active ? 'Active' : 'Inactive'}
    </span>
  );
}

function BillName({ bill }: { bill: CategoryBill }) {
  const label = `${bill.name}: due day ${bill.due_day}, ${fmt(bill.expected_amount)} expected`;
  return (
    <Tooltip>
      <TooltipTrigger asChild>
        <span title={label} className="font-medium text-foreground underline-offset-4 hover:underline">
          {bill.name}
        </span>
      </TooltipTrigger>
      <TooltipContent className="max-w-72 leading-relaxed">
        <p>{label}</p>
        <p className="opacity-85">
          {plural(bill.payment_count || 0, 'payment')} / {fmt(bill.total_paid)} paid
        </p>
      </TooltipContent>
    </Tooltip>
  );
}

function ExpandedBills({ category }: { category: CatRow }) {
  const bills = category.bills || [];

  if (!bills.length) {
    return (
      <div className="border-t border-border/60 bg-muted/15 px-4 py-5 sm:px-6">
        <div className="flex flex-col gap-3 rounded-lg bg-muted/20 p-4 text-sm text-muted-foreground sm:flex-row sm:items-center sm:justify-between">
          <span>No bills in this category yet.</span>
          <Button asChild variant="outline" size="sm" className="w-fit">
            <Link to="/bills">Add a bill →</Link>
          </Button>
        </div>
      </div>
    );
  }

  return (
    <div className="border-t border-border/60 bg-muted/15 px-4 py-4 sm:px-5">
      <div className="hidden overflow-hidden rounded-lg border border-border/60 bg-background/75 lg:block">
        <table className="w-full text-sm">
          <thead className="bg-muted/45 text-xs uppercase tracking-wide text-muted-foreground">
            <tr>
              <th className="px-4 py-3 text-left font-semibold">Bill</th>
              <th className="px-4 py-3 text-left font-semibold">State</th>
              <th className="px-4 py-3 text-right font-semibold">Expected</th>
              <th className="px-4 py-3 text-right font-semibold">Paid</th>
              <th className="px-4 py-3 text-right font-semibold">History</th>
            </tr>
          </thead>
          <tbody className="divide-y divide-border/50">
            {bills.map(bill => (
              <tr key={bill.id} className="hover:bg-muted/25">
                <td className="px-4 py-3">
                  <BillName bill={bill} />
                  <p className="mt-1 text-xs text-muted-foreground">Due day {bill.due_day}</p>
                </td>
                <td className="px-4 py-3"><StatusPill active={bill.active} /></td>
                <td className="tracker-number px-4 py-3 text-right font-semibold">{fmt(bill.expected_amount)}</td>
                <td className="tracker-number px-4 py-3 text-right font-semibold">{fmt(bill.total_paid)}</td>
                <td className="px-4 py-3 text-right">
                  <p className="tabular-nums">{plural(bill.payment_count || 0, 'payment')}</p>
                  <p className="mt-1 text-xs tabular-nums text-muted-foreground">{fmtDate(bill.last_paid_date)}</p>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      <div className="grid gap-3 lg:hidden">
        {bills.map(bill => (
          <div key={bill.id} className="rounded-lg border border-border/60 bg-background/75 p-4">
            <div className="flex items-start justify-between gap-3">
              <div className="min-w-0">
                <p className="truncate text-sm"><BillName bill={bill} /></p>
                <p className="mt-1 text-xs text-muted-foreground">Due day {bill.due_day}</p>
              </div>
              <StatusPill active={bill.active} />
            </div>
            <div className="mt-4 grid grid-cols-2 gap-3 text-xs sm:grid-cols-4">
              <div>
                <p className="text-muted-foreground">Expected</p>
                <p className="tracker-number mt-0.5 font-semibold">{fmt(bill.expected_amount)}</p>
              </div>
              <div>
                <p className="text-muted-foreground">Paid</p>
                <p className="tracker-number mt-0.5 font-semibold">{fmt(bill.total_paid)}</p>
              </div>
              <div>
                <p className="text-muted-foreground">Payments</p>
                <p className="mt-0.5 font-semibold tabular-nums">{bill.payment_count || 0}</p>
              </div>
              <div>
                <p className="text-muted-foreground">Last Paid</p>
                <p className="mt-0.5 font-semibold tabular-nums">{fmtDate(bill.last_paid_date)}</p>
              </div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

export default function CategoriesPage() {
  const [categories, setCategories] = useState<CatRow[]>([]);
  const [loading, setLoading]       = useState(true);
  const [loadError, setLoadError]   = useState<string | null>(null);
  const [newName, setNewName]       = useState('');
  const [adding, setAdding]         = useState(false);
  const [expanded, setExpanded]     = useState<Set<number>>(() => new Set());
  const [showEmptyCategories, setShowEmptyCategories] = useState(false);
  const [draggingId, setDraggingId] = useState<number | null>(null);
  const [dropTargetId, setDropTargetId] = useState<number | null>(null);
  const [movingCategoryId, setMovingCategoryId] = useState<number | string | null>(null);
  const addInputRef                 = useRef<HTMLInputElement>(null);

  const [renameTarget, setRenameTarget] = useState<CatRow | null>(null);
  const [renaming, setRenaming]         = useState(false);
  const [deleteTarget, setDeleteTarget] = useState<CatRow | null>(null);
  const [deleting, setDeleting]         = useState(false);

  const load = useCallback(async () => {
    setLoadError(null);
    try {
      const cats = await api.categories() as CatRow[];
      setCategories(cats);
    } catch (err) {
      setLoadError(errMessage(err, 'Failed to load categories'));
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => { load(); }, [load]);

  function toggleCategory(id: number) {
    setExpanded(prev => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  }

  async function handleAdd(e: React.FormEvent) {
    e.preventDefault();
    const trimmed = newName.trim();
    if (!trimmed) {
      toast.error('Enter a category name');
      addInputRef.current?.focus();
      return;
    }
    setAdding(true);
    try {
      await api.createCategory({ name: trimmed });
      toast.success(`"${trimmed}" added`);
      setNewName('');
      setShowEmptyCategories(true);
      addInputRef.current?.focus();
      load();
    } catch (err) {
      toast.error(errMessage(err, 'Failed to add category'));
    } finally {
      setAdding(false);
    }
  }

  function openRename(event: React.MouseEvent, cat: CatRow) {
    event.stopPropagation();
    setRenameTarget(cat);
  }

  async function handleRename(name: string) {
    if (!renameTarget) return;
    setRenaming(true);
    try {
      await api.updateCategory(renameTarget.id, { name });
      toast.success('Category renamed');
      setRenameTarget(null);
      load();
    } catch (err) {
      toast.error(errMessage(err, 'Failed to rename category'));
    } finally {
      setRenaming(false);
    }
  }

  function openDelete(event: React.MouseEvent, cat: CatRow) {
    event.stopPropagation();
    setDeleteTarget(cat);
  }

  async function handleDelete() {
    const category = deleteTarget;
    if (!category) return;
    setDeleting(true);
    try {
      await api.deleteCategory(category.id);
      toast.success(`"${category.name}" moved to recovery`, {
        description: 'Bills keep their category if you restore it within 30 days.',
        action: {
          label: 'Undo',
          onClick: async () => {
            try {
              await api.restoreCategory(category.id);
              toast.success(`"${category.name}" restored`);
              load();
            } catch (err) {
              toast.error(errMessage(err, 'Failed to restore category'));
            }
          },
        },
      });
      setExpanded(prev => {
        const next = new Set(prev);
        next.delete(category.id);
        return next;
      });
      setDeleteTarget(null);
      load();
    } catch (err) {
      toast.error(errMessage(err, 'Could not delete category.'));
    } finally {
      setDeleting(false);
    }
  }

  const activeBills = categories.reduce((sum, cat) => sum + (cat.active_bill_count || 0), 0);
  const inactiveBills = categories.reduce((sum, cat) => sum + (cat.inactive_bill_count || 0), 0);
  const paymentCount = categories.reduce((sum, cat) => sum + (cat.payment_count || 0), 0);
  const emptyCategories = categories.filter(cat => categoryBillCount(cat) === 0);
  const visibleCategories = showEmptyCategories
    ? categories
    : categories.filter(cat => categoryBillCount(cat) > 0);
  const reorderEnabled = !loading && !loadError;

  async function persistCategoryOrder(nextCategories: CatRow[], movedId: number | string | null) {
    setCategories(nextCategories);
    setMovingCategoryId(movedId);
    try {
      await api.reorderCategories(reorderPayload(nextCategories));
      toast.success('Category order saved');
      load();
    } catch (err) {
      toast.error(errMessage(err, 'Failed to save category order'));
      load();
    } finally {
      setMovingCategoryId(null);
    }
  }

  function reorderVisibleCategories(orderedVisible: CatRow[]) {
    if (!reorderEnabled) return;
    const visibleIds = new Set(visibleCategories.map(cat => cat.id));
    const replacements = [...orderedVisible];
    const nextCategories = categories.map(cat => (
      visibleIds.has(cat.id) ? replacements.shift()! : cat
    ));
    persistCategoryOrder(nextCategories, movedItemId(visibleCategories, orderedVisible));
  }

  function categoryMoveControls(cat: CatRow, index: number): CategoryMoveControls {
    return {
      enabled: reorderEnabled,
      moving: movingCategoryId === cat.id,
      canMoveUp: index > 0,
      canMoveDown: index < visibleCategories.length - 1,
      onMoveUp: (event) => {
        event.stopPropagation();
        reorderVisibleCategories(moveInArray(visibleCategories, index, index - 1));
      },
      onMoveDown: (event) => {
        event.stopPropagation();
        reorderVisibleCategories(moveInArray(visibleCategories, index, index + 1));
      },
    };
  }

  function categoryDragProps(cat: CatRow, index: number): CategoryDragProps {
    if (!reorderEnabled) return { draggable: false };
    return {
      draggable: true,
      isDragging: draggingId === cat.id,
      isDropTarget: dropTargetId === cat.id && draggingId !== cat.id,
      onDragStart: (event) => {
        setDraggingId(cat.id);
        event.dataTransfer.effectAllowed = 'move';
        event.dataTransfer.setData('text/plain', String(cat.id));
      },
      onDragEnter: () => {
        if (draggingId && draggingId !== cat.id) setDropTargetId(cat.id);
      },
      onDragOver: (event) => {
        event.preventDefault();
        event.dataTransfer.dropEffect = 'move';
        if (draggingId && draggingId !== cat.id) setDropTargetId(cat.id);
      },
      onDrop: (event) => {
        event.preventDefault();
        event.stopPropagation();
        const sourceId = Number(event.dataTransfer.getData('text/plain') || draggingId);
        const fromIndex = visibleCategories.findIndex(item => item.id === sourceId);
        if (fromIndex >= 0) reorderVisibleCategories(moveInArray(visibleCategories, fromIndex, index));
        setDraggingId(null);
        setDropTargetId(null);
      },
      onDragEnd: () => {
        setDraggingId(null);
        setDropTargetId(null);
      },
    };
  }

  return (
    <TooltipProvider delayDuration={180}>
      <div className="mx-auto w-full max-w-5xl space-y-5">
        <div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
          <div className="min-w-0">
            <div className="flex items-center gap-2">
              <div className="flex h-9 w-9 items-center justify-center rounded-lg border border-border/70 bg-card shadow-sm">
                <ReceiptText className="h-4 w-4 text-primary" />
              </div>
              <div>
                <h1 className="text-2xl font-bold tracking-tight">Categories</h1>
                <p className="mt-0.5 text-sm text-muted-foreground">Organize bills by purpose, status, and payment activity.</p>
              </div>
            </div>
          </div>
          <ChipLegend />
        </div>

        <div className="grid gap-3 md:grid-cols-[1fr_minmax(20rem,26rem)]">
          <div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
            {([
              ['Categories', categories.length],
              ['Active bills', activeBills],
              ['Inactive', inactiveBills],
              ['Payments', paymentCount],
            ] as [string, number][]).map(([label, value]) => (
              <div key={label} className="rounded-xl border border-border/70 bg-card/80 px-4 py-3 shadow-sm">
                <p className="text-[11px] font-semibold uppercase tracking-normal text-muted-foreground">{label}</p>
                <p className="tracker-number mt-1 text-xl font-bold text-foreground">{value}</p>
              </div>
            ))}
          </div>

          <form onSubmit={handleAdd} className="flex min-w-0 flex-col gap-2 rounded-xl border border-border/70 bg-card/80 p-3 shadow-sm sm:flex-row md:flex-col lg:flex-row">
            <Input
              ref={addInputRef}
              value={newName}
              onChange={(e) => setNewName(e.target.value)}
              placeholder="New category name..."
              disabled={adding}
              className="h-9 min-w-0 text-sm"
            />
            <Button type="submit" size="sm" className="h-9 shrink-0 sm:w-auto" disabled={adding || !newName.trim()}>
              <Plus className="mr-1.5 h-3.5 w-3.5" />
              {adding ? 'Adding...' : 'Add'}
            </Button>
          </form>
        </div>

        <div className="table-surface overflow-hidden rounded-xl">
          {loading ? (
            <div className="py-16 text-center text-sm text-muted-foreground">Loading...</div>
          ) : loadError ? (
            <div className="flex flex-col items-center justify-center py-16 text-center">
              <AlertCircle className="h-8 w-8 text-destructive mb-3" />
              <p className="text-sm font-medium text-foreground">Failed to load categories</p>
              <p className="mt-1 text-xs text-muted-foreground">{loadError}</p>
              <Button size="sm" variant="outline" onClick={load}
                className="mt-4 gap-1.5 text-xs border-destructive/30 hover:bg-destructive/10 hover:text-destructive hover:border-destructive/50">
                <RefreshCw className="h-3 w-3" />
                Try again
              </Button>
            </div>
          ) : visibleCategories.length === 0 ? (
            <div className="flex flex-col items-center justify-center gap-3 px-4 py-16 text-center text-sm text-muted-foreground">
              <span>
                {categories.length === 0
                  ? 'No categories yet. Add one above.'
                  : 'No categories with bills yet.'}
              </span>
              {emptyCategories.length > 0 && (
                <Button
                  type="button"
                  variant="outline"
                  size="sm"
                  onClick={() => setShowEmptyCategories(true)}
                >
                  Show Empty Categories
                </Button>
              )}
            </div>
          ) : (
            <div className="divide-y divide-border/50">
              {emptyCategories.length > 0 && (
                <div className="flex flex-col gap-2 bg-muted/20 px-4 py-3 text-xs text-muted-foreground sm:flex-row sm:items-center sm:justify-between sm:px-5">
                  <span>
                    {showEmptyCategories
                      ? `Showing ${plural(emptyCategories.length, 'empty category')}.`
                      : `${plural(emptyCategories.length, 'empty category')} hidden until a bill uses them.`}
                  </span>
                  <Button
                    type="button"
                    variant="ghost"
                    size="sm"
                    className="h-8 w-fit px-2 text-xs"
                    onClick={() => setShowEmptyCategories(value => !value)}
                  >
                    {showEmptyCategories ? 'Hide Empty' : 'Show Empty'}
                  </Button>
                </div>
              )}
              {visibleCategories.map((cat, index) => {
                const isExpanded = expanded.has(cat.id);
                const preview = billPreview(cat.bill_names);
                const moveControls = categoryMoveControls(cat, index);
                const dragProps = categoryDragProps(cat, index);
                return (
                  <section
                    key={cat.id}
                    draggable={dragProps?.draggable}
                    onDragStart={dragProps?.onDragStart}
                    onDragEnter={dragProps?.onDragEnter}
                    onDragOver={dragProps?.onDragOver}
                    onDragEnd={dragProps?.onDragEnd}
                    onDrop={dragProps?.onDrop}
                    className={cn(
                      'bg-card/35',
                      dragProps?.isDragging && 'opacity-45',
                      dragProps?.isDropTarget && 'ring-2 ring-inset ring-primary/35',
                    )}
                  >
                    {/* Plain container (not role=button) so the nested action
                        buttons aren't interactive-in-interactive (a11y QA-B14-02).
                        Mouse click-anywhere still expands; keyboard/SR users use
                        the dedicated chevron toggle button below. */}
                    <div
                      title={preview}
                      onClick={() => toggleCategory(cat.id)}
                      className={cn(
                        'group grid cursor-pointer gap-4 px-4 py-4 transition-colors sm:px-5 md:grid-cols-[minmax(0,1fr)_auto] md:items-center',
                        'hover:bg-muted/35',
                        isExpanded && 'bg-muted/25',
                      )}
                    >
                      <div className="flex min-w-0 flex-1 items-start gap-3">
                        <div
                          className="flex shrink-0 items-center gap-0.5"
                          onClick={event => event.stopPropagation()}
                          onKeyDown={event => event.stopPropagation()}
                        >
                          <GripVertical
                            className={cn(
                              'h-4 w-4 text-muted-foreground/55',
                              moveControls.enabled && 'cursor-grab group-active:cursor-grabbing',
                              !moveControls.enabled && 'opacity-30',
                            )}
                            aria-hidden="true"
                          />
                          <div className="flex flex-col">
                            <button
                              type="button"
                              onClick={moveControls.onMoveUp}
                              disabled={!moveControls.enabled || !moveControls.canMoveUp || moveControls.moving}
                              className="rounded text-muted-foreground transition-colors hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-25"
                              title="Move category up"
                              aria-label={`Move ${cat.name} up`}
                            >
                              <ArrowUp className="h-3 w-3" />
                            </button>
                            <button
                              type="button"
                              onClick={moveControls.onMoveDown}
                              disabled={!moveControls.enabled || !moveControls.canMoveDown || moveControls.moving}
                              className="rounded text-muted-foreground transition-colors hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-25"
                              title="Move category down"
                              aria-label={`Move ${cat.name} down`}
                            >
                              <ArrowDown className="h-3 w-3" />
                            </button>
                          </div>
                        </div>
                        <button
                          type="button"
                          onClick={event => { event.stopPropagation(); toggleCategory(cat.id); }}
                          aria-expanded={isExpanded}
                          aria-label={`${isExpanded ? 'Collapse' : 'Expand'} ${cat.name}`}
                          className="mt-0.5 shrink-0 rounded text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                        >
                          <ChevronDown
                            className={cn(
                              'h-4 w-4 transition-transform',
                              isExpanded && 'rotate-180 text-foreground',
                            )}
                          />
                        </button>
                        <div className="min-w-0 flex-1">
                          <div className="flex flex-col gap-2 sm:flex-row sm:items-center">
                            <Tooltip>
                              <TooltipTrigger asChild>
                                <span className="truncate text-sm font-semibold tracking-tight text-foreground" title={preview}>
                                  {cat.name}
                                </span>
                              </TooltipTrigger>
                              <TooltipContent className="max-w-72 leading-relaxed">
                                <p className="font-medium">{cat.name}</p>
                                <p className="opacity-85">{preview}</p>
                              </TooltipContent>
                            </Tooltip>
                            <StatChips category={cat} />
                          </div>
                          <p className="mt-1 line-clamp-1 text-xs text-muted-foreground">{preview}</p>
                        </div>
                      </div>

                      <div className="flex items-center justify-end gap-1 opacity-80 transition-opacity group-hover:opacity-100">
                        <Tooltip>
                          <TooltipTrigger asChild>
                            <Button
                              type="button"
                              variant="ghost"
                              size="icon"
                              className={`h-8 w-8 ${cat.spending_enabled ? 'text-emerald-500 hover:text-emerald-400' : 'text-muted-foreground/40 hover:text-muted-foreground'}`}
                              onClick={async (event) => {
                                event.stopPropagation();
                                try {
                                  const result = await api.toggleCategorySpending(cat.id, !cat.spending_enabled) as { spending_enabled?: number | boolean };
                                  setCategories(prev => prev.map(c => c.id === cat.id ? { ...c, spending_enabled: result.spending_enabled } : c));
                                } catch (err) {
                                  toast.error(errMessage(err, 'Failed to update category'));
                                }
                              }}
                              aria-label={cat.spending_enabled ? `Disable spending for ${cat.name}` : `Enable spending for ${cat.name}`}
                            >
                              <ShoppingCart className="h-3.5 w-3.5" />
                            </Button>
                          </TooltipTrigger>
                          <TooltipContent>
                            {cat.spending_enabled ? 'Shown in Spending page — click to disable' : 'Enable for Spending page'}
                          </TooltipContent>
                        </Tooltip>
                        <Button
                          type="button"
                          variant="ghost"
                          size="icon"
                          className="h-8 w-8"
                          onClick={(event) => openRename(event, cat)}
                          aria-label={`Rename ${cat.name}`}
                        >
                          <Pencil className="h-3.5 w-3.5" />
                        </Button>
                        <Button
                          type="button"
                          variant="ghost"
                          size="icon"
                          className="h-8 w-8 text-destructive hover:text-destructive"
                          onClick={(event) => openDelete(event, cat)}
                          aria-label={`Delete ${cat.name}`}
                        >
                          <Trash2 className="h-3.5 w-3.5" />
                        </Button>
                      </div>
                    </div>

                    {isExpanded && <ExpandedBills category={cat} />}
                  </section>
                );
              })}
            </div>
          )}
        </div>

        <div className="text-xs text-muted-foreground">
          Category totals include active and inactive bills in your account only.
        </div>

        <InputDialog
          open={!!renameTarget}
          onOpenChange={(open) => { if (!open) setRenameTarget(null); }}
          title="Rename Category"
          label="Name"
          defaultValue={renameTarget?.name ?? ''}
          placeholder="Category name"
          confirmLabel="Rename"
          loading={renaming}
          onConfirm={handleRename}
        />

        <AlertDialog open={!!deleteTarget} onOpenChange={(open) => { if (!open) setDeleteTarget(null); }}>
          <AlertDialogContent>
            <AlertDialogHeader>
              <AlertDialogTitle>Move {deleteTarget?.name} to recovery?</AlertDialogTitle>
              <AlertDialogDescription>
                This hides the category from normal views. Bills keep their category link, and you can restore it for 30 days.
              </AlertDialogDescription>
            </AlertDialogHeader>
            <AlertDialogFooter>
              <AlertDialogCancel disabled={deleting}>Cancel</AlertDialogCancel>
              <AlertDialogAction
                className={cn(buttonVariants({ variant: 'destructive' }))}
                onClick={handleDelete}
                disabled={deleting}
              >
                {deleting ? 'Moving...' : 'Move to Recovery'}
              </AlertDialogAction>
            </AlertDialogFooter>
          </AlertDialogContent>
        </AlertDialog>
      </div>
    </TooltipProvider>
  );
}
