// ledger-views.jsx — main content views

const { useState: useStateV, useMemo: useMemoV, useRef: useRefV } = React;

// ─────────────── TRANSACTIONS VIEW ───────────────
function TransactionsView({ txs, month, categoryFilter, setCategoryFilter, search, onOpenTx, onAddTx, onQuickAdd }) {
  const monthTxs = useMemoV(() => txs
    .filter(t => monthOf(t.date) === month)
    .filter(t => !categoryFilter || t.category === categoryFilter)
    .filter(t => !search || (t.desc || '').toLowerCase().includes(search.toLowerCase()) || (t.tags||[]).some(x => x.toLowerCase().includes(search.toLowerCase())))
    .sort((a, b) => b.date.localeCompare(a.date) || b.id.localeCompare(a.id)),
    [txs, month, categoryFilter, search]);

  const { income, spend, net } = useMemoV(() => totals(txs.filter(t => monthOf(t.date) === month)), [txs, month]);
  const totalBudget = BUDGETS.reduce((a,b) => a + b.amount, 0);
  const budgetLeft = totalBudget - spend;
  const budgetUsedPct = Math.min(100, (spend / totalBudget) * 100);

  const [typeFilter, setTypeFilter] = useStateV('all'); // all|in|out

  const filtered = useMemoV(() =>
    typeFilter === 'all' ? monthTxs : monthTxs.filter(t => t.type === typeFilter),
    [monthTxs, typeFilter]);

  // group by date for visual rhythm
  const grouped = useMemoV(() => {
    const g = [];
    let curDate = null;
    filtered.forEach(t => {
      if (t.date !== curDate) { g.push({ kind: 'sep', date: t.date }); curDate = t.date; }
      g.push({ kind: 'tx', tx: t });
    });
    return g;
  }, [filtered]);

  const noDataAtAll = txs.length === 0;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      {/* Summary strip */}
      <div style={{ padding: '20px 24px 16px', display: 'flex', flexDirection: 'column', gap: 14 }}>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 12, flexWrap: 'wrap' }}>
          <div className="display" style={{ fontSize: 30, lineHeight: 1.1 }}>
            {new Date(month + '-01').toLocaleDateString('en-GB', { month: 'long', year: 'numeric' })}
          </div>
          <span style={{ fontSize: 13, color: 'var(--ink-3)' }}>{monthTxs.length} transactions</span>
        </div>

        <div style={{
          display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 1,
          background: 'var(--line)', border: '1px solid var(--line)',
          borderRadius: 'var(--r-lg)', overflow: 'hidden',
        }}>
          <SumCard label="Net" value={net} tone="net" big />
          <SumCard label="Income" value={income} tone="pos" sign />
          <SumCard label="Spend" value={-spend} tone="neg" sign />
          <SumCard label="Budget left" value={budgetLeft} pct={budgetUsedPct} budget />
        </div>
      </div>

      {/* Quick add bar */}
      <QuickAddBar onAdd={onQuickAdd} onExpand={onAddTx} />

      {/* Filters */}
      <div style={{
        padding: '10px 24px', display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap',
        borderTop: '1px solid var(--line)',
      }}>
        <div style={{ display: 'flex', background: 'var(--surface-2)', borderRadius: 'var(--r-sm)', padding: 2 }}>
          {[['all','All'],['out','Spend'],['in','Income']].map(([v,l]) => (
            <button key={v} onClick={() => setTypeFilter(v)} style={{
              padding: '3px 12px', borderRadius: 4,
              background: typeFilter === v ? 'var(--surface)' : 'transparent',
              boxShadow: typeFilter === v ? 'var(--shadow-sm)' : 'none',
              fontSize: 12, fontWeight: 500,
              color: typeFilter === v ? 'var(--ink)' : 'var(--ink-3)',
            }}>{l}</button>
          ))}
        </div>
        <span className="chip"><Icon name="filter" size={11} /> filter</span>
        {categoryFilter && (
          <button onClick={() => setCategoryFilter(null)} className="chip accent" style={{ cursor: 'pointer' }}>
            <span className="cdot" style={{ background: CAT_MAP[categoryFilter].color }}></span>
            {CAT_MAP[categoryFilter].name}
            <Icon name="close" size={11} style={{ marginLeft: 2 }} />
          </button>
        )}
        <div style={{ flex: 1 }} />
        <span style={{ fontSize: 12, color: 'var(--ink-3)' }}>
          {filtered.length} of {monthTxs.length}
        </span>
        <span className="chip"><Icon name="export" size={11} /> CSV</span>
      </div>

      {/* List */}
      <div style={{ flex: 1, overflow: 'auto' }}>
        {noDataAtAll ? (
          <div style={{
            padding: '60px 24px', textAlign: 'center',
            display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10,
          }}>
            <div style={{
              width: 64, height: 64, borderRadius: 16,
              background: 'var(--accent-soft)', color: 'var(--accent)',
              display: 'grid', placeItems: 'center',
              fontFamily: 'var(--font-display)', fontSize: 36, lineHeight: 1,
            }}>£</div>
            <div style={{ fontFamily: 'var(--font-display)', fontSize: 24, marginTop: 6 }}>A clean slate</div>
            <div style={{ fontSize: 13.5, color: 'var(--ink-3)', maxWidth: 320 }}>
              Type your first transaction in the quick-add bar above, or hit <span className="kbd">N</span> to open the full form.
            </div>
          </div>
        ) : (
          <table className="table">
            <thead>
              <tr>
                <th style={{ width: 80 }}>Date</th>
                <th style={{ width: 200 }}>Category</th>
                <th>Description</th>
                <th style={{ width: 200 }}>Tags</th>
                <th className="num" style={{ width: 130 }}>Amount</th>
                <th style={{ width: 40 }}></th>
              </tr>
            </thead>
            <tbody>
              {grouped.map((g, i) => g.kind === 'sep' ? (
                <tr key={'s' + i} style={{ background: 'var(--surface-2)' }}>
                  <td colSpan={6} style={{ padding: '4px 12px', height: 'auto' }}>
                    <span className="micro">{dateGroupLabel(g.date)}</span>
                  </td>
                </tr>
              ) : (
                <TxRow key={g.tx.id} tx={g.tx} onClick={() => onOpenTx(g.tx)} />
              ))}
              {filtered.length === 0 && (
                <tr><td colSpan={6} style={{ height: 120, textAlign: 'center', color: 'var(--ink-3)' }}>
                  No transactions match these filters.
                </td></tr>
              )}
            </tbody>
          </table>
        )}
      </div>
    </div>
  );
}

function dateGroupLabel(iso) {
  const d = new Date(iso + 'T00:00:00');
  const today = new Date(); today.setHours(0,0,0,0);
  const diff = Math.round((today - d) / 86400000);
  const base = d.toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long' });
  if (diff === 0) return 'Today · ' + base;
  if (diff === 1) return 'Yesterday · ' + base;
  return base;
}

function SumCard({ label, value, tone, sign, big, pct, budget }) {
  const color = tone === 'pos' ? 'var(--pos)' : tone === 'neg' ? 'var(--neg)' : 'var(--ink)';
  return (
    <div style={{ background: 'var(--surface)', padding: '14px 18px' }}>
      <div className="micro" style={{ marginBottom: 4 }}>{label}</div>
      <div className="display tnum" style={{ fontSize: big ? 32 : 24, color, lineHeight: 1.15 }}>
        {budget ? fmt(value) : fmt(value, { sign })}
      </div>
      {budget && (
        <div style={{ marginTop: 8 }}>
          <div style={{ height: 4, background: 'var(--surface-2)', borderRadius: 999, overflow: 'hidden' }}>
            <div style={{ height: '100%', width: `${pct}%`, background: pct > 90 ? 'var(--neg)' : 'var(--accent)', borderRadius: 999 }}></div>
          </div>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 4 }} className="tnum">
            {pct.toFixed(0)}% used · {fmt0(BUDGETS.reduce((a,b)=>a+b.amount,0))} total
          </div>
        </div>
      )}
    </div>
  );
}

function TxRow({ tx, onClick }) {
  const cat = CAT_MAP[tx.category];
  const isIn = tx.type === 'in';
  return (
    <tr onClick={onClick} style={{ cursor: 'pointer' }}>
      <td className="mono tnum" style={{ fontSize: 12, color: 'var(--ink-3)' }}>{dateLabel(tx.date)}</td>
      <td>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
          <span className="cglyph" style={{ background: cat?.color, color: 'white', fontSize: 12 }}>{cat?.icon}</span>
          <span style={{ fontSize: 13 }}>{cat?.name}</span>
        </span>
      </td>
      <td>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span>{tx.desc}</span>
          {tx.recurring && <span title="Recurring" style={{ color: 'var(--ink-3)', display: 'inline-flex' }}><Icon name="repeat" size={12} /></span>}
          {tx.fuel && <span title="Fuel-up" style={{ color: 'var(--ink-3)', display: 'inline-flex' }} className="mono tnum">· {tx.fuel.litres}L</span>}
        </div>
      </td>
      <td>
        <div style={{ display: 'flex', gap: 3, flexWrap: 'wrap' }}>
          {(tx.tags || []).map(t => <span key={t} className="tag">{t}</span>)}
        </div>
      </td>
      <td className="num tnum" style={{ fontSize: 14, fontWeight: 500, color: isIn ? 'var(--pos)' : 'var(--ink)' }}>
        {isIn ? '+' : '−'}{fmt(tx.amount)}
      </td>
      <td>
        <div className="row-actions">
          <button className="btn ghost icon" onClick={e => { e.stopPropagation(); onClick(); }} title="Edit">
            <Icon name="edit" size={13} />
          </button>
        </div>
      </td>
    </tr>
  );
}

// ─────────────── QUICK ADD BAR ───────────────
function QuickAddBar({ onAdd, onExpand }) {
  const [amount, setAmount] = useStateV('');
  const [desc, setDesc] = useStateV('');
  const [cat, setCat] = useStateV('food');
  const [type, setType] = useStateV('out');
  const [date, setDate] = useStateV('2026-05-21');
  const [catOpen, setCatOpen] = useStateV(false);
  const amountRef = useRefV();

  const submit = () => {
    if (!amount) return;
    onAdd({
      id: 'tx' + Date.now(),
      date, type, category: cat,
      amount: parseFloat(amount),
      desc: desc || CAT_MAP[cat]?.name,
      tags: [],
    });
    setAmount(''); setDesc('');
    amountRef.current?.focus();
  };

  const c = CAT_MAP[cat];

  return (
    <div style={{
      padding: '0 24px 14px',
    }}>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 8,
        background: 'var(--surface)', border: '1px solid var(--line-2)',
        borderRadius: 'var(--r-lg)', padding: 6,
        boxShadow: 'var(--shadow-sm)',
      }}>
        {/* type toggle */}
        <button onClick={() => setType(t => t === 'out' ? 'in' : 'out')}
          title="Toggle type" style={{
            width: 30, height: 30, borderRadius: 'var(--r-sm)',
            background: type === 'out' ? 'var(--surface-2)' : 'var(--pos-soft)',
            color: type === 'out' ? 'var(--ink)' : 'var(--pos)',
            display: 'grid', placeItems: 'center', fontSize: 18, lineHeight: 1, fontWeight: 500,
          }}>
          {type === 'out' ? '−' : '+'}
        </button>

        {/* date */}
        <div className="field" style={{ width: 110, background: 'transparent', border: '0', padding: '0 4px' }}>
          <Icon name="calendar" size={13} style={{ color: 'var(--ink-3)', marginRight: 6 }} />
          <input type="date" value={date} onChange={e => setDate(e.target.value)} style={{ fontSize: 12.5 }} />
        </div>

        <span style={{ width: 1, alignSelf: 'stretch', background: 'var(--line)' }}></span>

        {/* amount */}
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 4, padding: '0 8px', minWidth: 110 }}>
          <span style={{ color: 'var(--ink-3)', fontSize: 16 }}>£</span>
          <input ref={amountRef} type="number" step="0.01" placeholder="0.00"
            value={amount} onChange={e => setAmount(e.target.value)}
            onKeyDown={e => e.key === 'Enter' && submit()}
            className="tnum"
            style={{ fontSize: 18, fontWeight: 500, width: 90 }}
          />
        </div>

        <span style={{ width: 1, alignSelf: 'stretch', background: 'var(--line)' }}></span>

        {/* category picker */}
        <div style={{ position: 'relative' }}>
          <button onClick={() => setCatOpen(o => !o)} className="btn ghost" style={{ padding: '5px 10px', gap: 6 }}>
            <span className="cglyph" style={{ background: c.color, color: 'white', fontSize: 11, width: 20, height: 20 }}>{c.icon}</span>
            <span style={{ fontSize: 13 }}>{c.name}</span>
            <Icon name="chevDown" size={12} style={{ color: 'var(--ink-3)' }} />
          </button>
          {catOpen && (
            <CategoryPopover type={type} value={cat} onChange={v => { setCat(v); setCatOpen(false); }} onClose={() => setCatOpen(false)} />
          )}
        </div>

        <span style={{ width: 1, alignSelf: 'stretch', background: 'var(--line)' }}></span>

        {/* description */}
        <input value={desc} onChange={e => setDesc(e.target.value)} placeholder="What was it?"
          onKeyDown={e => e.key === 'Enter' && submit()}
          style={{ flex: 1, fontSize: 13.5, padding: '0 6px', minWidth: 0 }} />

        <button className="btn ghost icon" onClick={onExpand} title="More options">
          <Icon name="edit" size={14} />
        </button>
        <button onClick={submit} className="btn accent" disabled={!amount} style={{ padding: '6px 14px' }}>
          Add <span className="kbd" style={{ background: 'rgba(255,255,255,.2)', borderColor: 'transparent', color: 'white', marginLeft: 2 }}>↵</span>
        </button>
      </div>
    </div>
  );
}

function CategoryPopover({ type, value, onChange, onClose }) {
  return (
    <>
      <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 20 }} />
      <div style={{
        position: 'absolute', top: 'calc(100% + 6px)', left: 0,
        width: 280, background: 'var(--surface)',
        border: '1px solid var(--line)', borderRadius: 'var(--r-md)',
        boxShadow: 'var(--shadow-lg)', zIndex: 21,
        padding: 6, animation: 'scaleIn .12s',
      }}>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 2 }}>
          {CATEGORIES.filter(c => c.kind === type || (type === 'xfer')).map(c => (
            <button key={c.id} onClick={() => onChange(c.id)} style={{
              display: 'flex', alignItems: 'center', gap: 8,
              padding: '6px 8px', borderRadius: 'var(--r-sm)',
              background: value === c.id ? 'var(--surface-2)' : 'transparent',
              fontSize: 13, textAlign: 'left',
            }}>
              <span className="cglyph" style={{ background: c.color, color: 'white', fontSize: 12, width: 22, height: 22 }}>{c.icon}</span>
              {c.name}
            </button>
          ))}
        </div>
      </div>
    </>
  );
}

// ─────────────── STATS VIEW ───────────────
function StatsView({ txs, month }) {
  const monthTxs = useMemoV(() => txs.filter(t => monthOf(t.date) === month), [txs, month]);
  const { income, spend, net } = totals(monthTxs);
  const byCat = useMemoV(() => spendByCategory(monthTxs), [monthTxs]);
  const series = useMemoV(() => sixMonthSeries(txs, month), [txs, month]);

  const total = byCat.reduce((a, b) => a + b.amount, 0) || 1;

  // pie geometry
  const R = 78, CX = 90, CY = 90;
  let acc = 0;
  const slices = byCat.map(s => {
    const start = acc / total;
    acc += s.amount;
    const end = acc / total;
    const a0 = start * Math.PI * 2 - Math.PI / 2;
    const a1 = end * Math.PI * 2 - Math.PI / 2;
    const large = (end - start) > 0.5 ? 1 : 0;
    const x0 = CX + Math.cos(a0) * R, y0 = CY + Math.sin(a0) * R;
    const x1 = CX + Math.cos(a1) * R, y1 = CY + Math.sin(a1) * R;
    const d = `M${CX},${CY} L${x0},${y0} A${R},${R} 0 ${large} 1 ${x1},${y1} Z`;
    return { ...s, d };
  });

  return (
    <div style={{ padding: 24, overflow: 'auto', height: '100%' }}>
      <div className="display" style={{ fontSize: 30, lineHeight: 1, marginBottom: 6 }}>Stats</div>
      <div style={{ fontSize: 13, color: 'var(--ink-3)', marginBottom: 20 }}>
        {new Date(month + '-01').toLocaleDateString('en-GB', { month: 'long', year: 'numeric' })}
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'minmax(320px, 1fr) minmax(400px, 2fr)', gap: 16, alignItems: 'start' }}>
        {/* Pie + breakdown */}
        <div style={{ background: 'var(--surface)', border: '1px solid var(--line)', borderRadius: 'var(--r-lg)', padding: 18 }}>
          <div className="micro" style={{ marginBottom: 4 }}>Spend by category</div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
            <div style={{ position: 'relative', flexShrink: 0 }}>
              <svg width="180" height="180" viewBox="0 0 180 180">
                <circle cx="90" cy="90" r="78" fill="var(--surface-2)" />
                {slices.map((s, i) => <path key={s.category} d={s.d} fill={s.cat.color} stroke="var(--surface)" strokeWidth="1.5" />)}
                <circle cx="90" cy="90" r="44" fill="var(--surface)" />
              </svg>
              <div style={{ position: 'absolute', inset: 0, display: 'grid', placeItems: 'center' }}>
                <div style={{ textAlign: 'center' }}>
                  <div className="micro">total spend</div>
                  <div className="display tnum" style={{ fontSize: 22 }}>{fmt0(total)}</div>
                </div>
              </div>
            </div>
            <div style={{ flex: 1, minWidth: 0 }}>
              {byCat.slice(0, 6).map(s => (
                <div key={s.category} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '4px 0' }}>
                  <span className="cdot" style={{ background: s.cat.color }}></span>
                  <span style={{ fontSize: 13, flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.cat.name}</span>
                  <span className="mono tnum" style={{ fontSize: 12, color: 'var(--ink-3)' }}>{((s.amount / total) * 100).toFixed(0)}%</span>
                  <span className="tnum" style={{ fontSize: 13, width: 70, textAlign: 'right' }}>{fmt(s.amount)}</span>
                </div>
              ))}
            </div>
          </div>
        </div>

        {/* 6-month bars */}
        <div style={{ background: 'var(--surface)', border: '1px solid var(--line)', borderRadius: 'var(--r-lg)', padding: 18 }}>
          <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between' }}>
            <div>
              <div className="micro">Last 6 months</div>
              <div className="display" style={{ fontSize: 22 }}>
                <span style={{ color: 'var(--pos)' }}>income</span>{' vs '}
                <span style={{ color: 'var(--accent)' }}>spend</span>
              </div>
            </div>
            <div className="tnum" style={{ fontSize: 13, color: 'var(--ink-3)' }}>
              avg net <b style={{ color: 'var(--ink)' }}>{fmt0(series.reduce((a,s)=>a+s.income-s.spend,0) / 6)}</b>/mo
            </div>
          </div>
          <BarChart series={series} />
        </div>
      </div>

      {/* Summary cards */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: 12, marginTop: 16 }}>
        <MiniStat label="Average daily spend" value={fmt(spend / 21)} sub="based on 21 days so far" />
        <MiniStat label="Biggest single expense" value={fmt(Math.max(...monthTxs.filter(t=>t.type==='out').map(t=>t.amount), 0))} sub="Rent · 15 May" />
        <MiniStat label="Most frequent category" value={byCat[0]?.cat.name || '—'} sub={`${monthTxs.filter(t=>t.category===byCat[0]?.category).length} transactions`} />
        <MiniStat label="Savings rate" value={`${((net / (income || 1)) * 100).toFixed(0)}%`} sub={fmt(net) + ' kept'} tone={net > 0 ? 'pos' : 'neg'} />
      </div>
    </div>
  );
}

function BarChart({ series }) {
  const max = Math.max(...series.flatMap(s => [s.income, s.spend]));
  const W = 480, H = 180, padL = 28, padR = 12, padT = 12, padB = 26;
  const groupW = (W - padL - padR) / series.length;
  const barW = (groupW - 8) / 2;
  return (
    <svg viewBox={`0 0 ${W} ${H}`} style={{ width: '100%', height: 'auto', marginTop: 10 }}>
      {/* y gridlines */}
      {[0, 0.5, 1].map(t => {
        const y = padT + (H - padT - padB) * (1 - t);
        return (
          <g key={t}>
            <line x1={padL} x2={W-padR} y1={y} y2={y} stroke="var(--line)" strokeDasharray={t === 0 ? '' : '2 3'} />
            <text x={padL - 6} y={y + 3} textAnchor="end" fontSize="9" fill="var(--ink-3)" fontFamily="var(--font-mono)">
              {t === 0 ? '0' : `${Math.round(max * t / 1000)}k`}
            </text>
          </g>
        );
      })}
      {series.map((s, i) => {
        const cx = padL + groupW * i + groupW / 2;
        const inH = (s.income / max) * (H - padT - padB);
        const outH = (s.spend / max) * (H - padT - padB);
        return (
          <g key={s.key}>
            <rect x={cx - barW - 2} y={H - padB - inH} width={barW} height={inH}
                  fill="var(--pos)" opacity="0.85" rx="2" />
            <rect x={cx + 2} y={H - padB - outH} width={barW} height={outH}
                  fill="var(--accent)" opacity="0.85" rx="2" />
            <text x={cx} y={H - 8} textAnchor="middle" fontSize="10" fill="var(--ink-3)" fontFamily="var(--font-mono)">{s.label}</text>
          </g>
        );
      })}
    </svg>
  );
}

function MiniStat({ label, value, sub, tone }) {
  const color = tone === 'pos' ? 'var(--pos)' : tone === 'neg' ? 'var(--neg)' : 'var(--ink)';
  return (
    <div style={{ background: 'var(--surface)', border: '1px solid var(--line)', borderRadius: 'var(--r-md)', padding: 14 }}>
      <div className="micro" style={{ marginBottom: 6 }}>{label}</div>
      <div className="display tnum" style={{ fontSize: 22, color }}>{value}</div>
      <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 2 }}>{sub}</div>
    </div>
  );
}

// ─────────────── BUDGETS VIEW ───────────────
function BudgetsView({ txs, month, budgets, onAddBudget, onEditBudget, onDeleteBudget }) {
  const monthTxs = useMemoV(() => txs.filter(t => monthOf(t.date) === month && t.type === 'out'), [txs, month]);
  const spentBy = useMemoV(() => {
    const m = {};
    monthTxs.forEach(t => { m[t.category] = (m[t.category] || 0) + t.amount; });
    return m;
  }, [monthTxs]);

  const totalBudget = budgets.reduce((a, b) => a + b.amount, 0);
  const totalSpent = Object.values(spentBy).reduce((a, b) => a + b, 0);

  return (
    <div style={{ padding: 24, overflow: 'auto', height: '100%' }}>
      <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', marginBottom: 16 }}>
        <div>
          <div className="display" style={{ fontSize: 30, lineHeight: 1 }}>Budgets</div>
          <div style={{ fontSize: 13, color: 'var(--ink-3)', marginTop: 4 }}>
            {new Date(month + '-01').toLocaleDateString('en-GB', { month: 'long', year: 'numeric' })} · {10} days remaining
          </div>
        </div>
        <button className="btn" onClick={onAddBudget}><Icon name="plus" size={13} /> New budget</button>
      </div>

      {/* Total ring */}
      <div style={{
        background: 'var(--surface)', border: '1px solid var(--line)', borderRadius: 'var(--r-lg)',
        padding: 24, display: 'flex', alignItems: 'center', gap: 24, marginBottom: 24,
      }}>
        <Ring spent={totalSpent} budget={totalBudget} size={140} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div className="micro">This month</div>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginTop: 4, flexWrap: 'wrap' }}>
            <span className="display tnum" style={{ fontSize: 36, lineHeight: 1 }}>{fmt0(totalSpent)}</span>
            <span style={{ color: 'var(--ink-3)', fontSize: 14 }}>of {fmt0(totalBudget)} budget</span>
          </div>
          <div style={{ display: 'flex', gap: 16, marginTop: 14, fontSize: 13, color: 'var(--ink-2)', flexWrap: 'wrap' }}>
            <span>📅 Pace: <b className="tnum">{fmt0(totalSpent / 21)}/day</b></span>
            <span>🏁 Projected: <b className="tnum">{fmt0((totalSpent / 21) * 31)}</b></span>
            <span>💷 Left: <b className="tnum" style={{ color: 'var(--pos)' }}>{fmt0(totalBudget - totalSpent)}</b></span>
          </div>
        </div>
      </div>

      {/* List of budgets */}
      <div style={{ background: 'var(--surface)', border: '1px solid var(--line)', borderRadius: 'var(--r-lg)', overflow: 'hidden' }}>
        {budgets.map((b, i) => {
          const cat = CAT_MAP[b.category];
          const spent = spentBy[b.category] || 0;
          const pct = (spent / b.amount) * 100;
          const danger = pct > 90;
          return (
            <div key={b.category} onClick={() => onEditBudget(b)} style={{
              padding: '14px 18px',
              borderTop: i === 0 ? 0 : '1px solid var(--line)',
              display: 'grid', gridTemplateColumns: '36px 1fr 90px 100px 28px', gap: 14, alignItems: 'center',
              cursor: 'pointer',
              transition: 'background .08s',
            }}
            onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
            onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
            >
              <span className="cglyph cglyph-lg" style={{ background: cat.color, color: 'white' }}>{cat.icon}</span>
              <div style={{ minWidth: 0 }}>
                <div style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
                  <span style={{ fontWeight: 500 }}>{cat.name}</span>
                  {danger && <span style={{ fontSize: 11, color: 'var(--neg)' }}>over pace</span>}
                </div>
                <div style={{ height: 6, background: 'var(--surface-2)', borderRadius: 999, marginTop: 6, overflow: 'hidden' }}>
                  <div style={{ height: '100%', width: `${Math.min(100, pct)}%`, background: danger ? 'var(--neg)' : cat.color, borderRadius: 999, transition: 'width .3s' }}></div>
                </div>
              </div>
              <div className="tnum" style={{ textAlign: 'right', fontSize: 13 }}>
                <span style={{ color: danger ? 'var(--neg)' : 'var(--ink)' }}>{fmt0(spent)}</span>
                <span style={{ color: 'var(--ink-3)' }}> / {fmt0(b.amount)}</span>
              </div>
              <div style={{ textAlign: 'right' }}>
                <span className="tnum" style={{ fontSize: 13, fontWeight: 500, color: danger ? 'var(--neg)' : 'var(--ink-2)' }}>
                  {pct.toFixed(0)}%
                </span>
              </div>
              <Icon name="chev" size={13} style={{ color: 'var(--ink-3)' }} />
            </div>
          );
        })}
        {budgets.length === 0 && (
          <div style={{ padding: '40px 18px', textAlign: 'center', color: 'var(--ink-3)' }}>
            <div style={{ fontSize: 32, marginBottom: 4 }}>🎯</div>
            <div style={{ fontSize: 14, marginBottom: 4 }}>No budgets yet</div>
            <div style={{ fontSize: 12, marginBottom: 14 }}>Set a cap on a category to keep an eye on it.</div>
            <button className="btn accent" onClick={onAddBudget}><Icon name="plus" size={13} /> New budget</button>
          </div>
        )}
      </div>
    </div>
  );
}

// ─────────────── BUDGET MODAL ───────────────
function BudgetModal({ budget, budgets, onClose, onSave, onDelete }) {
  const isNew = !budget?.category || !budgets.find(b => b.category === budget?.category);
  const [draft, setDraft] = useStateV(budget || { category: '', amount: 200 });
  if (!budget) return null;
  const taken = new Set(budgets.filter(b => b.category !== budget.category).map(b => b.category));
  const available = CATEGORIES.filter(c => c.kind === 'out' && !taken.has(c.id));
  const cat = CAT_MAP[draft.category];

  return (
    <>
      <div onClick={onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(40,30,20,.35)',
        backdropFilter: 'blur(2px)', zIndex: 50, animation: 'fadeIn .15s',
      }} />
      <div style={{
        position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
        width: 'min(440px, calc(100vw - 32px))',
        background: 'var(--bg)', border: '1px solid var(--line)',
        borderRadius: 'var(--r-xl)', boxShadow: 'var(--shadow-lg)', zIndex: 51,
        animation: 'scaleIn .16s', display: 'flex', flexDirection: 'column', maxHeight: '90vh',
      }}>
        <div style={{ padding: '18px 22px 14px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 12 }}>
          {cat ? (
            <span className="cglyph cglyph-lg" style={{ background: cat.color, color: 'white' }}>{cat.icon}</span>
          ) : (
            <span className="cglyph cglyph-lg" style={{ background: 'var(--surface-2)', color: 'var(--ink-3)' }}>🎯</span>
          )}
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="micro">{isNew ? 'New budget' : 'Edit budget'}</div>
            <div style={{ fontSize: 16, fontWeight: 500 }}>{cat?.name || 'Choose a category'}</div>
          </div>
          <button className="btn ghost icon" onClick={onClose}><Icon name="close" size={16} /></button>
        </div>

        <div style={{ padding: '18px 22px', display: 'flex', flexDirection: 'column', gap: 16, overflow: 'auto' }}>
          <div>
            <div className="micro" style={{ marginBottom: 8 }}>Category</div>
            {isNew ? (
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
                {available.map(c => (
                  <button key={c.id} onClick={() => setDraft(d => ({ ...d, category: c.id }))} style={{
                    display: 'inline-flex', alignItems: 'center', gap: 6,
                    padding: '5px 10px', borderRadius: 999,
                    border: draft.category === c.id ? `1px solid ${c.color}` : '1px solid var(--line-2)',
                    background: 'var(--surface)', fontSize: 12.5,
                    boxShadow: draft.category === c.id ? `inset 0 0 0 1px ${c.color}` : 'none',
                  }}>
                    <span style={{ fontSize: 14 }}>{c.icon}</span>
                    {c.name}
                  </button>
                ))}
                {available.length === 0 && (
                  <div style={{ fontSize: 12.5, color: 'var(--ink-3)', padding: '6px 0' }}>Every category already has a budget — edit those instead.</div>
                )}
              </div>
            ) : (
              <div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '6px 12px',
                  background: 'var(--surface-2)', borderRadius: 'var(--r-sm)' }}>
                <span className="cglyph" style={{ background: cat.color, color: 'white', fontSize: 12 }}>{cat.icon}</span>
                {cat.name}
              </div>
            )}
          </div>

          <div>
            <div className="micro" style={{ marginBottom: 6 }}>Monthly limit</div>
            <div className="field field-lg">
              <span style={{ color: 'var(--ink-3)', marginRight: 4, fontSize: 16 }}>£</span>
              <input
                type="number" step="1" min="0"
                value={draft.amount}
                onChange={e => setDraft(d => ({ ...d, amount: parseFloat(e.target.value) || 0 }))}
                className="tnum"
                style={{ fontSize: 18, fontWeight: 500 }}
                autoFocus
              />
              <span style={{ color: 'var(--ink-3)', fontSize: 12 }}>/ month</span>
            </div>
            <div style={{ display: 'flex', gap: 4, marginTop: 8, flexWrap: 'wrap' }}>
              {[50, 100, 200, 300, 500].map(v => (
                <button key={v} type="button" onClick={() => setDraft(d => ({ ...d, amount: v }))} className="chip" style={{
                  background: draft.amount === v ? 'var(--accent-soft)' : 'transparent',
                  color: draft.amount === v ? 'var(--accent-ink)' : 'var(--ink-2)',
                  borderColor: draft.amount === v ? 'transparent' : 'var(--line-2)',
                }}>£{v}</button>
              ))}
            </div>
          </div>
        </div>

        <div style={{
          padding: '12px 22px', borderTop: '1px solid var(--line)',
          display: 'flex', gap: 8, background: 'var(--surface)',
        }}>
          {!isNew && (
            <button className="btn ghost" onClick={() => onDelete(budget.category)} style={{ color: 'var(--neg)' }}>
              <Icon name="trash" size={14} /> Delete
            </button>
          )}
          <div style={{ flex: 1 }} />
          <button className="btn" onClick={onClose}>Cancel</button>
          <button className="btn accent" disabled={!draft.category || !draft.amount}
                  onClick={() => onSave(draft)}>
            {isNew ? 'Add budget' : 'Save changes'}
          </button>
        </div>
      </div>
    </>
  );
}

function Ring({ spent, budget, size = 120 }) {
  const r = size / 2 - 6;
  const c = 2 * Math.PI * r;
  const pct = Math.min(1, spent / budget);
  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
      <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="var(--surface-2)" strokeWidth="10" />
      <circle cx={size/2} cy={size/2} r={r} fill="none"
        stroke={pct > 0.9 ? 'var(--neg)' : 'var(--accent)'} strokeWidth="10" strokeLinecap="round"
        strokeDasharray={`${c * pct} ${c}`}
        transform={`rotate(-90 ${size/2} ${size/2})`} />
      <text x={size/2} y={size/2 - 4} textAnchor="middle" fontFamily="var(--font-display)" fontSize="22" fill="var(--ink)">{(pct * 100).toFixed(0)}%</text>
      <text x={size/2} y={size/2 + 14} textAnchor="middle" fontSize="10" fontFamily="var(--font-mono)" fill="var(--ink-3)" letterSpacing="0.06em">USED</text>
    </svg>
  );
}

// ─────────────── RECURRING VIEW ───────────────
function RecurringView({ recurring, onAdd, onEdit }) {
  return (
    <div style={{ padding: 24, overflow: 'auto', height: '100%' }}>
      <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', marginBottom: 16 }}>
        <div>
          <div className="display" style={{ fontSize: 30, lineHeight: 1 }}>Recurring</div>
          <div style={{ fontSize: 13, color: 'var(--ink-3)', marginTop: 4 }}>Auto-posted on schedule. Skip or edit any time.</div>
        </div>
        <button className="btn" onClick={onAdd}><Icon name="plus" size={13} /> New repeat</button>
      </div>
      <div style={{ background: 'var(--surface)', border: '1px solid var(--line)', borderRadius: 'var(--r-lg)', overflow: 'hidden' }}>
        {recurring.map((r, i) => {
          const cat = CAT_MAP[r.category];
          const isIn = cat?.kind === 'in';
          return (
            <div key={r.id} onClick={() => onEdit(r)} style={{
              padding: '14px 18px',
              borderTop: i === 0 ? 0 : '1px solid var(--line)',
              display: 'grid', gridTemplateColumns: '36px 1fr auto auto auto 24px', gap: 14, alignItems: 'center',
              cursor: 'pointer', transition: 'background .08s',
            }}
            onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
            onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
            >
              <span className="cglyph cglyph-lg" style={{ background: cat?.color, color: 'white' }}>{cat?.icon}</span>
              <div>
                <div style={{ fontWeight: 500 }}>{r.desc}</div>
                <div className="micro" style={{ marginTop: 2 }}>{cat?.name} · {r.cadence}</div>
              </div>
              <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>next</div>
              <div className="mono tnum" style={{ fontSize: 12 }}>{dateLabel(r.nextDate)}</div>
              <div className="tnum" style={{ fontWeight: 500, fontSize: 15, color: isIn ? 'var(--pos)' : 'var(--ink)', width: 90, textAlign: 'right' }}>
                {isIn ? '+' : '−'}{fmt(r.amount)}
              </div>
              <Icon name="chev" size={13} style={{ color: 'var(--ink-3)' }} />
            </div>
          );
        })}
        {recurring.length === 0 && (
          <div style={{ padding: '40px 18px', textAlign: 'center', color: 'var(--ink-3)' }}>
            <div style={{ fontSize: 32, marginBottom: 4 }}>🔁</div>
            <div style={{ fontSize: 14, marginBottom: 4 }}>No recurring transactions yet</div>
            <div style={{ fontSize: 12, marginBottom: 14 }}>Set up rent, salary, subscriptions… and they'll auto-log.</div>
            <button className="btn accent" onClick={onAdd}><Icon name="plus" size={13} /> New repeat</button>
          </div>
        )}
      </div>
    </div>
  );
}

// ─────────────── FUEL VIEW ───────────────
function FuelView({ txs, onAddFuel }) {
  const fuelTxs = txs.filter(t => t.category === 'fuel' && t.fuel?.odo).sort((a,b) => b.date.localeCompare(a.date));
  // compute mpl per fill (using prev odo)
  const enriched = fuelTxs.map((t, i) => {
    const prev = fuelTxs[i + 1];
    if (!prev) return { ...t, miles: null, mpl: null };
    const miles = t.fuel.odo - prev.fuel.odo;
    return { ...t, miles, mpl: miles / t.fuel.litres, ppl: t.amount / t.fuel.litres };
  });
  const valid = enriched.filter(t => t.mpl != null);
  const avgMpl = valid.reduce((a, t) => a + t.mpl, 0) / (valid.length || 1);
  const lastMpl = valid[0]?.mpl;

  return (
    <div style={{ padding: 24, overflow: 'auto', height: '100%' }}>
      <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', marginBottom: 16 }}>
        <div>
          <div className="display" style={{ fontSize: 30, lineHeight: 1 }}>⛽ Fuel log</div>
          <div style={{ fontSize: 13, color: 'var(--ink-3)', marginTop: 4 }}>Track every fill-up. mi/L is computed from your odometer.</div>
        </div>
        <button className="btn accent" onClick={onAddFuel}><Icon name="plus" size={13} /> Log fill-up</button>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 12, marginBottom: 20 }}>
        <FuelStat label="Average efficiency" big={`${avgMpl.toFixed(2)} mi/L`} sub={`${(avgMpl * 4.546).toFixed(1)} MPG UK`} />
        <FuelStat label="Last fill-up" big={`${lastMpl?.toFixed(2)} mi/L`} sub={dateLabel(valid[0]?.date || '')} tone={lastMpl > avgMpl ? 'pos' : 'neg'} />
        <FuelStat label="Spent on fuel · YTD" big={fmt0(fuelTxs.reduce((a,t)=>a+t.amount,0))} sub={`${fuelTxs.length} fill-ups`} />
        <FuelStat label="Total miles logged" big={`${(fuelTxs[0]?.fuel.odo - fuelTxs[fuelTxs.length-1]?.fuel.odo || 0)} mi`} sub={`since ${dateLabel(fuelTxs[fuelTxs.length-1]?.date)}`} />
      </div>

      <div style={{ background: 'var(--surface)', border: '1px solid var(--line)', borderRadius: 'var(--r-lg)', overflow: 'hidden' }}>
        <table className="table">
          <thead>
            <tr>
              <th style={{ width: 80 }}>Date</th>
              <th>Station</th>
              <th className="num">Litres</th>
              <th className="num">Odometer</th>
              <th className="num">Trip</th>
              <th className="num" style={{ color: 'var(--accent-ink)' }}>mi/L</th>
              <th className="num">£/L</th>
              <th className="num">Cost</th>
            </tr>
          </thead>
          <tbody>
            {enriched.map(t => (
              <tr key={t.id}>
                <td className="mono tnum" style={{ fontSize: 12, color: 'var(--ink-3)' }}>{dateLabel(t.date)}</td>
                <td>{t.desc}</td>
                <td className="num tnum">{t.fuel.litres.toFixed(1)} L</td>
                <td className="num tnum">{t.fuel.odo.toLocaleString()}</td>
                <td className="num tnum" style={{ color: 'var(--ink-3)' }}>{t.miles ? `${t.miles} mi` : '—'}</td>
                <td className="num tnum" style={{ fontWeight: 500, color: t.mpl == null ? 'var(--ink-3)' : t.mpl > avgMpl ? 'var(--pos)' : 'var(--ink)' }}>
                  {t.mpl ? t.mpl.toFixed(2) : '—'}
                </td>
                <td className="num tnum" style={{ color: 'var(--ink-3)' }}>{(t.amount / t.fuel.litres).toFixed(3)}</td>
                <td className="num tnum" style={{ color: 'var(--neg)' }}>−{fmt(t.amount)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function FuelStat({ label, big, sub, tone }) {
  const color = tone === 'pos' ? 'var(--pos)' : tone === 'neg' ? 'var(--neg)' : 'var(--ink)';
  return (
    <div style={{ background: 'var(--surface)', border: '1px solid var(--line)', borderRadius: 'var(--r-md)', padding: 14 }}>
      <div className="micro" style={{ marginBottom: 6 }}>{label}</div>
      <div className="display tnum" style={{ fontSize: 22, color }}>{big}</div>
      <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 2 }}>{sub}</div>
    </div>
  );
}

Object.assign(window, {
  TransactionsView, StatsView, BudgetsView, BudgetModal, RecurringView, FuelView,
  QuickAddBar, TxRow, SumCard,
});
