"use client";

import { useState, useEffect, Fragment, useMemo } from "react";
import {
  ComposedChart,
  LineChart,
  AreaChart,
  Area,
  Bar,
  Line,
  XAxis,
  YAxis,
  Tooltip,
  ResponsiveContainer,
  Cell,
  ReferenceLine,
} from "recharts";
import { useQueryClient } from "@tanstack/react-query";
import { useMonthlyAnalytics, useDailySpend, useTransactions, useUpdateTransaction } from "@/lib/hooks";
import { formatCategory, CATEGORIES } from "@/lib/categories";
import { CATEGORY_COLORS, CHART, TOOLTIP_STYLE } from "@/lib/category-colors";

function currentMonthStr(): string {
  const now = new Date();
  return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
}
function prevMonth(m: string): string {
  const [year, month] = m.split("-").map(Number);
  const d = new Date(year, month - 2, 1);
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
}
function formatMonth(m: string): string {
  const [year, month] = m.split("-");
  return new Date(Number(year), Number(month) - 1, 1).toLocaleString("default", { month: "long", year: "numeric" });
}
function formatShortMonth(m: string): string {
  const [year, month] = m.split("-");
  return new Date(Number(year), Number(month) - 1, 1).toLocaleString("default", { month: "short" });
}
function daysInMonthOf(m: string): number {
  const [year, month] = m.split("-").map(Number);
  return new Date(year, month, 0).getDate();
}
/**
 * How much of `month` has actually happened. A month in progress is only
 * complete up to today; every earlier month is complete.
 */
function elapsedDays(m: string): number {
  return m === currentMonthStr() ? new Date().getDate() : daysInMonthOf(m);
}
/** Spend in `month` from day 1 through `throughDay` inclusive. */
function spendThrough(days: Record<number, number> | undefined, throughDay: number): number {
  if (!days) return 0;
  let sum = 0;
  for (const [d, v] of Object.entries(days)) {
    if (Number(d) <= throughDay) sum += v;
  }
  return sum;
}
function fmt(n: number): string { return `$${Math.round(n).toLocaleString()}`; }
function fmtExact(n: number): string { return `$${n.toFixed(2)}`; }
function fmtSigned(n: number): string { return `${n >= 0 ? "+" : "−"}$${Math.abs(n) >= 100 ? Math.round(Math.abs(n)).toLocaleString() : Math.abs(n).toFixed(0)}`; }

// ─── Tooltips ────────────────────────────────────────────────────────────────

function ParetoTooltip({ active, payload }: { active?: boolean; payload?: { payload: { category: string; spent: number; pct: number; cumulative: number } }[] }) {
  if (!active || !payload?.length) return null;
  const d = payload[0].payload;
  return (
    <div style={TOOLTIP_STYLE} className="p-2.5 text-xs">
      <p className="font-medium text-zinc-300 mb-1">{formatCategory(d.category)}</p>
      <div className="flex justify-between gap-4"><span className="text-zinc-400">Spend</span><span className="font-mono">{fmtExact(d.spent)}</span></div>
      <div className="flex justify-between gap-4"><span className="text-zinc-400">Share</span><span className="font-mono">{d.pct}%</span></div>
      <div className="flex justify-between gap-4"><span className="text-zinc-400">Cumulative</span><span className="font-mono">{d.cumulative}%</span></div>
    </div>
  );
}

function CumulativeTooltip({ active, payload, label }: { active?: boolean; payload?: { dataKey: string; value: number; stroke: string }[]; label?: number }) {
  if (!active || !payload?.length) return null;
  return (
    <div style={TOOLTIP_STYLE} className="p-2.5 text-xs">
      <p className="text-zinc-400 mb-1 font-medium">Day {label}</p>
      {payload.map((p) => p.value != null && (
        <div key={p.dataKey} className="flex items-center gap-2 mb-0.5">
          <span className="w-2 h-2 rounded-sm shrink-0" style={{ background: p.stroke }} />
          <span className="text-zinc-400">{p.dataKey === "actual" ? "Actual" : "Typical pace"}:</span>
          <span className="text-zinc-100 font-mono tabular-nums ml-auto pl-2">{fmtExact(p.value)}</span>
        </div>
      ))}
    </div>
  );
}

// ─── Month spine ─────────────────────────────────────────────────────────────
// Twelve clickable columns, one per month, scaled to that month's spend.
// Doubles as period navigation and year-at-a-glance context.

function MonthSpine({
  months,
  totals,
  selected,
  onSelect,
}: {
  months: string[]; // ascending
  totals: Record<string, { spent: number }>;
  selected: string;
  onSelect: (m: string) => void;
}) {
  const max = Math.max(...months.map((m) => totals[m]?.spent || 0), 1);
  return (
    <div className="flex items-end gap-1 sm:gap-1.5">
      {months.map((m) => {
        const spent = totals[m]?.spent || 0;
        const h = Math.max(4, Math.round((spent / max) * 56));
        const active = m === selected;
        return (
          <button
            key={m}
            onClick={() => onSelect(m)}
            title={`${formatMonth(m)} — ${fmt(spent)}`}
            className="group flex-1 flex flex-col items-center gap-1.5 min-w-0"
          >
            <span
              style={{ height: h }}
              className={`w-full max-w-9 rounded-t-sm transition-colors ${
                active ? "bg-indigo-400" : "bg-zinc-700 group-hover:bg-zinc-600"
              }`}
            />
            <span className={`text-[10px] leading-none uppercase tracking-wide ${active ? "text-indigo-300" : "text-zinc-500 group-hover:text-zinc-400"}`}>
              {formatShortMonth(m)}
            </span>
          </button>
        );
      })}
    </div>
  );
}

// ─── CategoryPanel (drill-down) ──────────────────────────────────────────────

function CategoryPanel({ category, selectedMonth }: { category: string; selectedMonth: string }) {
  const qc = useQueryClient();
  const updateTx = useUpdateTransaction();
  const from = `${selectedMonth}-01`;
  const [year, month] = selectedMonth.split("-").map(Number);
  const nextDate = new Date(year, month, 1);
  const to = `${nextDate.getFullYear()}-${String(nextDate.getMonth() + 1).padStart(2, "0")}-01`;
  const { data, isLoading } = useTransactions({ categories: category ? [category] : [], from, to, limit: 200 });
  const txns = data?.data || [];

  return (
    <tr>
      <td colSpan={4} className="px-0 pb-2 bg-zinc-950/60 border-b border-zinc-800">
        {isLoading ? (
          <p className="text-xs text-zinc-500 px-6 py-2">Loading…</p>
        ) : txns.length === 0 ? (
          <p className="text-xs text-zinc-600 px-6 py-2">No transactions</p>
        ) : (
          <table className="w-full text-xs">
            <thead>
              <tr className="text-zinc-600">
                <th className="text-left px-6 py-1 font-normal w-24">Date</th>
                <th className="text-left px-2 py-1 font-normal">Description</th>
                <th className="text-right px-2 py-1 font-normal w-24">Amount</th>
                <th className="text-right px-4 py-1 font-normal w-36">Category</th>
              </tr>
            </thead>
            <tbody>
              {txns.map((tx) => (
                <tr key={tx.id} className="border-t border-zinc-800/30 hover:bg-zinc-800/20">
                  <td className="px-6 py-1.5 text-zinc-500 font-mono tabular-nums">{tx.transaction_date.slice(5).replace("-", "/")}</td>
                  <td className="px-2 py-1.5 text-zinc-300 max-w-xs truncate">{tx.effective_merchant || tx.description}</td>
                  <td className="px-2 py-1.5 text-right font-mono tabular-nums text-zinc-300">{fmtExact(Number(tx.amount))}</td>
                  <td className="px-4 py-1.5 text-right">
                    <select
                      className="bg-zinc-800 border border-zinc-700 rounded px-2 py-0.5 text-xs text-zinc-300 focus:outline-none focus:border-indigo-500 cursor-pointer"
                      defaultValue={tx.effective_category}
                      onChange={(e) =>
                        updateTx.mutate({ id: tx.id, category: e.target.value }, {
                          onSuccess: () => qc.invalidateQueries({ queryKey: ["analytics"] }),
                        })
                      }
                    >
                      {CATEGORIES.map((cat) => (
                        <option key={cat} value={cat}>{formatCategory(cat)}</option>
                      ))}
                    </select>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        )}
      </td>
    </tr>
  );
}

// ─── Page ─────────────────────────────────────────────────────────────────────

export default function AnalyticsPage() {
  const [selectedMonth, setSelectedMonth] = useState(currentMonthStr);
  const [expandedCategory, setExpandedCategory] = useState<string | null>(null);
  const { data: analytics, isLoading } = useMonthlyAnalytics(12);

  const months = useMemo(() => analytics ? [...analytics.months].reverse() : [], [analytics]);

  // If the initial month has no data yet (e.g. the 1st of the month), land on
  // the most recent month that does.
  useEffect(() => {
    if (months.length && !months.includes(selectedMonth)) {
      setSelectedMonth(months[months.length - 1]);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [months]);

  // Day-of-month spend, split-adjusted server-side by the same rules as the
  // headline. Also what makes the comparisons below like-for-like.
  const { data: dailyData } = useDailySpend(12);

  // A month in progress is only comparable to prior months through the same day.
  // Comparing 27 days of July against a full June always flattered July.
  const compareDay = elapsedDays(selectedMonth);
  const selectedIsPartial = selectedMonth === currentMonthStr();

  // Category rows for selected month
  const categoryRows = useMemo(() => {
    if (!analytics) return [];
    return analytics.rows
      .filter((r) => (r.spent[selectedMonth] || 0) > 0)
      .map((r) => ({ category: r.category, spent: r.spent[selectedMonth] || 0, txCount: r.txCount[selectedMonth] || 0 }))
      .sort((a, b) => b.spent - a.spent);
  }, [analytics, selectedMonth]);

  // Small multiples — top 8 categories by 12-month total
  const sparkData = useMemo(() => {
    if (!analytics) return [];
    return analytics.rows
      .map((r) => ({
        category: r.category,
        total: months.reduce((s, m) => s + (r.spent[m] || 0), 0),
        thisMonth: r.spent[selectedMonth] || 0,
        delta: (r.spent[selectedMonth] || 0) - (r.spent[prevMonth(selectedMonth)] || 0),
        series: months.map((m) => ({ month: m, v: r.spent[m] || 0 })),
      }))
      .sort((a, b) => b.total - a.total)
      .slice(0, 8);
  }, [analytics, months, selectedMonth]);

  // Top movers vs previous month, compared through the same day of the month so
  // a month in progress is not measured against a complete one.
  const movers = useMemo(() => {
    if (!analytics) return [];
    const pm = prevMonth(selectedMonth);
    if (!months.includes(pm)) return [];

    const catsNow = dailyData?.byCategory?.[selectedMonth] ?? {};
    const catsBefore = dailyData?.byCategory?.[pm] ?? {};
    const categories = new Set([...Object.keys(catsNow), ...Object.keys(catsBefore)]);

    return Array.from(categories)
      .map((category) => {
        const now = spendThrough(catsNow[category], compareDay);
        const before = spendThrough(catsBefore[category], compareDay);
        return { category, delta: now - before, now, before };
      })
      .filter((r) => Math.abs(r.delta) >= 1)
      .sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta))
      .slice(0, 6);
  }, [analytics, dailyData, months, selectedMonth, compareDay]);

  // Pareto chart data
  const paretoData = useMemo(() => {
    const total = categoryRows.reduce((s, r) => s + r.spent, 0);
    let running = 0;
    return categoryRows.map((r) => {
      running += r.spent;
      return {
        category: r.category,
        spent: r.spent,
        pct: total > 0 ? Math.round((r.spent / total) * 1000) / 10 : 0,
        cumulative: total > 0 ? Math.round((running / total) * 1000) / 10 : 0,
      };
    });
  }, [categoryRows]);

  // Cumulative spend chart data.
  //
  // Both series now come from the same server-side spend definition as the
  // headline. The typical line is also a real averaged curve rather than the
  // month total spread evenly — spending is lumpy (rent on the 1st, a shop on
  // the weekend), so a straight line made ordinary months look erratic.
  const cumulativeData = useMemo(() => {
    const daysInMonth = daysInMonthOf(selectedMonth);
    const lastDay = elapsedDays(selectedMonth);

    const daily = dailyData?.daily?.[selectedMonth] ?? {};
    // Only complete months with data form the baseline. A month still in
    // progress has no spend recorded past today, so including it would pull the
    // typical curve down by however much of it has not happened yet.
    const priorMonths = (analytics?.months ?? []).filter(
      (m) => m !== selectedMonth && m !== currentMonthStr() && (analytics?.totals[m]?.spent || 0) > 0
    );

    let cum = 0;
    return Array.from({ length: daysInMonth }, (_, i) => {
      const day = i + 1;
      if (day <= lastDay) cum += daily[day] || 0;

      const typical = priorMonths.length
        ? priorMonths.reduce((s, m) => s + spendThrough(dailyData?.daily?.[m], day), 0) / priorMonths.length
        : 0;

      return {
        day,
        actual: day <= lastDay ? Math.round(cum * 100) / 100 : null,
        typical: Math.round(typical * 100) / 100,
      };
    });
  }, [dailyData, analytics, selectedMonth]);

  if (isLoading || !analytics) {
    return (
      <div className="space-y-6 max-w-5xl">
        <h2 className="text-2xl font-display">Analytics</h2>
        <p className="text-zinc-500 text-sm">Loading…</p>
      </div>
    );
  }

  const totals = analytics.totals[selectedMonth] ?? { spent: 0, income: 0, investments: 0, net: 0 };
  const hasIncome = months.some((m) => (analytics.totals[m]?.income || 0) > 0);
  // `!== 0`, not `> 0`: the investments line is signed, so a net-disinvesting
  // month is real data, not an empty one. A window where every month nets
  // negative would otherwise render as "—".
  const hasInvestments = months.some((m) => (analytics.totals[m]?.investments || 0) !== 0);

  // Hero delta vs the average of the other *complete* months that have data.
  //
  // Two partial-month traps here. The month in progress never belongs in the
  // baseline, because most of it has not happened. And when the month in
  // progress is the one selected, its running total has to be measured against
  // the same slice of each prior month, not against their full totals.
  const otherMonths = months.filter(
    (m) => m !== selectedMonth && m !== currentMonthStr() && (analytics.totals[m]?.spent || 0) > 0
  );
  const comparableSpend = selectedIsPartial
    ? spendThrough(dailyData?.daily?.[selectedMonth], compareDay)
    : totals.spent;
  const avgSpend = otherMonths.length
    ? otherMonths.reduce(
        (s, m) => s + (selectedIsPartial ? spendThrough(dailyData?.daily?.[m], compareDay) : analytics.totals[m]?.spent || 0),
        0
      ) / otherMonths.length
    : 0;
  const avgDeltaPct = avgSpend > 0 ? Math.round(((comparableSpend - avgSpend) / avgSpend) * 100) : 0;
  const throughQualifier = selectedIsPartial ? ` through day ${compareDay}` : "";
  const heroSentence =
    avgSpend === 0 ? "" :
    Math.abs(avgDeltaPct) <= 3 ? `in line with your ${otherMonths.length}-month average${throughQualifier}` :
    `${Math.abs(avgDeltaPct)}% ${avgDeltaPct > 0 ? "above" : "below"} your ${otherMonths.length}-month average of ${fmt(avgSpend)}${throughQualifier}`;

  const pareto80idx = paretoData.findIndex((r) => r.cumulative >= 80);
  const tableMonths = analytics.months.slice(0, 6); // newest-first, last 6
  const maxMoverDelta = Math.max(...movers.map((m) => Math.abs(m.delta)), 1);

  return (
    <div className="space-y-6 max-w-5xl">

      {/* ── Hero + month spine ── */}
      <div className="border-b border-zinc-800 pb-5">
        <p className="text-[11px] uppercase tracking-[0.18em] text-zinc-500 mb-2">Ledger · {formatMonth(selectedMonth)}</p>
        <div className="flex flex-wrap items-end justify-between gap-x-8 gap-y-4">
          <div>
            <p className="font-display text-5xl text-zinc-50 leading-none">
              {fmt(totals.spent)}
            </p>
            {heroSentence && (
              <p className="text-sm text-zinc-400 mt-2">
                <span className={avgDeltaPct > 3 ? "text-indigo-300" : avgDeltaPct < -3 ? "text-emerald-400" : "text-zinc-400"}>
                  {heroSentence}
                </span>
              </p>
            )}
          </div>
          <div className="w-full sm:w-auto sm:min-w-80 sm:flex-1 sm:max-w-md">
            <MonthSpine months={months} totals={analytics.totals} selected={selectedMonth} onSelect={(m) => { setSelectedMonth(m); setExpandedCategory(null); }} />
          </div>
        </div>
      </div>

      {/* ── Cashflow strip ── */}
      <div className="bg-zinc-900 border border-zinc-800 rounded-xl grid grid-cols-2 sm:grid-cols-4 divide-x divide-y sm:divide-y-0 divide-zinc-800 overflow-hidden">
        <div className="px-4 py-3.5">
          <p className="text-[10px] uppercase tracking-wider text-zinc-500 mb-1.5">Income</p>
          <p className={`text-xl font-mono tabular-nums ${hasIncome ? "text-emerald-400" : "text-zinc-600"}`}>{hasIncome ? fmt(totals.income) : "—"}</p>
        </div>
        <div className="px-4 py-3.5">
          <p className="text-[10px] uppercase tracking-wider text-zinc-500 mb-1.5">Expenses</p>
          <p className="text-xl font-mono tabular-nums text-zinc-100">{fmt(totals.spent)}</p>
        </div>
        <div className="px-4 py-3.5">
          <p className="text-[10px] uppercase tracking-wider text-zinc-500 mb-1.5">Invested</p>
          <p className={`text-xl font-mono tabular-nums ${hasInvestments ? "text-indigo-300" : "text-zinc-600"}`}>{hasInvestments ? fmt(totals.investments) : "—"}</p>
        </div>
        <div className="px-4 py-3.5">
          <p className="text-[10px] uppercase tracking-wider text-zinc-500 mb-1.5">Net cash</p>
          {hasIncome ? (
            <p className={`text-xl font-mono tabular-nums ${totals.net >= 0 ? "text-emerald-400" : "text-red-400"}`}>{totals.net >= 0 ? "+" : ""}{fmt(totals.net)}</p>
          ) : (
            <p className="text-xl font-mono tabular-nums text-zinc-600">—</p>
          )}
        </div>
      </div>

      {/* ── Top movers vs last month ── */}
      {movers.length > 0 && (
        <div className="bg-zinc-900 border border-zinc-800 rounded-xl p-4">
          <h3 className="text-sm font-medium mb-1">What changed</h3>
          <p className="text-xs text-zinc-500 mb-4">
            Biggest category moves vs {formatShortMonth(prevMonth(selectedMonth))}
            {selectedIsPartial && `, both through day ${compareDay}`}
          </p>
          <div className="grid sm:grid-cols-2 gap-x-8 gap-y-2.5">
            {movers.map((m) => (
              <div key={m.category} className="flex items-center gap-3">
                <span className="w-2 h-2 rounded-sm shrink-0" style={{ background: CATEGORY_COLORS[m.category] || CHART.axis }} />
                <span className="text-sm text-zinc-300 w-32 truncate shrink-0">{formatCategory(m.category)}</span>
                <div className="flex-1 h-1.5 rounded-full bg-zinc-800 overflow-hidden">
                  <div
                    className={`h-full rounded-full ${m.delta > 0 ? "bg-indigo-400" : "bg-emerald-500"}`}
                    style={{ width: `${Math.max(6, Math.round((Math.abs(m.delta) / maxMoverDelta) * 100))}%` }}
                  />
                </div>
                <span className={`text-xs font-mono tabular-nums w-16 text-right ${m.delta > 0 ? "text-indigo-300" : "text-emerald-400"}`}>
                  {fmtSigned(m.delta)}
                </span>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* ── Category small multiples ── */}
      {sparkData.length > 0 && (
        <div>
          <div className="flex items-baseline justify-between mb-3">
            <h3 className="text-sm font-medium">Category trends</h3>
            <span className="text-xs text-zinc-500">12 months · top {sparkData.length} by total spend</span>
          </div>
          <div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
            {sparkData.map((s) => (
              <div key={s.category} className="bg-zinc-900 border border-zinc-800 rounded-xl px-3.5 pt-3 pb-1.5">
                <div className="flex items-center justify-between gap-2 mb-0.5">
                  <span className="flex items-center gap-1.5 text-xs text-zinc-400 truncate">
                    <span className="w-1.5 h-1.5 rounded-sm shrink-0" style={{ background: CATEGORY_COLORS[s.category] || CHART.axis }} />
                    {formatCategory(s.category)}
                  </span>
                  {Math.abs(s.delta) >= 1 && (
                    <span className={`text-[10px] font-mono tabular-nums shrink-0 ${s.delta > 0 ? "text-indigo-300" : "text-emerald-400"}`}>{fmtSigned(s.delta)}</span>
                  )}
                </div>
                <p className="text-lg font-mono tabular-nums text-zinc-100 mb-1">{fmt(s.thisMonth)}</p>
                <div className="h-10 -mx-1">
                  <ResponsiveContainer width="100%" height="100%">
                    <AreaChart data={s.series} margin={{ top: 2, right: 0, bottom: 0, left: 0 }}>
                      <Area
                        dataKey="v"
                        stroke={CATEGORY_COLORS[s.category] || CHART.axis}
                        strokeWidth={1.5}
                        fill={CATEGORY_COLORS[s.category] || CHART.axis}
                        fillOpacity={0.12}
                        isAnimationActive={false}
                      />
                    </AreaChart>
                  </ResponsiveContainer>
                </div>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* ── Pareto ── */}
      {paretoData.length > 0 && (
        <div className="bg-zinc-900 border border-zinc-800 rounded-xl p-4">
          <div className="flex items-center justify-between mb-4">
            <h3 className="text-sm font-medium">Spend concentration</h3>
            {pareto80idx >= 0 && (
              <span className="text-xs text-zinc-500">
                Top {pareto80idx + 1} categor{pareto80idx === 0 ? "y" : "ies"} = 80% of spend
              </span>
            )}
          </div>
          <ResponsiveContainer width="100%" height={220}>
            <ComposedChart data={paretoData} margin={{ top: 4, right: 48, bottom: 0, left: 8 }}>
              <XAxis
                dataKey="category"
                tick={{ fill: CHART.axis, fontSize: 11 }}
                axisLine={false}
                tickLine={false}
                tickFormatter={formatCategory}
              />
              <YAxis
                yAxisId="left"
                tick={{ fill: CHART.axis, fontSize: 11 }}
                axisLine={false}
                tickLine={false}
                tickFormatter={(v) => `$${v}`}
                width={52}
              />
              <YAxis
                yAxisId="right"
                orientation="right"
                tick={{ fill: CHART.axis, fontSize: 11 }}
                axisLine={false}
                tickLine={false}
                tickFormatter={(v) => `${v}%`}
                domain={[0, 100]}
                width={36}
              />
              <Tooltip content={<ParetoTooltip />} cursor={{ fill: "rgba(232,224,204,0.04)" }} />
              <ReferenceLine yAxisId="right" y={80} stroke={CHART.faint} strokeDasharray="4 2" label={{ value: "80%", fill: CHART.axis, fontSize: 10, position: "right" }} />
              <Bar yAxisId="left" dataKey="spent" radius={[3, 3, 0, 0]} maxBarSize={40}>
                {paretoData.map((entry, i) => (
                  <Cell
                    key={entry.category}
                    fill={i <= pareto80idx ? (CATEGORY_COLORS[entry.category] || CHART.accent) : CHART.dim}
                  />
                ))}
              </Bar>
              <Line
                yAxisId="right"
                dataKey="cumulative"
                stroke={CHART.accentSoft}
                strokeWidth={2}
                dot={{ fill: CHART.accentSoft, r: 3 }}
                activeDot={{ r: 5 }}
              />
            </ComposedChart>
          </ResponsiveContainer>
          <div className="flex items-center gap-4 mt-2 justify-end">
            <span className="flex items-center gap-1.5 text-xs text-zinc-500"><span className="w-3 h-0.5 inline-block" style={{ background: CHART.accentSoft }} />Cumulative %</span>
            <span className="flex items-center gap-1.5 text-xs text-zinc-500"><span className="w-3 h-2 rounded-sm inline-block" style={{ background: CHART.dim }} />Beyond 80%</span>
          </div>
        </div>
      )}

      {/* ── Cumulative pace ── */}
      <div className="bg-zinc-900 border border-zinc-800 rounded-xl p-4">
        <div className="flex items-center justify-between mb-4">
          <h3 className="text-sm font-medium">Spend pace</h3>
          <span className="text-xs text-zinc-500">cumulative through the month, vs typical</span>
        </div>
        <ResponsiveContainer width="100%" height={180}>
          <LineChart data={cumulativeData} margin={{ top: 4, right: 8, bottom: 0, left: 8 }}>
            <XAxis dataKey="day" tick={{ fill: CHART.axis, fontSize: 11 }} axisLine={false} tickLine={false} tickFormatter={(v) => `${v}`} interval={4} />
            <YAxis tick={{ fill: CHART.axis, fontSize: 11 }} axisLine={false} tickLine={false} tickFormatter={(v) => `$${(v / 1000).toFixed(1)}k`} width={44} />
            <Tooltip content={<CumulativeTooltip />} cursor={{ stroke: "rgba(232,224,204,0.08)", strokeWidth: 1 }} />
            <Line dataKey="typical" stroke={CHART.faint} strokeWidth={1.5} strokeDasharray="4 3" dot={false} name="typical" />
            <Line dataKey="actual" stroke={CHART.accent} strokeWidth={2} dot={false} activeDot={{ r: 4 }} connectNulls={false} name="actual" />
          </LineChart>
        </ResponsiveContainer>
        <div className="flex items-center gap-4 mt-2 justify-end">
          <span className="flex items-center gap-1.5 text-xs text-zinc-500"><span className="w-4 h-0.5 inline-block" style={{ background: CHART.accent }} />This month</span>
          <span className="flex items-center gap-1.5 text-xs text-zinc-500"><span className="w-4 h-0.5 inline-block border-t border-dashed" style={{ borderColor: CHART.faint }} />Typical pace</span>
        </div>
      </div>

      {/* ── Category breakdown table — expandable rows ── */}
      {categoryRows.length > 0 && (
        <div className="bg-zinc-900 border border-zinc-800 rounded-xl overflow-hidden">
          <div className="px-4 py-3 border-b border-zinc-800">
            <h3 className="text-sm font-medium">Where it went — {formatMonth(selectedMonth)}</h3>
          </div>
          <table className="w-full text-sm">
            <thead>
              <tr className="border-b border-zinc-800">
                <th className="text-left px-4 py-2 text-xs text-zinc-500 font-medium">Category</th>
                <th className="text-right px-4 py-2 text-xs text-zinc-500 font-medium">Spent</th>
                <th className="text-right px-4 py-2 text-xs text-zinc-500 font-medium"># Txns</th>
                <th className="text-right px-4 py-2 text-xs text-zinc-500 font-medium">% of total</th>
              </tr>
            </thead>
            <tbody>
              {categoryRows.map(({ category, spent, txCount }) => {
                const isExpanded = expandedCategory === category;
                return (
                  <Fragment key={category}>
                    <tr
                      className={`border-b border-zinc-800/50 cursor-pointer select-none transition-colors ${isExpanded ? "bg-zinc-800/40" : "hover:bg-zinc-800/30"}`}
                      onClick={() => setExpandedCategory(isExpanded ? null : category)}
                    >
                      <td className="px-4 py-2.5 font-medium">
                        <span className="flex items-center gap-2">
                          <span className="w-2 h-2 rounded-sm shrink-0" style={{ background: CATEGORY_COLORS[category] || CHART.axis }} />
                          {formatCategory(category)}
                          <span className="text-zinc-600 text-xs ml-1">{isExpanded ? "▲" : "▼"}</span>
                        </span>
                      </td>
                      <td className="px-4 py-2.5 text-right font-mono tabular-nums">{fmtExact(spent)}</td>
                      <td className="px-4 py-2.5 text-right text-zinc-400 font-mono tabular-nums">{txCount}</td>
                      <td className="px-4 py-2.5 text-right text-zinc-400 font-mono tabular-nums">
                        {totals.spent > 0 ? ((spent / totals.spent) * 100).toFixed(1) : "0.0"}%
                      </td>
                    </tr>
                    {isExpanded && <CategoryPanel category={category} selectedMonth={selectedMonth} />}
                  </Fragment>
                );
              })}
            </tbody>
          </table>
        </div>
      )}

      {/* ── 6-month ledger table (heat-tinted) ── */}
      {tableMonths.length > 0 && (
        <div>
          <h3 className="text-sm font-medium text-zinc-400 mb-3">Six-month ledger</h3>
          <div className="overflow-x-auto rounded-xl border border-zinc-800">
            <table className="w-full text-xs border-collapse">
              <thead>
                <tr className="border-b border-zinc-800 bg-zinc-900">
                  <th className="text-left px-3 py-2 text-zinc-500 font-medium sticky left-0 bg-zinc-900 min-w-32">Category</th>
                  {tableMonths.map((m) => (
                    <th
                      key={m}
                      className={`text-right px-3 py-2 font-medium whitespace-nowrap cursor-pointer hover:text-zinc-300 ${m === selectedMonth ? "text-indigo-300" : "text-zinc-500"}`}
                      onClick={() => setSelectedMonth(m)}
                    >
                      {formatShortMonth(m)}
                    </th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {analytics.rows.map((row) => {
                  const rowMax = Math.max(...tableMonths.map((m) => row.spent[m] || 0), 1);
                  return (
                    <tr key={row.category} className="border-b border-zinc-800/40 hover:bg-zinc-900/30">
                      <td className="px-3 py-2 font-medium sticky left-0 bg-zinc-950">
                        <span className="flex items-center gap-1.5">
                          <span className="w-1.5 h-1.5 rounded-sm shrink-0" style={{ background: CATEGORY_COLORS[row.category] || CHART.axis }} />
                          {formatCategory(row.category)}
                        </span>
                      </td>
                      {tableMonths.map((m) => {
                        const spent = row.spent[m];
                        const heat = spent !== undefined ? (spent / rowMax) * 0.28 : 0;
                        return (
                          <td
                            key={m}
                            className={`px-3 py-2 text-right font-mono tabular-nums ${spent === undefined ? "text-zinc-700" : "text-zinc-300"}`}
                            style={heat > 0.02 ? { background: `rgba(188, 111, 48, ${heat.toFixed(3)})` } : undefined}
                          >
                            {spent !== undefined ? fmt(spent) : "—"}
                          </td>
                        );
                      })}
                    </tr>
                  );
                })}
                {hasIncome && (
                  <tr className="border-b border-zinc-800/40">
                    <td className="px-3 py-2 font-medium sticky left-0 bg-zinc-950 text-emerald-600">Income</td>
                    {tableMonths.map((m) => {
                      const inc = analytics.income[m];
                      return (
                        <td key={m} className="px-3 py-2 text-right font-mono tabular-nums text-emerald-500">
                          {inc ? fmt(inc) : "—"}
                        </td>
                      );
                    })}
                  </tr>
                )}
                {hasInvestments && (
                  <tr className="border-b border-zinc-800/40">
                    <td className="px-3 py-2 font-medium sticky left-0 bg-zinc-950 text-indigo-400">Invested</td>
                    {tableMonths.map((m) => {
                      const inv = analytics.investments[m];
                      return (
                        // A net-disinvesting month is a different fact from an
                        // investing one; same-coloured digits hide the sign.
                        <td key={m} className={`px-3 py-2 text-right font-mono tabular-nums ${inv < 0 ? "text-amber-400" : "text-indigo-300"}`}>
                          {inv ? fmt(inv) : "—"}
                        </td>
                      );
                    })}
                  </tr>
                )}
                <tr className="border-t-2 border-zinc-700 font-semibold bg-zinc-900/50">
                  <td className="px-3 py-2 sticky left-0 bg-zinc-900">Expenses</td>
                  {tableMonths.map((m) => {
                    const t = analytics.totals[m];
                    return (
                      <td key={m} className={`px-3 py-2 text-right font-mono tabular-nums ${m === selectedMonth ? "text-indigo-300" : ""}`}>
                        {fmt(t?.spent || 0)}
                      </td>
                    );
                  })}
                </tr>
                {hasIncome && (
                  <tr className="font-semibold bg-zinc-900/50">
                    <td className="px-3 py-2 sticky left-0 bg-zinc-900">Net cash</td>
                    {tableMonths.map((m) => {
                      const t = analytics.totals[m];
                      const net = t?.net || 0;
                      return (
                        <td key={m} className={`px-3 py-2 text-right font-mono tabular-nums ${net >= 0 ? "text-emerald-400" : "text-red-400"}`}>
                          {net >= 0 ? "+" : ""}{fmt(net)}
                        </td>
                      );
                    })}
                  </tr>
                )}
              </tbody>
            </table>
          </div>
        </div>
      )}
    </div>
  );
}
