// screens-stats.jsx — Revolut-style paged monthly Stats
//
// Each month is a horizontal "page" the user swipes between (CSS scroll-snap).
// Inside each page:
//   - Big serif total income for the month
//   - Delta pill vs previous month
//   - Cumulative running-total line chart (day 1 → end of month)
//     with a ghost line for the previous month overlaid for comparison
//   - Splits breakdown card
//   - Subscriptions charged + Incomes lists
//
// Top bar: a horizontally scrolling timeline (Jan…Dec) — tapping a month scrolls to its page.
// The active month has a thicker underline and bolder color, animated in.

function StatsScreen({ store }) {
  const { state, totalIncome, totalSubs, subsPct } = store;
  const allTime = computeAllTime(state, { totalIncome, totalSubs, subsPct });

  // Build a 12-month window ending at current month.
  const months = React.useMemo(() => {
    const cur = new Date();
    const out = [];
    for (let i = 11; i >= 0; i--) {
      const d = new Date(cur.getFullYear(), cur.getMonth() - i, 1);
      const ym = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}`;
      const m = allTime.monthly.find(x => x.ym === ym);
      out.push({
        ym, date: d,
        income: m ? m.income : 0,
        subs: m ? m.subs : 0,
        subsPct: m ? m.subsPct : 0,
        empty: !m || m.income === 0,
      });
    }
    return out;
  }, [allTime, state.history, state.incomes, state.subscriptions]);

  // Active page index — defaults to last (current month).
  const [activeIdx, setActiveIdx] = React.useState(months.length - 1);
  React.useEffect(() => { setActiveIdx(months.length - 1); }, [months.length]);

  return (
    <div className="page-in" style={{
      position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
      display: 'flex', flexDirection: 'column',
    }}>
      <ScreenHeader eyebrow="Monthly stats" title="Stats"/>

      {/* Timeline (12 months across) — sticky-ish header */}
      <MonthTimeline
        months={months}
        activeIdx={activeIdx}
        onPick={setActiveIdx}
      />

      {/* Pager — each month is its own snap-page */}
      <MonthPager
        months={months}
        activeIdx={activeIdx}
        setActiveIdx={setActiveIdx}
        store={store}
      />
    </div>
  );
}

// ── Top timeline of months ───────────────────────────────────────────────
function MonthTimeline({ months, activeIdx, onPick }) {
  const ref = React.useRef(null);

  // Scroll active month label into center when activeIdx changes.
  React.useEffect(() => {
    const root = ref.current;
    if (!root) return;
    const target = root.querySelector(`[data-mt-idx="${activeIdx}"]`);
    if (!target) return;
    const rRoot = root.getBoundingClientRect();
    const rTarget = target.getBoundingClientRect();
    const desired = (rTarget.left - rRoot.left) - (rRoot.width / 2 - rTarget.width / 2);
    root.scrollTo({ left: root.scrollLeft + desired, behavior: 'smooth' });
  }, [activeIdx]);

  // Group consecutive months by year so we can show the year tick once
  const yearGroups = React.useMemo(() => {
    const groups = [];
    months.forEach((m, i) => {
      const y = m.date.getFullYear();
      const last = groups[groups.length - 1];
      if (last && last.year === y) last.indices.push(i);
      else groups.push({ year: y, indices: [i] });
    });
    return groups;
  }, [months]);

  return (
    <div style={{
      flexShrink: 0,
      borderBottom: `1px solid ${tokens.hairline}`,
      background: tokens.paper,
      position: 'relative',
    }}>
      <div ref={ref} style={{
        display: 'flex',
        alignItems: 'flex-end',
        padding: '14px 50% 12px 50%',
        overflowX: 'auto',
        scrollbarWidth: 'none',
        WebkitOverflowScrolling: 'touch',
        gap: 4,
      }}>
        {months.map((m, i) => {
          const active = i === activeIdx;
          const distance = Math.abs(i - activeIdx);
          // Adjacent months get medium emphasis
          const adjacent = distance === 1;
          return (
            <button
              key={m.ym}
              data-mt-idx={i}
              onClick={() => onPick(i)}
              style={{
                flex: '0 0 auto',
                padding: '0 12px',
                background: 'none',
                border: 'none',
                cursor: 'pointer',
                position: 'relative',
                color: active ? tokens.ink : (adjacent ? tokens.ink2 : tokens.ink3),
                fontFamily: tokens.sans,
                fontSize: 12,
                fontWeight: active ? 600 : 400,
                letterSpacing: 0.4,
                textTransform: 'uppercase',
                transition: 'color 280ms cubic-bezier(0.22,1,0.36,1), opacity 280ms cubic-bezier(0.22,1,0.36,1)',
                whiteSpace: 'nowrap',
                opacity: active ? 1 : (adjacent ? 0.85 : 0.55),
                paddingBottom: 10,
              }}>
              {/* Tiny dot above active */}
              {active && (
                <span style={{
                  position: 'absolute',
                  top: -2, left: '50%',
                  transform: 'translateX(-50%)',
                  width: 4, height: 4, borderRadius: 2,
                  background: tokens.sage,
                  animation: 'chip-pop 280ms cubic-bezier(0.34,1.4,0.64,1) both',
                }}/>
              )}
              <span style={{ display: 'block' }}>{MONTHS_SHORT[m.date.getMonth()]}</span>
              {/* Year — only show on Jan or first of group */}
              {(m.date.getMonth() === 0 || i === 0) && (
                <span style={{
                  display: 'block',
                  fontSize: 9,
                  fontWeight: 400,
                  marginTop: 2,
                  letterSpacing: 0.6,
                  color: active ? tokens.ink2 : tokens.ink3,
                  opacity: 0.7,
                }}>{m.date.getFullYear()}</span>
              )}
            </button>
          );
        })}
      </div>

      {/* Center indicator pill behind active item */}
      <div aria-hidden style={{
        position: 'absolute',
        top: 10, bottom: 6,
        left: '50%', transform: 'translateX(-50%)',
        width: 56, borderRadius: 14,
        background: tokens.inset,
        zIndex: 0,
        pointerEvents: 'none',
        opacity: 0.5,
        transition: 'opacity 280ms cubic-bezier(0.22,1,0.36,1)',
      }}/>

      {/* Edge fades */}
      <div aria-hidden style={{
        position: 'absolute', top: 0, bottom: 0, left: 0, width: 36,
        background: `linear-gradient(90deg, ${tokens.paper} 0%, ${tokens.paper}00 100%)`,
        pointerEvents: 'none',
      }}/>
      <div aria-hidden style={{
        position: 'absolute', top: 0, bottom: 0, right: 0, width: 36,
        background: `linear-gradient(270deg, ${tokens.paper} 0%, ${tokens.paper}00 100%)`,
        pointerEvents: 'none',
      }}/>
    </div>
  );
}

// ── Horizontal paged container with scroll-snap ──────────────────────────
function MonthPager({ months, activeIdx, setActiveIdx, store }) {
  const ref = React.useRef(null);
  const [pageW, setPageW] = React.useState(0);
  const [isScrolling, setIsScrolling] = React.useState(false);
  // Track which month-ym's we've already animated. After the first reveal,
  // returning to a month should show the chart instantly (no re-draw).
  const [seen, setSeen] = React.useState(() => new Set());
  const programmatic = React.useRef(false);
  const fromScroll = React.useRef(false); // last activeIdx change came from native scroll
  const scrollEndTimer = React.useRef(null);
  const drag = React.useRef(null); // { startX, startScroll, startIdx }

  // Mark the active month as "seen" once we settle on it.
  React.useEffect(() => {
    if (isScrolling) return;
    const ym = months[activeIdx]?.ym;
    if (!ym) return;
    if (seen.has(ym)) return;
    // Defer one tick so the draw-on animation gets to play before we flip the flag.
    const t = window.setTimeout(() => {
      setSeen(prev => {
        if (prev.has(ym)) return prev;
        const next = new Set(prev);
        next.add(ym);
        return next;
      });
    }, 1100); // long enough for the line draw (980ms) + dot pop
    return () => clearTimeout(t);
  }, [activeIdx, isScrolling, months, seen]);

  // Measure page width
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const ro = new ResizeObserver(() => setPageW(el.clientWidth));
    ro.observe(el);
    setPageW(el.clientWidth);
    return () => ro.disconnect();
  }, []);

  // Scroll to active page when activeIdx changes (e.g. timeline tap).
  React.useEffect(() => {
    const el = ref.current;
    if (!el || pageW === 0) return;
    // If the change originated from native scroll, the container is already
    // at (or snapping to) the right place — DO NOT re-trigger scrollTo.
    // Two competing smooth animations cause a "start, slow, speed up" stutter.
    if (fromScroll.current) {
      fromScroll.current = false;
      return;
    }
    const target = activeIdx * pageW;
    if (Math.abs(el.scrollLeft - target) > 4) {
      programmatic.current = true;
      el.scrollTo({ left: target, behavior: 'smooth' });
      window.setTimeout(() => { programmatic.current = false; }, 600);
    }
  }, [activeIdx, pageW]);

  // Update activeIdx when user scrolls (touch/wheel).
  // Commit IMMEDIATELY when scrollLeft crosses the half-way point of a page —
  // this keeps the timeline label in lockstep with the user's finger.
  // We also set isScrolling=true and clear it ~60ms after the last scroll event,
  // so chart draw-on animations defer briefly until the user has actually landed.
  const onScroll = (e) => {
    if (programmatic.current) {
      if (scrollEndTimer.current) clearTimeout(scrollEndTimer.current);
      scrollEndTimer.current = window.setTimeout(() => setIsScrolling(false), 60);
      return;
    }
    if (pageW === 0) return;
    if (!isScrolling) setIsScrolling(true);
    if (scrollEndTimer.current) clearTimeout(scrollEndTimer.current);
    scrollEndTimer.current = window.setTimeout(() => setIsScrolling(false), 60);
    const idx = Math.round(e.target.scrollLeft / pageW);
    if (idx !== activeIdx && idx >= 0 && idx < months.length) {
      fromScroll.current = true;
      setActiveIdx(idx);
    }
  };

  // Mouse drag fallback (desktop preview) — converts horizontal mouse drag to swipe-snap.
  const onPointerDown = (e) => {
    // Only primary button; ignore touch (handled natively by scroll-snap)
    if (e.pointerType !== 'mouse') return;
    drag.current = { startX: e.clientX, startScroll: ref.current.scrollLeft, startIdx: activeIdx, moved: false };
    ref.current.style.scrollBehavior = 'auto';
    ref.current.setPointerCapture(e.pointerId);
  };
  const onPointerMove = (e) => {
    if (!drag.current) return;
    const dx = e.clientX - drag.current.startX;
    if (Math.abs(dx) > 4) drag.current.moved = true;
    ref.current.scrollLeft = drag.current.startScroll - dx;
  };
  const onPointerUp = (e) => {
    if (!drag.current) return;
    const dx = e.clientX - drag.current.startX;
    const threshold = pageW * 0.18; // 18% of page width
    let newIdx = drag.current.startIdx;
    if (dx < -threshold) newIdx = Math.min(months.length - 1, drag.current.startIdx + 1);
    else if (dx > threshold) newIdx = Math.max(0, drag.current.startIdx - 1);
    drag.current = null;
    ref.current.style.scrollBehavior = 'smooth';
    if (newIdx !== activeIdx) {
      setActiveIdx(newIdx);
    } else {
      // snap back to current
      programmatic.current = true;
      ref.current.scrollTo({ left: activeIdx * pageW, behavior: 'smooth' });
      window.setTimeout(() => { programmatic.current = false; }, 400);
    }
  };

  return (
    <div
      ref={ref}
      onScroll={onScroll}
      onPointerDown={onPointerDown}
      onPointerMove={onPointerMove}
      onPointerUp={onPointerUp}
      onPointerCancel={onPointerUp}
      style={{
        flex: 1,
        display: 'flex',
        overflowX: 'auto',
        overflowY: 'hidden',
        scrollSnapType: 'x mandatory',
        scrollbarWidth: 'none',
        WebkitOverflowScrolling: 'touch',
        scrollBehavior: 'smooth',
        touchAction: 'pan-x pan-y',
        userSelect: 'none',
      }}>
      {months.map((m, i) => {
        const prev = i > 0 ? months[i - 1] : null;
        const isActive = i === activeIdx;
        return (
          <div key={m.ym} style={{
            flex: `0 0 100%`, minWidth: '100%', maxWidth: '100%',
            scrollSnapAlign: 'start',
            scrollSnapStop: 'always',
            overflowY: 'auto',
            padding: '0 0 10px',
          }}>
            <MonthPage month={m} prev={prev} store={store} active={isActive && !isScrolling} animate={isActive && !isScrolling && !seen.has(m.ym)}/>
          </div>
        );
      })}
    </div>
  );
}

// ── A single month "page" ────────────────────────────────────────────────
function MonthPage({ month, prev, store, active, animate }) {
  const { state } = store;
  const isCurrent = month.ym === ymKey(new Date());

  // Get incomes + subs for this month
  const monthIncomes = isCurrent
    ? state.incomes
    : (state.history[month.ym]?.incomes || []);
  const monthSubs = isCurrent
    ? state.subscriptions.filter(s => s.active).map(s => ({ name: s.name, amount: s.amount, date: defaultBillDateForCurrent(s, month) }))
    : (state.history[month.ym]?.subsCharged || []);

  // Splits for this month
  const splits = {
    investments: month.income * ((state.splits.investments || 0) / 100),
    savings:     month.income * ((state.splits.savings     || 0) / 100),
    free:        month.income * ((state.splits.free        || 0) / 100),
  };

  // Delta vs previous month
  const delta = (prev && prev.income > 0)
    ? ((month.income - prev.income) / prev.income) * 100
    : null;

  const monthName = `${MONTHS[month.date.getMonth()]} ${month.date.getFullYear()}`;

  // Build daily cumulative income series for THIS month and PREV month.
  const daysInMonth = new Date(month.date.getFullYear(), month.date.getMonth() + 1, 0).getDate();
  const series = buildCumulativeSeries(monthIncomes, daysInMonth);
  let prevSeries = null;
  if (prev) {
    const prevDays = new Date(prev.date.getFullYear(), prev.date.getMonth() + 1, 0).getDate();
    const prevIncomes = prev.ym === ymKey(new Date())
      ? state.incomes
      : (state.history[prev.ym]?.incomes || []);
    prevSeries = buildCumulativeSeries(prevIncomes, prevDays);
  }

  return (
    <div style={{ padding: '16px 18px 140px' }}>
      {/* Month label + NOW badge */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
        <div style={{
          fontFamily: tokens.sans, fontSize: 11, fontWeight: 500, letterSpacing: 0.6,
          textTransform: 'uppercase', color: tokens.ink3,
        }}>{monthName}</div>
        {isCurrent && (
          <span style={{
            padding: '1px 6px', borderRadius: 4,
            background: tokens.sageSoft, color: tokens.sageDeep,
            fontSize: 9, letterSpacing: 0.4, fontFamily: tokens.sans, fontWeight: 600,
          }}>NOW</span>
        )}
      </div>

      {/* Big serif income */}
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, flexWrap: 'wrap' }}>
        <div style={{
          fontFamily: tokens.serif, fontSize: 56, color: tokens.ink, lineHeight: 1,
          fontVariantNumeric: 'tabular-nums', letterSpacing: -1.6,
        }}>
          {active ? <CountNumber value={month.income} format={(v) => fmtEUR(v, { sym: false, decimals: 0 })}/> : fmtEUR(month.income, { sym: false, decimals: 0 })}
        </div>
        <span style={{ fontSize: 32, color: tokens.ink3, fontFamily: tokens.serif, fontStyle: 'italic' }}>€</span>
      </div>

      {/* Delta pill */}
      <div style={{ marginTop: 8, minHeight: 22 }}>
        {delta != null && (
          <div style={{
            display: 'inline-flex', alignItems: 'center', gap: 6,
            fontFamily: tokens.sans, fontSize: 12, color: tokens.ink2,
            padding: '3px 10px', borderRadius: 999,
            background: delta >= 0 ? tokens.sageSoft : tokens.inset,
            border: `1px solid ${tokens.hairline}`,
          }}>
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke={delta >= 0 ? tokens.sageDeep : tokens.ink2} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
              {delta >= 0 ? <polyline points="6 14 12 8 18 14"/> : <polyline points="6 10 12 16 18 10"/>}
            </svg>
            <span style={{ fontWeight: 600, color: delta >= 0 ? tokens.sageDeep : tokens.ink2 }}>
              {delta >= 0 ? '+' : ''}{delta.toFixed(1)}%
            </span>
            <span style={{ color: tokens.ink3 }}>vs {MONTHS_SHORT[prev.date.getMonth()]}</span>
          </div>
        )}
        {delta == null && prev == null && (
          <div style={{ fontFamily: tokens.sans, fontSize: 12, color: tokens.ink3 }}>
            Earliest tracked month
          </div>
        )}
      </div>

      {/* Cumulative chart */}
      <div style={{ marginTop: 16 }}>
        <CumulativeChart
          series={series}
          prevSeries={prevSeries}
          daysInMonth={daysInMonth}
          active={active}
          animate={animate}
          monthLabel={MONTHS_SHORT[month.date.getMonth()]}
          prevLabel={prev ? MONTHS_SHORT[prev.date.getMonth()] : null}
          isCurrent={isCurrent}
        />
      </div>

      {/* Numeric comparison card */}
      {prev && (
        <div style={{ marginTop: 14 }}>
          <CompareCard month={month} prev={prev}/>
        </div>
      )}

      {/* Splits */}
      <div style={{ marginTop: 18 }}>
        <SectionHeader>Where it goes</SectionHeader>
        <SplitBreakdown splits={splits} total={month.income}/>
      </div>

      {/* Subs */}
      {monthSubs.length > 0 && (
        <div style={{ marginTop: 14 }}>
          <SectionHeader trailing={fmtEUR(monthSubs.reduce((a,b)=>a+b.amount,0), { decimals: 2 })}>
            {isCurrent ? 'Subscriptions active' : 'Subscriptions charged'}
          </SectionHeader>
          <Card padding={0}>
            {monthSubs.map((s, i) => (
              <div key={i} style={{
                display: 'flex', alignItems: 'center', gap: 12,
                padding: '12px 14px',
                borderBottom: i < monthSubs.length - 1 ? `1px solid ${tokens.hairline}` : 'none',
              }}>
                <SubAvatar name={s.name} size={32}/>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{
                    fontFamily: tokens.sans, fontSize: 14, color: tokens.ink, fontWeight: 500,
                    overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                  }}>{s.name}</div>
                </div>
                <div style={{
                  fontFamily: tokens.serif, fontSize: 16, color: tokens.ink,
                  fontVariantNumeric: 'tabular-nums', letterSpacing: -0.2, whiteSpace: 'nowrap',
                }}>
                  {fmtEUR(s.amount, { decimals: 2 })}
                </div>
              </div>
            ))}
          </Card>
        </div>
      )}

      {/* Incomes */}
      {monthIncomes.length > 0 && (
        <div style={{ marginTop: 14 }}>
          <SectionHeader trailing={fmtEUR(monthIncomes.reduce((a,b)=>a+(Number(b.amount)||0),0), { decimals: 0 })}>
            Incomes
          </SectionHeader>
          <Card padding={0}>
            {monthIncomes.map((inc, i) => (
              <div key={inc.id || i} style={{
                display: 'flex', alignItems: 'center', gap: 12,
                padding: '12px 14px',
                borderBottom: i < monthIncomes.length - 1 ? `1px solid ${tokens.hairline}` : 'none',
              }}>
                <div style={{
                  width: 32, height: 32, borderRadius: 16,
                  background: tokens.sageSoft, color: tokens.sageDeep,
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  fontFamily: tokens.sans, fontSize: 14, fontWeight: 600,
                }}>+</div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{
                    fontFamily: tokens.sans, fontSize: 14, color: tokens.ink, fontWeight: 500,
                    overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                  }}>{inc.name}</div>
                </div>
                <div style={{
                  fontFamily: tokens.serif, fontSize: 16, color: tokens.ink,
                  fontVariantNumeric: 'tabular-nums', letterSpacing: -0.2, whiteSpace: 'nowrap',
                }}>
                  +{fmtEUR(inc.amount, { decimals: 0 })}
                </div>
              </div>
            ))}
          </Card>
        </div>
      )}

      {monthIncomes.length === 0 && monthSubs.length === 0 && (
        <div style={{ marginTop: 18 }}>
          <Card>
            <div style={{ textAlign: 'center', padding: '20px 8px' }}>
              <div style={{ fontFamily: tokens.serif, fontStyle: 'italic', fontSize: 18, color: tokens.ink2, marginBottom: 4 }}>
                No data for this month
              </div>
              <div style={{ fontFamily: tokens.sans, fontSize: 13, color: tokens.ink3 }}>
                Swipe to find a month with activity.
              </div>
            </div>
          </Card>
        </div>
      )}
    </div>
  );
}

// Estimate a billing date for an active sub against the current month — for sorting/labels.
// (The seed data flow uses billingDay; keep it simple here.)
function defaultBillDateForCurrent(sub, month) {
  const day = Math.min(sub.billingDay || 1, new Date(month.date.getFullYear(), month.date.getMonth()+1, 0).getDate());
  const d = new Date(month.date.getFullYear(), month.date.getMonth(), day);
  return d.toISOString().slice(0, 10);
}

// Build a cumulative running-total array of length daysInMonth+1 (index 0 = 0, index N = total).
// Each entry adds incomes whose date.getDate() === day.
function buildCumulativeSeries(incomes, daysInMonth) {
  const arr = new Array(daysInMonth + 1).fill(0);
  // sum amounts per day
  const perDay = new Array(daysInMonth + 1).fill(0);
  incomes.forEach(inc => {
    const day = inc.date ? new Date(inc.date).getDate() : 1;
    const clamped = Math.min(daysInMonth, Math.max(1, day));
    perDay[clamped] += Number(inc.amount) || 0;
  });
  let running = 0;
  for (let i = 0; i <= daysInMonth; i++) {
    running += perDay[i];
    arr[i] = running;
  }
  return arr;
}

// ── Cumulative line chart with optional ghost overlay ────────────────────
function CumulativeChart({ series, prevSeries, daysInMonth, active, animate, monthLabel, prevLabel, isCurrent }) {
  const W = 320, H = 180; // viewBox
  const padL = 8, padR = 8, padT = 14, padB = 26;

  // shared y-max so prev/this share scale
  const allMax = Math.max(1,
    series[series.length - 1] || 0,
    prevSeries ? prevSeries[prevSeries.length - 1] || 0 : 0,
  );

  const x = (day) => padL + (day / daysInMonth) * (W - padL - padR);
  const y = (val) => H - padB - (val / allMax) * (H - padT - padB);

  // Build path d strings
  const buildPath = (s, days) => {
    if (!s || s.length === 0) return '';
    let d = `M ${x(0).toFixed(2)} ${y(s[0]).toFixed(2)}`;
    for (let i = 1; i < s.length; i++) {
      d += ` L ${x(i * (daysInMonth / (s.length - 1))).toFixed(2)} ${y(s[i]).toFixed(2)}`;
    }
    return d;
  };
  const buildAreaPath = (s, days) => {
    if (!s || s.length === 0) return '';
    let d = `M ${x(0).toFixed(2)} ${y(0).toFixed(2)}`;
    for (let i = 0; i < s.length; i++) {
      d += ` L ${x(i * (daysInMonth / (s.length - 1))).toFixed(2)} ${y(s[i]).toFixed(2)}`;
    }
    d += ` L ${x(daysInMonth).toFixed(2)} ${y(0).toFixed(2)} Z`;
    return d;
  };

  const mainPath = buildPath(series, daysInMonth);
  const areaPath = buildAreaPath(series, daysInMonth);
  const prevPath = prevSeries ? buildPath(prevSeries, prevSeries.length - 1) : '';

  // Path animation length — for stroke-dashoffset draw-on
  const [pathLen, setPathLen] = React.useState(0);
  const pathRef = React.useRef(null);
  React.useEffect(() => {
    if (pathRef.current) {
      try { setPathLen(pathRef.current.getTotalLength()); } catch (e) {}
    }
  }, [mainPath, active]);

  // x-axis day ticks (week markers: 1, 7, 14, 21, 28 + last)
  const ticks = [1, 7, 14, 21, 28];
  if (daysInMonth > 28 && !ticks.includes(daysInMonth)) ticks.push(daysInMonth);

  // Animation key — re-mount the path when we want to draw-on (animate=true)
  // so it draws each first visit. For repeat visits (active && !animate),
  // the path stays mounted with the same key — no re-draw.
  const animKey = animate ? `anim-${monthLabel}` : (active ? `static-${monthLabel}` : `off-${monthLabel}`);

  return (
    <Card padding={0}>
      <div style={{ padding: '14px 12px 8px' }}>
        {/* Legend */}
        {prevSeries && (
          <div style={{
            display: 'flex', justifyContent: 'flex-end', gap: 12,
            padding: '0 4px 6px',
            fontFamily: tokens.sans, fontSize: 10, color: tokens.ink3, letterSpacing: 0.3,
          }}>
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
              <span style={{ width: 14, height: 2, background: tokens.ink, borderRadius: 1 }}/>
              {monthLabel}
            </span>
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
              <span style={{
                width: 14, height: 0,
                borderTop: `2px dashed ${tokens.ink3}`,
              }}/>
              {prevLabel}
            </span>
          </div>
        )}

        <svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ display: 'block', overflow: 'visible' }}>
          <defs>
            <linearGradient id={`area-${monthLabel}`} x1="0" x2="0" y1="0" y2="1">
              <stop offset="0%" stopColor={tokens.sage} stopOpacity="0.20"/>
              <stop offset="100%" stopColor={tokens.sage} stopOpacity="0"/>
            </linearGradient>
          </defs>

          {/* Horizontal gridlines (subtle) */}
          {[0.25, 0.5, 0.75].map(p => (
            <line
              key={p}
              x1={padL} x2={W - padR}
              y1={padT + p * (H - padT - padB)}
              y2={padT + p * (H - padT - padB)}
              stroke={tokens.hairline}
              strokeDasharray="2 4"
            />
          ))}

          {/* Previous month ghost line */}
          {prevPath && (
            <path
              d={prevPath}
              fill="none"
              stroke={tokens.ink3}
              strokeWidth="1.5"
              strokeDasharray="3 4"
              strokeLinecap="round"
              strokeLinejoin="round"
              opacity="0.5"
            />
          )}

          {/* Filled area under main line */}
          <path
            key={'area-' + animKey}
            d={areaPath}
            fill={`url(#area-${monthLabel})`}
            style={{
              animation: animate ? 'fade-in 720ms cubic-bezier(0.22,1,0.36,1) both 220ms' : 'none',
              opacity: active ? undefined : 0,
            }}
          />

          {/* Main line — draw-on animation via stroke-dashoffset on first visit only */}
          <path
            key={'main-' + animKey}
            ref={pathRef}
            d={mainPath}
            fill="none"
            stroke={tokens.ink}
            strokeWidth="2.4"
            strokeLinecap="round"
            strokeLinejoin="round"
            style={
              animate && pathLen > 0 ? {
                strokeDasharray: pathLen,
                strokeDashoffset: pathLen,
                animation: 'stats-line-draw 980ms cubic-bezier(0.22,1,0.36,1) forwards',
              } : (active ? undefined : { opacity: 0 })
            }
          />

          {/* End-point dot for current month */}
          {active && series.length > 0 && (
            <circle
              cx={x(daysInMonth)}
              cy={y(series[series.length - 1])}
              r="4"
              fill={tokens.ink}
              style={animate ? { animation: 'stats-dot-pop 320ms cubic-bezier(0.34,1.4,0.64,1) both 1000ms', transformOrigin: `${x(daysInMonth)}px ${y(series[series.length - 1])}px` } : undefined}
            />
          )}

          {/* X-axis day labels */}
          {ticks.map(t => (
            <text
              key={t}
              x={x(t)}
              y={H - 8}
              fontFamily={tokens.sans}
              fontSize="9"
              fill={tokens.ink3}
              textAnchor="middle"
            >{t}</text>
          ))}
        </svg>

        <div style={{
          display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
          padding: '0 4px',
        }}>
          <span style={{ fontFamily: tokens.sans, fontSize: 11, color: tokens.ink3, letterSpacing: 0.4 }}>
            CUMULATIVE INCOME
          </span>
          <span style={{
            fontFamily: tokens.serif, fontSize: 16, color: tokens.ink,
            fontVariantNumeric: 'tabular-nums', letterSpacing: -0.2,
          }}>
            {fmtEUR(series[series.length - 1] || 0, { decimals: 0 })}
          </span>
        </div>
      </div>
    </Card>
  );
}

// ── Numeric compare card ─────────────────────────────────────────────────
function CompareCard({ month, prev }) {
  const incomeDelta = month.income - prev.income;
  const subsDelta = month.subs - prev.subs;
  const incomeDeltaPct = prev.income > 0 ? (incomeDelta / prev.income) * 100 : 0;
  const subsDeltaPct = prev.subs > 0 ? (subsDelta / prev.subs) * 100 : 0;

  const Row = ({ label, value, prevValue, delta, deltaPct, format, lowerIsBetter }) => {
    const positive = lowerIsBetter ? delta < 0 : delta > 0;
    const negative = lowerIsBetter ? delta > 0 : delta < 0;
    return (
      <div style={{
        display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 12,
        padding: '10px 0',
      }}>
        <div style={{
          fontFamily: tokens.sans, fontSize: 13, color: tokens.ink3, letterSpacing: 0.3,
          textTransform: 'uppercase', fontWeight: 500, fontSize: 11,
        }}>{label}</div>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 10 }}>
          <span style={{
            fontFamily: tokens.serif, fontSize: 18, color: tokens.ink,
            fontVariantNumeric: 'tabular-nums', letterSpacing: -0.3,
          }}>{format(value)}</span>
          {prev.income > 0 && (
            <span style={{
              fontFamily: tokens.sans, fontSize: 11,
              color: positive ? tokens.sageDeep : (negative ? tokens.bad : tokens.ink3),
              fontVariantNumeric: 'tabular-nums',
              padding: '2px 6px', borderRadius: 4,
              background: positive ? tokens.sageSoft : (negative ? `${tokens.bad}18` : tokens.inset),
            }}>
              {delta >= 0 ? '+' : ''}{deltaPct.toFixed(0)}%
            </span>
          )}
        </div>
      </div>
    );
  };

  return (
    <Card padding={0}>
      <div style={{ padding: '4px 14px' }}>
        <Row
          label={`vs ${MONTHS_SHORT[prev.date.getMonth()]}: Income`}
          value={month.income} prevValue={prev.income}
          delta={incomeDelta} deltaPct={incomeDeltaPct}
          format={(v) => fmtEUR(v, { decimals: 0 })}
          lowerIsBetter={false}
        />
        <div style={{ borderTop: `1px solid ${tokens.hairline}` }}/>
        <Row
          label="Subs spent"
          value={month.subs} prevValue={prev.subs}
          delta={subsDelta} deltaPct={subsDeltaPct}
          format={(v) => fmtEUR(v, { decimals: 0 })}
          lowerIsBetter={true}
        />
      </div>
    </Card>
  );
}

// ── Section header ────────────────────────────────────────────────────────
function SectionHeader({ children, trailing }) {
  return (
    <div style={{
      display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
      padding: '0 4px 8px',
    }}>
      <span style={{
        fontFamily: tokens.sans, fontSize: 11, fontWeight: 500, letterSpacing: 0.6,
        textTransform: 'uppercase', color: tokens.ink3,
      }}>{children}</span>
      {trailing && (
        <span style={{
          fontFamily: tokens.sans, fontSize: 11, color: tokens.ink3,
          fontVariantNumeric: 'tabular-nums',
        }}>{trailing}</span>
      )}
    </div>
  );
}

// ── Splits breakdown ─────────────────────────────────────────────────────
function SplitBreakdown({ splits, total }) {
  const rows = [
    { label: 'Invest', amount: splits.investments, color: tokens.sage },
    { label: 'Save',   amount: splits.savings,     color: tokens.sageDeep },
    { label: 'Free',   amount: splits.free,        color: tokens.ink2 },
  ];
  return (
    <Card padding={0}>
      <div style={{ padding: '14px' }}>
        <div style={{
          display: 'flex', height: 8, borderRadius: 4, overflow: 'hidden',
          background: tokens.inset, marginBottom: 14,
        }}>
          {rows.map((r, i) => {
            const pct = total > 0 ? (r.amount / total) * 100 : 0;
            return (
              <div key={i} style={{
                width: `${pct}%`, background: r.color,
                transition: 'width 520ms cubic-bezier(0.32, 0.72, 0, 1)',
              }}/>
            );
          })}
        </div>
        {rows.map((r, i) => {
          const pct = total > 0 ? (r.amount / total) * 100 : 0;
          return (
            <div key={i} style={{
              display: 'flex', alignItems: 'center', gap: 10,
              padding: '8px 0',
              borderTop: i > 0 ? `1px solid ${tokens.hairline}` : 'none',
            }}>
              <span style={{ width: 8, height: 8, borderRadius: 4, background: r.color, flexShrink: 0 }}/>
              <span style={{ flex: 1, fontFamily: tokens.sans, fontSize: 14, color: tokens.ink }}>{r.label}</span>
              <span style={{
                fontFamily: tokens.sans, fontSize: 12, color: tokens.ink3,
                fontVariantNumeric: 'tabular-nums', minWidth: 38, textAlign: 'right',
              }}>{Math.round(pct)}%</span>
              <span style={{
                fontFamily: tokens.serif, fontSize: 16, color: tokens.ink,
                fontVariantNumeric: 'tabular-nums', letterSpacing: -0.2, minWidth: 70, textAlign: 'right',
              }}>{fmtEUR(r.amount, { decimals: 0 })}</span>
            </div>
          );
        })}
      </div>
    </Card>
  );
}

Object.assign(window, {
  StatsScreen,
  MonthTimeline, MonthPager, MonthPage, CumulativeChart, CompareCard, SplitBreakdown,
});
