import { useActionState, useEffect, useState } from 'react';
import { ChevronDown, Copy, Layers, Link2, Link2Off, Loader2, Pencil, Plus, RefreshCw, Trash2 } from 'lucide-react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
  Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter,
} from '@/components/ui/dialog';
import { Checkbox } from '@/components/ui/checkbox';
import {
  AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
  AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import {
  Select, SelectTrigger, SelectValue, SelectContent, SelectItem,
} from '@/components/ui/select';
import { api } from '@/api';
import { cn, fmt, fmtDate, todayStr } from '@/lib/utils';
import BillMerchantRules from '@/components/BillMerchantRules';
import {
  BILLING_SCHEDULE_OPTIONS,
  billingCycleForSchedule,
  defaultCycleDayForSchedule,
  scheduleValue,
} from '@/lib/billingSchedule';

function getOrdinalSuffix(day) {
  if (day > 3 && day < 21) return 'th';
  switch (day % 10) {
    case 1: return 'st';
    case 2: return 'nd';
    case 3: return 'rd';
    default: return 'th';
  }
}

// Radix Select crashes on empty string value
const CAT_NONE = 'none';
const PAYMENT_METHODS = [
  ['manual', 'Manual'],
  ['bank', 'Bank Transfer'],
  ['card', 'Card'],
  ['autopay', 'Autopay'],
  ['check', 'Check'],
  ['cash', 'Cash'],
];

const DEBT_KEYWORDS = ['credit', 'loan', 'mortgage', 'housing', 'debt'];
const SNOWBALL_KEYWORDS = ['credit', 'loan', 'debt', 'mortgage', 'housing'];
const SUBSCRIPTION_TYPES = [
  ['streaming', 'Streaming'],
  ['software', 'Software'],
  ['cloud', 'Cloud'],
  ['music', 'Music'],
  ['news', 'News'],
  ['fitness', 'Fitness'],
  ['gaming', 'Gaming'],
  ['utilities', 'Utilities'],
  ['insurance', 'Insurance'],
  ['other', 'Other'],
];

function fmtTransactionAmount(amount, currency = 'USD') {
  const cents = Number(amount || 0);
  const value = Math.abs(cents) / 100;
  const sign = cents < 0 ? '-' : '+';
  return `${sign}${new Intl.NumberFormat(undefined, {
    style: 'currency',
    currency: currency || 'USD',
  }).format(value)}`;
}

function transactionDate(tx) {
  return tx?.posted_date || String(tx?.transacted_at || '').slice(0, 10) || null;
}

function transactionTitle(tx) {
  return tx?.payee || tx?.description || tx?.memo || 'Transaction';
}

function isTransactionLinkedPayment(payment) {
  return payment?.payment_source === 'transaction_match' || payment?.transaction_id != null;
}

function isHistoryOnlyPayment(payment) {
  return !!payment?.accounting_excluded;
}

function paymentSourceLabel(source) {
  const labels = {
    manual: 'Manual',
    file_import: 'File import',
    provider_sync: 'Sync',
    transaction_match: 'Transaction',
    auto_match: 'SimpleFIN',
  };
  return labels[source] || source || 'Manual';
}

function paymentSourceTone(source) {
  const tones = {
    manual: 'border-emerald-500/25 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400',
    file_import: 'border-sky-500/25 bg-sky-500/10 text-sky-600 dark:text-sky-400',
    provider_sync: 'border-violet-500/25 bg-violet-500/10 text-violet-600 dark:text-violet-400',
    transaction_match: 'border-primary/25 bg-primary/10 text-primary',
    auto_match: 'border-violet-500/25 bg-violet-500/10 text-violet-600 dark:text-violet-400',
  };
  return tones[source] || tones.manual;
}

function isDebtCat(categories, catId) {
  if (!catId || catId === CAT_NONE) return false;
  const cat = categories.find(c => String(c.id) === catId);
  return cat ? DEBT_KEYWORDS.some(kw => cat.name.toLowerCase().includes(kw)) : false;
}
function isSnowballCat(categories, catId) {
  if (!catId || catId === CAT_NONE) return false;
  const cat = categories.find(c => String(c.id) === catId);
  return cat ? SNOWBALL_KEYWORDS.some(kw => cat.name.toLowerCase().includes(kw)) : false;
}

function normalizePayee(s) {
  return (s || '').toLowerCase().replace(/[^a-z0-9]/g, '');
}

function isSimilarPayee(a, b) {
  const na = normalizePayee(a);
  const nb = normalizePayee(b);
  const minLen = Math.min(na.length, nb.length);
  if (minLen < 3) return false;
  return na.startsWith(nb) || nb.startsWith(na);
}

export default function BillModal({ bill, initialBill, categories, onClose, onSave, onDuplicate }) {
  const isNew = !bill;
  const sourceBill = bill || initialBill || null;

  const [name,           setName]         = useState(sourceBill?.name            || '');
  const [categoryId,     setCategoryId]   = useState(sourceBill?.category_id ? String(sourceBill.category_id) : CAT_NONE);
  const [dueDay,         setDueDay]       = useState(String(sourceBill?.due_day   || ''));
  const [expectedAmount, setExpected]     = useState(String(sourceBill?.expected_amount || ''));
  const [interestRate,   setInterestRate] = useState(sourceBill?.interest_rate == null ? '' : String(sourceBill.interest_rate));
  const initialCycleType = scheduleValue(sourceBill || {});
  const [cycleType,      setCycleType]    = useState(initialCycleType);
  const [cycleDay,       setCycleDay]     = useState(sourceBill?.cycle_day || defaultCycleDayForSchedule(initialCycleType));
  const [autopay,        setAutopay]      = useState(!!sourceBill?.autopay_enabled);
  const [autodraftStatus, setAutodraftStatus] = useState(sourceBill?.autodraft_status || (sourceBill?.autopay_enabled ? 'assumed_paid' : 'none'));
  const [autoMarkPaid,   setAutoMarkPaid] = useState(!!sourceBill?.auto_mark_paid);
  const [isSubscription, setIsSubscription] = useState(!!sourceBill?.is_subscription);
  const [subscriptionType, setSubscriptionType] = useState(sourceBill?.subscription_type || 'other');
  const [reminderDaysBefore, setReminderDaysBefore] = useState(String(sourceBill?.reminder_days_before ?? 3));
  const [has2fa,         setHas2fa]       = useState(!!sourceBill?.has_2fa);
  const [website,        setWebsite]      = useState(sourceBill?.website          || '');
  const [username,       setUsername]     = useState(sourceBill?.username          || '');
  const [accountInfo,    setAccountInfo]  = useState(sourceBill?.account_info     || '');
  const [notes,          setNotes]        = useState(sourceBill?.notes            || '');
  const [currentBalance, setCurrentBalance] = useState(sourceBill?.current_balance == null ? '' : String(sourceBill.current_balance));
  const [minimumPayment, setMinimumPayment] = useState(sourceBill?.minimum_payment == null ? '' : String(sourceBill.minimum_payment));
  const [snowballInclude, setSnowballInclude] = useState(!!sourceBill?.snowball_include);
  const [snowballExempt, setSnowballExempt] = useState(!!sourceBill?.snowball_exempt);
  const [syncingPayments, setSyncingPayments] = useState(false);
  // Track whether rules exist locally so the Sync button appears immediately
  // after the first rule is added without waiting for sourceBill to refetch.
  const [localHasRules, setLocalHasRules] = useState(!!sourceBill?.has_merchant_rule);
  const [showDebtSection, setShowDebtSection] = useState(
    () => isDebtCat(categories, sourceBill?.category_id ? String(sourceBill.category_id) : CAT_NONE)
      || !!sourceBill?.snowball_include
      || !!sourceBill?.snowball_exempt
      || sourceBill?.current_balance != null
      || sourceBill?.minimum_payment != null
  );
  const [saveTemplate, setSaveTemplate] = useState(false);
  const [templateName, setTemplateName] = useState('');
  const [errors,         setErrors]       = useState({});
  const [payments,       setPayments]     = useState([]);
  const [paymentsLoading, setPaymentsLoading] = useState(false);
  const [linkedTransactions, setLinkedTransactions] = useState([]);
  const [linkedTransactionsLoading, setLinkedTransactionsLoading] = useState(false);
  const [transactionBusyId, setTransactionBusyId] = useState(null);
  const [paymentBusy,    setPaymentBusy]  = useState(false);
  const [paymentFormOpen, setPaymentFormOpen] = useState(false);
  const [editingPayment, setEditingPayment] = useState(null);
  const [deletePaymentTarget, setDeletePaymentTarget] = useState(null);
  const [paymentAmount,  setPaymentAmount] = useState('');
  const [paymentDate,    setPaymentDate]   = useState(todayStr());
  const [paymentMethod,  setPaymentMethod] = useState('manual');
  const [paymentNotes,   setPaymentNotes]  = useState('');
  const [localVerifiedAt, setLocalVerifiedAt] = useState(
    bill?.autopay_verified_at ? new Date(bill.autopay_verified_at) : null
  );

  // Deactivate dialog state
  const [deactivateOpen,   setDeactivateOpen]   = useState(false);
  const [deactivateReason, setDeactivateReason] = useState('');

  // Unmatch dialog state
  const [unmatchTarget,      setUnmatchTarget]      = useState(null);
  const [unmatchConfirmOpen, setUnmatchConfirmOpen] = useState(false);
  const [bulkUnmatch,        setBulkUnmatch]        = useState(null);
  const [bulkBusy,           setBulkBusy]           = useState(false);

  const isDebtCategory = isDebtCat(categories, categoryId);
  const isSnowballCategory = isSnowballCat(categories, categoryId);
  const showOnSnowball = snowballInclude || (isSnowballCategory && !snowballExempt);
  const canAutoMarkPaid = autopay && autodraftStatus === 'assumed_paid';

  async function loadPayments() {
    if (isNew || !bill?.id) return;
    setPaymentsLoading(true);
    try {
      const data = await api.billPayments(bill.id, 1, 100);
      setPayments(data.payments || []);
    } catch (err) {
      toast.error(err.message || 'Failed to load payment history.');
    } finally {
      setPaymentsLoading(false);
    }
  }

  async function loadLinkedTransactions() {
    if (isNew || !bill?.id) return;
    setLinkedTransactionsLoading(true);
    try {
      const data = await api.billTransactions(bill.id);
      setLinkedTransactions(data.transactions || []);
    } catch (err) {
      toast.error(err.message || 'Failed to load linked transactions.');
    } finally {
      setLinkedTransactionsLoading(false);
    }
  }

  useEffect(() => {
    loadPayments();
    loadLinkedTransactions();
  }, [bill?.id]);

  const validateName = (val) => {
    if (!val || val.trim() === '') return 'Name is required';
    if (val.trim().length < 2) return 'Name must be at least 2 characters';
    return '';
  };

  const validateDueDay = (val) => {
    if (!val || val.trim() === '') return 'Due day is required';
    const num = parseInt(val, 10);
    if (isNaN(num) || num < 1 || num > 31) return 'Due day must be between 1 and 31';
    return '';
  };

  const validateExpectedAmount = (val) => {
    if (val === '' || val === null) return '';
    const num = parseFloat(val);
    if (isNaN(num) || num < 0) return 'Amount must be a positive number';
    return '';
  };

  const validateInterestRate = (val) => {
    if (val === '' || val === null) return '';
    const num = parseFloat(val);
    if (isNaN(num)) return 'Invalid number';
    if (num < 0 || num > 100) return 'Interest rate must be between 0 and 100';
    return '';
  };

  const validateCurrentBalance = (val) => {
    if (val === '' || val === null) return '';
    const num = parseFloat(val);
    if (isNaN(num) || num < 0) return 'Balance must be a non-negative number';
    return '';
  };

  const validateMinimumPayment = (val) => {
    if (val === '' || val === null) return '';
    const num = parseFloat(val);
    if (isNaN(num) || num < 0) return 'Min payment must be a non-negative number';
    return '';
  };

  const validateForm = () => {
    const newErrors = {
      name: validateName(name),
      dueDay: validateDueDay(dueDay),
      expectedAmount: validateExpectedAmount(expectedAmount),
      interestRate: validateInterestRate(interestRate),
      currentBalance: validateCurrentBalance(currentBalance),
      minimumPayment: validateMinimumPayment(minimumPayment),
    };
    setErrors(newErrors);
    return Object.values(newErrors).every(err => err === '');
  };

  const handleBlur = (field, validator) => {
    setErrors(prev => ({ ...prev, [field]: validator(
      field === 'name' ? name :
      field === 'dueDay' ? dueDay :
      field === 'expectedAmount' ? expectedAmount :
      interestRate
    )}));
  };

  const handleCategoryChange = (val) => {
    setCategoryId(val);
    if (isDebtCat(categories, val)) {
      setShowDebtSection(true);
    } else {
      setSnowballExempt(false);
    }
  };

  const handleSnowballVisibilityChange = (checked) => {
    if (checked) {
      setSnowballExempt(false);
      setSnowballInclude(!isSnowballCategory);
    } else {
      setSnowballInclude(false);
      setSnowballExempt(isSnowballCategory);
    }
  };

  async function handleVerifyAutopay() {
    if (!bill?.id) return;
    try {
      const res = await api.verifyAutopay(bill.id);
      setLocalVerifiedAt(new Date(res.autopay_verified_at));
      toast.success('Autopay marked as verified.');
    } catch (err) {
      toast.error(err.message);
    }
  }

  const handleAutopayChange = (checked) => {
    setAutopay(checked);
    if (checked) {
      setAutodraftStatus(prev => (prev && prev !== 'none' ? prev : 'assumed_paid'));
    } else {
      setAutodraftStatus('none');
      setAutoMarkPaid(false);
    }
  };

  const handleCycleTypeChange = (value) => {
    setCycleType(value);
    setCycleDay(defaultCycleDayForSchedule(value));
  };

  function resetPaymentForm() {
    setPaymentAmount('');
    setPaymentDate(todayStr());
    setPaymentMethod('manual');
    setPaymentNotes('');
    setEditingPayment(null);
  }

  function startAddPayment() {
    resetPaymentForm();
    setPaymentFormOpen(true);
  }

  function startEditPayment(payment) {
    setEditingPayment(payment);
    setPaymentAmount(String(payment.amount ?? ''));
    setPaymentDate(payment.paid_date || todayStr());
    setPaymentMethod(payment.method || 'manual');
    setPaymentNotes(payment.notes || '');
    setPaymentFormOpen(true);
  }

  async function handlePaymentSubmit(e) {
    e.preventDefault();
    const parsedAmount = parseFloat(paymentAmount);
    if (!Number.isFinite(parsedAmount) || parsedAmount <= 0) {
      toast.error('Enter a positive payment amount.');
      return;
    }
    if (!paymentDate) {
      toast.error('Choose a paid date.');
      return;
    }

    setPaymentBusy(true);
    try {
      const payload = {
        amount: parsedAmount,
        paid_date: paymentDate,
        method: paymentMethod,
        notes: paymentNotes || null,
        payment_source: 'manual',
      };
      if (editingPayment) {
        await api.updatePayment(editingPayment.id, payload);
        toast.success('Payment updated');
      } else {
        await api.createPayment({ ...payload, bill_id: bill.id });
        toast.success('Payment added');
      }
      resetPaymentForm();
      setPaymentFormOpen(false);
      await loadPayments();
      onSave?.();
    } catch (err) {
      toast.error(err.message || 'Payment could not be saved.');
    } finally {
      setPaymentBusy(false);
    }
  }

  async function handleDeletePayment() {
    if (!deletePaymentTarget) return;
    const payment = deletePaymentTarget;
    setPaymentBusy(true);
    try {
      await api.deletePayment(payment.id);
      setDeletePaymentTarget(null);
      toast.success('Payment moved to recovery.', {
        action: {
          label: 'Undo',
          onClick: async () => {
            try {
              await api.restorePayment(payment.id);
              toast.success('Payment restored');
              await loadPayments();
              onSave?.();
            } catch (err) {
              toast.error(err.message || 'Failed to restore payment.');
            }
          },
        },
      });
      if (editingPayment?.id === payment.id) {
        resetPaymentForm();
        setPaymentFormOpen(false);
      }
      await loadPayments();
      onSave?.();
    } catch (err) {
      toast.error(err.message || 'Payment could not be removed.');
    } finally {
      setPaymentBusy(false);
    }
  }

  function openUnmatch(transaction) {
    setUnmatchTarget(transaction);
    setBulkUnmatch(null);
    setUnmatchConfirmOpen(false);
  }

  function closeUnmatch() {
    setUnmatchTarget(null);
    setBulkUnmatch(null);
    setUnmatchConfirmOpen(false);
  }

  async function handleSingleUnmatch() {
    if (!unmatchTarget?.id) return;
    setTransactionBusyId(unmatchTarget.id);
    try {
      await api.unmatchTransaction(unmatchTarget.id);
      toast.success('Transaction unmatched');
      closeUnmatch();
      await Promise.all([loadPayments(), loadLinkedTransactions()]);
      onSave?.();
    } catch (err) {
      toast.error(err.message || 'Transaction could not be unmatched.');
    } finally {
      setTransactionBusyId(null);
    }
  }

  async function handleOpenBulkUnmatch() {
    if (!unmatchTarget || !bill?.id) return;
    const targetPayee = transactionTitle(unmatchTarget);
    const similar = linkedTransactions.filter(tx =>
      isSimilarPayee(transactionTitle(tx), targetPayee)
    );
    if (!similar.find(tx => tx.id === unmatchTarget.id)) {
      similar.unshift(unmatchTarget);
    }
    let rules = [];
    try {
      const ruleData = await api.billMerchantRules(bill.id);
      rules = (ruleData || []).filter(r => isSimilarPayee(r.merchant, targetPayee));
    } catch {
      // ignore — rules are optional
    }
    setBulkUnmatch({
      similar,
      rules,
      checkedIds: new Set(similar.map(tx => tx.id)),
      removeRuleId: null,
    });
  }

  async function handleBulkConfirm() {
    if (!bulkUnmatch) return;
    const { similar, checkedIds, removeRuleId } = bulkUnmatch;
    const matches = similar
      .filter(tx => checkedIds.has(tx.id) && tx.linked_payment)
      .map(tx => ({
        transaction_id: tx.id,
        payment_id: tx.linked_payment.id,
        payment_source: tx.linked_payment.payment_source,
      }));
    if (matches.length === 0) { closeUnmatch(); return; }
    setBulkBusy(true);
    try {
      await api.unmatchTransactionBulk(matches);
      if (removeRuleId) {
        try {
          await api.deleteMerchantRule(bill.id, removeRuleId);
        } catch {
          toast.error('Transactions unmatched, but could not remove merchant rule.');
        }
      }
      toast.success(`${matches.length} transaction${matches.length !== 1 ? 's' : ''} unmatched`);
      closeUnmatch();
      await Promise.all([loadPayments(), loadLinkedTransactions()]);
      onSave?.();
    } catch (err) {
      toast.error(err.message || 'Could not unmatch transactions.');
    } finally {
      setBulkBusy(false);
    }
  }

  const [, submitAction, isPending] = useActionState(async () => {
    if (!validateForm()) {
      toast.error('Please fix the form errors before saving.');
      return;
    }

    const parsedDueDay = Number(dueDay);
    if (!Number.isInteger(parsedDueDay) || parsedDueDay < 1 || parsedDueDay > 31) {
      toast.error('Due day must be a whole number from 1 to 31.');
      return;
    }

    const trimmedInterestRate = interestRate.trim();
    const parsedInterestRate = trimmedInterestRate === '' ? null : Number(trimmedInterestRate);
    if (parsedInterestRate !== null && (!Number.isFinite(parsedInterestRate) || parsedInterestRate < 0 || parsedInterestRate > 100)) {
      toast.error('Interest rate must be blank or a number from 0 to 100.');
      return;
    }

    const data = {
      source_bill_id:    sourceBill?.source_bill_id,
      name:              name.trim(),
      category_id:       categoryId === CAT_NONE ? null : parseInt(categoryId, 10),
      due_day:           parsedDueDay,
      override_due_date: sourceBill?.override_due_date,
      expected_amount:   parseFloat(expectedAmount) || 0,
      interest_rate:     parsedInterestRate,
      billing_cycle:     billingCycleForSchedule(cycleType),
      cycle_type:        cycleType,
      cycle_day:         cycleDay,
      autopay_enabled:   autopay,
      autodraft_status:  autopay ? autodraftStatus : 'none',
      auto_mark_paid:    canAutoMarkPaid && autoMarkPaid,
      is_subscription:   isSubscription,
      subscription_type: isSubscription ? subscriptionType : null,
      reminder_days_before: isSubscription ? parseInt(reminderDaysBefore || '3', 10) : 3,
      subscription_source: sourceBill?.subscription_source || 'manual',
      subscription_detected_at: sourceBill?.subscription_detected_at,
      has_2fa:           has2fa,
      website:           website     || null,
      username:          username    || null,
      account_info:      accountInfo || null,
      notes:             notes       || null,
      history_visibility: sourceBill?.history_visibility,
      current_balance:   currentBalance === '' ? null : parseFloat(currentBalance),
      minimum_payment:   minimumPayment === '' ? null : parseFloat(minimumPayment),
      snowball_order:    sourceBill?.snowball_order,
      snowball_include:  snowballInclude,
      snowball_exempt:   snowballExempt,
    };
    try {
      let savedBill;
      if (isNew) {
        if (data.source_bill_id) {
          savedBill = await api.duplicateBill(data.source_bill_id, data);
        } else {
          savedBill = await api.createBill(data);
        }
        toast.success('Bill added');
      } else {
        savedBill = await api.updateBill(bill.id, data);
        toast.success('Bill updated');
      }
      if (saveTemplate) {
        const safeTemplateName = templateName.trim() || data.name;
        await api.saveBillTemplate({ name: safeTemplateName, data });
        toast.success('Template saved');
      }
      onSave(savedBill);
      onClose();
    } catch (err) {
      toast.error(err.message);
    }
  }, null);

  async function handleDeactivate() {
    if (!bill?.id) return;
    try {
      const payload = { active: bill.active ? 0 : 1 };
      if (bill.active && deactivateReason) payload.inactive_reason = deactivateReason;
      await api.updateBill(bill.id, payload);
      toast.success(bill.active ? 'Bill deactivated' : 'Bill reactivated');
      onSave?.();
      onClose();
    } catch (err) {
      toast.error(err.message);
    }
  }

  const inp = 'bg-background/50 border-border/60 h-9 text-sm w-full';

  return (
    <Dialog open onOpenChange={v => { if (!v) onClose(); }}>
      <DialogContent className="max-h-[92svh] overflow-y-auto border-border/60 bg-card/95 backdrop-blur-xl sm:max-w-2xl">
        <DialogHeader>
          <DialogTitle className="text-base font-semibold tracking-tight">
            {isNew ? 'Add Bill' : 'Edit Bill'}
          </DialogTitle>
        </DialogHeader>

        <form id="bill-modal-form" action={submitAction}>
          <div className="grid gap-x-5 gap-y-4 py-2 sm:grid-cols-2">

            {/* Name */}
            <div className="col-span-2 space-y-1.5">
              <Label className="text-xs uppercase tracking-wider text-muted-foreground">Name *</Label>
              <Input
                className={cn(inp, errors.name && 'border-red-500 focus-visible:ring-red-500')}
                placeholder="e.g. Electricity"
                value={name}
                onChange={e => {
                  setName(e.target.value);
                  setTimeout(() => setErrors(prev => ({ ...prev, name: validateName(e.target.value) })), 300);
                }}
                onBlur={() => handleBlur('name', validateName)}
                required
              />
              {errors.name && (
                <span className="text-[10px] text-red-500 font-medium">{errors.name}</span>
              )}
            </div>

            {/* Category */}
            <div className="space-y-1.5">
              <Label className="text-xs uppercase tracking-wider text-muted-foreground">Category</Label>
              <Select value={categoryId} onValueChange={handleCategoryChange}>
                <SelectTrigger className={cn(inp, 'w-full')}>
                  <SelectValue placeholder="— none —" />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value={CAT_NONE}>— none —</SelectItem>
                  {categories.map(c => (
                    <SelectItem key={c.id} value={String(c.id)}>{c.name}</SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>

            {/* Due Day */}
            <div className="space-y-1.5">
              <Label className="text-xs uppercase tracking-wider text-muted-foreground">Due day of month *</Label>
              <Input
                className={cn(inp, errors.dueDay && 'border-red-500 focus-visible:ring-red-500')}
                type="number" min="1" max="31" required
                value={dueDay}
                onChange={e => {
                  setDueDay(e.target.value);
                  setTimeout(() => setErrors(prev => ({ ...prev, dueDay: validateDueDay(e.target.value) })), 300);
                }}
                onBlur={() => handleBlur('dueDay', validateDueDay)}
              />
              {errors.dueDay && (
                <span className="text-[10px] text-red-500 font-medium">{errors.dueDay}</span>
              )}
              <p className="text-[10px] text-muted-foreground/70">
                Enter the day of the month this bill is due.
              </p>
            </div>

            {/* Expected Amount */}
            <div className="space-y-1.5">
              <Label className="text-xs uppercase tracking-wider text-muted-foreground">Expected Amount ($)</Label>
              <Input
                className={cn(inp, 'font-mono', errors.expectedAmount && 'border-red-500 focus-visible:ring-red-500')}
                type="number" min="0" step="0.01" placeholder="0.00"
                value={expectedAmount}
                onChange={e => {
                  setExpected(e.target.value);
                  setTimeout(() => setErrors(prev => ({ ...prev, expectedAmount: validateExpectedAmount(e.target.value) })), 300);
                }}
                onBlur={() => handleBlur('expectedAmount', validateExpectedAmount)}
              />
              {errors.expectedAmount && (
                <span className="text-[10px] text-red-500 font-medium">{errors.expectedAmount}</span>
              )}
            </div>

            {/* Billing Schedule */}
            <div className="space-y-1.5">
              <Label className="text-xs uppercase tracking-wider text-muted-foreground">Billing Schedule</Label>
              <Select value={cycleType} onValueChange={handleCycleTypeChange}>
                <SelectTrigger className={cn(inp, 'w-full')}>
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  {BILLING_SCHEDULE_OPTIONS.map(([value, label]) => (
                    <SelectItem key={value} value={value}>{label}</SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>

            {/* Cycle Day */}
            <div className="space-y-1.5">
              <Label className="text-xs uppercase tracking-wider text-muted-foreground">Cycle Day</Label>
              {cycleType === 'monthly' ? (
                <Select value={cycleDay} onValueChange={setCycleDay}>
                  <SelectTrigger className={cn(inp, 'w-full')}>
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    {[...Array(31)].map((_, i) => (
                      <SelectItem key={i+1} value={String(i+1)}>{i+1}{getOrdinalSuffix(i+1)}</SelectItem>
                    ))}
                  </SelectContent>
                </Select>
              ) : cycleType === 'weekly' || cycleType === 'biweekly' ? (
                <Select value={cycleDay} onValueChange={setCycleDay}>
                  <SelectTrigger className={cn(inp, 'w-full')}>
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectItem value="monday">Monday</SelectItem>
                    <SelectItem value="tuesday">Tuesday</SelectItem>
                    <SelectItem value="wednesday">Wednesday</SelectItem>
                    <SelectItem value="thursday">Thursday</SelectItem>
                    <SelectItem value="friday">Friday</SelectItem>
                    <SelectItem value="saturday">Saturday</SelectItem>
                    <SelectItem value="sunday">Sunday</SelectItem>
                  </SelectContent>
                </Select>
              ) : (
                <Select value={cycleDay} onValueChange={setCycleDay}>
                  <SelectTrigger className={cn(inp, 'w-full')}>
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    {[
                      'January', 'February', 'March', 'April', 'May', 'June',
                      'July', 'August', 'September', 'October', 'November', 'December',
                    ].map((label, index) => (
                      <SelectItem key={label} value={String(index + 1)}>{label}</SelectItem>
                    ))}
                  </SelectContent>
                </Select>
              )}
              <p className="text-[10px] text-muted-foreground/70">
                {cycleType === 'monthly' ? 'Day of the month' :
                 cycleType === 'weekly' || cycleType === 'biweekly' ? 'Day of the week' :
                 cycleType === 'quarterly' ? 'First month of the quarterly cycle' :
                 'Month due each year'}
              </p>
            </div>

            {/* Subscription Details */}
            <div className="col-span-2 rounded-lg border border-border/60 bg-muted/20 p-3">
              <label className="flex items-start gap-2.5 cursor-pointer group">
                <input
                  type="checkbox"
                  checked={isSubscription}
                  onChange={e => setIsSubscription(e.target.checked)}
                  className="mt-0.5 h-4 w-4 rounded border-border accent-primary"
                />
                <span>
                  <span className="block text-sm font-medium text-muted-foreground group-hover:text-foreground transition-colors">
                    Track as subscription
                  </span>
                  <span className="mt-0.5 block text-[10px] text-muted-foreground/70">
                    Adds this bill to the subscription view with monthly equivalent cost and SimpleFIN recommendation matching.
                  </span>
                </span>
              </label>

              {isSubscription && (
                <div className="mt-3 grid gap-3 border-t border-border/40 pt-3 sm:grid-cols-2">
                  <div className="space-y-1.5">
                    <Label className="text-xs uppercase tracking-wider text-muted-foreground">Subscription Type</Label>
                    <Select value={subscriptionType} onValueChange={setSubscriptionType}>
                      <SelectTrigger className={cn(inp, 'w-full')}>
                        <SelectValue />
                      </SelectTrigger>
                      <SelectContent>
                        {SUBSCRIPTION_TYPES.map(([value, label]) => (
                          <SelectItem key={value} value={value}>{label}</SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                  </div>
                  <div className="space-y-1.5">
                    <Label className="text-xs uppercase tracking-wider text-muted-foreground">Reminder Days</Label>
                    <Input
                      className={cn(inp, 'tracker-number')}
                      type="number"
                      min="0"
                      max="30"
                      value={reminderDaysBefore}
                      onChange={e => setReminderDaysBefore(e.target.value)}
                    />
                    <p className="text-[10px] text-muted-foreground/70">0-30 days before renewal.</p>
                  </div>
                  {/* Bank sync button moved to the Transactions tab → Bank Matching Rules section */}
                </div>
              )}
            </div>

            {/* Debt / Snowball Details — collapsible */}
            <div className="col-span-2">
              <button
                type="button"
                onClick={() => setShowDebtSection(s => !s)}
                className="flex items-center gap-2 text-xs uppercase tracking-wider text-muted-foreground hover:text-foreground transition-colors w-full text-left py-1"
              >
                <ChevronDown
                  className={cn('h-3.5 w-3.5 transition-transform duration-150', !showDebtSection && '-rotate-90')}
                />
                Debt / Snowball Details
                {isSnowballCategory && (
                  <span className="ml-1 text-[9px] text-emerald-400 font-semibold tracking-normal normal-case">
                    · snowball auto-detected
                  </span>
                )}
                {!showOnSnowball && isSnowballCategory && (
                  <span className="ml-1 text-[9px] text-amber-400 font-semibold tracking-normal normal-case">
                    · exempt
                  </span>
                )}
              </button>

              {showDebtSection && (
                <div className="grid sm:grid-cols-2 gap-x-5 gap-y-4 mt-3 pt-3 border-t border-border/40">

                  {/* Interest Rate */}
                  <div className="space-y-1.5">
                    <Label className="text-xs uppercase tracking-wider text-muted-foreground">Interest rate (APR %)</Label>
                    <Input
                      className={cn(inp, 'font-mono', errors.interestRate && 'border-red-500 focus-visible:ring-red-500')}
                      type="number" min="0" max="100" step="0.01" placeholder="Optional"
                      value={interestRate}
                      onChange={e => {
                        setInterestRate(e.target.value);
                        setTimeout(() => setErrors(prev => ({ ...prev, interestRate: validateInterestRate(e.target.value) })), 300);
                      }}
                      onBlur={() => handleBlur('interestRate', validateInterestRate)}
                    />
                    {errors.interestRate && (
                      <span className="text-[10px] text-red-500 font-medium">{errors.interestRate}</span>
                    )}
                    <p className="text-[10px] text-muted-foreground/70">Enter 29.99 for 29.99%.</p>
                  </div>

                  {/* Current Balance */}
                  <div className="space-y-1.5">
                    <Label className="text-xs uppercase tracking-wider text-muted-foreground">Current Balance ($)</Label>
                    <Input
                      className={cn(inp, 'font-mono', errors.currentBalance && 'border-red-500 focus-visible:ring-red-500')}
                      type="number" min="0" step="0.01" placeholder="Optional"
                      value={currentBalance}
                      onChange={e => {
                        setCurrentBalance(e.target.value);
                        setTimeout(() => setErrors(prev => ({ ...prev, currentBalance: validateCurrentBalance(e.target.value) })), 300);
                      }}
                      onBlur={() => setErrors(prev => ({ ...prev, currentBalance: validateCurrentBalance(currentBalance) }))}
                    />
                    {errors.currentBalance && (
                      <span className="text-[10px] text-red-500 font-medium">{errors.currentBalance}</span>
                    )}
                    <p className="text-[10px] text-muted-foreground/70">Outstanding debt balance.</p>
                  </div>

                  {/* Minimum Payment */}
                  <div className="space-y-1.5">
                    <Label className="text-xs uppercase tracking-wider text-muted-foreground">Minimum Payment ($)</Label>
                    <Input
                      className={cn(inp, 'font-mono', errors.minimumPayment && 'border-red-500 focus-visible:ring-red-500')}
                      type="number" min="0" step="0.01" placeholder="Optional"
                      value={minimumPayment}
                      onChange={e => {
                        setMinimumPayment(e.target.value);
                        setTimeout(() => setErrors(prev => ({ ...prev, minimumPayment: validateMinimumPayment(e.target.value) })), 300);
                      }}
                      onBlur={() => setErrors(prev => ({ ...prev, minimumPayment: validateMinimumPayment(minimumPayment) }))}
                    />
                    {errors.minimumPayment && (
                      <span className="text-[10px] text-red-500 font-medium">{errors.minimumPayment}</span>
                    )}
                    <p className="text-[10px] text-muted-foreground/70">Required minimum monthly payment.</p>
                  </div>

                  {/* Include in Snowball */}
                  <div className="flex flex-col justify-end pb-1 space-y-1">
                    <label className="flex items-center gap-2.5 cursor-pointer group">
                      <input
                        type="checkbox"
                        checked={showOnSnowball}
                        onChange={e => handleSnowballVisibilityChange(e.target.checked)}
                        className="h-4 w-4 rounded border-border accent-emerald-500"
                      />
                      <span className="text-sm text-muted-foreground group-hover:text-foreground transition-colors">
                        Show on Debt Snowball
                      </span>
                    </label>
                    <p className="text-[10px] text-muted-foreground/70 pl-6">
                      Uncheck to exempt an auto-detected snowball bill, or check to include this bill manually.
                    </p>
                  </div>

                </div>
              )}
            </div>

            {/* Website */}
            <div className="space-y-1.5">
              <Label className="text-xs uppercase tracking-wider text-muted-foreground">Website</Label>
              <Input
                className={inp}
                placeholder="https://…"
                value={website}
                onChange={e => setWebsite(e.target.value)}
              />
            </div>

            {/* Username */}
            <div className="space-y-1.5">
              <Label className="text-xs uppercase tracking-wider text-muted-foreground">Username / Email</Label>
              <Input
                className={inp}
                value={username}
                onChange={e => setUsername(e.target.value)}
              />
            </div>

            {/* Account Info */}
            <div className="space-y-1.5">
              <Label className="text-xs uppercase tracking-wider text-muted-foreground">Account Info</Label>
              <Input
                className={inp}
                placeholder="Last 4 digits, account #…"
                value={accountInfo}
                onChange={e => setAccountInfo(e.target.value)}
              />
            </div>

            {/* Checkboxes */}
            <div className="space-y-2.5 flex flex-col justify-end">
              <label className="flex items-center gap-2.5 cursor-pointer group">
                <input
                  type="checkbox"
                  checked={autopay}
                  onChange={e => handleAutopayChange(e.target.checked)}
                  className="h-4 w-4 rounded border-border accent-emerald-500"
                />
                <span className="text-sm text-muted-foreground group-hover:text-foreground transition-colors">
                  Autopay / Autodraft
                </span>
              </label>
              <label
                className={cn('flex items-center gap-2.5 group', canAutoMarkPaid ? 'cursor-pointer' : 'cursor-not-allowed opacity-50')}
                title={canAutoMarkPaid ? undefined : 'Auto-mark requires Autodraft Status set to Assumed paid'}
              >
                <input
                  type="checkbox"
                  checked={canAutoMarkPaid && autoMarkPaid}
                  onChange={e => setAutoMarkPaid(e.target.checked)}
                  disabled={!canAutoMarkPaid}
                  className="h-4 w-4 rounded border-border accent-sky-500"
                />
                <span className="text-sm text-muted-foreground group-hover:text-foreground transition-colors">
                  Auto-mark paid on due date
                </span>
              </label>
              <label className="flex items-center gap-2.5 cursor-pointer group">
                <input
                  type="checkbox"
                  checked={has2fa}
                  onChange={e => setHas2fa(e.target.checked)}
                  className="h-4 w-4 rounded border-border accent-violet-500"
                />
                <span className="text-sm text-muted-foreground group-hover:text-foreground transition-colors">
                  Has 2FA
                </span>
              </label>
            </div>

            <div className="space-y-1.5">
              <Label className="text-xs uppercase tracking-wider text-muted-foreground">Autodraft Status</Label>
              <Select value={autodraftStatus} onValueChange={setAutodraftStatus} disabled={!autopay}>
                <SelectTrigger className={cn(inp, !autopay && 'opacity-60')}>
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="none">None</SelectItem>
                  <SelectItem value="pending">Pending</SelectItem>
                  <SelectItem value="assumed_paid">Assumed paid</SelectItem>
                  <SelectItem value="confirmed">Confirmed</SelectItem>
                </SelectContent>
              </Select>
            </div>

            {/* Autopay trust indicator — edit mode only */}
            {!isNew && autopay && (() => {
              const stats = bill?.autopay_stats;
              const total = stats?.total ?? 0;
              const failures = stats?.failures ?? 0;
              const daysSince = localVerifiedAt
                ? Math.floor((Date.now() - localVerifiedAt.getTime()) / 86400000)
                : null;
              const needsVerify = daysSince === null || daysSince > 90;
              return (
                <div className="rounded-md border border-border/50 bg-muted/20 px-3 py-2.5 space-y-1.5">
                  <div className="flex items-center justify-between gap-2">
                    <span className={cn('text-xs font-medium', failures > 0 ? 'text-amber-500' : total > 0 ? 'text-emerald-500' : 'text-muted-foreground/60')}>
                      {total > 0
                        ? `${failures > 0 ? '⚠' : '✓'} ${total - failures}/${total} successful (12 mo)`
                        : 'No payment history yet'}
                    </span>
                    <button
                      type="button"
                      onClick={handleVerifyAutopay}
                      className="text-[11px] text-sky-500 hover:text-sky-400 underline underline-offset-2 transition-colors"
                    >
                      Mark verified
                    </button>
                  </div>
                  {needsVerify && (
                    <p className="text-[11px] text-amber-500/80">
                      {daysSince === null
                        ? "Autopay never confirmed — verify it's still active."
                        : `Last verified ${daysSince}d ago — confirm autopay is still on.`}
                    </p>
                  )}
                  {!needsVerify && (
                    <p className="text-[11px] text-muted-foreground/60">Verified {daysSince}d ago</p>
                  )}
                  {failures > 0 && stats?.last_failure_date && (
                    <p className="text-[11px] text-amber-500/80">
                      Last failure: {stats.last_failure_date}{stats?.last_failure_notes ? ` — ${stats.last_failure_notes}` : ''}
                    </p>
                  )}
                </div>
              );
            })()}

            {/* Notes */}
            <div className="col-span-2 space-y-1.5">
              <Label className="text-xs uppercase tracking-wider text-muted-foreground">Notes</Label>
              <textarea
                rows={2}
                value={notes}
                onChange={e => setNotes(e.target.value)}
                className={cn(
                  'w-full rounded-md border border-border/60 bg-background/50 px-3 py-2',
                  'text-sm text-foreground placeholder:text-muted-foreground/50',
                  'resize-none outline-none focus:ring-1 focus:ring-ring transition-shadow',
                )}
                placeholder="Any additional notes…"
              />
            </div>

            <div className="col-span-2 rounded-lg border border-border/60 bg-muted/20 p-3">
              <label className="flex items-center gap-2.5 cursor-pointer group">
                <input
                  type="checkbox"
                  checked={saveTemplate}
                  onChange={e => setSaveTemplate(e.target.checked)}
                  className="h-4 w-4 rounded border-border accent-primary"
                />
                <span className="text-sm text-muted-foreground group-hover:text-foreground transition-colors">
                  Save this setup as a reusable template
                </span>
              </label>
              {saveTemplate && (
                <div className="mt-2 space-y-1.5">
                  <Label className="text-xs uppercase tracking-wider text-muted-foreground">Template Name</Label>
                  <Input
                    className={inp}
                    value={templateName}
                    onChange={e => setTemplateName(e.target.value)}
                    placeholder={name || 'My bill template'}
                  />
                </div>
              )}
            </div>

          </div>
        </form>

        {!isNew && (
          <div className="mt-4 border-t border-border/50 pt-4">
            <div className="mb-3 flex items-center justify-between gap-3">
              <div>
                <p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">Payment history</p>
                <p className="text-[11px] text-muted-foreground/75">{payments.length} recorded</p>
              </div>
              <Button type="button" size="sm" variant="outline" disabled={paymentBusy} onClick={startAddPayment} className="h-8 gap-2 text-xs">
                <Plus className="h-3.5 w-3.5" />
                Add Payment
              </Button>
            </div>

            {paymentsLoading ? (
              <div className="rounded-lg border border-border/60 bg-muted/20 px-3 py-8 text-center text-sm text-muted-foreground">
                Loading payment history...
              </div>
            ) : payments.length === 0 ? (
              <div className="flex flex-col items-center gap-1.5 rounded-lg bg-muted/20 px-3 py-8 text-center">
                <p className="text-sm font-medium text-muted-foreground">No payments yet</p>
                <p className="text-xs text-muted-foreground/60">Use the form below to record the first payment.</p>
              </div>
            ) : (
              <div className="max-h-52 space-y-2 overflow-y-auto pr-1">
                {payments.map(payment => {
                  const linkedPayment = isTransactionLinkedPayment(payment);
                  const historyOnly = isHistoryOnlyPayment(payment);
                  return (
                    <div key={payment.id} className={cn(
                      'flex items-start justify-between gap-3 rounded-lg border px-3 py-2.5',
                      historyOnly
                        ? 'border-amber-500/25 bg-amber-500/[0.06] opacity-85'
                        : 'border-border/60 bg-background/35'
                    )}>
                      <div className="min-w-0">
                        <div className="flex flex-wrap items-center gap-2">
                          <p className={cn('font-mono text-sm font-semibold', historyOnly ? 'text-muted-foreground line-through decoration-amber-500/70' : 'text-foreground')}>{fmt(payment.amount)}</p>
                          <span className={cn(
                            'rounded-md border px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide',
                            paymentSourceTone(payment.payment_source),
                          )}>
                            {paymentSourceLabel(payment.payment_source)}
                          </span>
                          {historyOnly && (
                            <span className="rounded-md border border-amber-500/30 bg-amber-500/10 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-amber-600 dark:text-amber-300">
                              History only
                            </span>
                          )}
                        </div>
                        <p className="mt-0.5 truncate text-xs text-muted-foreground">
                          {fmtDate(payment.paid_date)} · {payment.method || 'manual'}
                        </p>
                        {payment.notes && (
                          <p className="mt-0.5 truncate text-xs text-muted-foreground/80">{payment.notes}</p>
                        )}
                      </div>
                      <div className="flex shrink-0 gap-1">
                        {historyOnly ? (
                          <span className="inline-flex h-8 items-center rounded-md border border-amber-500/25 bg-amber-500/10 px-2 text-[11px] font-medium text-amber-600 dark:text-amber-300">
                            Overridden
                          </span>
                        ) : linkedPayment ? (
                          <span className="inline-flex h-8 items-center gap-1.5 rounded-md border border-primary/20 bg-primary/5 px-2 text-[11px] font-medium text-primary">
                            <Link2 className="h-3.5 w-3.5" />
                            Matched
                          </span>
                        ) : (
                          <>
                            <Button type="button" size="icon" variant="ghost" disabled={paymentBusy} onClick={() => startEditPayment(payment)} className="h-8 w-8" aria-label="Edit payment">
                              <Pencil className="h-3.5 w-3.5" />
                            </Button>
                            <Button type="button" size="icon" variant="ghost" disabled={paymentBusy} onClick={() => setDeletePaymentTarget(payment)} className="h-8 w-8 text-destructive hover:bg-destructive/10 hover:text-destructive" aria-label="Remove payment">
                              <Trash2 className="h-3.5 w-3.5" />
                            </Button>
                          </>
                        )}
                      </div>
                    </div>
                  );
                })}
              </div>
            )}

            {/* Bank Matching Rules */}
            {!isNew && (
              <div className="mt-4 rounded-lg border border-border/60 bg-background/35">
                <div className="flex items-center justify-between gap-3 border-b border-border/50 px-3 py-2.5">
                  <div>
                    <p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">Bank matching rules</p>
                    <p className="mt-0.5 text-[11px] text-muted-foreground/70">
                      Transactions whose description contains these patterns are automatically imported as payments.
                    </p>
                  </div>
                  {localHasRules && (
                    <Button
                      type="button"
                      size="sm"
                      variant="outline"
                      className="shrink-0 gap-1.5 text-xs"
                      disabled={syncingPayments}
                      title="Scan unmatched bank transactions and import any matching payments for this bill"
                      onClick={async () => {
                        setSyncingPayments(true);
                        try {
                          const result = await api.syncBillSimplefinPayments(sourceBill.id);
                          if (result.added > 0) {
                            toast.success(`${result.added} payment${result.added !== 1 ? 's' : ''} imported from bank history.`);
                            loadLinkedTransactions?.();
                          } else {
                            toast.info('No new matching transactions found.');
                          }
                          // Surface late-attribution prompts to the tracker page
                          if (result.late_attributions?.length) {
                            window.dispatchEvent(new CustomEvent('tracker:late-attributions', {
                              detail: { attributions: result.late_attributions },
                            }));
                          }
                        } catch (err) {
                          toast.error(err.message || 'Sync failed.');
                        } finally {
                          setSyncingPayments(false);
                        }
                      }}
                    >
                      {syncingPayments
                        ? <><Loader2 className="h-3.5 w-3.5 animate-spin" />Syncing…</>
                        : <><RefreshCw className="h-3.5 w-3.5" />Sync</>}
                    </Button>
                  )}
                </div>
                <div className="px-3 py-3">
                  <BillMerchantRules
                    billId={sourceBill?.id}
                    billName={sourceBill?.name}
                    onRulesChanged={() => {
                      setLocalHasRules(true);
                      loadLinkedTransactions?.();
                    }}
                  />
                </div>
              </div>
            )}

            <div className="mt-4 rounded-lg border border-border/60 bg-background/35">
              <div className="flex items-center justify-between gap-3 border-b border-border/50 px-3 py-2.5">
                <div className="min-w-0">
                  <p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">Linked transactions</p>
                  <p className="text-[11px] text-muted-foreground/75">{linkedTransactions.length} confirmed matches</p>
                </div>
                <span className={cn(
                  'inline-flex h-7 items-center gap-1.5 rounded-md border px-2 text-[11px] font-medium',
                  linkedTransactions.length > 0
                    ? 'border-primary/20 bg-primary/5 text-primary'
                    : 'border-border/60 bg-muted/30 text-muted-foreground',
                )}>
                  <Link2 className="h-3.5 w-3.5" />
                  {linkedTransactions.length}
                </span>
              </div>

              {linkedTransactionsLoading ? (
                <div className="px-3 py-8 text-center text-sm text-muted-foreground">
                  Loading linked transactions...
                </div>
              ) : linkedTransactions.length === 0 ? (
                <div className="flex items-center justify-center gap-2 px-3 py-8 text-sm text-muted-foreground">
                  <Link2Off className="h-4 w-4" />
                  No transactions linked to this bill yet.
                </div>
              ) : (
                <div className="max-h-56 divide-y divide-border/40 overflow-y-auto">
                  {linkedTransactions.map(transaction => (
                    <div key={transaction.id} className="flex items-start justify-between gap-3 px-3 py-3">
                      <div className="min-w-0">
                        <div className="flex flex-wrap items-center gap-2">
                          <p className="truncate text-sm font-medium text-foreground">{transactionTitle(transaction)}</p>
                          <span className="rounded-md border border-border/60 bg-muted/40 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
                            {transaction.source_label || transaction.source_type_label || 'Transaction'}
                          </span>
                        </div>
                        <p className="mt-0.5 truncate text-xs text-muted-foreground">
                          {transactionDate(transaction) ? fmtDate(transactionDate(transaction)) : 'No date'} · {transaction.description || transaction.memo || 'No description'}
                        </p>
                        {transaction.account_name && (
                          <p className="mt-0.5 truncate text-[11px] text-muted-foreground/75">{transaction.account_name}</p>
                        )}
                      </div>
                      <div className="flex shrink-0 flex-col items-end gap-2 sm:flex-row sm:items-center">
                        <p className={cn(
                          'font-mono text-sm font-semibold tabular-nums',
                          Number(transaction.amount) < 0 ? 'text-destructive' : 'text-emerald-600',
                        )}>
                          {fmtTransactionAmount(transaction.amount, transaction.currency)}
                        </p>
                        <Button
                          type="button"
                          size="sm"
                          variant="outline"
                          disabled={transactionBusyId === transaction.id}
                          onClick={() => openUnmatch(transaction)}
                          className="h-8 gap-1.5 text-xs"
                        >
                          <Link2Off className="h-3.5 w-3.5" />
                          {transactionBusyId === transaction.id ? 'Unmatching...' : 'Unmatch'}
                        </Button>
                      </div>
                    </div>
                  ))}
                </div>
              )}
            </div>

            {paymentFormOpen && (
              <form onSubmit={handlePaymentSubmit} className="mt-3 rounded-lg border border-border/60 bg-muted/20 p-3">
                <div className="mb-3 flex items-center justify-between gap-3">
                  <p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
                    {editingPayment ? 'Edit payment' : 'Add payment'}
                  </p>
                  <span className="rounded-md border border-emerald-500/25 bg-emerald-500/10 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-emerald-600 dark:text-emerald-400">
                    manual
                  </span>
                </div>
                <div className="grid gap-3 sm:grid-cols-2">
                  <div className="space-y-1.5">
                    <Label className="text-xs uppercase tracking-wider text-muted-foreground">Amount ($)</Label>
                    <Input
                      type="number"
                      min="0"
                      step="0.01"
                      value={paymentAmount}
                      onChange={e => setPaymentAmount(e.target.value)}
                      className={cn(inp, 'font-mono')}
                      required
                    />
                  </div>
                  <div className="space-y-1.5">
                    <Label className="text-xs uppercase tracking-wider text-muted-foreground">Paid Date</Label>
                    <Input
                      type="date"
                      value={paymentDate}
                      onChange={e => setPaymentDate(e.target.value)}
                      className={cn(inp, 'font-mono')}
                      required
                    />
                  </div>
                  <div className="space-y-1.5">
                    <Label className="text-xs uppercase tracking-wider text-muted-foreground">Method</Label>
                    <Select value={paymentMethod} onValueChange={setPaymentMethod}>
                      <SelectTrigger className={cn(inp, 'w-full')}>
                        <SelectValue />
                      </SelectTrigger>
                      <SelectContent>
                        {PAYMENT_METHODS.map(([value, label]) => (
                          <SelectItem key={value} value={value}>{label}</SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                  </div>
                  <div className="space-y-1.5">
                    <Label className="text-xs uppercase tracking-wider text-muted-foreground">Notes</Label>
                    <Input
                      value={paymentNotes}
                      onChange={e => setPaymentNotes(e.target.value)}
                      className={inp}
                      placeholder="Paid from checking"
                    />
                  </div>
                </div>
                <div className="mt-3 flex justify-end gap-2">
                  <Button
                    type="button"
                    variant="ghost"
                    disabled={paymentBusy}
                    onClick={() => {
                      resetPaymentForm();
                      setPaymentFormOpen(false);
                    }}
                    className="text-xs"
                  >
                    Cancel
                  </Button>
                  <Button type="submit" disabled={paymentBusy} className="text-xs">
                    {paymentBusy ? 'Saving...' : editingPayment ? 'Save Payment' : 'Add Payment'}
                  </Button>
                </div>
              </form>
            )}
          </div>
        )}

        <DialogFooter className="mt-2 gap-2 sm:justify-between">
          <div className="flex gap-2">
            {!isNew && onDuplicate && (
              <Button
                type="button"
                variant="outline"
                disabled={isPending}
                onClick={() => onDuplicate(bill)}
                className="gap-2 text-xs"
              >
                <Copy className="h-3.5 w-3.5" />
                Duplicate
              </Button>
            )}
            {!isNew && (
              <Button
                type="button"
                variant="outline"
                disabled={isPending}
                onClick={() => bill?.active ? setDeactivateOpen(true) : handleDeactivate()}
                className={cn('gap-1.5 text-xs', bill?.active ? 'border-destructive/40 text-destructive hover:bg-destructive/10 hover:border-destructive/60' : 'border-emerald-500/40 text-emerald-600 dark:text-emerald-400 hover:bg-emerald-500/10')}
              >
                {bill?.active ? 'Deactivate' : 'Reactivate'}
              </Button>
            )}
          </div>
          <div className="flex gap-2">
            <Button type="button" variant="ghost" disabled={isPending} onClick={onClose} className="text-xs">
              Cancel
            </Button>
            <Button type="submit" form="bill-modal-form" disabled={isPending} className="gap-1.5 text-xs">
              {isPending && <Loader2 className="h-3.5 w-3.5 animate-spin" />}
              {isNew ? 'Add Bill' : 'Save Changes'}
            </Button>
          </div>
        </DialogFooter>

        <AlertDialog open={deactivateOpen} onOpenChange={open => { if (!open) { setDeactivateOpen(false); setDeactivateReason(''); } }}>
          <AlertDialogContent>
            <AlertDialogHeader>
              <AlertDialogTitle>Deactivate "{bill?.name}"?</AlertDialogTitle>
              <AlertDialogDescription>
                This bill will be hidden from the tracker. You can reactivate it at any time.
              </AlertDialogDescription>
            </AlertDialogHeader>
            <div className="space-y-1.5 py-1">
              <label className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
                Reason (optional)
              </label>
              <select
                value={deactivateReason}
                onChange={e => setDeactivateReason(e.target.value)}
                className="w-full rounded-md border border-border/60 bg-background px-3 py-2 text-sm text-foreground outline-none focus:ring-1 focus:ring-ring"
              >
                <option value="">Select a reason…</option>
                <option value="Moved to spouse">Moved to spouse</option>
                <option value="Switched providers">Switched providers</option>
                <option value="Paid off">Paid off</option>
                <option value="Cancelled">Cancelled</option>
                <option value="Other">Other</option>
              </select>
            </div>
            <AlertDialogFooter>
              <AlertDialogCancel onClick={() => { setDeactivateOpen(false); setDeactivateReason(''); }}>Cancel</AlertDialogCancel>
              <AlertDialogAction
                className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
                onClick={() => { setDeactivateOpen(false); handleDeactivate(); }}
              >
                Deactivate
              </AlertDialogAction>
            </AlertDialogFooter>
          </AlertDialogContent>
        </AlertDialog>

        <AlertDialog
          open={!!deletePaymentTarget}
          onOpenChange={open => {
            if (!open && !paymentBusy) setDeletePaymentTarget(null);
          }}
        >
          <AlertDialogContent>
            <AlertDialogHeader>
              <AlertDialogTitle>Remove this payment?</AlertDialogTitle>
              <AlertDialogDescription>
                This moves the payment to recovery and removes it from bill status calculations.
              </AlertDialogDescription>
            </AlertDialogHeader>
            <AlertDialogFooter>
              <AlertDialogCancel disabled={paymentBusy}>Cancel</AlertDialogCancel>
              <AlertDialogAction
                disabled={paymentBusy}
                onClick={handleDeletePayment}
                className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
              >
                {paymentBusy ? 'Removing...' : 'Remove Payment'}
              </AlertDialogAction>
            </AlertDialogFooter>
          </AlertDialogContent>
        </AlertDialog>

        {/* ── Unmatch choice dialog ─────────────────────────────────── */}
        <Dialog
          open={!!unmatchTarget && !bulkUnmatch}
          onOpenChange={open => { if (!open) closeUnmatch(); }}
        >
          <DialogContent className="sm:max-w-md border-border/60 bg-card/95 backdrop-blur-xl">
            <DialogHeader>
              <DialogTitle className="text-base font-semibold">Unmatch transaction</DialogTitle>
              <DialogDescription className="text-sm text-muted-foreground">
                How would you like to proceed?
              </DialogDescription>
            </DialogHeader>

            {unmatchTarget && (
              <div className="rounded-lg border border-border/60 bg-muted/20 px-3 py-2.5">
                <p className="text-sm font-medium text-foreground">{transactionTitle(unmatchTarget)}</p>
                <p className="mt-0.5 text-xs text-muted-foreground">
                  {transactionDate(unmatchTarget) ? fmtDate(transactionDate(unmatchTarget)) : 'No date'}
                  {' · '}
                  <span className="font-mono">{fmtTransactionAmount(unmatchTarget.amount, unmatchTarget.currency)}</span>
                </p>
              </div>
            )}

            <div className="grid gap-2.5">
              <button
                type="button"
                onClick={() => setUnmatchConfirmOpen(true)}
                className="flex items-start gap-3 rounded-lg border border-border/60 bg-background/50 p-3 text-left transition-colors hover:border-primary/40 hover:bg-primary/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
              >
                <Link2Off className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
                <div>
                  <p className="text-sm font-medium">Unmatch this payment only</p>
                  <p className="mt-0.5 text-xs text-muted-foreground">Remove just this one transaction from the bill.</p>
                </div>
              </button>
              <button
                type="button"
                onClick={handleOpenBulkUnmatch}
                className="flex items-start gap-3 rounded-lg border border-border/60 bg-background/50 p-3 text-left transition-colors hover:border-primary/40 hover:bg-primary/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
              >
                <Layers className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
                <div>
                  <p className="text-sm font-medium">Review all similar matches</p>
                  <p className="mt-0.5 text-xs text-muted-foreground">
                    See all transactions with a similar payee name and manage them together. Optionally remove a merchant rule too.
                  </p>
                </div>
              </button>
            </div>

            <DialogFooter>
              <Button type="button" variant="ghost" onClick={closeUnmatch} className="text-xs">
                Cancel
              </Button>
            </DialogFooter>
          </DialogContent>
        </Dialog>

        {/* ── Single unmatch confirm ────────────────────────────────── */}
        <AlertDialog
          open={!!unmatchTarget && unmatchConfirmOpen}
          onOpenChange={open => { if (!open) setUnmatchConfirmOpen(false); }}
        >
          <AlertDialogContent>
            <AlertDialogHeader>
              <AlertDialogTitle>Unmatch this transaction?</AlertDialogTitle>
              <AlertDialogDescription>
                {unmatchTarget && (
                  <>
                    <span className="font-medium text-foreground">{transactionTitle(unmatchTarget)}</span>
                    {' '}will be unlinked from this bill.
                    {unmatchTarget.linked_payment?.payment_source === 'provider_sync'
                      ? ' The payment record will be removed and the balance restored.'
                      : ' The payment record will be removed.'}
                  </>
                )}
              </AlertDialogDescription>
            </AlertDialogHeader>
            <AlertDialogFooter>
              <AlertDialogCancel disabled={!!transactionBusyId} onClick={() => setUnmatchConfirmOpen(false)}>
                Cancel
              </AlertDialogCancel>
              <AlertDialogAction
                disabled={!!transactionBusyId}
                onClick={handleSingleUnmatch}
              >
                {transactionBusyId ? 'Unmatching…' : 'Confirm Unmatch'}
              </AlertDialogAction>
            </AlertDialogFooter>
          </AlertDialogContent>
        </AlertDialog>

        {/* ── Bulk unmatch dialog ───────────────────────────────────── */}
        <Dialog
          open={!!unmatchTarget && !!bulkUnmatch}
          onOpenChange={open => { if (!open) closeUnmatch(); }}
        >
          <DialogContent className="sm:max-w-lg border-border/60 bg-card/95 backdrop-blur-xl">
            <DialogHeader>
              <DialogTitle className="text-base font-semibold">Review similar matches</DialogTitle>
              {unmatchTarget && (
                <DialogDescription className="text-sm text-muted-foreground">
                  Transactions similar to <span className="font-medium text-foreground">{transactionTitle(unmatchTarget)}</span>.
                  Uncheck any you want to keep matched.
                </DialogDescription>
              )}
            </DialogHeader>

            {bulkUnmatch && (
              <>
                <div className="flex items-center gap-2">
                  <span className="text-xs text-muted-foreground">Quick select:</span>
                  <Button
                    type="button"
                    size="sm"
                    variant="outline"
                    className="h-6 px-2 text-xs"
                    onClick={() => setBulkUnmatch(p => ({ ...p, checkedIds: new Set(p.similar.map(t => t.id)) }))}
                  >
                    All
                  </Button>
                  <Button
                    type="button"
                    size="sm"
                    variant="outline"
                    className="h-6 px-2 text-xs"
                    onClick={() => setBulkUnmatch(p => ({ ...p, checkedIds: new Set() }))}
                  >
                    None
                  </Button>
                  <span className="ml-auto text-xs text-muted-foreground">
                    {bulkUnmatch.checkedIds.size} of {bulkUnmatch.similar.length} selected
                  </span>
                </div>

                <div className="max-h-60 space-y-2 overflow-y-auto pr-1">
                  {bulkUnmatch.similar.map(tx => (
                    <label
                      key={tx.id}
                      className={cn(
                        'flex cursor-pointer items-center gap-3 rounded-lg border px-3 py-2.5 transition-colors',
                        bulkUnmatch.checkedIds.has(tx.id)
                          ? 'border-primary/30 bg-primary/5'
                          : 'border-border/60 bg-background/35 opacity-60',
                      )}
                    >
                      <Checkbox
                        checked={bulkUnmatch.checkedIds.has(tx.id)}
                        onCheckedChange={checked => {
                          setBulkUnmatch(p => {
                            const next = new Set(p.checkedIds);
                            checked ? next.add(tx.id) : next.delete(tx.id);
                            return { ...p, checkedIds: next };
                          });
                        }}
                      />
                      <div className="min-w-0 flex-1">
                        <p className="truncate text-sm font-medium text-foreground">{transactionTitle(tx)}</p>
                        <p className="text-xs text-muted-foreground">
                          {transactionDate(tx) ? fmtDate(transactionDate(tx)) : 'No date'}
                          {tx.account_name && ` · ${tx.account_name}`}
                        </p>
                      </div>
                      <p className={cn(
                        'shrink-0 font-mono text-sm font-semibold tabular-nums',
                        Number(tx.amount) < 0 ? 'text-destructive' : 'text-emerald-600',
                      )}>
                        {fmtTransactionAmount(tx.amount, tx.currency)}
                      </p>
                    </label>
                  ))}
                </div>

                {bulkUnmatch.rules.length > 0 && (
                  <div className="rounded-lg border border-amber-500/25 bg-amber-500/5 px-3 py-2.5">
                    <p className="mb-2 text-xs font-semibold uppercase tracking-wider text-amber-600 dark:text-amber-400">
                      Merchant rules
                    </p>
                    {bulkUnmatch.rules.map(rule => (
                      <label key={rule.id} className="flex cursor-pointer items-start gap-2.5">
                        <Checkbox
                          className="mt-0.5"
                          checked={bulkUnmatch.removeRuleId === rule.id}
                          onCheckedChange={checked =>
                            setBulkUnmatch(p => ({ ...p, removeRuleId: checked ? rule.id : null }))
                          }
                        />
                        <div>
                          <p className="text-sm">
                            Remove rule <span className="font-mono font-semibold">"{rule.merchant}"</span>
                          </p>
                          <p className="mt-0.5 text-xs text-muted-foreground">
                            Future syncs won't auto-match this pattern to this bill.
                          </p>
                        </div>
                      </label>
                    ))}
                  </div>
                )}
              </>
            )}

            <DialogFooter className="gap-2">
              <Button type="button" variant="ghost" onClick={closeUnmatch} className="text-xs">
                Cancel
              </Button>
              <Button
                type="button"
                variant="outline"
                onClick={() => setBulkUnmatch(null)}
                className="text-xs"
              >
                Back
              </Button>
              <Button
                type="button"
                disabled={bulkBusy || !bulkUnmatch || bulkUnmatch.checkedIds.size === 0}
                onClick={handleBulkConfirm}
                className="text-xs"
              >
                {bulkBusy
                  ? 'Unmatching…'
                  : `Unmatch ${bulkUnmatch?.checkedIds?.size ?? 0} transaction${bulkUnmatch?.checkedIds?.size !== 1 ? 's' : ''}`}
              </Button>
            </DialogFooter>
          </DialogContent>
        </Dialog>
      </DialogContent>
    </Dialog>
  );
}
