/* eslint-disable */
// Budget Tracker — household expense organizer with dashboard, transactions, recurring bills, categories

const DEFAULT_BUDGET_CATEGORIES = [
  "Mortgage", "Utilities", "Insurance", "Phone/Internet", "Subscriptions",
  "Groceries", "Transportation", "Home Maintenance", "Kids", "Health",
  "Entertainment", "Miscellaneous"
];

const EXPENSE_CATEGORIES = DEFAULT_BUDGET_CATEGORIES;

const CATEGORY_COLORS = {
  Mortgage: "bg-blue-500", Utilities: "bg-yellow-500", Insurance: "bg-purple-500",
  "Phone/Internet": "bg-indigo-500", Subscriptions: "bg-pink-500", Groceries: "bg-green-500",
  Transportation: "bg-orange-500", "Home Maintenance": "bg-teal-500", Kids: "bg-cyan-500",
  Health: "bg-red-500", Entertainment: "bg-fuchsia-500", Miscellaneous: "bg-gray-500",
};

const DEFAULT_HOUSEHOLD = ["Utilities", "Insurance", "Mortgage", "Phone/Internet"];

const CATEGORY_COLOR_PALETTE = [
  "bg-blue-500", "bg-green-500", "bg-yellow-500", "bg-purple-500", "bg-pink-500", "bg-indigo-500",
  "bg-orange-500", "bg-teal-500", "bg-cyan-500", "bg-red-500", "bg-fuchsia-500", "bg-gray-500",
];

// Resolves a category's bar/dot color: user-picked color first, then the built-in default.
const getCategoryColor = (name, categories) => {
  const cat = (categories || []).find(c => (c.name || c) === name);
  return (cat && cat.color) || CATEGORY_COLORS[name] || "bg-gray-500";
};

const shiftMonthValue = (value, delta) => {
  const [y, m] = value.split("-").map(Number);
  const d = new Date(y, m - 1 + delta, 1);
  return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0");
};

const toISODate = (d) => d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");

// Monthly portion of a bill — biweekly counts as 2x, weekly as 4x, and
// quarterly/semiannual/yearly are amortized into a monthly slice.
const toMonthlyAmount = (amount, frequency) => {
  const amt = parseFloat(amount) || 0;
  switch (frequency) {
    case 'weekly':     return amt * 4;
    case 'biweekly':   return amt * 2;
    case 'quarterly':  return amt / 3;
    case 'semiannual': return amt / 6;
    case 'yearly':     return amt / 12;
    default:           return amt;
  }
};

const getHouseholdCategories = (categories) => {
  if (!categories || categories.length === 0) return DEFAULT_HOUSEHOLD;
  const withFlag = categories.filter(c => typeof c === 'object' && 'isHousehold' in c);
  if (withFlag.length === 0) return DEFAULT_HOUSEHOLD;
  return categories.filter(c => c.isHousehold).map(c => c.name || c);
};

const saveBudgetField = (fieldName, value) => {
  const store = Storage.get('familyhub_budget') || {};
  store[fieldName] = value;
  Storage.set('familyhub_budget', store);
};

const generateMonthlyBills = (recurringBills, generatedBills, month, year) => {
  const newBills = [];
  recurringBills.filter(b => b.enabled).forEach(bill => {
    const exists = generatedBills.some(g => g.recurringBillId === bill.id && g.month === month && g.year === year);
    if (!exists) {
      if (bill.frequency === 'yearly') {
        const startMonth = bill.startMonth || 0;
        if (month !== startMonth) return;
      }
      if (bill.frequency === 'quarterly') {
        const startMonth = bill.startMonth || 0;
        if ((month - startMonth + 12) % 3 !== 0) return;
      }
      if (bill.frequency === 'semiannual') {
        const startMonth = bill.startMonth || 0;
        if ((month - startMonth + 12) % 6 !== 0) return;
      }
      if (bill.frequency === 'biweekly' && bill.startDate) {
        const start = new Date(bill.startDate);
        const check = new Date(year, month, bill.dueDay || 1);
        const diffDays = Math.round((check - start) / 86400000);
        if (diffDays < 0 || diffDays % 14 !== 0) return;
      }
      newBills.push({
        id: Date.now() + '-' + Math.random().toString(36).slice(2),
        recurringBillId: bill.id,
        name: bill.name,
        category: bill.category,
        month, year,
        amount: bill.amount,
        status: 'pending',
        dueDay: bill.dueDay,
        confirmedDate: null
      });
    }
  });
  return newBills;
};

// Returns the next due date on or after fromDate, derived from the bill's recurrence schedule.
const getNextRenewalDate = (bill, fromDate) => {
  const from = new Date(fromDate);
  from.setHours(0, 0, 0, 0);
  const dueDay = parseInt(bill.dueDay) || 1;
  const freq = bill.frequency || 'monthly';
  const startDate = bill.startDate ? new Date(bill.startDate + 'T00:00:00') : null;
  const endDate = bill.endDate ? new Date(bill.endDate + 'T00:00:00') : null;
  let candidate;

  if (freq === 'monthly') {
    candidate = new Date(from.getFullYear(), from.getMonth(), dueDay);
    if (candidate < from) candidate = new Date(from.getFullYear(), from.getMonth() + 1, dueDay);
  } else if (freq === 'weekly' || freq === 'biweekly') {
    if (!startDate) return null;
    const intervalMs = (freq === 'weekly' ? 7 : 14) * 86400000;
    if (from <= startDate) {
      candidate = new Date(startDate);
    } else {
      const periods = Math.ceil((from - startDate) / intervalMs);
      candidate = new Date(startDate.getTime() + periods * intervalMs);
    }
  } else {
    // yearly, quarterly, semiannual
    if (!startDate) return null;
    const intervalMonths = freq === 'quarterly' ? 3 : freq === 'semiannual' ? 6 : 12;
    const anchorYear = startDate.getFullYear();
    const anchorMonth = startDate.getMonth();
    if (from <= new Date(anchorYear, anchorMonth, dueDay)) {
      candidate = new Date(anchorYear, anchorMonth, dueDay);
    } else {
      const totalMonths = (from.getFullYear() - anchorYear) * 12 + (from.getMonth() - anchorMonth);
      const periods = Math.floor(totalMonths / intervalMonths);
      for (let i = periods; i <= periods + 2; i++) {
        const totalM = anchorMonth + i * intervalMonths;
        const y = anchorYear + Math.floor(totalM / 12);
        const m = ((totalM % 12) + 12) % 12;
        const d = new Date(y, m, dueDay);
        if (d >= from) { candidate = d; break; }
      }
    }
  }

  if (!candidate || isNaN(candidate.getTime())) return null;
  if (endDate && candidate > endDate) return null;
  return candidate;
};

// Computes upcoming bills from the recurrence schedule (not pre-generated instances).
// Cross-references generatedBills for confirmed/skipped status.
const getUpcomingFromSchedule = (recurringBills, generatedBills, days) => {
  const now = new Date();
  now.setHours(0, 0, 0, 0);
  const cutoff = new Date(now.getTime() + days * 86400000);
  const result = [];
  (recurringBills || []).filter(b => b.enabled).forEach(bill => {
    const next = getNextRenewalDate(bill, now);
    if (!next || next > cutoff) return;
    const month = next.getMonth();
    const year = next.getFullYear();
    const dueDay = next.getDate();
    const gen = (generatedBills || []).find(g => g.recurringBillId === bill.id && g.month === month && g.year === year);
    if (gen && gen.status === 'skipped') return;
    result.push({ ...bill, nextRenewalDate: next, month, year, dueDay, status: gen?.status || 'pending', generatedId: gen?.id || null });
  });
  return result.sort((a, b) => a.nextRenewalDate - b.nextRenewalDate);
};

const getSpendingByCategory = (expenses, month, year) => {
  const prefix = year + "-" + String(month + 1).padStart(2, "0");
  const map = {};
  expenses.filter(e => (e.date || "").startsWith(prefix)).forEach(e => {
    const cat = e.category || "Miscellaneous";
    map[cat] = (map[cat] || 0) + (parseFloat(e.amount) || 0);
  });
  return Object.entries(map).map(([category, total]) => ({ category, total })).sort((a, b) => b.total - a.total);
};

const getMonthOptions = () => {
  const months = [];
  const now = new Date();
  for (let i = 0; i < 12; i++) {
    const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
    months.push({ value: d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0"), label: d.toLocaleDateString('en-US', { month: 'long', year: 'numeric' }) });
  }
  return months;
};

const getMonthlyTotals = (expenses, monthsBack, anchor) => {
  const now = anchor || new Date();
  const results = [];
  for (let i = monthsBack - 1; i >= 0; i--) {
    const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
    const prefix = d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0");
    const total = expenses.filter(e => (e.date || "").startsWith(prefix)).reduce((s, e) => s + (parseFloat(e.amount) || 0), 0);
    results.push({ label: d.toLocaleDateString('en-US', { month: 'short' }), total, month: d.getMonth(), year: d.getFullYear() });
  }
  return results;
};

const ordinalDay = (d) => {
  const s = ["th","st","nd","rd"];
  const v = d % 100;
  return d + (s[(v - 20) % 10] || s[v] || s[0]);
};

const SummaryCard = ({ label, value, color, sub }) => (
  <Card className="!p-4">
    <p className="text-xs text-gray-500 dark:text-gray-400">{label}</p>
    <p className={"text-2xl font-bold " + (color || "text-gray-900 dark:text-white")}>{value}</p>
    {sub && <p className="text-xs text-gray-500 dark:text-gray-400">{sub}</p>}
  </Card>
);

const SpendingBarChart = ({ data, categories, onSelect }) => {
  const maxVal = Math.max(...data.map(d => d.total), 1);
  return (
    <Card>
      <h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-4">Spending by Category</h3>
      {data.length === 0 && <p className="text-gray-400 text-sm text-center py-4">No spending data yet</p>}
      <div className="space-y-3">
        {data.map(d => (
          <div key={d.category} className={onSelect ? "cursor-pointer hover:opacity-75 transition-opacity" : ""}
            onClick={() => onSelect && onSelect(d.category)} title={onSelect ? "View transactions" : undefined}>
            <div className="flex justify-between text-xs text-gray-600 dark:text-gray-400 mb-1">
              <span>{d.category}</span>
              <span className="font-medium">${d.total.toFixed(2)}</span>
            </div>
            <div className="w-full bg-gray-100 dark:bg-gray-700 rounded-full h-2.5">
              <div className={getCategoryColor(d.category, categories) + " h-2.5 rounded-full transition-all"} style={{ width: Math.max(2, (d.total / maxVal) * 100) + "%" }}></div>
            </div>
          </div>
        ))}
      </div>
    </Card>
  );
};

const BudgetProgressCard = ({ categories, expenses, month, year, onSelect }) => {
  const withBudget = categories.filter(c => parseFloat(c.budget) > 0);
  if (withBudget.length === 0) return null;
  const prefix = year + "-" + String(month + 1).padStart(2, "0");
  return (
    <Card>
      <h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-4">Budgets</h3>
      <div className="space-y-3">
        {withBudget.map(c => {
          const name = c.name || c;
          const budget = parseFloat(c.budget);
          const spent = expenses.filter(e => e.category === name && (e.date || "").startsWith(prefix)).reduce((s, e) => s + (parseFloat(e.amount) || 0), 0);
          const over = spent > budget;
          const barColor = over ? "bg-red-500" : spent > budget * 0.8 ? "bg-yellow-500" : "bg-green-500";
          return (
            <div key={name} className="cursor-pointer hover:opacity-75 transition-opacity" onClick={() => onSelect && onSelect(name)} title="View transactions">
              <div className="flex justify-between text-xs text-gray-600 dark:text-gray-400 mb-1">
                <span>{name}</span>
                <span className={"font-medium " + (over ? "text-red-600 dark:text-red-400" : "")}>
                  ${spent.toFixed(2)} / ${budget.toFixed(2)}{over ? " · $" + (spent - budget).toFixed(2) + " over" : ""}
                </span>
              </div>
              <div className="w-full bg-gray-100 dark:bg-gray-700 rounded-full h-2.5">
                <div className={barColor + " h-2.5 rounded-full transition-all"} style={{ width: Math.max(2, Math.min(100, (spent / budget) * 100)) + "%" }}></div>
              </div>
            </div>
          );
        })}
      </div>
    </Card>
  );
};

const MonthlyTrendChart = ({ data }) => {
  const maxVal = Math.max(...data.map(d => d.total), 1);
  return (
    <Card>
      <h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-4">Monthly Trend</h3>
      <div className="flex items-end gap-2 h-32">
        {data.map((d, i) => (
          <div key={i} className="flex-1 flex flex-col items-center gap-1">
            <span className="text-xs text-gray-500 dark:text-gray-400 font-medium">${d.total >= 1000 ? (d.total / 1000).toFixed(1) + 'k' : Math.round(d.total)}</span>
            <div className="w-full bg-blue-500 dark:bg-blue-400 rounded-t transition-all" style={{ height: Math.max(4, (d.total / maxVal) * 100) + "px" }}></div>
            <span className="text-xs text-gray-500 dark:text-gray-400">{d.label}</span>
          </div>
        ))}
      </div>
    </Card>
  );
};

const BudgetDashboard = ({ expenses, recurringBills, generatedBills, categories, onNavigate, onQuickAdd }) => {
  const now = new Date();
  const [selectedMonth, setSelectedMonth] = React.useState(() =>
    now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0")
  );
  const monthOptions = React.useMemo(() => getMonthOptions(), []);

  const [yearStr, monthStr] = selectedMonth.split("-");
  const curMonth = parseInt(monthStr, 10) - 1;
  const curYear = parseInt(yearStr, 10);
  const isCurrentMonth = curMonth === now.getMonth() && curYear === now.getFullYear();
  const monthLabel = new Date(curYear, curMonth, 1).toLocaleDateString('en-US', { month: 'long', year: 'numeric' });

  const stats = React.useMemo(() => {
    const prefix = curYear + "-" + String(curMonth + 1).padStart(2, "0");
    const prevDate = new Date(curYear, curMonth - 1, 1);
    const prevPrefix = prevDate.getFullYear() + "-" + String(prevDate.getMonth() + 1).padStart(2, "0");

    const householdCats = getHouseholdCategories(categories);
    const isHousehold = (cat) => householdCats.includes(cat);
    const isSubscription = (cat) => cat === "Subscriptions";

    const thisMonthExpenses = expenses.filter(e => (e.date || "").startsWith(prefix));
    const prevMonthExpenses = expenses.filter(e => (e.date || "").startsWith(prevPrefix));

    // Other Transactions: logged expenses that are NOT subscriptions and NOT household
    const otherTransactionsTotal = thisMonthExpenses
      .filter(e => !isSubscription(e.category) && !isHousehold(e.category))
      .reduce((s, e) => s + (parseFloat(e.amount) || 0), 0);
    const prevOtherTotal = prevMonthExpenses
      .filter(e => !isSubscription(e.category) && !isHousehold(e.category))
      .reduce((s, e) => s + (parseFloat(e.amount) || 0), 0);
    const pctChange = prevOtherTotal > 0 ? Math.round(((otherTransactionsTotal - prevOtherTotal) / prevOtherTotal) * 100) : 0;

    // Subscriptions: monthly portion of every enabled subscription bill (yearly → /12, etc.)
    const activeSubs = recurringBills.filter(b => b.enabled && isSubscription(b.category));
    const subTotal = activeSubs.reduce((s, b) => s + toMonthlyAmount(b.amount, b.frequency), 0);

    // Household: logged household expenses this month + monthly portion of every
    // enabled household recurring bill (biweekly = 2x, quarterly = /3, yearly = /12, etc.)
    const householdExpensesTotal = thisMonthExpenses
      .filter(e => isHousehold(e.category))
      .reduce((s, e) => s + (parseFloat(e.amount) || 0), 0);
    const householdRecurringTotal = recurringBills
      .filter(b => b.enabled && isHousehold(b.category))
      .reduce((s, b) => s + toMonthlyAmount(b.amount, b.frequency), 0);
    const householdTotal = householdExpensesTotal + householdRecurringTotal;

    // This Month Total = Other + Household + Subscriptions
    const grandTotal = otherTransactionsTotal + householdTotal + subTotal;

    const upcoming = getUpcomingFromSchedule(recurringBills, generatedBills, 14);
    const upcomingTotal = upcoming.reduce((s, b) => s + (parseFloat(b.amount) || 0), 0);

    const byCat = getSpendingByCategory(expenses, curMonth, curYear);
    const largestCat = byCat.length > 0 ? byCat[0] : null;

    return {
      thisMonthTotal: otherTransactionsTotal,
      prevMonthTotal: prevOtherTotal,
      pctChange,
      subTotal,
      subCount: activeSubs.length,
      householdTotal,
      grandTotal,
      upcomingTotal,
      upcomingCount: upcoming.length,
      largestCat,
      byCat,
      householdCats,
    };
  }, [expenses, recurringBills, generatedBills, categories, curMonth, curYear]);

  const monthlyTrend = React.useMemo(() => getMonthlyTotals(expenses, 6, new Date(curYear, curMonth, 1)), [expenses, curMonth, curYear]);
  const upcomingBills = React.useMemo(() => getUpcomingFromSchedule(recurringBills, generatedBills, 7), [recurringBills, generatedBills]);

  const nav = (view, filter) => onNavigate && onNavigate(view, filter);

  const hasOlder = monthOptions.some(m => m.value === shiftMonthValue(selectedMonth, -1));
  const hasNewer = monthOptions.some(m => m.value === shiftMonthValue(selectedMonth, 1));

  if (expenses.length === 0 && recurringBills.length === 0) {
    return (
      <Card className="text-center !py-10">
        <p className="text-lg font-semibold text-gray-900 dark:text-white mb-1">Welcome to your Budget Tracker</p>
        <p className="text-sm text-gray-500 dark:text-gray-400 mb-5">Log your first expense or set up a recurring bill to start seeing your spending here.</p>
        <div className="flex flex-col sm:flex-row justify-center gap-3">
          <BtnPrimary onClick={onQuickAdd}>+ Add First Expense</BtnPrimary>
          <BtnSecondary onClick={() => nav("recurring")}>Set Up Recurring Bills</BtnSecondary>
        </div>
      </Card>
    );
  }

  return (
    <div className="space-y-6">
      <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
        <p className="text-sm font-semibold text-gray-700 dark:text-gray-300">Showing: {monthLabel}</p>
        <div className="flex items-center gap-1">
          <button onClick={() => setSelectedMonth(shiftMonthValue(selectedMonth, -1))} disabled={!hasOlder} aria-label="Previous month"
            className="px-3 py-2 rounded-lg text-sm font-medium bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 disabled:opacity-40">‹</button>
          <div className="flex-1 sm:w-52">
            <Select value={selectedMonth} onChange={e => setSelectedMonth(e.target.value)}>
              {monthOptions.map(m => <option key={m.value} value={m.value}>{m.label}</option>)}
            </Select>
          </div>
          <button onClick={() => setSelectedMonth(shiftMonthValue(selectedMonth, 1))} disabled={!hasNewer} aria-label="Next month"
            className="px-3 py-2 rounded-lg text-sm font-medium bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 disabled:opacity-40">›</button>
        </div>
      </div>

      <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
        <Card className="!p-4 cursor-pointer hover:ring-2 hover:ring-green-400 transition-all" onClick={() => nav("transactions", { month: selectedMonth })}>
          <p className="text-xs text-gray-500 dark:text-gray-400">{isCurrentMonth ? "This Month Total" : monthLabel + " Total"}</p>
          <p className="text-2xl font-bold text-green-600 dark:text-green-400">${stats.grandTotal.toFixed(2)}</p>
          <p className="text-xs text-gray-500 dark:text-gray-400">All expenses & bills</p>
        </Card>
        <Card className="!p-4 cursor-pointer hover:ring-2 hover:ring-pink-400 transition-all" onClick={() => nav("recurring", { category: "Subscriptions" })}>
          <p className="text-xs text-gray-500 dark:text-gray-400">Subscriptions</p>
          <p className="text-2xl font-bold text-pink-600 dark:text-pink-400">${stats.subTotal.toFixed(2)}/mo</p>
          <p className="text-xs text-gray-500 dark:text-gray-400">{stats.subCount} active</p>
        </Card>
        <Card className="!p-4 cursor-pointer hover:ring-2 hover:ring-purple-400 transition-all" onClick={() => nav("recurring", { categories: stats.householdCats, label: "Household" })}>
          <p className="text-xs text-gray-500 dark:text-gray-400">Household</p>
          <p className="text-2xl font-bold text-purple-600 dark:text-purple-400">${stats.householdTotal.toFixed(2)}</p>
          <p className="text-xs text-gray-500 dark:text-gray-400">Bills & utilities</p>
        </Card>
        <Card className="!p-4 cursor-pointer hover:ring-2 hover:ring-blue-400 transition-all" onClick={() => nav("transactions", { month: selectedMonth })}>
          <p className="text-xs text-gray-500 dark:text-gray-400">Other Transactions</p>
          <p className="text-2xl font-bold text-blue-600 dark:text-blue-400">${stats.thisMonthTotal.toFixed(2)}</p>
          {stats.pctChange !== 0 && (
            <p className={"text-xs font-medium " + (stats.pctChange > 0 ? "text-red-500" : "text-green-500")}>
              {stats.pctChange > 0 ? "+" : ""}{stats.pctChange}% vs {isCurrentMonth ? "last" : "prior"} month
            </p>
          )}
        </Card>
        <Card className="!p-4 cursor-pointer hover:ring-2 hover:ring-orange-400 transition-all" onClick={() => nav("upcoming")}>
          <p className="text-xs text-gray-500 dark:text-gray-400">Due Next 14 Days</p>
          <p className="text-2xl font-bold text-orange-600 dark:text-orange-400">${stats.upcomingTotal.toFixed(2)}</p>
          <p className="text-xs text-gray-500 dark:text-gray-400">{stats.upcomingCount} bills</p>
        </Card>
        <Card className="!p-4 cursor-pointer hover:ring-2 hover:ring-gray-400 transition-all" onClick={() => nav("transactions", { category: stats.largestCat ? stats.largestCat.category : "", month: selectedMonth })}>
          <p className="text-xs text-gray-500 dark:text-gray-400">Largest Category</p>
          {stats.largestCat ? (
            <React.Fragment>
              <p className="text-lg font-bold text-gray-900 dark:text-white">{stats.largestCat.category}</p>
              <p className="text-sm font-medium text-gray-600 dark:text-gray-400">${stats.largestCat.total.toFixed(2)}</p>
            </React.Fragment>
          ) : <p className="text-sm text-gray-400">No data</p>}
        </Card>
      </div>

      <div className="grid md:grid-cols-2 gap-6">
        <SpendingBarChart data={stats.byCat.slice(0, 5)} categories={categories}
          onSelect={cat => nav("transactions", { category: cat, month: selectedMonth })} />
        <MonthlyTrendChart data={monthlyTrend} />
      </div>

      <BudgetProgressCard categories={categories} expenses={expenses} month={curMonth} year={curYear}
        onSelect={cat => nav("transactions", { category: cat, month: selectedMonth })} />

      <Card>
        <h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">Upcoming Bills (This Week)</h3>
        {upcomingBills.length === 0 && <p className="text-gray-400 text-sm text-center py-3">No upcoming bills</p>}
        <div className="space-y-2">
          {upcomingBills.map(b => (
            <div key={b.id} className="flex justify-between items-center py-2 border-b border-gray-100 dark:border-gray-700 last:border-0">
              <div>
                <p className="text-sm font-medium text-gray-900 dark:text-white">{b.name}</p>
                <p className="text-xs text-gray-500 dark:text-gray-400">Due: {ordinalDay(b.dueDay || 1)}</p>
              </div>
              <div className="flex items-center gap-2">
                <span className="text-sm font-bold text-gray-900 dark:text-white">${parseFloat(b.amount).toFixed(2)}</span>
                <Pill tone="info">Recurring</Pill>
              </div>
            </div>
          ))}
        </div>
      </Card>
    </div>
  );
};

const TransactionList = ({ expenses, categories, onEdit, onDelete, initialCategory, initialMonth }) => {
  const [search, setSearch] = React.useState("");
  const [selectedMonth, setSelectedMonth] = React.useState(() => {
    if (initialMonth) return initialMonth;
    const now = new Date();
    return now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0");
  });
  const [selectedCategory, setSelectedCategory] = React.useState(initialCategory || "");
  const [page, setPage] = React.useState(0);
  const perPage = 20;

  const monthOptions = React.useMemo(() => getMonthOptions(), []);

  const filtered = React.useMemo(() => {
    let result = [...expenses];
    if (search) {
      const q = search.toLowerCase();
      result = result.filter(e => (e.name || "").toLowerCase().includes(q) || (e.notes || "").toLowerCase().includes(q));
    }
    if (selectedMonth) result = result.filter(e => (e.date || "").startsWith(selectedMonth));
    if (selectedCategory) result = result.filter(e => e.category === selectedCategory);
    result.sort((a, b) => (b.date || "").localeCompare(a.date || ""));
    return result;
  }, [expenses, search, selectedMonth, selectedCategory]);

  const totalPages = Math.max(1, Math.ceil(filtered.length / perPage));
  const pageItems = filtered.slice(page * perPage, (page + 1) * perPage);

  React.useEffect(() => { setPage(0); }, [search, selectedMonth, selectedCategory]);

  return (
    <div className="space-y-4">
      <div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
        <Input placeholder="Search expenses..." value={search} onChange={e => setSearch(e.target.value)} />
        <div className="flex items-center gap-1">
          <button onClick={() => setSelectedMonth(shiftMonthValue(selectedMonth, -1))}
            disabled={!selectedMonth || !monthOptions.some(m => m.value === shiftMonthValue(selectedMonth, -1))} aria-label="Previous month"
            className="px-3 py-2 rounded-lg text-sm font-medium bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 disabled:opacity-40">‹</button>
          <Select value={selectedMonth} onChange={e => setSelectedMonth(e.target.value)}>
            <option value="">All Months</option>
            {monthOptions.map(m => <option key={m.value} value={m.value}>{m.label}</option>)}
          </Select>
          <button onClick={() => setSelectedMonth(shiftMonthValue(selectedMonth, 1))}
            disabled={!selectedMonth || !monthOptions.some(m => m.value === shiftMonthValue(selectedMonth, 1))} aria-label="Next month"
            className="px-3 py-2 rounded-lg text-sm font-medium bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 disabled:opacity-40">›</button>
        </div>
        <Select value={selectedCategory} onChange={e => setSelectedCategory(e.target.value)}>
          <option value="">All Categories</option>
          {categories.map(c => <option key={c.name || c} value={c.name || c}>{c.name || c}</option>)}
        </Select>
      </div>

      <p className="text-sm text-gray-500 dark:text-gray-400">
        {filtered.length} transaction{filtered.length !== 1 ? 's' : ''}
        {filtered.length > 0 && <span> · <span className="font-semibold text-gray-700 dark:text-gray-300">${filtered.reduce((s, e) => s + (parseFloat(e.amount) || 0), 0).toFixed(2)} total</span></span>}
      </p>

      <div className="space-y-3">
        {pageItems.map(e => (
          <Card key={e.id}>
            <div className="flex justify-between items-start">
              <div className="min-w-0 flex-1">
                <h3 className="text-base font-semibold text-gray-900 dark:text-white truncate">{e.name || e.category}</h3>
                <div className="flex items-center gap-2 mt-1">
                  <Pill tone="info">{e.category}</Pill>
                  {e.recurring && <Pill tone="purple">Recurring</Pill>}
                </div>
                <p className="text-xs text-gray-500 dark:text-gray-400 mt-1">{e.date}</p>
              </div>
              <div className="text-right ml-3">
                <p className="text-lg font-bold text-blue-600 dark:text-blue-400">${(parseFloat(e.amount) || 0).toFixed(2)}</p>
              </div>
            </div>
            {e.notes && <p className="text-xs text-gray-500 dark:text-gray-400 mt-2">{e.notes}</p>}
            <div className="flex gap-3 mt-2 text-sm">
              <button onClick={() => onEdit(e)} className="text-blue-600 dark:text-blue-400 hover:underline">Edit</button>
              <button onClick={() => onDelete(e.id)} className="text-red-600 dark:text-red-400 hover:underline">Delete</button>
            </div>
          </Card>
        ))}
        {pageItems.length === 0 && <p className="text-gray-400 text-center py-8">No transactions found</p>}
      </div>

      {totalPages > 1 && (
        <div className="flex justify-center items-center gap-4 pt-2">
          <button onClick={() => setPage(Math.max(0, page - 1))} disabled={page === 0}
            className="px-3 py-1 rounded text-sm font-medium bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 disabled:opacity-40">Prev</button>
          <span className="text-sm text-gray-600 dark:text-gray-400">{page + 1} / {totalPages}</span>
          <button onClick={() => setPage(Math.min(totalPages - 1, page + 1))} disabled={page >= totalPages - 1}
            className="px-3 py-1 rounded text-sm font-medium bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 disabled:opacity-40">Next</button>
        </div>
      )}
    </div>
  );
};

const RecurringBillsView = ({ recurringBills, generatedBills, categories, onSaveBills, onSaveGenerated, initialFilter }) => {
  const [showAdd, setShowAdd] = React.useState(false);
  const [editBill, setEditBill] = React.useState(null);
  const [draft, setDraft] = React.useState({ name: "", amount: "", category: "Mortgage", frequency: "monthly", dueDay: "1", enabled: true, startDate: "", endDate: "" });
  const [search, setSearch] = React.useState("");
  const [filterCategory, setFilterCategory] = React.useState(initialFilter?.category || "");
  const [filterStatus, setFilterStatus] = React.useState("");
  const now = new Date();
  const viewMonth = now.getMonth();
  const viewYear = now.getFullYear();

  const filterLabel = initialFilter?.label || null;
  const filterCategories = initialFilter?.categories || null;

  React.useEffect(() => {
    if (initialFilter?.category) setFilterCategory(initialFilter.category);
  }, [initialFilter]);

  const filteredBills = React.useMemo(() => {
    let result = [...recurringBills];
    if (search) {
      const q = search.toLowerCase();
      result = result.filter(b => (b.name || "").toLowerCase().includes(q));
    }
    if (filterCategory) {
      result = result.filter(b => b.category === filterCategory);
    } else if (filterCategories) {
      result = result.filter(b => filterCategories.includes(b.category));
    }
    if (filterStatus === "active") result = result.filter(b => b.enabled);
    if (filterStatus === "disabled") result = result.filter(b => !b.enabled);
    return result;
  }, [recurringBills, search, filterCategory, filterCategories, filterStatus]);

  const thisMonthBills = React.useMemo(() => {
    let bills = generatedBills.filter(g => g.month === viewMonth && g.year === viewYear);
    if (filterCategory) {
      bills = bills.filter(g => g.category === filterCategory);
    } else if (filterCategories) {
      bills = bills.filter(g => filterCategories.includes(g.category));
    }
    if (search) {
      const q = search.toLowerCase();
      bills = bills.filter(g => (g.name || "").toLowerCase().includes(q));
    }
    return bills.sort((a, b) => (a.dueDay || 1) - (b.dueDay || 1));
  }, [generatedBills, viewMonth, viewYear, filterCategory, filterCategories, search]);

  const addBill = () => {
    if (!draft.name?.trim() || !draft.amount) return;
    const startMonth = draft.startDate ? new Date(draft.startDate).getMonth() : 0;
    const newBill = { id: Date.now(), name: draft.name.trim(), amount: parseFloat(draft.amount), category: draft.category, frequency: draft.frequency, dueDay: parseInt(draft.dueDay) || 1, enabled: true, startDate: draft.startDate || null, endDate: draft.endDate || null, startMonth };
    const updated = [...recurringBills, newBill];
    onSaveBills(updated);
    const newGen = generateMonthlyBills(updated, generatedBills, viewMonth, viewYear);
    if (newGen.length) onSaveGenerated([...generatedBills, ...newGen]);
    setDraft({ name: "", amount: "", category: "Mortgage", frequency: "monthly", dueDay: "1", enabled: true, startDate: "", endDate: "" });
    setShowAdd(false);
  };

  const saveEdit = () => {
    if (!editBill.name?.trim() || !editBill.amount) return;
    const editStartMonth = editBill.startDate ? new Date(editBill.startDate).getMonth() : 0;
    onSaveBills(recurringBills.map(b => b.id === editBill.id ? { ...b, name: editBill.name.trim(), amount: parseFloat(editBill.amount), category: editBill.category, frequency: editBill.frequency, dueDay: parseInt(editBill.dueDay) || 1, startDate: editBill.startDate || null, endDate: editBill.endDate || null, startMonth: editStartMonth } : b));
    setEditBill(null);
  };

  const deleteBill = (id) => {
    if (!window.confirm("Delete this recurring bill?")) return;
    onSaveBills(recurringBills.filter(b => b.id !== id));
  };

  const toggleBill = (id) => {
    onSaveBills(recurringBills.map(b => b.id === id ? { ...b, enabled: !b.enabled } : b));
  };

  const confirmBill = (id) => {
    onSaveGenerated(generatedBills.map(g => g.id === id ? { ...g, status: 'confirmed', confirmedDate: new Date().toISOString().split('T')[0] } : g));
  };

  const skipBill = (id) => {
    onSaveGenerated(generatedBills.map(g => g.id === id ? { ...g, status: 'skipped' } : g));
  };

  const deleteGeneratedBill = (id) => {
    if (!window.confirm("Are you sure you want to delete this bill?")) return;
    onSaveGenerated(generatedBills.filter(g => g.id !== id));
  };

  const statusTone = { pending: "warn", confirmed: "success", skipped: "gray" };
  const catNames = categories.map(c => c.name || c);

  const heading = filterLabel || "Recurring Bills";

  return (
    <div className="space-y-6">
      <div className="flex justify-between items-center">
        <h3 className="text-lg font-semibold text-gray-900 dark:text-white">{heading}</h3>
        <BtnPrimary onClick={() => setShowAdd(true)}>+ Add Bill</BtnPrimary>
      </div>

      <div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
        <Input placeholder="Search bills..." value={search} onChange={e => setSearch(e.target.value)} />
        <Select value={filterCategory} onChange={e => setFilterCategory(e.target.value)}>
          <option value="">All Categories</option>
          {catNames.map(c => <option key={c} value={c}>{c}</option>)}
        </Select>
        <Select value={filterStatus} onChange={e => setFilterStatus(e.target.value)}>
          <option value="">All Status</option>
          <option value="active">Active</option>
          <option value="disabled">Disabled</option>
        </Select>
      </div>

      {showAdd && (
        <Modal title="Add Recurring Bill" onClose={() => setShowAdd(false)} onSubmit={addBill} submitLabel="Add Bill">
          <div className="space-y-4">
            <div><Label required>Name</Label><Input placeholder="e.g. Mortgage, Netflix" value={draft.name} onChange={e => setDraft({...draft, name: e.target.value})} autoFocus /></div>
            <div><Label required>Amount ($)</Label><Input type="number" step="0.01" placeholder="0.00" value={draft.amount} onChange={e => setDraft({...draft, amount: e.target.value})} /></div>
            <div><Label required>Category</Label><Select value={draft.category} onChange={e => setDraft({...draft, category: e.target.value})}>{catNames.map(c => <option key={c} value={c}>{c}</option>)}</Select></div>
            <div><Label required>Frequency</Label><Select value={draft.frequency} onChange={e => setDraft({...draft, frequency: e.target.value})}><option value="weekly">Weekly</option><option value="biweekly">Bi-weekly</option><option value="monthly">Monthly</option><option value="quarterly">Quarterly</option><option value="semiannual">Semi-annually</option><option value="yearly">Yearly</option></Select></div>
            <div><Label required>Due Day</Label><Input type="number" min="1" max="31" value={draft.dueDay} onChange={e => setDraft({...draft, dueDay: e.target.value})} /></div>
            <div className="grid grid-cols-2 gap-3">
              <div><Label>Start Date</Label><Input type="date" value={draft.startDate} onChange={e => setDraft({...draft, startDate: e.target.value})} /></div>
              <div><Label>End Date</Label><Input type="date" value={draft.endDate} onChange={e => setDraft({...draft, endDate: e.target.value})} /></div>
            </div>
          </div>
        </Modal>
      )}

      {editBill && (
        <Modal title="Edit Recurring Bill" onClose={() => setEditBill(null)} onSubmit={saveEdit} submitLabel="Save">
          <div className="space-y-4">
            <div><Label required>Name</Label><Input value={editBill.name} onChange={e => setEditBill({...editBill, name: e.target.value})} autoFocus /></div>
            <div><Label required>Amount ($)</Label><Input type="number" step="0.01" value={editBill.amount} onChange={e => setEditBill({...editBill, amount: e.target.value})} /></div>
            <div><Label required>Category</Label><Select value={editBill.category} onChange={e => setEditBill({...editBill, category: e.target.value})}>{catNames.map(c => <option key={c} value={c}>{c}</option>)}</Select></div>
            <div><Label required>Frequency</Label><Select value={editBill.frequency} onChange={e => setEditBill({...editBill, frequency: e.target.value})}><option value="weekly">Weekly</option><option value="biweekly">Bi-weekly</option><option value="monthly">Monthly</option><option value="quarterly">Quarterly</option><option value="semiannual">Semi-annually</option><option value="yearly">Yearly</option></Select></div>
            <div><Label required>Due Day</Label><Input type="number" min="1" max="31" value={editBill.dueDay} onChange={e => setEditBill({...editBill, dueDay: e.target.value})} /></div>
            <div className="grid grid-cols-2 gap-3">
              <div><Label>Start Date</Label><Input type="date" value={editBill.startDate || ''} onChange={e => setEditBill({...editBill, startDate: e.target.value})} /></div>
              <div><Label>End Date</Label><Input type="date" value={editBill.endDate || ''} onChange={e => setEditBill({...editBill, endDate: e.target.value})} /></div>
            </div>
          </div>
        </Modal>
      )}

      <div className="space-y-3">
        <p className="text-sm text-gray-500 dark:text-gray-400">{filteredBills.length} bill{filteredBills.length !== 1 ? 's' : ''}</p>
        {filteredBills.length === 0 && <p className="text-gray-400 text-center py-4">No recurring bills found</p>}
        {filteredBills.map(b => (
          <Card key={b.id}>
            <div className="flex justify-between items-start">
              <div>
                <h4 className="text-base font-semibold text-gray-900 dark:text-white">{b.name}</h4>
                <div className="flex items-center gap-2 mt-1 flex-wrap">
                  <Pill tone="info">{b.category}</Pill>
                  <Pill tone={b.enabled ? "success" : "gray"}>{b.enabled ? "Active" : "Disabled"}</Pill>
                  <span className="text-xs text-gray-500 dark:text-gray-400">{b.frequency} · Day {b.dueDay}{b.startDate ? ` · From ${b.startDate}` : ''}{b.endDate ? ` to ${b.endDate}` : ''}</span>
                </div>
              </div>
              <p className="text-lg font-bold text-blue-600 dark:text-blue-400">${parseFloat(b.amount).toFixed(2)}</p>
            </div>
            <div className="flex gap-3 mt-2 text-sm">
              <button onClick={() => toggleBill(b.id)} className="text-yellow-600 dark:text-yellow-400 hover:underline">{b.enabled ? "Disable" : "Enable"}</button>
              <button onClick={() => setEditBill({ ...b, amount: String(b.amount), dueDay: String(b.dueDay), startDate: b.startDate || '', endDate: b.endDate || '' })} className="text-blue-600 dark:text-blue-400 hover:underline">Edit</button>
              <button onClick={() => deleteBill(b.id)} className="text-red-600 dark:text-red-400 hover:underline">Delete</button>
            </div>
          </Card>
        ))}
      </div>

      <div>
        <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-3">
          This Month's Bills ({new Date(viewYear, viewMonth).toLocaleDateString('en-US', { month: 'long', year: 'numeric' })})
        </h3>
        {thisMonthBills.length === 0 && <p className="text-gray-400 text-center py-4">No generated bills this month. Add recurring bills above to auto-generate.</p>}
        <div className="space-y-2">
          {thisMonthBills.map(g => (
            <Card key={g.id} className="!p-4">
              <div className="flex justify-between items-center">
                <div>
                  <p className="text-sm font-medium text-gray-900 dark:text-white">{g.name}</p>
                  <div className="flex items-center gap-2 mt-1">
                    <Pill tone={statusTone[g.status]}>{g.status}</Pill>
                    <span className="text-xs text-gray-500 dark:text-gray-400">Due: {ordinalDay(g.dueDay || 1)}</span>
                  </div>
                </div>
                <div className="flex items-center gap-2">
                  <span className="text-sm font-bold text-gray-900 dark:text-white">${parseFloat(g.amount).toFixed(2)}</span>
                  {g.status === 'pending' && (
                    <React.Fragment>
                      <button onClick={() => confirmBill(g.id)} className="text-xs px-2 py-1 rounded bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 hover:bg-green-200 dark:hover:bg-green-800">Confirm</button>
                      <button onClick={() => skipBill(g.id)} className="text-xs px-2 py-1 rounded bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-600">Skip</button>
                    </React.Fragment>
                  )}
                  <button onClick={() => deleteGeneratedBill(g.id)} className="text-xs px-2 py-1 rounded bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300 hover:bg-red-200 dark:hover:bg-red-800">Delete</button>
                </div>
              </div>
            </Card>
          ))}
        </div>
      </div>
    </div>
  );
};

const UpcomingBillsView = ({ recurringBills, generatedBills, onSaveGenerated, onAddExpense }) => {
  const now = new Date();
  now.setHours(0, 0, 0, 0);

  const upcoming = React.useMemo(() => getUpcomingFromSchedule(recurringBills, generatedBills, 14), [recurringBills, generatedBills]);

  const groups = React.useMemo(() => {
    const todayBills = [], week = [], twoWeeks = [];
    upcoming.forEach(b => {
      const diffDays = Math.ceil((b.nextRenewalDate - now) / 86400000);
      if (diffDays <= 0) todayBills.push(b);
      else if (diffDays <= 7) week.push(b);
      else twoWeeks.push(b);
    });
    return [
      { label: "Due Today", bills: todayBills, tone: "danger" },
      { label: "Next 7 Days", bills: week, tone: "warn" },
      { label: "7–14 Days", bills: twoWeeks, tone: "info" },
    ];
  }, [upcoming]);

  const upsertGenerated = (bill, patch) => {
    if (bill.generatedId) {
      onSaveGenerated(generatedBills.map(g => g.id === bill.generatedId ? { ...g, ...patch } : g));
    } else {
      const newEntry = {
        id: Date.now() + '-' + Math.random().toString(36).slice(2),
        recurringBillId: bill.id, name: bill.name, category: bill.category,
        month: bill.month, year: bill.year, amount: bill.amount, dueDay: bill.dueDay,
        status: 'pending', confirmedDate: null, ...patch,
      };
      onSaveGenerated([...generatedBills, newEntry]);
    }
  };

  const confirmBill = (bill) => upsertGenerated(bill, { status: 'confirmed', confirmedDate: new Date().toISOString().split('T')[0] });
  const skipBill = (bill) => upsertGenerated(bill, { status: 'skipped' });
  // Confirms the bill AND records it as an expense so it shows up in transactions and spending charts.
  const confirmAndLog = (bill) => {
    confirmBill(bill);
    onAddExpense && onAddExpense({
      id: Date.now(),
      name: bill.name,
      amount: parseFloat(bill.amount) || 0,
      category: bill.category,
      date: toISODate(bill.nextRenewalDate),
      notes: "Logged from recurring bill",
      recurring: true,
    });
  };

  const statusTone = { pending: "warn", confirmed: "success", skipped: "gray" };
  const totalAmount = upcoming.reduce((s, b) => s + (parseFloat(b.amount) || 0), 0);

  return (
    <div className="space-y-6">
      <div className="flex justify-between items-center">
        <h3 className="text-lg font-semibold text-gray-900 dark:text-white">Due in Next 14 Days</h3>
        <span className="text-lg font-bold text-orange-600 dark:text-orange-400">${totalAmount.toFixed(2)} total</span>
      </div>

      {upcoming.length === 0 && <p className="text-gray-400 text-center py-8">No upcoming bills in the next 14 days</p>}

      {groups.map(group => (
        group.bills.length > 0 && (
          <div key={group.label}>
            <div className="flex items-center gap-2 mb-3">
              <Pill tone={group.tone}>{group.label}</Pill>
              <span className="text-xs text-gray-500 dark:text-gray-400">
                {group.bills.length} bill{group.bills.length !== 1 ? 's' : ''} · ${group.bills.reduce((s, b) => s + (parseFloat(b.amount) || 0), 0).toFixed(2)}
              </span>
            </div>
            <div className="space-y-2 mb-4">
              {group.bills.map(b => (
                <Card key={b.id + '-' + b.year + '-' + b.month} className="!p-4">
                  <div className="flex justify-between items-start mb-2">
                    <p className="text-sm font-medium text-gray-900 dark:text-white">{b.name}</p>
                    <span className="text-sm font-bold text-gray-900 dark:text-white ml-2 shrink-0">${parseFloat(b.amount).toFixed(2)}</span>
                  </div>
                  <div className="flex flex-wrap items-center gap-1.5 mb-2">
                    <Pill tone={statusTone[b.status]}>{b.status}</Pill>
                    <Pill tone="info">{b.category}</Pill>
                    <span className="text-xs text-gray-500 dark:text-gray-400">
                      Due: {b.nextRenewalDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
                    </span>
                    <span className="text-xs text-gray-400 dark:text-gray-500">{b.frequency}</span>
                  </div>
                  {b.status === 'pending' && (
                    <div className="flex flex-wrap gap-2">
                      <button onClick={() => confirmAndLog(b)} className="text-xs px-3 py-1 rounded bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 hover:bg-green-200 dark:hover:bg-green-800">Confirm + Log Expense</button>
                      <button onClick={() => confirmBill(b)} className="text-xs px-3 py-1 rounded bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-800">Confirm Only</button>
                      <button onClick={() => skipBill(b)} className="text-xs px-3 py-1 rounded bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-600">Skip</button>
                    </div>
                  )}
                </Card>
              ))}
            </div>
          </div>
        )
      ))}
    </div>
  );
};

const CategoriesView = ({ categories, onSave, expenses, recurringBills }) => {
  const [newCat, setNewCat] = React.useState("");
  const [newHousehold, setNewHousehold] = React.useState(false);
  const [editCat, setEditCat] = React.useState(null);

  const addCategory = () => {
    const name = newCat.trim();
    if (!name || categories.some(c => (c.name || c) === name)) return;
    onSave([...categories, { id: Date.now(), name, isDefault: false, isHousehold: newHousehold }]);
    setNewCat("");
    setNewHousehold(false);
  };

  const getCategoryUsageCount = (catName) => {
    const expCount = (expenses || []).filter(e => e.category === catName).length;
    const billCount = (recurringBills || []).filter(b => b.category === catName).length;
    return expCount + billCount;
  };

  const deleteCategory = (id, catName) => {
    const usageCount = getCategoryUsageCount(catName);
    if (usageCount > 0) {
      alert("Cannot delete category, used by " + usageCount + " item" + (usageCount !== 1 ? "s" : ""));
      return;
    }
    if (!window.confirm("Are you sure you want to delete this category?")) return;
    onSave(categories.filter(c => c.id !== id));
  };

  const startEdit = (c) => {
    setEditCat({ id: c.id, name: c.name || c, isHousehold: !!c.isHousehold, budget: c.budget != null ? String(c.budget) : "", color: c.color || "" });
  };

  const saveEdit = () => {
    if (!editCat.name?.trim()) return;
    const newName = editCat.name.trim();
    if (categories.some(c => c.id !== editCat.id && (c.name || c) === newName)) return;
    const budget = parseFloat(editCat.budget) > 0 ? parseFloat(editCat.budget) : null;
    onSave(categories.map(c => c.id === editCat.id ? { ...c, name: newName, isHousehold: editCat.isHousehold, budget, color: editCat.color || null } : c));
    setEditCat(null);
  };

  return (
    <div className="space-y-6">
      <div className="space-y-3">
        <div className="flex gap-3">
          <Input placeholder="New category name..." value={newCat} onChange={e => setNewCat(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter') addCategory(); }} />
          <BtnPrimary onClick={addCategory}>Add</BtnPrimary>
        </div>
        <div className="flex items-center gap-2 ml-1">
          <input type="checkbox" id="new-household" checked={newHousehold} onChange={e => setNewHousehold(e.target.checked)}
            className="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
          <label htmlFor="new-household" className="text-sm text-gray-600 dark:text-gray-400">Include in Household total</label>
        </div>
      </div>

      {editCat && (
        <Modal title="Edit Category" onClose={() => setEditCat(null)} onSubmit={saveEdit} submitLabel="Save">
          <div className="space-y-4">
            <div><Label required>Name</Label><Input value={editCat.name} onChange={e => setEditCat({...editCat, name: e.target.value})} autoFocus /></div>
            <div>
              <Label>Monthly budget ($)</Label>
              <Input type="number" step="0.01" inputMode="decimal" placeholder="No budget set" value={editCat.budget}
                onChange={e => setEditCat({...editCat, budget: e.target.value})} />
              <p className="text-xs text-gray-500 dark:text-gray-400 mt-1">Set a limit to track this category on the dashboard Budgets card.</p>
            </div>
            <div>
              <Label>Color</Label>
              <div className="flex flex-wrap gap-2">
                {CATEGORY_COLOR_PALETTE.map(col => (
                  <button key={col} type="button" onClick={() => setEditCat({...editCat, color: col})} aria-label={col}
                    className={col + " w-7 h-7 rounded-full " + ((editCat.color || CATEGORY_COLORS[editCat.name] || "bg-gray-500") === col ? "ring-2 ring-offset-2 ring-blue-500 dark:ring-offset-gray-800" : "hover:scale-110 transition-transform")} />
                ))}
              </div>
            </div>
            <div className="flex items-center gap-3">
              <input type="checkbox" id="edit-household" checked={editCat.isHousehold} onChange={e => setEditCat({...editCat, isHousehold: e.target.checked})}
                className="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
              <label htmlFor="edit-household" className="text-sm text-gray-700 dark:text-gray-300">Include in Household total on dashboard</label>
            </div>
          </div>
        </Modal>
      )}

      <div className="space-y-2">
        {categories.map(c => {
          const name = c.name || c;
          const isDefault = c.isDefault !== false;
          const isHousehold = !!c.isHousehold;
          const usageCount = getCategoryUsageCount(name);
          return (
            <Card key={c.id || name} className="!p-3">
              <div className="flex justify-between items-start mb-1.5">
                <div className="flex items-center gap-2">
                  <div className={(c.color || CATEGORY_COLORS[name] || "bg-gray-500") + " w-3 h-3 rounded-full shrink-0"}></div>
                  <span className="text-sm font-medium text-gray-900 dark:text-white">{name}</span>
                </div>
                <div className="flex items-center gap-2 ml-2 shrink-0">
                  <button onClick={() => startEdit(c)} className="text-blue-500 hover:text-blue-700 text-sm">Edit</button>
                  {!isDefault && (
                    <button onClick={() => deleteCategory(c.id, name)} className="text-red-500 hover:text-red-700 text-sm">Remove</button>
                  )}
                </div>
              </div>
              <div className="flex flex-wrap items-center gap-1.5">
                {isDefault && <Pill tone="gray">Default</Pill>}
                {isHousehold && <Pill tone="purple">Household</Pill>}
                {parseFloat(c.budget) > 0 && <Pill tone="success">${parseFloat(c.budget).toFixed(0)}/mo budget</Pill>}
                {usageCount > 0 && <span className="text-xs text-gray-400">{usageCount} item{usageCount !== 1 ? 's' : ''}</span>}
              </div>
            </Card>
          );
        })}
      </div>
    </div>
  );
};

const QuickAddModal = ({ categories, expenses, onSave, onClose }) => {
  const [draft, setDraft] = React.useState({
    name: "", amount: "", category: (categories[0] && (categories[0].name || categories[0])) || "Miscellaneous",
    date: new Date().toISOString().split("T")[0], recurring: false, notes: ""
  });
  const [catTouched, setCatTouched] = React.useState(false);
  const [addAnother, setAddAnother] = React.useState(false);
  const [justAdded, setJustAdded] = React.useState("");

  // Remembers the category used last time an expense with the same name was logged.
  const onNameChange = (val) => {
    const next = { ...draft, name: val };
    if (!catTouched && val.trim().length >= 2) {
      const q = val.trim().toLowerCase();
      const match = [...(expenses || [])]
        .sort((a, b) => (b.date || "").localeCompare(a.date || ""))
        .find(e => (e.name || "").trim().toLowerCase() === q);
      if (match && match.category) next.category = match.category;
    }
    setDraft(next);
  };

  const submit = () => {
    if (!draft.name?.trim() || !draft.amount) return;
    onSave({
      id: Date.now(),
      name: draft.name.trim(),
      amount: parseFloat(draft.amount),
      category: draft.category,
      date: draft.date,
      notes: draft.notes,
      recurring: draft.recurring
    });
    if (addAnother) {
      setJustAdded(draft.name.trim());
      setDraft({ ...draft, name: "", amount: "", notes: "", recurring: false });
      setCatTouched(false);
    } else {
      onClose();
    }
  };

  const catNames = categories.map(c => c.name || c);

  return (
    <Modal title="Quick Add Expense" onClose={onClose} onSubmit={submit} submitLabel="Add">
      <div className="space-y-4">
        {justAdded && <p className="text-sm text-green-600 dark:text-green-400 font-medium">Added "{justAdded}" ✓</p>}
        <div><Label required>Name</Label><Input placeholder="e.g. Groceries, Gas" value={draft.name} onChange={e => onNameChange(e.target.value)} autoFocus /></div>
        <div><Label required>Amount ($)</Label><Input type="number" step="0.01" inputMode="decimal" placeholder="0.00" value={draft.amount} onChange={e => setDraft({...draft, amount: e.target.value})} /></div>
        <div><Label required>Category</Label><Select value={draft.category} onChange={e => { setCatTouched(true); setDraft({...draft, category: e.target.value}); }}>{catNames.map(c => <option key={c} value={c}>{c}</option>)}</Select></div>
        <div><Label>Date</Label><Input type="date" value={draft.date} onChange={e => setDraft({...draft, date: e.target.value})} /></div>
        <div className="flex items-center gap-3">
          <input type="checkbox" id="qa-recurring" checked={draft.recurring} onChange={e => setDraft({...draft, recurring: e.target.checked})}
            className="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
          <label htmlFor="qa-recurring" className="text-sm text-gray-700 dark:text-gray-300">Recurring expense</label>
        </div>
        <div><Label>Notes</Label><Input placeholder="Optional notes" value={draft.notes} onChange={e => setDraft({...draft, notes: e.target.value})} /></div>
        <div className="flex items-center gap-3">
          <input type="checkbox" id="qa-add-another" checked={addAnother} onChange={e => setAddAnother(e.target.checked)}
            className="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
          <label htmlFor="qa-add-another" className="text-sm text-gray-700 dark:text-gray-300">Keep open to add another</label>
        </div>
      </div>
    </Modal>
  );
};

const BudgetScreen = () => {
  const [budgetView, setBudgetView] = React.useState("dashboard");
  const [budgetFilter, setBudgetFilter] = React.useState(null);
  const [showQuickAdd, setShowQuickAdd] = React.useState(false);

  const [expenses, setExpenses] = React.useState(() => Storage.getArray('familyhub_budget', 'expenses'));
  const [recurringBills, setRecurringBills] = React.useState(() => Storage.getArray('familyhub_budget', 'recurringBills'));
  const [generatedBills, setGeneratedBills] = React.useState(() => Storage.getArray('familyhub_budget', 'generatedBills'));
  const [categories, setCategories] = React.useState(() => {
    const stored = Storage.getArray('familyhub_budget', 'categories');
    if (stored.length > 0) return stored;
    return DEFAULT_BUDGET_CATEGORIES.map((name, i) => ({ id: i + 1, name, isDefault: true, isHousehold: DEFAULT_HOUSEHOLD.includes(name) }));
  });

  const [editExp, setEditExp] = React.useState(null);

  React.useEffect(() => {
    const needsMigration = categories.length > 0 && !categories.some(c => 'isHousehold' in c);
    if (needsMigration) {
      const migrated = categories.map(c => ({ ...c, isHousehold: DEFAULT_HOUSEHOLD.includes(c.name || c) }));
      setCategories(migrated);
      saveBudgetField('categories', migrated);
    } else if (categories.length > 0 && Storage.getArray('familyhub_budget', 'categories').length === 0) {
      saveBudgetField('categories', categories);
    }
    const now = new Date();
    const newGen = generateMonthlyBills(recurringBills, generatedBills, now.getMonth(), now.getFullYear());
    if (newGen.length) {
      const updated = [...generatedBills, ...newGen];
      setGeneratedBills(updated);
      saveBudgetField('generatedBills', updated);
    }
  }, []);

  const navigateTo = (view, filter) => {
    setBudgetView(view);
    setBudgetFilter(filter || null);
  };

  const saveExpenses = (val) => { setExpenses(val); saveBudgetField('expenses', val); };
  const saveRecurringBills = (val) => { setRecurringBills(val); saveBudgetField('recurringBills', val); };
  const saveGeneratedBills = (val) => { setGeneratedBills(val); saveBudgetField('generatedBills', val); };
  const saveCategories = (val) => { setCategories(val); saveBudgetField('categories', val); };

  const [undoExp, setUndoExp] = React.useState(null);
  const undoTimer = React.useRef(null);

  const addExpense = (exp) => { saveExpenses([...expenses, exp]); };
  const deleteExpense = (id) => {
    const exp = expenses.find(e => e.id === id);
    if (!exp) return;
    saveExpenses(expenses.filter(e => e.id !== id));
    setUndoExp(exp);
    if (undoTimer.current) clearTimeout(undoTimer.current);
    undoTimer.current = setTimeout(() => setUndoExp(null), 6000);
  };
  const undoDelete = () => {
    if (!undoExp) return;
    saveExpenses([...expenses, undoExp]);
    setUndoExp(null);
    if (undoTimer.current) clearTimeout(undoTimer.current);
  };
  const startEditExp = (e) => { setEditExp({ ...e, name: e.name || e.notes || '', amount: String(e.amount || '') }); };
  const saveEditExp = () => {
    if (!editExp.name?.trim() || !editExp.amount) return;
    saveExpenses(expenses.map(e => e.id === editExp.id ? { ...e, name: editExp.name.trim(), category: editExp.category, amount: parseFloat(editExp.amount), date: editExp.date, notes: editExp.notes } : e));
    setEditExp(null);
  };

  const catNames = categories.map(c => c.name || c);

  const tabs = [
    { id: "dashboard", label: "Dashboard" },
    { id: "transactions", label: "Transactions" },
    { id: "recurring", label: "Recurring Bills" },
    { id: "upcoming", label: "Upcoming" },
    { id: "categories", label: "Categories" },
  ];

  return (
    <section className="p-4 md:p-6">
      <div className="flex justify-between items-center mb-4">
        <h2 className="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white">Budget Tracker</h2>
        <BtnPrimary onClick={() => setShowQuickAdd(true)}>+ Quick Add</BtnPrimary>
      </div>

      <div className="flex gap-1 mb-6 overflow-x-auto pb-1 border-b border-gray-200 dark:border-gray-700 -mx-4 px-4 md:mx-0 md:px-0">
        {tabs.map(t => (
          <button key={t.id} onClick={() => navigateTo(t.id)}
            className={"whitespace-nowrap px-3 py-2 text-sm font-medium border-b-2 transition-colors " +
              (budgetView === t.id
                ? "border-blue-500 text-blue-600 dark:text-blue-400"
                : "border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200")}>
            {t.label}
          </button>
        ))}
      </div>

      {budgetView === "dashboard" && (
        <BudgetDashboard expenses={expenses} recurringBills={recurringBills} generatedBills={generatedBills} categories={categories}
          onNavigate={navigateTo} onQuickAdd={() => setShowQuickAdd(true)} />
      )}

      {budgetView === "transactions" && (
        <TransactionList expenses={expenses} categories={categories} onEdit={startEditExp} onDelete={deleteExpense}
          initialCategory={budgetFilter?.category} initialMonth={budgetFilter?.month} />
      )}

      {budgetView === "recurring" && (
        <RecurringBillsView recurringBills={recurringBills} generatedBills={generatedBills} categories={categories}
          onSaveBills={saveRecurringBills} onSaveGenerated={saveGeneratedBills} initialFilter={budgetFilter} />
      )}

      {budgetView === "upcoming" && (
        <UpcomingBillsView recurringBills={recurringBills} generatedBills={generatedBills} onSaveGenerated={saveGeneratedBills} onAddExpense={addExpense} />
      )}

      {budgetView === "categories" && (
        <CategoriesView categories={categories} onSave={saveCategories} expenses={expenses} recurringBills={recurringBills} />
      )}

      {showQuickAdd && (
        <QuickAddModal categories={categories} expenses={expenses} onSave={addExpense} onClose={() => setShowQuickAdd(false)} />
      )}

      {undoExp && (
        <div className="fixed bottom-4 left-1/2 -translate-x-1/2 z-50 bg-gray-900 text-white px-4 py-3 rounded-lg shadow-lg flex items-center gap-4">
          <span className="text-sm">Deleted "{undoExp.name || 'expense'}"</span>
          <button onClick={undoDelete} className="text-sm font-semibold text-blue-300 hover:text-blue-200">Undo</button>
        </div>
      )}

      {editExp && (
        <Modal title="Edit Expense" onClose={() => setEditExp(null)} onSubmit={saveEditExp} submitLabel="Save">
          <div className="space-y-4">
            <div><Label required>Name</Label><Input value={editExp.name || ''} onChange={e => setEditExp({...editExp, name: e.target.value})} autoFocus /></div>
            <div><Label required>Category</Label><Select value={editExp.category} onChange={e => setEditExp({...editExp, category: e.target.value})}>{catNames.map(c => <option key={c} value={c}>{c}</option>)}</Select></div>
            <div><Label required>Amount ($)</Label><Input type="number" step="0.01" value={editExp.amount} onChange={e => setEditExp({...editExp, amount: e.target.value})} /></div>
            <div><Label>Date</Label><Input type="date" value={editExp.date || ''} onChange={e => setEditExp({...editExp, date: e.target.value})} /></div>
            <div><Label>Notes</Label><Input value={editExp.notes || ''} onChange={e => setEditExp({...editExp, notes: e.target.value})} /></div>
          </div>
        </Modal>
      )}
    </section>
  );
};

window.BudgetScreen = BudgetScreen;
