/* Clear Mint — Calendar (financial timeline)
   A premium Google/Apple-Calendar-style view WIRED to live CM data:
   income & expenses come from the bank ledger (CM.S.bankTx), bills from
   CM.upcomingBills()/CM.S.bills, subscriptions from CM.S.subscriptions.
   Month / Week / Day / Agenda views, KPI bar, filters, insights panel, AI strip.
   Exposes window.PageCalendar. Loaded after cm-pages-core.jsx.
   Depends on cm-components (CMIcon), cm-an-charts (AnLine), cm-charts (DonutChart). */
(function () {
  const I = window.CMIcon || {};
  const LineChart = window.AnLine, Donut = window.DonutChart, Legend = window.Legend;

  const G = '#10915F', RD = '#C35B5B', GOLD = '#C99A2E', BL = '#3E7CC4',
        PU = '#8A6E9E', AM = '#C8742E', TEAL = '#0E9F8E', SL = '#94A29B', EM = '#16A34A';
  const usd  = n => (n < 0 ? '-' : '+') + '$' + Math.abs(Number(n || 0)).toLocaleString('en-CA', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  const usd0 = n => '$' + Math.round(Math.abs(Number(n || 0))).toLocaleString('en-CA');
  const key = d => d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate();
  const sameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
  const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
  const DOW = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];

  /* ---- category → icon/color for events ---- */
  const CAT = {
    Salary:{c:G,ic:'Dollar'}, Payroll:{c:G,ic:'Dollar'}, Income:{c:G,ic:'Dollar'}, Freelance:{c:G,ic:'Dollar'},
    Groceries:{c:RD,ic:'Cart'}, 'Food & Dining':{c:RD,ic:'Dining'}, Dining:{c:RD,ic:'Dining'},
    Transport:{c:AM,ic:'Transport'}, Transportation:{c:AM,ic:'Transport'}, Fuel:{c:AM,ic:'Transport'},
    Housing:{c:BL,ic:'House'}, Rent:{c:BL,ic:'House'}, Mortgage:{c:BL,ic:'House'},
    Utilities:{c:TEAL,ic:'Bolt'}, Insurance:{c:PU,ic:'Shield'}, Shopping:{c:BL,ic:'Cart'},
    Entertainment:{c:PU,ic:'Film'}, Subscriptions:{c:AM,ic:'Repeat'}, Investments:{c:EM,ic:'Grow'},
    'Credit Card Payment':{c:GOLD,ic:'Card'},
  };
  const meta = c => CAT[c] || { c: SL, ic: 'Dots' };
  const Ico = (name, props) => (I[name] ? React.createElement(I[name], props) : (I.Dots ? React.createElement(I.Dots, props) : null));

  /* ============================ build events from live data ============================ */
  function buildEvents(CM) {
    const evs = [];
    (CM.S.bankTx || []).forEach(t => {
      const d = t.date ? new Date(t.date) : null; if (!d || isNaN(d)) return;
      const inc = +t.cr || 0, exp = +t.dr || 0;
      if (inc > 0) evs.push({ date: d, type: 'income', label: t.desc || t.cat || 'Income', cat: t.cat || 'Income', amount: inc });
      else if (exp > 0) evs.push({ date: d, type: 'expense', label: t.desc || t.cat || 'Expense', cat: t.cat || 'Other', amount: -exp });
    });
    // bills (due dates) — project recurring bills forward up to 12 months
    let bills = [];
    try { bills = CM.upcomingBills ? (CM.upcomingBills() || []) : []; } catch (e) {}
    if (!bills.length) bills = (CM.S.bills || []).map(b => ({ dueDate: b.date || b.dueDate, amount: b.amount, cat: b.cat, name: b.desc || b.name, recur: b.recur !== false, _projected: b._projected }));
    bills = bills.filter(b => !b._projected);   // stored 24-month projections render via the virtual loop below — skip to avoid duplicates
    bills.forEach(b => {
      const d0 = b.dueDate ? new Date(b.dueDate) : null; if (!d0 || isNaN(d0)) return;
      const reps = (b.recur === false || /one.?time/i.test(String(b.recur||''))) ? 1 : 24;
      for (let i = 0; i < reps; i++) {
        const d = new Date(d0.getFullYear(), d0.getMonth() + i, d0.getDate());
        evs.push({ date: d, type: 'bill', label: (b.name || b.cat || 'Bill') + ' due', cat: b.cat || 'Bill', amount: -(+b.amount || 0), recur: reps > 1 });
      }
    });
    // subscriptions (renewals) — project monthly renewals forward up to 12 months
    (CM.S.subscriptions || []).forEach(s => {
      const base = s.nextDate || s.date || s.renew; const d0 = base ? new Date(base) : null;
      if (!d0 || isNaN(d0)) return;
      const annual = (s.cycle === 'yearly' || s.cycle === 'annual');
      const reps = annual ? 2 : 24;
      for (let i = 0; i < reps; i++) {
        const d = new Date(d0.getFullYear(), d0.getMonth() + (annual ? i * 12 : i), d0.getDate());
        evs.push({ date: d, type: 'sub', label: (s.name || 'Subscription') + ' renews', cat: 'Subscriptions', amount: -(+s.price || 0), recur: true });
      }
    });
    // recurring earnings (paycheques etc. detected from OFX or added manually) — project 24 months forward
    (CM.S.earnings || []).forEach(e => {
      if (e._projected) return;   // stored projections are covered by this virtual loop — skip to avoid duplicates
      const rec = String(e.recur || (e.recurring ? 'Monthly' : '') || '');
      if (!rec || /one.?time/i.test(rec)) return;
      const base = e.nextDate || e.date; const d0 = base ? new Date(base) : null;
      if (!d0 || isNaN(d0)) return;
      const cyc = String(e.cycle || rec).toLowerCase();
      const stepDays = /biweek/.test(cyc) ? 14 : (/week/.test(cyc) ? 7 : 0);
      const monthStep = /quarter/.test(cyc) ? 3 : (/year|annual/.test(cyc) ? 12 : 1);
      const horizon = new Date(); horizon.setMonth(horizon.getMonth() + 24);
      const today0 = new Date(); today0.setHours(0, 0, 0, 0);
      let d = new Date(d0), guard = 0;
      const step = dt => stepDays ? new Date(dt.getTime() + stepDays * 864e5) : new Date(dt.getFullYear(), dt.getMonth() + monthStep, dt.getDate());
      while (d < today0 && guard++ < 800) d = step(d);
      guard = 0;
      while (d <= horizon && guard++ < 800) {
        evs.push({ date: new Date(d), type: 'income', label: e.desc || e.source || 'Income', cat: e.cat || 'Income', amount: +e.amount || 0, recur: true });
        d = step(d);
      }
    });
    return evs;
  }
  const COLORS = { income: G, expense: RD, bill: GOLD, sub: AM };

  /* ============================ component ============================ */
  function PageCalendar() {
    const CM = window.useStore ? window.useStore() : window.CM;
    const today = CM.today ? CM.today() : new Date();
    const allEvents = React.useMemo(() => buildEvents(CM), [CM]);

    const [cursor, setCursor] = React.useState(new Date(today.getFullYear(), today.getMonth(), 1));
    const [view, setView] = React.useState('Month');
    const [range, setRange] = React.useState('thismonth');
    const [filters, setFilters] = React.useState({ income: true, expense: true, bill: true, sub: true });
    const [filterOpen, setFilterOpen] = React.useState(false);
    const [rangeOpen, setRangeOpen] = React.useState(false);
    const [sel, setSel] = React.useState(null); // selected day {date}

    const events = allEvents.filter(e => filters[e.type]);
    const byDay = React.useMemo(() => {
      const m = {}; events.forEach(e => { const k = key(e.date); (m[k] = m[k] || []).push(e); }); return m;
    }, [events]);

    /* ----- date-range window (drives KPIs, insights, alerts & the surface) ----- */
    const RANGES = [
      { id:'thismonth', label:'This Month' }, { id:'nextmonth', label:'Next Month' },
      { id:'next30', label:'Next 30 Days' }, { id:'next60', label:'Next 60 Days' }, { id:'next90', label:'Next 90 Days' },
      { id:'thisyear', label:'This Year' },
    ];
    const isMonthRange = range === 'thismonth' || range === 'nextmonth';
    const win = React.useMemo(() => {
      const t = today;
      if (range === 'next30' || range === 'next60' || range === 'next90') {
        const days = +range.slice(4); const s = new Date(t.getFullYear(), t.getMonth(), t.getDate());
        const e = new Date(s); e.setDate(s.getDate() + days); e.setHours(23,59,59);
        return { start: s, end: e, label: 'Next ' + days + ' Days', span: days };
      }
      if (range === 'thisyear') return { start: new Date(t.getFullYear(),0,1), end: new Date(t.getFullYear(),11,31,23,59,59), label: t.getFullYear() + '', span: 365 };
      // month-based: thismonth (cursor) / nextmonth
      const base = range === 'nextmonth' ? new Date(t.getFullYear(), t.getMonth()+1, 1) : cursor;
      return { start: new Date(base.getFullYear(), base.getMonth(), 1), end: new Date(base.getFullYear(), base.getMonth()+1, 0, 23,59,59), label: MONTHS[base.getMonth()] + ' ' + base.getFullYear(), span: 31, monthRef: base };
    }, [range, cursor, today]);
    const gridMonth = win.monthRef || cursor;

    /* ----- month grid cells (for the active month) ----- */
    const monthStart = new Date(gridMonth.getFullYear(), gridMonth.getMonth(), 1);
    const gridStart = new Date(monthStart); gridStart.setDate(1 - monthStart.getDay());
    const cells = []; for (let i = 0; i < 42; i++) { const d = new Date(gridStart); d.setDate(gridStart.getDate() + i); cells.push(d); }

    /* ----- figures over the selected window ----- */
    const inWin = d => d >= win.start && d <= win.end;
    const winEvents = events.filter(e => inWin(e.date));
    const income = winEvents.filter(e => e.type === 'income').reduce((s, e) => s + e.amount, 0);
    const expenses = winEvents.filter(e => e.type === 'expense').reduce((s, e) => s + Math.abs(e.amount), 0);
    const net = income - expenses;
    const upBills = winEvents.filter(e => (e.type === 'bill' || e.type === 'sub') && e.date >= today).reduce((s, e) => s + Math.abs(e.amount), 0);
    const upIncome = winEvents.filter(e => e.type === 'income' && e.date >= today).reduce((s, e) => s + e.amount, 0);
    const savings = income ? Math.round(net / income * 100) : 0;
    const footLabel = isMonthRange ? 'in ' + win.label.split(' ')[0] : 'in range';

    const go = n => { setRange('thismonth'); setCursor(new Date(gridMonth.getFullYear(), gridMonth.getMonth() + n, 1)); };
    const goToday = () => { setRange('thismonth'); setCursor(new Date(today.getFullYear(), today.getMonth(), 1)); };
    const pickMonth = (mi) => { setRange('thismonth'); setCursor(new Date(cursor.getFullYear(), mi, 1)); };
    const pickYear = (yr) => { setRange('thismonth'); setCursor(new Date(yr, cursor.getMonth(), 1)); };

    /* ----- insights (over the window) ----- */
    const upcomingInWin = winEvents.filter(e => e.date >= today).sort((a, b) => a.date - b.date);
    const weekAhead = upcomingInWin.slice(0, 6);
    const catTotals = {}; winEvents.filter(e => e.type === 'expense').forEach(e => { catTotals[e.cat] = (catTotals[e.cat] || 0) + Math.abs(e.amount); });
    const topCats = Object.keys(catTotals).map(k => ({ label: k, value: catTotals[k], color: meta(k).c })).sort((a, b) => b.value - a.value).slice(0, 6);
    const forecast = React.useMemo(() => {
      const days = Math.min(120, Math.max(30, win.span || 30));
      try { const cf = CM.last6CashFlow ? CM.last6CashFlow() : []; const last = cf[cf.length - 1] || { inc: income, exp: expenses };
        const dayNet = ((last.inc || income) - (last.exp || expenses)) / 30; let bal = CM.totalCash ? CM.totalCash() : 0; const out = [];
        for (let i = 0; i < days; i++) { bal += dayNet; out.push(Math.round(bal)); } return out;
      } catch (e) { return []; }
    }, [CM, income, expenses, win.span]);
    /* ----- financial alerts: upcoming bills/subs across the whole selected window ----- */
    const alerts = [];
    upcomingInWin.filter(e => e.type === 'bill' || e.type === 'sub').slice(0, 5).forEach(e => {
      const days = Math.round((e.date - today) / 864e5);
      alerts.push({ tone: days <= 1 ? RD : (days <= 7 ? GOLD : SL), text: e.label + ' (' + usd0(Math.abs(e.amount)) + ')' + (days <= 0 ? ' — due today' : days === 1 ? ' — due tomorrow' : ' — in ' + days + ' days') });
    });
    if (winEvents.length === 0) alerts.push({ tone: SL, text: 'No financial events in this range yet.' });
    else if (net > 0) alerts.push({ tone: G, text: 'Net positive cash flow of ' + usd0(net) + ' across ' + win.label + '.' });
    else if (net < 0) alerts.push({ tone: RD, text: 'Spending exceeds income by ' + usd0(Math.abs(net)) + ' in ' + win.label + '.' });

    /* ----- styles ----- */
    const card = { background: 'var(--surface,#fff)', border: '1px solid var(--line,#ECE6D8)', borderRadius: 16, boxShadow: '0 10px 30px -24px rgba(11,40,30,.4)' };

    const Tab = (label) => (
      <button key={label} onClick={() => setView(label)} style={{ border: 0, background: view === label ? 'var(--deep,#0F3D2C)' : 'transparent', color: view === label ? '#fff' : 'var(--ink-500,#4C5C53)', fontWeight: 700, fontSize: 13, padding: '8px 16px', borderRadius: 9, cursor: 'pointer', fontFamily: 'inherit' }}>{label}</button>
    );
    const FilterChip = (k, label, color) => (
      <label key={k} style={{ display: 'flex', alignItems: 'center', gap: 7, fontSize: 13, fontWeight: 600, color: 'var(--ink-700,#33433B)', cursor: 'pointer', padding: '6px 4px' }}>
        <input type="checkbox" checked={filters[k]} onChange={() => setFilters(f => ({ ...f, [k]: !f[k] }))} />
        <span style={{ width: 10, height: 10, borderRadius: 3, background: color, display: 'inline-block' }}></span>{label}
      </label>
    );

    /* ----- day cell ----- */
    function DayCell(d, big) {
      const isOther = d.getMonth() !== cursor.getMonth();
      const isToday = sameDay(d, today);
      const list = (byDay[key(d)] || []).slice().sort((a, b) => Math.abs(b.amount) - Math.abs(a.amount));
      const dIn = list.filter(e => e.amount > 0).reduce((s, e) => s + e.amount, 0);
      const dOut = list.filter(e => e.amount < 0).reduce((s, e) => s + Math.abs(e.amount), 0);
      const show = list.slice(0, big ? 8 : 3);
      return (
        <div key={key(d)} onClick={() => list.length && setSel({ date: d, list })}
          style={{ minHeight: big ? 150 : 116, padding: 8, borderRight: '1px solid var(--line,#EEEAE0)', borderBottom: '1px solid var(--line,#EEEAE0)',
            background: isOther ? 'var(--paper,#FAF8F2)' : '#fff', opacity: isOther ? .55 : 1, cursor: list.length ? 'pointer' : 'default', position: 'relative', overflow: 'hidden' }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 5 }}>
            <span className="num" style={{ fontSize: 12.5, fontWeight: 700, color: isToday ? '#fff' : (isOther ? 'var(--ink-400,#9AA7A0)' : 'var(--ink-700,#33433B)'),
              background: isToday ? G : 'transparent', width: isToday ? 22 : 'auto', height: 22, borderRadius: 999, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', padding: isToday ? 0 : '0 2px', minWidth: 22 }}>{d.getDate()}</span>
            {list.length > (big ? 8 : 3) ? <span style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--ink-400,#9AA7A0)' }}>+{list.length - (big ? 8 : 3)}</span> : null}
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
            {show.map((e, i) => (
              <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 5, fontSize: 11, lineHeight: 1.2 }}>
                <span style={{ width: 5, height: 5, borderRadius: 999, background: COLORS[e.type], flex: '0 0 5px' }}></span>
                <span style={{ flex: 1, minWidth: 0, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', color: 'var(--ink-700,#33433B)', fontWeight: 600 }}>{e.label}</span>
                <span className="num" style={{ fontWeight: 700, color: e.amount > 0 ? G : 'var(--ink-500,#5b6a61)', flex: '0 0 auto' }}>{usd(e.amount)}</span>
              </div>
            ))}
          </div>
          {(dIn || dOut) && !big ? (
            <div style={{ position: 'absolute', left: 8, right: 8, bottom: 6, display: 'flex', justifyContent: 'space-between', fontSize: 10, fontWeight: 700 }}>
              {dIn ? <span style={{ color: G }} className="num">+{usd0(dIn)}</span> : <span></span>}
              {dOut ? <span style={{ color: RD }} className="num">-{usd0(dOut)}</span> : null}
            </div>
          ) : null}
        </div>
      );
    }

    /* ----- agenda rows ----- */
    function Agenda(days) {
      const groups = {};
      const list = days ? events.filter(e => { const dd = (e.date - today) / 864e5; return dd >= 0 && dd < days; }) : winEvents;
      list.sort((a, b) => a.date - b.date).forEach(e => { const k = key(e.date); (groups[k] = groups[k] || []).push(e); });
      const keys = Object.keys(groups);
      if (!keys.length) return <div style={{ ...card, padding: 32, textAlign: 'center', color: 'var(--ink-400,#9AA7A0)', fontWeight: 600 }}>No financial events in this range.</div>;
      return (
        <div style={{ ...card, padding: 6 }}>
          {keys.map(k => { const d = groups[k][0].date; return (
            <div key={k} style={{ display: 'flex', gap: 16, padding: '14px 16px', borderBottom: '1px solid var(--line,#EEEAE0)' }}>
              <div style={{ flex: '0 0 64px', textAlign: 'center' }}>
                <div style={{ fontSize: 11, fontWeight: 700, color: 'var(--ink-400,#9AA7A0)', textTransform: 'uppercase' }}>{DOW[d.getDay()]}</div>
                <div className="num" style={{ fontSize: 22, fontWeight: 800, color: sameDay(d, today) ? G : 'var(--ink-900,#16241D)' }}>{d.getDate()}</div>
              </div>
              <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 8 }}>
                {groups[k].map((e, i) => (
                  <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                    <span style={{ width: 30, height: 30, borderRadius: 8, background: meta(e.cat).c + '1A', color: COLORS[e.type], display: 'flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 30px' }}>{Ico(meta(e.cat).ic, { size: 15 })}</span>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: 13.5, fontWeight: 700, color: 'var(--ink-900,#16241D)' }}>{e.label}{e.recur ? <span style={{ marginLeft: 6, color: 'var(--ink-400,#9AA7A0)' }} title="Recurring">{Ico('Repeat', { size: 12 })}</span> : null}</div>
                      <div style={{ fontSize: 12, color: 'var(--ink-400,#7B8A80)' }}>{e.cat}</div>
                    </div>
                    <span className="num" style={{ fontWeight: 800, fontSize: 14, color: e.amount > 0 ? G : RD }}>{usd(e.amount)}</span>
                  </div>
                ))}
              </div>
            </div>
          ); })}
        </div>
      );
    }

    /* ----- week grid (7 cells for the cursor's week of today, or month's current week) ----- */
    function weekCells() {
      const base = (cursor.getMonth() === today.getMonth() && cursor.getFullYear() === today.getFullYear()) ? new Date(today) : new Date(cursor);
      const s = new Date(base); s.setDate(base.getDate() - base.getDay());
      const out = []; for (let i = 0; i < 7; i++) { const d = new Date(s); d.setDate(s.getDate() + i); out.push(d); } return out;
    }

    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
        {/* AI insight strip */}
        <div style={{ ...card, background: 'linear-gradient(135deg,#0F3D2C,#0A2418)', border: '1px solid rgba(212,175,55,.2)', padding: '14px 18px', display: 'flex', alignItems: 'center', gap: 14 }}>
          <span style={{ width: 38, height: 38, borderRadius: 11, background: 'rgba(212,175,55,.16)', color: '#E7C763', display: 'flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 38px' }}>{Ico('Spark', { size: 19 }) || '✦'}</span>
          <div style={{ flex: 1, color: '#EAF1EC', fontSize: 14, fontWeight: 500 }}>
            <b style={{ color: '#fff' }}>{winEvents.length === 0 ? 'No financial events in ' + win.label + ' yet.' : (net >= 0 ? "You're net positive by " + usd0(net) + ' across ' + win.label + '.' : 'Spending outpaces income in ' + win.label + '.')}</b>
            <span style={{ color: 'rgba(232,239,233,.7)' }}> {upcomingInWin.filter(e => e.type === 'bill' || e.type === 'sub').length} bills/renewals upcoming · {usd0(upIncome)} income expected.</span>
          </div>
        </div>

        {/* KPI bar — brand-canonical MetricCard tiles (consistent with every other page) */}
        {window.MetricCard ? (
          <div className="cm-grid-6 cm-mb-24">
            <window.MetricCard label="Total Income" labelColor="var(--green-600)" value={usd0(income)} sub={footLabel}/>
            <window.MetricCard label="Total Expenses" labelColor="var(--red-500)" value={usd0(expenses)} sub={footLabel}/>
            <window.MetricCard label="Net Cash Flow" labelColor={net >= 0 ? 'var(--green-600)' : 'var(--red-500)'} value={(net < 0 ? '-' : '+') + usd0(net)} sub={footLabel}/>
            <window.MetricCard label="Upcoming Bills" labelColor="var(--gold)" value={usd0(upBills)} sub={footLabel}/>
            <window.MetricCard label="Upcoming Income" labelColor="var(--green-600)" value={usd0(upIncome)} sub={footLabel}/>
            <window.MetricCard label="Savings Rate" labelColor={savings >= 0 ? 'var(--green-600)' : 'var(--red-500)'} value={savings + '%'} sub="of income"/>
          </div>
        ) : null}

        {/* toolbar */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
          <div style={{ display: 'flex', gap: 6 }}>
            <button title="Previous month" onClick={() => go(-1)} style={{ ...card, boxShadow: 'none', width: 38, height: 38, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', color: 'var(--ink-700,#33433B)' }}><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4"><path d="M15 6l-6 6 6 6" strokeLinecap="round" strokeLinejoin="round"/></svg></button>
            <button title="Next month" onClick={() => go(1)} style={{ ...card, boxShadow: 'none', width: 38, height: 38, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', color: 'var(--ink-700,#33433B)' }}><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4"><path d="M9 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round"/></svg></button>
            <button onClick={goToday} style={{ ...card, boxShadow: 'none', padding: '0 16px', height: 38, fontWeight: 700, fontSize: 13, cursor: 'pointer', color: 'var(--ink-700,#33433B)' }}>Today</button>
          </div>

          {/* month + year pickers */}
          <select value={gridMonth.getMonth()} onChange={e => pickMonth(+e.target.value)} style={{ ...card, boxShadow: 'none', height: 38, padding: '0 10px', fontWeight: 700, fontSize: 13, color: 'var(--ink-900,#16241D)', cursor: 'pointer', fontFamily: 'inherit' }}>
            {MONTHS.map((m, i) => <option key={i} value={i}>{m}</option>)}
          </select>
          <select value={gridMonth.getFullYear()} onChange={e => pickYear(+e.target.value)} style={{ ...card, boxShadow: 'none', height: 38, padding: '0 10px', fontWeight: 700, fontSize: 13, color: 'var(--ink-900,#16241D)', cursor: 'pointer', fontFamily: 'inherit' }}>
            {[today.getFullYear() - 1, today.getFullYear(), today.getFullYear() + 1, today.getFullYear() + 2].map(y => <option key={y} value={y}>{y}</option>)}
          </select>

          {/* range selector */}
          <div style={{ position: 'relative' }}>
            <button onClick={() => { setRangeOpen(o => !o); setFilterOpen(false); }} style={{ ...card, boxShadow: 'none', padding: '0 14px', height: 38, fontWeight: 700, fontSize: 13, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 8, color: 'var(--deep,#0F3D2C)' }}>
              <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="3" y="4" width="18" height="17" rx="2"/><path d="M3 9h18M8 2v4M16 2v4" strokeLinecap="round"/></svg>
              {(RANGES.find(r => r.id === range) || {}).label || 'Range'}
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4"><path d="M6 9l6 6 6-6" strokeLinecap="round" strokeLinejoin="round"/></svg>
            </button>
            {rangeOpen ? (
              <div style={{ position: 'absolute', right: 0, top: 44, zIndex: 25, ...card, padding: 6, width: 190, maxHeight: 360, overflowY: 'auto' }}>
                {RANGES.map(r => (
                  <button key={r.id} onClick={() => { setRange(r.id); setRangeOpen(false); if (r.id === 'next30' || r.id === 'next60' || r.id === 'next90' || r.id === 'thisyear') setView('Agenda'); else setView('Month'); }}
                    style={{ display: 'block', width: '100%', textAlign: 'left', border: 0, background: range === r.id ? 'var(--mint-100,#E4F1EA)' : 'transparent', color: range === r.id ? 'var(--deep,#0F3D2C)' : 'var(--ink-700,#33433B)', fontWeight: range === r.id ? 800 : 600, fontSize: 13, padding: '9px 11px', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit' }}>{r.label}</button>
                ))}
                <div style={{ borderTop: '1px solid var(--line,#EEEAE0)', margin: '6px 4px', paddingTop: 6 }}>
                  <div style={{ fontSize: 10.5, fontWeight: 800, letterSpacing: '.05em', textTransform: 'uppercase', color: 'var(--ink-400,#9AA7A0)', padding: '2px 11px 4px' }}>Jump to month</div>
                  {MONTHS.map((m, i) => {
                    const onMonth = range === 'thismonth' && gridMonth.getMonth() === i && gridMonth.getFullYear() === today.getFullYear();
                    return (
                      <button key={i} onClick={() => { pickMonth(i); setView('Month'); setRangeOpen(false); }}
                        style={{ display: 'block', width: '100%', textAlign: 'left', border: 0, background: onMonth ? 'var(--mint-100,#E4F1EA)' : 'transparent', color: onMonth ? 'var(--deep,#0F3D2C)' : 'var(--ink-700,#33433B)', fontWeight: onMonth ? 800 : 600, fontSize: 13, padding: '8px 11px', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit' }}>{m} {today.getFullYear()}</button>
                    );
                  })}
                </div>
              </div>
            ) : null}
          </div>

          <div style={{ flex: 1, minWidth: 8 }}></div>
          <div style={{ display: 'flex', gap: 4, ...card, boxShadow: 'none', padding: 4 }}>{['Month', 'Week', 'Day', 'Agenda'].map(Tab)}</div>
          <div style={{ position: 'relative' }}>
            <button onClick={() => { setFilterOpen(o => !o); setRangeOpen(false); }} style={{ ...card, boxShadow: 'none', padding: '0 14px', height: 38, fontWeight: 700, fontSize: 13, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 7, color: 'var(--ink-700,#33433B)' }}>{Ico('Filter', { size: 15 })} Filter</button>
            {filterOpen ? (
              <div style={{ position: 'absolute', right: 0, top: 44, zIndex: 20, ...card, padding: 12, width: 180 }}>
                {FilterChip('income', 'Income', G)}{FilterChip('expense', 'Expenses', RD)}{FilterChip('bill', 'Bills & due dates', GOLD)}{FilterChip('sub', 'Subscriptions', AM)}
              </div>
            ) : null}
          </div>
        </div>
        <div style={{ fontFamily: 'var(--serif),serif', fontWeight: 700, fontSize: 20, color: 'var(--ink-900,#16241D)', marginTop: -4 }}>{isMonthRange ? MONTHS[gridMonth.getMonth()] + ' ' + gridMonth.getFullYear() : win.label}</div>

        {/* main + insights */}
        <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0,1fr) 320px', gap: 16, alignItems: 'start' }} className="cm-cal-grid">
          {/* calendar surface */}
          <div style={{ ...card, overflow: 'hidden' }}>
            {(isMonthRange && (view === 'Month' || view === 'Week')) ? (
              <React.Fragment>
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7,1fr)', background: 'var(--paper,#FAF8F2)', borderBottom: '1px solid var(--line,#EEEAE0)' }}>
                  {DOW.map(w => <div key={w} style={{ padding: '11px 8px', fontSize: 12, fontWeight: 700, color: 'var(--ink-400,#7B8A80)', textAlign: 'left' }}>{w}</div>)}
                </div>
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7,1fr)' }}>
                  {(view === 'Month' ? cells : weekCells()).map(d => DayCell(d, view === 'Week'))}
                </div>
              </React.Fragment>
            ) : (isMonthRange && view === 'Day') ? (
              <div style={{ padding: 16 }}>{Agenda(1)}</div>
            ) : (
              <div style={{ padding: 16 }}>{Agenda(0)}</div>
            )}
          </div>

          {/* insights panel */}
          <div style={{ display: 'flex', flexDirection: 'column', gap: 16, position: 'sticky', top: 12 }}>
            <div style={{ ...card, padding: 16 }}>
              <div style={{ fontFamily: 'var(--serif,serif)', fontWeight: 700, fontSize: 15, color: 'var(--ink-900,#16241D)', marginBottom: 12 }}>Upcoming · {isMonthRange ? win.label.split(' ')[0] : win.label}</div>
              {weekAhead.length ? weekAhead.map((e, i) => (
                <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 11 }}>
                  <span style={{ width: 30, height: 30, borderRadius: 8, background: meta(e.cat).c + '1A', color: COLORS[e.type], display: 'flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 30px' }}>{Ico(meta(e.cat).ic, { size: 14 })}</span>
                  <div style={{ flex: 1, minWidth: 0 }}><div style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink-900,#16241D)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{e.label}</div><div style={{ fontSize: 11.5, color: 'var(--ink-400,#7B8A80)' }}>{MONTHS[e.date.getMonth()].slice(0, 3)} {e.date.getDate()}</div></div>
                  <span className="num" style={{ fontWeight: 700, fontSize: 13, color: e.amount > 0 ? G : RD }}>{usd(e.amount)}</span>
                </div>
              )) : <div style={{ fontSize: 13, color: 'var(--ink-400,#9AA7A0)' }}>Nothing scheduled this week.</div>}
            </div>

            <div style={{ ...card, padding: 16 }}>
              <div style={{ fontFamily: 'var(--serif,serif)', fontWeight: 700, fontSize: 15, color: 'var(--ink-900,#16241D)', marginBottom: 4 }}>Cash flow forecast</div>
              <div style={{ fontSize: 11.5, color: 'var(--ink-400,#7B8A80)', marginBottom: 10 }}>Next {Math.min(120, Math.max(30, win.span || 30))} days</div>
              {LineChart && forecast.length ? <LineChart data={forecast} color={G} height={110} yfmt={v => '$' + Math.round(v / 1000) + 'K'} /> : <div style={{ fontSize: 13, color: 'var(--ink-400)' }}>—</div>}
            </div>

            <div style={{ ...card, padding: 16 }}>
              <div style={{ fontFamily: 'var(--serif,serif)', fontWeight: 700, fontSize: 15, color: 'var(--ink-900,#16241D)', marginBottom: 12 }}>Largest expense categories</div>
              {topCats.length ? (
                <React.Fragment>
                  {Donut ? <div style={{ display: 'flex', justifyContent: 'center', marginBottom: 10 }}><Donut segments={topCats.map(c => ({ value: c.value, color: c.color }))} size={130} thickness={16} center={{ value: usd0(topCats.reduce((s, c) => s + c.value, 0)), label: 'Spend' }} /></div> : null}
                  {topCats.slice(0, 5).map((c, i) => (
                    <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 7 }}>
                      <span style={{ width: 9, height: 9, borderRadius: 3, background: c.color }}></span>
                      <span style={{ flex: 1, fontSize: 12.5, color: 'var(--ink-700,#33433B)', fontWeight: 600 }}>{c.label}</span>
                      <span className="num" style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--ink-900,#16241D)' }}>{usd0(c.value)}</span>
                    </div>
                  ))}
                </React.Fragment>
              ) : <div style={{ fontSize: 13, color: 'var(--ink-400,#9AA7A0)' }}>No expenses yet this month.</div>}
            </div>

            <div style={{ ...card, padding: 16 }}>
              <div style={{ fontFamily: 'var(--serif,serif)', fontWeight: 700, fontSize: 15, color: 'var(--ink-900,#16241D)', marginBottom: 12 }}>Financial alerts</div>
              {alerts.length ? alerts.map((a, i) => (
                <div key={i} style={{ display: 'flex', gap: 9, alignItems: 'flex-start', marginBottom: 10 }}>
                  <span style={{ width: 7, height: 7, borderRadius: 999, background: a.tone, marginTop: 5, flex: '0 0 7px' }}></span>
                  <span style={{ fontSize: 12.5, color: 'var(--ink-700,#33433B)', fontWeight: 600, lineHeight: 1.4 }}>{a.text}</span>
                </div>
              )) : <div style={{ fontSize: 13, color: 'var(--ink-400,#9AA7A0)' }}>You're all caught up.</div>}
            </div>
          </div>
        </div>

        {/* upcoming 30 days — earnings & expenses list */}
        {(function(){
          const start = new Date(today.getFullYear(), today.getMonth(), today.getDate());
          const end = new Date(start.getFullYear(), start.getMonth(), start.getDate()+30);
          const up = allEvents.filter(e=> e.date>=start && e.date<=end).sort((a,b)=> a.date-b.date);
          const inc = up.filter(e=> e.amount>0), exp = up.filter(e=> e.amount<0);
          const incTot = inc.reduce((s,e)=>s+e.amount,0), expTot = exp.reduce((s,e)=>s+Math.abs(e.amount),0);
          const Row = (e,i,arr)=> (
            <div key={i} style={{ display:'flex', alignItems:'center', gap:11, padding:'10px 4px', borderBottom:i<arr.length-1?'1px solid var(--line,#F1ECE1)':0 }}>
              <span style={{ width:34, height:34, borderRadius:9, background:meta(e.cat).c+'1A', color:COLORS[e.type], display:'flex', alignItems:'center', justifyContent:'center', flex:'0 0 34px' }}>{Ico(meta(e.cat).ic,{size:16})}</span>
              <div style={{ flex:1, minWidth:0 }}><div style={{ fontSize:13.5, fontWeight:700, color:'var(--ink-900,#16241D)', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{e.label}</div><div style={{ fontSize:11.5, color:'var(--ink-400,#7B8A80)' }}>{DOW[e.date.getDay()]} · {MONTHS[e.date.getMonth()].slice(0,3)} {e.date.getDate()}{e.recur?' · recurring':''}</div></div>
              <span className="num" style={{ fontWeight:800, fontSize:14, color:e.amount>0?G:RD, whiteSpace:'nowrap' }}>{usd(e.amount)}</span>
            </div>
          );
          return (
            <div style={{ ...card, padding:18, marginTop:18 }}>
              <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', gap:12, marginBottom:6, flexWrap:'wrap' }}>
                <div>
                  <div style={{ fontFamily:'var(--serif,serif)', fontWeight:700, fontSize:17, color:'var(--ink-900,#16241D)' }}>Upcoming Transactions · Next 30 Days</div>
                  <div style={{ fontSize:12.5, color:'var(--ink-400,#7B8A80)', marginTop:2 }}>Projected earnings and expenses so you can track what's ahead.</div>
                </div>
                <div style={{ display:'flex', gap:10, flexWrap:'wrap' }}>
                  <span style={{ display:'inline-flex', flexDirection:'column', alignItems:'flex-end', background:G+'12', borderRadius:11, padding:'7px 13px' }}><span style={{ fontSize:11, fontWeight:700, color:G }}>Expected In</span><span className="num" style={{ fontSize:16, fontWeight:800, color:G }}>+${Math.round(incTot).toLocaleString('en-CA')}</span></span>
                  <span style={{ display:'inline-flex', flexDirection:'column', alignItems:'flex-end', background:RD+'12', borderRadius:11, padding:'7px 13px' }}><span style={{ fontSize:11, fontWeight:700, color:RD }}>Expected Out</span><span className="num" style={{ fontSize:16, fontWeight:800, color:RD }}>-${Math.round(expTot).toLocaleString('en-CA')}</span></span>
                  <span style={{ display:'inline-flex', flexDirection:'column', alignItems:'flex-end', background:'var(--paper,#F4F0E6)', borderRadius:11, padding:'7px 13px' }}><span style={{ fontSize:11, fontWeight:700, color:'var(--ink-500,#6B7A70)' }}>Net</span><span className="num" style={{ fontSize:16, fontWeight:800, color:(incTot-expTot)>=0?G:RD }}>{usd(incTot-expTot)}</span></span>
                </div>
              </div>
              {up.length ? (
                <div className="cm-cal-up-grid" style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:'4px 28px', marginTop:8 }}>
                  <div>
                    <div style={{ fontSize:12, fontWeight:800, color:G, textTransform:'uppercase', letterSpacing:'.04em', padding:'8px 4px', borderBottom:'2px solid '+G+'33' }}>Earnings · {inc.length}</div>
                    {inc.length ? inc.map(Row) : <div style={{ fontSize:13, color:'var(--ink-400,#9AA7A0)', padding:'14px 4px' }}>No income expected in the next 30 days.</div>}
                  </div>
                  <div>
                    <div style={{ fontSize:12, fontWeight:800, color:RD, textTransform:'uppercase', letterSpacing:'.04em', padding:'8px 4px', borderBottom:'2px solid '+RD+'33' }}>Expenses · {exp.length}</div>
                    {exp.length ? exp.map(Row) : <div style={{ fontSize:13, color:'var(--ink-400,#9AA7A0)', padding:'14px 4px' }}>No expenses scheduled in the next 30 days.</div>}
                  </div>
                </div>
              ) : <div style={{ fontSize:13, color:'var(--ink-400,#9AA7A0)', padding:'20px 4px', textAlign:'center' }}>Nothing scheduled in the next 30 days. Import transactions, add bills or subscriptions to see them here.</div>}
            </div>
          );
        })()}

        {/* day popover */}
        {sel ? (
          <div onClick={() => setSel(null)} style={{ position: 'fixed', inset: 0, background: 'rgba(8,20,13,.45)', zIndex: 9000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20 }}>
            <div onClick={e => e.stopPropagation()} style={{ ...card, width: 420, maxWidth: '100%', maxHeight: '80vh', overflow: 'auto', padding: 0 }}>
              <div style={{ padding: '18px 20px', borderBottom: '1px solid var(--line,#EEEAE0)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                <div><div style={{ fontSize: 11.5, fontWeight: 700, color: 'var(--ink-400,#7B8A80)', textTransform: 'uppercase' }}>{DOW[sel.date.getDay()]}</div><div style={{ fontFamily: 'var(--serif,serif)', fontWeight: 700, fontSize: 21, color: 'var(--ink-900,#16241D)' }}>{MONTHS[sel.date.getMonth()]} {sel.date.getDate()}</div></div>
                <button onClick={() => setSel(null)} style={{ background: 'var(--paper,#F4F0E6)', border: 0, width: 30, height: 30, borderRadius: 999, cursor: 'pointer', fontSize: 16, color: 'var(--ink-500)' }}>×</button>
              </div>
              <div style={{ padding: 14 }}>
                {sel.list.map((e, i) => (
                  <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 11, padding: '9px 6px', borderBottom: i < sel.list.length - 1 ? '1px solid var(--line,#F1ECE1)' : 0 }}>
                    <span style={{ width: 34, height: 34, borderRadius: 9, background: meta(e.cat).c + '1A', color: COLORS[e.type], display: 'flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 34px' }}>{Ico(meta(e.cat).ic, { size: 16 })}</span>
                    <div style={{ flex: 1, minWidth: 0 }}><div style={{ fontSize: 14, fontWeight: 700, color: 'var(--ink-900,#16241D)' }}>{e.label}</div><div style={{ fontSize: 12, color: 'var(--ink-400,#7B8A80)' }}>{e.cat}{e.recur ? ' · recurring' : ''}</div></div>
                    <span className="num" style={{ fontWeight: 800, fontSize: 15, color: e.amount > 0 ? G : RD }}>{usd(e.amount)}</span>
                  </div>
                ))}
              </div>
            </div>
          </div>
        ) : null}

        <style>{`@media(max-width:1080px){.cm-cal-grid{grid-template-columns:1fr !important;}}@media(max-width:760px){.cm-cal-up-grid{grid-template-columns:1fr !important;}}`}</style>
      </div>
    );
  }

  window.PageCalendar = PageCalendar;
})();
