import { useState, useEffect, useRef } from 'react';
import { toast } from 'sonner';
import {
  Sparkles, CheckCircle2, Loader2, RefreshCw, Link2, Link2Off,
  XCircle, Eye, EyeOff, Search, Clock, ChevronLeft, ChevronRight,
  ArrowUp, ArrowDown, ArrowUpDown,
} from 'lucide-react';
import { api, type QueryParams } from '@/api';
import { cn, errMessage, parseUtc } from '@/lib/utils';
import { asDollars } from '@/lib/money';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { SectionCard, type SectionCardProps } from './dataShared';
import BillModal from '@/components/BillModal';
import {
  MatchBillDialog,
  transactionTitle,
  transactionDate,
  formatTransactionAmount,
} from '@/components/transactions/MatchBillDialog';
import type { BankTransaction, Bill, Category } from '@/types';

interface MatchSuggestion {
  id: number | string;
  score?: number;
  transactionId: number | string;
  billId: number | string;
  transaction?: BankTransaction;
  bill?: Bill;
  reasons?: string[];
}

interface SimplefinConn {
  id: number | string;
  last_error?: string | null;
  last_sync_at?: string | null;
}

const TRANSACTION_FILTERS: { id: string; label: string; params: Record<string, string> }[] = [
  { id: 'open', label: 'Open', params: { ignored: 'false' } },
  { id: 'unmatched', label: 'Unmatched', params: { match_status: 'unmatched', ignored: 'false' } },
  { id: 'matched', label: 'Matched', params: { match_status: 'matched', ignored: 'false' } },
  { id: 'ignored', label: 'Ignored', params: { match_status: 'ignored', ignored: 'true' } },
  { id: 'all', label: 'All', params: { ignored: 'all' } },
];

function transactionStatus(tx: BankTransaction | null | undefined): string {
  if (tx?.ignored) return 'ignored';
  return (tx?.match_status as string) || 'unmatched';
}

function TransactionStatusBadge({ tx }: { tx: BankTransaction }) {
  const status = transactionStatus(tx);
  const styles: Record<string, string> = {
    matched: 'border-emerald-500/30 bg-emerald-500/10 text-emerald-600',
    ignored: 'border-muted-foreground/30 bg-muted/40 text-muted-foreground',
    unmatched: 'border-amber-500/30 bg-amber-500/10 text-amber-600 dark:text-amber-400',
  };

  return (
    <span className={cn(
      'inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-semibold uppercase',
      styles[status] || styles.unmatched,
    )}>
      {status}
    </span>
  );
}

function matchScoreTone(score?: number | string): string {
  const value = Number(score) || 0;
  if (value >= 80) return 'border-emerald-500/30 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400';
  if (value >= 55) return 'border-sky-500/30 bg-sky-500/10 text-sky-600 dark:text-sky-400';
  return 'border-amber-500/30 bg-amber-500/10 text-amber-600 dark:text-amber-400';
}

interface SuggestedMatchesPanelProps {
  suggestions: MatchSuggestion[];
  loading: boolean;
  actionId: string | null;
  onAccept: (suggestion: MatchSuggestion) => void;
  onReject: (suggestion: MatchSuggestion) => void;
}

function SuggestedMatchesPanel({ suggestions, loading, actionId, onAccept, onReject }: SuggestedMatchesPanelProps) {
  return (
    <div className="rounded-lg border border-sky-500/20 bg-sky-500/[0.035]">
      <div className="flex flex-wrap items-center justify-between gap-3 border-b border-sky-500/10 px-4 py-3">
        <div className="flex items-center gap-2">
          <span className="inline-flex h-8 w-8 items-center justify-center rounded-md border border-sky-500/20 bg-sky-500/10 text-sky-600 dark:text-sky-400">
            <Sparkles className="h-4 w-4" />
          </span>
          <div>
            <p className="text-sm font-semibold">Suggested matches</p>
            <p className="text-xs text-muted-foreground">{loading ? 'Checking transactions' : `${suggestions.length} ready for review`}</p>
          </div>
        </div>
      </div>

      {loading ? (
        <div className="px-4 py-6 text-sm text-muted-foreground">
          <Loader2 className="mr-2 inline h-4 w-4 animate-spin" />
          Finding likely bill matches...
        </div>
      ) : suggestions.length === 0 ? (
        <div className="px-4 py-6 text-sm text-muted-foreground">
          No suggested matches right now.
        </div>
      ) : (
        <div className="grid gap-2 p-3 xl:grid-cols-2">
          {suggestions.map(suggestion => {
            const tx = suggestion.transaction || ({} as BankTransaction);
            const bill = suggestion.bill || ({} as Bill);
            const acceptBusy = actionId === `suggestion-match:${suggestion.id}`;
            const rejectBusy = actionId === `suggestion-reject:${suggestion.id}`;
            const busy = acceptBusy || rejectBusy;

            return (
              <div
                key={suggestion.id}
                className="rounded-lg border border-border/60 bg-background/80 p-3 shadow-sm transition-colors hover:border-sky-500/30"
              >
                <div className="flex items-start justify-between gap-3">
                  <div className="min-w-0 space-y-1">
                    <div className="flex flex-wrap items-center gap-2">
                      <span className={cn('rounded-full border px-2 py-0.5 text-[10px] font-semibold uppercase', matchScoreTone(suggestion.score))}>
                        {suggestion.score}
                      </span>
                      <p className="truncate text-sm font-medium">{transactionTitle(tx)}</p>
                    </div>
                    <p className="truncate text-xs text-muted-foreground">
                      {transactionDate(tx)} · {tx.source_label || tx.source_type_label || 'Transaction'}
                    </p>
                  </div>
                  <p className={cn(
                    'shrink-0 text-sm font-semibold tabular-nums',
                    Number(tx.amount) < 0 ? 'text-destructive' : 'text-emerald-600',
                  )}>
                    {formatTransactionAmount(tx.amount, tx.currency ?? undefined)}
                  </p>
                </div>

                <div className="mt-3 flex items-center gap-2 rounded-md border border-emerald-500/15 bg-emerald-500/[0.045] px-2.5 py-2">
                  <Link2 className="h-3.5 w-3.5 shrink-0 text-emerald-600 dark:text-emerald-400" />
                  <div className="min-w-0">
                    <p className="truncate text-sm font-medium">{bill.name || `Bill ${suggestion.billId}`}</p>
                    <p className="text-xs text-muted-foreground">
                      Expected ${Number(bill.expected_amount || 0).toFixed(2)}
                    </p>
                  </div>
                </div>

                {(suggestion.reasons?.length ?? 0) > 0 && (
                  <div className="mt-2 flex flex-wrap gap-1.5">
                    {suggestion.reasons?.slice(0, 4).map(reason => (
                      <span key={reason} className="rounded-full border border-border/60 bg-muted/30 px-2 py-0.5 text-[10px] text-muted-foreground">
                        {reason}
                      </span>
                    ))}
                  </div>
                )}

                <div className="mt-3 flex justify-end gap-2">
                  <Button
                    type="button"
                    size="sm"
                    variant="ghost"
                    disabled={busy}
                    onClick={() => onReject(suggestion)}
                    className="h-8 text-xs text-muted-foreground hover:text-destructive"
                  >
                    {rejectBusy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <XCircle className="h-3.5 w-3.5" />}
                    <span className="ml-1.5">Reject</span>
                  </Button>
                  <Button
                    type="button"
                    size="sm"
                    disabled={busy}
                    onClick={() => onAccept(suggestion)}
                    className="h-8 gap-1.5 text-xs"
                  >
                    {acceptBusy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <CheckCircle2 className="h-3.5 w-3.5" />}
                    Match
                  </Button>
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

function timeAgo(iso: string | null | undefined): string | null {
  const d = parseUtc(iso);
  if (!d) return null;
  const secs = Math.floor((Date.now() - d.getTime()) / 1000);
  if (secs < 60)  return 'just now';
  if (secs < 3600) return `${Math.floor(secs / 60)}m ago`;
  if (secs < 86400) return `${Math.floor(secs / 3600)}h ago`;
  return `${Math.floor(secs / 86400)}d ago`;
}

interface TransactionMatchingSectionProps {
  refreshKey?: number | string;
  simplefinConn?: SimplefinConn | null;
  cardProps?: Partial<SectionCardProps>;
}

export default function TransactionMatchingSection({ refreshKey, simplefinConn, cardProps = {} }: TransactionMatchingSectionProps) {
  const [transactions, setTransactions] = useState<BankTransaction[]>([]);
  const [suggestions, setSuggestions] = useState<MatchSuggestion[]>([]);
  const [bills, setBills] = useState<Bill[]>([]);
  const [filter, setFilter] = useState('open');
  const [loading, setLoading] = useState(true);
  const [suggestionsLoading, setSuggestionsLoading] = useState(true);
  const [billsLoading, setBillsLoading] = useState(true);
  const [actionId, setActionId] = useState<string | null>(null);
  const [matchOpen, setMatchOpen] = useState(false);
  const [matchTransaction, setMatchTransaction] = useState<BankTransaction | null>(null);
  const [categories, setCategories] = useState<Category[]>([]);
  const [createBillSourceTx, setCreateBillSourceTx] = useState<{ tx: BankTransaction; initialBill: Partial<Bill> } | null>(null);
  const [page, setPage] = useState(1);
  const [totalCount, setTotalCount] = useState(0);
  const [search, setSearch] = useState('');
  const [sortBy, setSortBy] = useState('date');
  const [sortDir, setSortDir] = useState('desc');
  const searchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  const PAGE_SIZE = 10;
  const totalPages = Math.ceil(totalCount / PAGE_SIZE) || 1;
  const currentFilter = TRANSACTION_FILTERS.find(item => item.id === filter) || TRANSACTION_FILTERS[0]!;

  const loadTransactions = async (pageNum?: number, searchOverride?: string, sortByOverride?: string, sortDirOverride?: string) => {
    const p   = pageNum       ?? page;
    const q   = searchOverride  !== undefined ? searchOverride  : search;
    const sb  = sortByOverride  !== undefined ? sortByOverride  : sortBy;
    const sd  = sortDirOverride !== undefined ? sortDirOverride : sortDir;
    setLoading(true);
    try {
      const params: QueryParams = { limit: PAGE_SIZE, offset: (p - 1) * PAGE_SIZE, sort_by: sb, sort_dir: sd, ...currentFilter.params };
      if (q) params.q = q;
      const resp = await api.transactions(params) as { transactions?: BankTransaction[]; total?: number };
      setTransactions(resp.transactions || []);
      setTotalCount(resp.total || 0);
    } catch (err) {
      toast.error(errMessage(err, 'Failed to load transactions.'));
      setTransactions([]);
    } finally {
      setLoading(false);
    }
  };

  const loadSuggestions = async () => {
    setSuggestionsLoading(true);
    try {
      const data = await api.matchSuggestions({ limit: 8 }) as MatchSuggestion[];
      setSuggestions(data || []);
    } catch (err) {
      toast.error(errMessage(err, 'Failed to load match suggestions.'));
      setSuggestions([]);
    } finally {
      setSuggestionsLoading(false);
    }
  };

  const refreshTransactionWorkbench = async () => {
    await Promise.all([loadTransactions(), loadSuggestions()]);
  };

  const loadBills = async () => {
    setBillsLoading(true);
    try {
      const data = await api.bills() as Bill[];
      setBills(data || []);
    } catch (err) {
      setBills([]);
      toast.error(errMessage(err, 'Failed to load bills for matching.'));
    } finally {
      setBillsLoading(false);
    }
  };

  useEffect(() => { loadBills(); }, []);
  // Intentional: reset + reload page 1 when the filter or refresh key changes.
  // eslint-disable-next-line react-hooks/exhaustive-deps
  useEffect(() => { setSearch(''); setPage(1); loadTransactions(1, ''); }, [filter, refreshKey]);
  useEffect(() => { loadSuggestions(); }, [refreshKey]);
  useEffect(() => {
    api.categories().then(data => setCategories((data as Category[]) || [])).catch(err => console.error('[TransactionMatchingSection] failed to load categories', err));
  }, []);

  const changePage = (newPage: number) => {
    setPage(newPage);
    loadTransactions(newPage);
  };

  const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value;
    setSearch(value);
    if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
    searchTimerRef.current = setTimeout(() => {
      setPage(1);
      loadTransactions(1, value);
    }, 300);
  };

  const handleSortClick = (column: string) => {
    const newDir = sortBy === column && sortDir === 'desc' ? 'asc' : 'desc';
    setSortBy(column);
    setSortDir(newDir);
    setPage(1);
    loadTransactions(1, undefined, column, newDir);
  };

  const openMatchDialog = (tx: BankTransaction) => {
    setMatchTransaction(tx);
    setMatchOpen(true);
    if (!bills.length && !billsLoading) loadBills();
  };

  const openCreateBill = (tx: BankTransaction | null, nameOverride?: string) => {
    if (!tx) return;
    const amount = Math.abs(Number(tx.amount || 0)) / 100;
    const dateStr = transactionDate(tx);
    const day = dateStr ? parseInt(dateStr.slice(8, 10), 10) : 1;
    setCreateBillSourceTx({
      tx,
      initialBill: {
        name: nameOverride || transactionTitle(tx),
        expected_amount: asDollars(amount || 0),
        due_day: day >= 1 && day <= 31 ? day : 1,
        billing_cycle: 'monthly',
        cycle_type: 'monthly',
        cycle_day: '1',
        active: 1,
      },
    });
  };

  const handleBillCreated = async (newBill?: Bill) => {
    const tx = createBillSourceTx?.tx;
    setCreateBillSourceTx(null);
    if (tx && newBill?.id) {
      try {
        await api.matchTransaction(tx.id, newBill.id);
        toast.success('Bill created and matched to transaction.');
      } catch (err) {
        toast.error(errMessage(err, 'Bill created but match failed.'));
      }
    }
    await Promise.all([loadBills(), refreshTransactionWorkbench()]);
  };

  const runTransactionAction = async (tx: BankTransaction, action: string) => {
    setActionId(`${action}:${tx.id}`);
    try {
      if (action === 'unmatch') {
        await api.unmatchTransaction(tx.id);
        toast.success('Transaction unmatched.');
      } else if (action === 'ignore') {
        await api.ignoreTransaction(tx.id);
        toast.success('Transaction ignored.');
      } else if (action === 'unignore') {
        await api.unignoreTransaction(tx.id);
        toast.success('Transaction restored.');
      }
      await refreshTransactionWorkbench();
    } catch (err) {
      toast.error(errMessage(err, 'Transaction action failed.'));
    } finally {
      setActionId(null);
    }
  };

  const confirmMatch = async (billId: number | string) => {
    if (!matchTransaction) return;
    setActionId(`match:${matchTransaction.id}`);
    try {
      await api.matchTransaction(matchTransaction.id, billId);
      toast.success('Transaction matched to bill.');
      setMatchOpen(false);
      setMatchTransaction(null);
      await refreshTransactionWorkbench();
    } catch (err) {
      toast.error(errMessage(err, 'Transaction match failed.'));
    } finally {
      setActionId(null);
    }
  };

  const acceptSuggestion = async (suggestion: MatchSuggestion) => {
    setActionId(`suggestion-match:${suggestion.id}`);
    try {
      await api.matchTransaction(suggestion.transactionId, suggestion.billId);
      toast.success('Suggested match confirmed.');
      await refreshTransactionWorkbench();
    } catch (err) {
      toast.error(errMessage(err, 'Suggested match failed.'));
    } finally {
      setActionId(null);
    }
  };

  const rejectSuggestion = async (suggestion: MatchSuggestion) => {
    setActionId(`suggestion-reject:${suggestion.id}`);
    try {
      await api.rejectMatchSuggestion(suggestion.id);
      toast.success('Suggestion rejected.');
      await loadSuggestions();
    } catch (err) {
      toast.error(errMessage(err, 'Suggestion could not be rejected.'));
    } finally {
      setActionId(null);
    }
  };

  const [quickSyncing, setQuickSyncing] = useState(false);

  const handleQuickSync = async () => {
    if (!simplefinConn) return;
    setQuickSyncing(true);
    try {
      await api.syncDataSource(simplefinConn.id);
      await refreshTransactionWorkbench();
    } catch (err) {
      toast.error(errMessage(err, 'Sync failed'));
    } finally {
      setQuickSyncing(false);
    }
  };

  return (
    <SectionCard
      title="Transactions"
      subtitle="Review imported or manual transactions and confirm matches to bills."
      {...cardProps}
    >
      {simplefinConn && (
        <div className="px-6 py-2 flex items-center justify-between gap-3 border-b border-border/50 bg-muted/20 text-xs text-muted-foreground">
          <span className="flex items-center gap-1.5">
            <span className={cn(
              'h-1.5 w-1.5 rounded-full shrink-0',
              simplefinConn.last_error ? 'bg-destructive' : 'bg-emerald-500',
            )} />
            SimpleFIN
            {simplefinConn.last_sync_at && (
              <span>· synced {timeAgo(simplefinConn.last_sync_at)}</span>
            )}
            {simplefinConn.last_error && (
              <span className="text-destructive">· {simplefinConn.last_error}</span>
            )}
          </span>
          <button
            type="button"
            onClick={handleQuickSync}
            disabled={quickSyncing}
            className="flex items-center gap-1 hover:text-foreground transition-colors disabled:opacity-50"
          >
            {quickSyncing
              ? <><Loader2 className="h-3 w-3 animate-spin" />Syncing…</>
              : <><RefreshCw className="h-3 w-3" />Sync Now</>}
          </button>
        </div>
      )}
      <div className="px-6 py-5 space-y-4">
        <div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
          <div className="flex flex-wrap gap-2">
            {TRANSACTION_FILTERS.map(item => (
              <button
                key={item.id}
                type="button"
                onClick={() => setFilter(item.id)}
                className={cn(
                  'rounded-full border px-3 py-1.5 text-xs font-medium transition-colors',
                  filter === item.id
                    ? 'border-primary/40 bg-primary/10 text-primary'
                    : 'border-border/60 bg-muted/20 text-muted-foreground hover:bg-muted hover:text-foreground',
                )}
              >
                {item.label}
              </button>
            ))}
          </div>
          <div className="flex items-center gap-2">
            <div className="relative">
              <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
                type="search"
                placeholder="Search transactions…"
                value={search}
                onChange={handleSearchChange}
                className="h-8 w-48 pl-8 text-sm"
              />
            </div>
            <Button size="sm" variant="outline" type="button" onClick={refreshTransactionWorkbench} disabled={loading || suggestionsLoading}>
              {loading ? <Loader2 className="h-3.5 w-3.5 mr-1.5 animate-spin" /> : <RefreshCw className="h-3.5 w-3.5 mr-1.5" />}
              Refresh
            </Button>
          </div>
        </div>

        <SuggestedMatchesPanel
          suggestions={suggestions}
          loading={suggestionsLoading}
          actionId={actionId}
          onAccept={acceptSuggestion}
          onReject={rejectSuggestion}
        />

        <div className="overflow-x-auto rounded-lg border border-border/60">
          {loading ? (
            <div className="px-4 py-8 text-center text-sm text-muted-foreground">Loading transactions…</div>
          ) : transactions.length === 0 ? (
            <div className="px-4 py-8 text-center text-sm text-muted-foreground">
              No transactions found for this filter.
            </div>
          ) : (
            <table className="w-full min-w-[860px] table-fixed text-sm">
              <colgroup>
                <col className="w-[104px]" />
                <col />
                <col className="w-[200px]" />
                <col className="w-[130px]" />
                <col className="w-[96px]" />
              </colgroup>
              <thead>
                <tr className="border-b border-border/50 bg-muted/40 text-[11px] font-medium text-muted-foreground">
                  <th
                    className="cursor-pointer select-none px-4 py-2.5 text-left transition-colors hover:text-foreground"
                    onClick={() => handleSortClick('date')}
                  >
                    <span className="flex items-center gap-1">
                      Date
                      {sortBy === 'date'
                        ? sortDir === 'desc'
                          ? <ArrowDown className="h-3 w-3 text-primary" />
                          : <ArrowUp className="h-3 w-3 text-primary" />
                        : <ArrowUpDown className="h-3 w-3 opacity-30" />}
                    </span>
                  </th>
                  <th className="px-4 py-2.5 text-left">Transaction</th>
                  <th className="px-4 py-2.5 text-left">Match</th>
                  <th
                    className="cursor-pointer select-none px-4 py-2.5 text-right transition-colors hover:text-foreground"
                    onClick={() => handleSortClick('amount')}
                  >
                    <span className="flex items-center justify-end gap-1">
                      {sortBy === 'amount'
                        ? sortDir === 'desc'
                          ? <ArrowDown className="h-3 w-3 text-primary" />
                          : <ArrowUp className="h-3 w-3 text-primary" />
                        : <ArrowUpDown className="h-3 w-3 opacity-30" />}
                      Amount
                    </span>
                  </th>
                  <th className="px-4 py-2.5 text-right">Actions</th>
                </tr>
              </thead>
              <tbody className="divide-y divide-border/30">
                {transactions.map(tx => {
                  const status = transactionStatus(tx);
                  const busy = actionId?.endsWith(`:${tx.id}`);
                  return (
                    <tr key={tx.id} className="hover:bg-muted/20">
                      <td className="px-4 py-3 text-xs text-muted-foreground whitespace-nowrap">
                        {transactionDate(tx)}
                      </td>
                      <td className="max-w-0 px-4 py-3">
                        <div className="min-w-0">
                          <p className="truncate text-sm font-medium">{transactionTitle(tx)}</p>
                          <p className="mt-0.5 truncate text-xs text-muted-foreground">
                            {[tx.description, tx.account_name, tx.source_label].filter(Boolean).join(' · ') || '—'}
                          </p>
                        </div>
                      </td>
                      <td className="px-4 py-3">
                        <div className="flex flex-col gap-1.5">
                          <div className="flex items-center gap-1.5">
                            <TransactionStatusBadge tx={tx} />
                            {tx.pending ? (
                              <span className="inline-flex items-center gap-1 rounded-full border border-sky-500/30 bg-sky-500/10 px-2 py-0.5 text-[10px] font-semibold uppercase text-sky-600 dark:text-sky-400">
                                <Clock className="h-2.5 w-2.5" />
                                Pending
                              </span>
                            ) : null}
                          </div>
                          {tx.matched_bill_name ? (
                            <span className="truncate text-xs text-foreground">{tx.matched_bill_name as string}</span>
                          ) : (tx.advisory_filter as { confidence?: string } | undefined)?.confidence === 'high' ? (
                            <span className="text-xs text-muted-foreground italic">Probably not a bill</span>
                          ) : (
                            <span className="text-xs text-muted-foreground">No bill linked</span>
                          )}
                        </div>
                      </td>
                      <td className={cn(
                        'px-4 py-3 text-right font-semibold tabular-nums whitespace-nowrap',
                        Number(tx.amount) < 0 ? 'text-destructive' : 'text-emerald-600',
                      )}>
                        {formatTransactionAmount(tx.amount, tx.currency ?? undefined)}
                      </td>
                      <td className="px-3 py-3">
                        <div className="flex justify-end gap-1.5 whitespace-nowrap">
                          {status === 'ignored' ? (
                            <Button
                              size="icon"
                              variant="outline"
                              type="button"
                              disabled={busy}
                              onClick={() => runTransactionAction(tx, 'unignore')}
                              className="h-8 w-8 shrink-0"
                              aria-label="Unignore transaction"
                              title="Unignore transaction"
                            >
                              {busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Eye className="h-3.5 w-3.5" />}
                              <span className="sr-only">Unignore</span>
                            </Button>
                          ) : (
                            <>
                              {status === 'matched' ? (
                                <Button
                                  size="icon"
                                  variant="outline"
                                  type="button"
                                  disabled={busy}
                                  onClick={() => runTransactionAction(tx, 'unmatch')}
                                  className="h-8 w-8 shrink-0"
                                  aria-label="Unmatch transaction"
                                  title="Unmatch transaction"
                                >
                                  {busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Link2Off className="h-3.5 w-3.5" />}
                                  <span className="sr-only">Unmatch</span>
                                </Button>
                              ) : (
                                <Button
                                  size="icon"
                                  type="button"
                                  disabled={busy || billsLoading}
                                  onClick={() => openMatchDialog(tx)}
                                  className="h-8 w-8 shrink-0"
                                  aria-label="Match transaction"
                                  title="Match transaction"
                                >
                                  <Link2 className="h-3.5 w-3.5" />
                                  <span className="sr-only">Match</span>
                                </Button>
                              )}
                              <Button
                                size="icon"
                                variant="ghost"
                                type="button"
                                disabled={busy}
                                onClick={() => runTransactionAction(tx, 'ignore')}
                                className="h-8 w-8 shrink-0"
                                aria-label="Ignore transaction"
                                title="Ignore transaction"
                              >
                                {busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <EyeOff className="h-3.5 w-3.5" />}
                                <span className="sr-only">Ignore</span>
                              </Button>
                            </>
                          )}
                        </div>
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          )}
          {totalCount > 0 && (
            <div className="flex items-center justify-between border-t border-border/50 px-4 py-3 text-sm text-muted-foreground">
              <span>{totalCount} transaction{totalCount !== 1 ? 's' : ''}</span>
              {totalPages > 1 && (
                <div className="flex items-center gap-2">
                  <button
                    type="button"
                    onClick={() => changePage(page - 1)}
                    disabled={page === 1}
                    className="rounded border border-border/60 p-1 hover:bg-muted disabled:opacity-40 disabled:cursor-not-allowed"
                    aria-label="Previous page"
                  >
                    <ChevronLeft className="h-4 w-4" />
                  </button>
                  <span>Page {page} of {totalPages}</span>
                  <button
                    type="button"
                    onClick={() => changePage(page + 1)}
                    disabled={page === totalPages}
                    className="rounded border border-border/60 p-1 hover:bg-muted disabled:opacity-40 disabled:cursor-not-allowed"
                    aria-label="Next page"
                  >
                    <ChevronRight className="h-4 w-4" />
                  </button>
                </div>
              )}
            </div>
          )}
        </div>
      </div>

      <MatchBillDialog
        open={matchOpen}
        onOpenChange={setMatchOpen}
        transaction={matchTransaction}
        bills={bills}
        loading={actionId === `match:${matchTransaction?.id}`}
        onConfirm={confirmMatch}
        onCreateBill={openCreateBill}
      />

      {createBillSourceTx && (
        <BillModal
          key={`create-from-tx-${createBillSourceTx.tx.id}`}
          initialBill={createBillSourceTx.initialBill}
          categories={categories}
          onClose={() => setCreateBillSourceTx(null)}
          onSave={handleBillCreated}
        />
      )}
    </SectionCard>
  );
}
