import { useState, useEffect, useMemo, useCallback } from "react";
import { supabase } from "@/integrations/supabase/client";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Switch } from "@/components/ui/switch";
import { Download, Loader2, TrendingUp, TrendingDown, DollarSign, Printer } from "lucide-react";
import { generateBudgetVsActualPdf } from "@/lib/budgetVsActualPdf";

type ActualRow = {
  association_id: string;
  period_month: string; // ISO date YYYY-MM-01
  account_type: "income" | "expense" | string;
  gl_account_id: string | null;
  category_name: string | null;
  amount: number;
};

type BudgetRow = {
  id: string;
  association_id: string;
  fiscal_year: number;
  category: string;
  budgeted_amount: number | null;
  account_type: string;
  is_parent: boolean;
  parent_id: string | null;
  gl_account_id: string | null;
};

type Timeframe = "ytd" | "month" | "quarter" | "year" | "custom";

interface Props {
  associationId: string;
  fiscalYear: number;
}

const fmt = (n: number) =>
  (n < 0 ? "-" : "") +
  "$" +
  Math.abs(n).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });

const fmtPct = (n: number) =>
  isFinite(n) ? `${n >= 0 ? "+" : ""}${n.toFixed(1)}%` : "—";

const monthShort = (iso: string) => {
  const d = new Date(iso + "T12:00:00");
  return d.toLocaleString("en-US", { month: "short", year: "2-digit" });
};

const normalizeName = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();

const parseMoney = (value: unknown): number => {
  if (typeof value === "number") return value;
  if (value == null) return 0;
  const raw = String(value).trim();
  if (!raw) return 0;
  const negative = raw.includes("(") && raw.includes(")");
  const cleaned = raw.replace(/[,$\s]/g, "").replace(/[()]/g, "");
  const parsed = Number(cleaned);
  return Number.isFinite(parsed) ? (negative ? -parsed : parsed) : 0;
};

const zohoChildKeys = ["account_transactions", "accounts", "sections", "children", "child_accounts", "rows"];

const asArray = (value: unknown): any[] => {
  if (Array.isArray(value)) return value;
  if (value && typeof value === "object") return [value];
  return [];
};

export default function BudgetVsActualReport({ associationId, fiscalYear }: Props) {
  const [actuals, setActuals] = useState<ActualRow[]>([]);
  const [budgets, setBudgets] = useState<BudgetRow[]>([]);
  const [accountParents, setAccountParents] = useState<Record<string, string | null>>({});
  const [accountNames, setAccountNames] = useState<Record<string, string>>({});
  const [accountNumbers, setAccountNumbers] = useState<Record<string, string>>({});
  const [coaIdByName, setCoaIdByName] = useState<Record<string, string>>({});
  const [association, setAssociation] = useState<
    { id: string; name: string; logo_url: string | null; zoho_organization_id: string | null } | null
  >(null);
  const [loading, setLoading] = useState(false);
  const [printing, setPrinting] = useState(false);
  const [timeframe, setTimeframe] = useState<Timeframe>("ytd");
  const [month, setMonth] = useState((new Date().getMonth() + 1).toString());
  const [quarter, setQuarter] = useState("1");
  const [customStart, setCustomStart] = useState(`${fiscalYear}-01-01`);
  const [customEnd, setCustomEnd] = useState(`${fiscalYear}-12-31`);
  const [comparison, setComparison] = useState<
    "none" | "prior_year" | "prior_year_full" | "prior_period" | "custom"
  >("prior_year");
  const [cmpCustomStart, setCmpCustomStart] = useState(`${fiscalYear}-01-01`);
  const [cmpCustomEnd, setCmpCustomEnd] = useState(`${fiscalYear}-12-31`);
  const [hideUnbudgeted, setHideUnbudgeted] = useState(true);

  const fetchData = useCallback(async () => {
    if (!associationId) return;
    setLoading(true);
    // Fetch a wide window so prior-year/prior-period comparisons work
    const startWindow = `${fiscalYear - 1}-01-01`;
    const endWindow = `${fiscalYear + 1}-01-01`;
    const [actualsRes, budgetsRes, assocRes] = await Promise.all([
      supabase
        .from("budget_actuals_monthly" as any)
        .select("*")
        .eq("association_id", associationId)
        .gte("period_month", startWindow)
        .lt("period_month", endWindow),
      supabase
        .from("budgets")
        .select("id, association_id, fiscal_year, category, budgeted_amount, account_type, is_parent, parent_id, gl_account_id")
        .eq("association_id", associationId)
        .in("fiscal_year", [fiscalYear, fiscalYear - 1]),
      supabase.from("associations").select("id, name, logo_url, zoho_organization_id").eq("id", associationId).maybeSingle(),
    ]);
    setActuals(((actualsRes.data as any) || []) as ActualRow[]);
    setBudgets(((budgetsRes.data as any) || []) as BudgetRow[]);
    setAssociation((assocRes.data as any) || null);
    // Fetch chart of accounts parent links so subaccount actuals roll up to parent budgets
    const coaRows: { id: string; parent_account_id: string | null; account_name: string | null }[] = [];
    let cFrom = 0;
    while (true) {
      const { data } = await supabase
        .from("chart_of_accounts")
        .select("id, parent_account_id, account_name, account_number")
        .range(cFrom, cFrom + 999);
      if (!data || data.length === 0) break;
      coaRows.push(...(data as any));
      if (data.length < 1000) break;
      cFrom += 1000;
    }
    const parentMap: Record<string, string | null> = {};
    const nameMap: Record<string, string> = {};
    const numMap: Record<string, string> = {};
    const idByName: Record<string, string> = {};
    coaRows.forEach((r) => {
      parentMap[r.id] = r.parent_account_id;
      if (r.account_name) {
        nameMap[r.id] = r.account_name;
        idByName[normalizeName(r.account_name)] = r.id;
      }
      if ((r as any).account_number) numMap[r.id] = String((r as any).account_number);
    });
    setAccountParents(parentMap);
    setAccountNames(nameMap);
    setAccountNumbers(numMap);
    setCoaIdByName(idByName);
    setLoading(false);
  }, [associationId, fiscalYear]);

  useEffect(() => {
    fetchData();
  }, [fetchData]);

  // Build current and comparison date ranges
  const ranges = useMemo(() => {
    let curStart: Date, curEnd: Date;
    if (timeframe === "ytd") {
      curStart = new Date(fiscalYear, 0, 1);
      const today = new Date();
      curEnd =
        today.getFullYear() === fiscalYear
          ? today
          : new Date(fiscalYear, 11, 31);
    } else if (timeframe === "year") {
      curStart = new Date(fiscalYear, 0, 1);
      curEnd = new Date(fiscalYear, 11, 31);
    } else if (timeframe === "month") {
      const m = parseInt(month) - 1;
      curStart = new Date(fiscalYear, m, 1);
      curEnd = new Date(fiscalYear, m + 1, 0);
    } else if (timeframe === "quarter") {
      const q = parseInt(quarter) - 1;
      curStart = new Date(fiscalYear, q * 3, 1);
      curEnd = new Date(fiscalYear, q * 3 + 3, 0);
    } else {
      curStart = new Date(customStart + "T12:00:00");
      curEnd = new Date(customEnd + "T12:00:00");
    }

    let cmpStart: Date | null = null,
      cmpEnd: Date | null = null;
    if (comparison === "prior_year") {
      cmpStart = new Date(curStart);
      cmpStart.setFullYear(cmpStart.getFullYear() - 1);
      cmpEnd = new Date(curEnd);
      cmpEnd.setFullYear(cmpEnd.getFullYear() - 1);
    } else if (comparison === "prior_year_full") {
      cmpStart = new Date(curStart.getFullYear() - 1, 0, 1);
      cmpEnd = new Date(curStart.getFullYear() - 1, 11, 31);
    } else if (comparison === "prior_period") {
      const days = Math.round((curEnd.getTime() - curStart.getTime()) / 86400000) + 1;
      cmpEnd = new Date(curStart);
      cmpEnd.setDate(cmpEnd.getDate() - 1);
      cmpStart = new Date(cmpEnd);
      cmpStart.setDate(cmpStart.getDate() - days + 1);
    } else if (comparison === "custom") {
      cmpStart = new Date(cmpCustomStart + "T12:00:00");
      cmpEnd = new Date(cmpCustomEnd + "T12:00:00");
    }
    return { curStart, curEnd, cmpStart, cmpEnd };
  }, [timeframe, month, quarter, customStart, customEnd, fiscalYear, comparison, cmpCustomStart, cmpCustomEnd]);

  const inRange = (iso: string, start: Date, end: Date) => {
    const d = new Date(iso + "T12:00:00");
    return d >= new Date(start.getFullYear(), start.getMonth(), 1) && d <= end;
  };

  // Budgets are stored as MONTHLY amounts in `budgeted_amount`.
  // The period budget = monthly × number of months in the selected range.
  // The annual budget = monthly × 12.
  // Example: 01/01 – 04/30 covers 4 months → multiplier = 4.
  const countBudgetMonths = (start: Date, end: Date) =>
    Math.max(0, (end.getFullYear() - start.getFullYear()) * 12 + (end.getMonth() - start.getMonth()) + 1);

  const budgetMonths = useMemo(() => countBudgetMonths(ranges.curStart, ranges.curEnd), [ranges.curStart, ranges.curEnd]);

  const comparisonBudgetMonths = useMemo(
    () => (ranges.cmpStart && ranges.cmpEnd ? countBudgetMonths(ranges.cmpStart, ranges.cmpEnd) : null),
    [ranges.cmpStart, ranges.cmpEnd],
  );

  // Aggregate actuals into two parallel indexes: one keyed by gl_account_id
  // and one keyed by lowercase category_name. This lets a budget line match
  // actuals whether it has a GL account linked or only a category label.
  type Bucket = { income: number; expense: number; glIds: Set<string>; names: Set<string>; display?: string };
  type Aggregated = { byGl: Record<string, Bucket>; byName: Record<string, Bucket> };
  const aggregate = (start: Date, end: Date): Aggregated => {
    const byGl: Record<string, Bucket> = {};
    const byName: Record<string, Bucket> = {};
    const make = (): Bucket => ({ income: 0, expense: 0, glIds: new Set(), names: new Set() });
    actuals.forEach((a) => {
      if (!inRange(a.period_month, start, end)) return;
      const amt = Number(a.amount) || 0;
      const displayName = String(a.category_name || "Uncategorized");
      const nameKey = normalizeName(displayName);
      // Always index by name so name-based budget matching still works for bills with a GL.
      const nb = (byName[nameKey] ||= make());
      if (!nb.display) nb.display = displayName;
      if (a.gl_account_id) nb.glIds.add(a.gl_account_id);
      nb.names.add(nameKey);
      if (a.account_type === "income") nb.income += amt; else nb.expense += amt;
      // Also index by gl_account_id when present for direct ID lookups.
      if (a.gl_account_id) {
        const gb = (byGl[a.gl_account_id] ||= make());
        gb.glIds.add(a.gl_account_id);
        if (a.category_name) gb.names.add(nameKey);
        if (a.account_type === "income") gb.income += amt; else gb.expense += amt;
      }
    });
    return { byGl, byName };
  };

  const localCurActuals = useMemo(() => aggregate(ranges.curStart, ranges.curEnd), [ranges, actuals]);
  const localCmpActuals = useMemo(
    () => (ranges.cmpStart && ranges.cmpEnd ? aggregate(ranges.cmpStart, ranges.cmpEnd) : null),
    [ranges, actuals],
  );

  const curActuals = localCurActuals;
  const cmpActuals = localCmpActuals;

  // Build report rows: one per leaf budget line, with matched actuals
  const reportRows = useMemo(() => {
    const fyAllBudgets = budgets.filter((b) => b.fiscal_year === fiscalYear);
    const parentNameById: Record<string, string> = {};
    fyAllBudgets.filter((b) => b.is_parent).forEach((p) => { parentNameById[p.id] = p.category; });
    const fyBudgets = fyAllBudgets.filter((b) => !b.is_parent);
    const matchActual = (b: BudgetRow, source: Aggregated) => {
      // Prefer gl_account_id match when budget is linked to a GL account.
      // Also roll up any subaccount actuals whose ancestor chain includes this GL.
      if (b.gl_account_id) {
        let total = 0;
        let hit = false;
        Object.entries(source.byGl).forEach(([glId, v]) => {
          if (glId === b.gl_account_id) { hit = true; total += b.account_type === "income" ? v.income : v.expense; return; }
          // walk parent chain
          let p: string | null | undefined = accountParents[glId];
          const seen = new Set<string>();
          while (p && !seen.has(p)) {
            seen.add(p);
            if (p === b.gl_account_id) {
              hit = true;
              total += b.account_type === "income" ? v.income : v.expense;
              break;
            }
            p = accountParents[p];
          }
        });
        if (hit) return total;
      }
      // Otherwise match by category name (covers legacy budgets and billable_expenses).
      const lc = normalizeName(b.category);
      const v = source.byName[lc];
      if (!v) return 0;
      return b.account_type === "income" ? v.income : v.expense;
    };

    const rows = fyBudgets.map((b) => {
      const cur = matchActual(b, curActuals);
      const cmp = cmpActuals ? matchActual(b, cmpActuals) : 0;
      const annualBudget = Number(b.budgeted_amount) || 0;
      const monthly = annualBudget / 12;
      const proratedBudget = monthly * budgetMonths;
      const variance = cur - proratedBudget;
      const pctOfBudget = proratedBudget > 0 ? (cur / proratedBudget) * 100 : 0;
      const comparisonBudget = comparisonBudgetMonths !== null ? monthly * comparisonBudgetMonths : 0;
      const comparisonVariance = cmp - comparisonBudget;
      const comparisonPctOfBudget = comparisonBudget > 0 ? (cmp / comparisonBudget) * 100 : 0;
      const cmpDelta = cmp !== 0 ? ((cur - cmp) / Math.abs(cmp)) * 100 : NaN;
      return {
        id: b.id,
        category: b.category,
        accountCode: b.gl_account_id ? (accountNumbers[b.gl_account_id] || "") : "",
        accountType: b.account_type,
        parentId: b.parent_id || null,
        parentCategory: b.parent_id ? (parentNameById[b.parent_id] || null) : null,
        parentCode: b.parent_id
          ? (fyAllBudgets.find((p) => p.id === b.parent_id)?.gl_account_id
              ? accountNumbers[fyAllBudgets.find((p) => p.id === b.parent_id)!.gl_account_id!] || ""
              : "")
          : "",
        budget: proratedBudget,
        annualBudget,
        actual: cur,
        variance,
        pctOfBudget,
        comparisonActual: cmp,
        comparisonBudget,
        comparisonVariance,
        comparisonPctOfBudget,
        cmpDelta,
      };
    });

    // Also include actuals that don't match any budget line.
    const matchedGlIds = new Set<string>();
    const matchedNames = new Set<string>();
    fyBudgets.forEach((b) => {
      if (b.gl_account_id) matchedGlIds.add(b.gl_account_id);
      matchedNames.add(normalizeName(b.category));
    });
    // Treat actuals as matched if any ancestor GL is a budgeted account.
    const isCoveredByParentBudget = (glId: string | undefined): boolean => {
      if (!glId) return false;
      let p: string | null | undefined = accountParents[glId];
      const seen = new Set<string>();
      while (p && !seen.has(p)) {
        seen.add(p);
        if (matchedGlIds.has(p)) return true;
        p = accountParents[p];
      }
      return false;
    };
    // Walk byName so each unbudgeted line shows under its human-readable name.
    const showUnbudgeted = !hideUnbudgeted;
    if (showUnbudgeted) {
    Object.entries(curActuals.byName).forEach(([key, v]) => {
      const isMatchedById = Array.from(v.glIds).some((id) => matchedGlIds.has(id));
      const isMatchedByName = matchedNames.has(key);
      const isMatchedByParent = Array.from(v.glIds).some((id) => isCoveredByParentBudget(id));
      if (isMatchedById || isMatchedByName || isMatchedByParent) return;
      // Add unbudgeted lines for income and expense if amount > 0
      ["income", "expense"].forEach((t) => {
        const amt = t === "income" ? v.income : v.expense;
        if (amt <= 0) return;
        const cmpBucket = cmpActuals?.byName[key];
        const cmpAmt = cmpBucket
          ? (t === "income" ? cmpBucket.income : cmpBucket.expense) || 0
          : 0;
        rows.push({
          id: `unb-${t}-${key}`,
          category: (v.display || key) + " (unbudgeted)",
          accountCode: "",
          accountType: t,
          parentId: null,
          parentCategory: null,
          parentCode: "",
          budget: 0,
        annualBudget: 0,
          actual: amt,
          variance: -amt,
          pctOfBudget: 0,
          comparisonActual: cmpAmt,
          comparisonBudget: 0,
          comparisonVariance: cmpAmt,
          comparisonPctOfBudget: 0,
          cmpDelta: cmpAmt !== 0 ? ((amt - cmpAmt) / Math.abs(cmpAmt)) * 100 : NaN,
        });
      });
    });
    }

    return rows.sort((a, b) => {
      if (a.accountType !== b.accountType) return a.accountType === "income" ? -1 : 1;
      // Sort by parent account code (then name), then child code (then name)
      const apc = a.parentCode || "~~~";
      const bpc = b.parentCode || "~~~";
      if (apc !== bpc) return apc.localeCompare(bpc, undefined, { numeric: true });
      const ap = a.parentCategory || "~~~";
      const bp = b.parentCategory || "~~~";
      if (ap !== bp) return ap.localeCompare(bp);
      const ac = a.accountCode || "~~~";
      const bc = b.accountCode || "~~~";
      if (ac !== bc) return ac.localeCompare(bc, undefined, { numeric: true });
      return a.category.localeCompare(b.category);
    });
  }, [budgets, fiscalYear, curActuals, cmpActuals, budgetMonths, comparisonBudgetMonths, accountParents, accountNumbers, hideUnbudgeted]);

  const totals = useMemo(() => {
    const income = reportRows.filter((r) => r.accountType === "income");
    const expense = reportRows.filter((r) => r.accountType !== "income");
    const sum = (rows: typeof reportRows, k: "budget" | "annualBudget" | "actual" | "comparisonActual" | "comparisonBudget") =>
      rows.reduce((s, r) => s + (r[k] || 0), 0);
    return {
      incomeBudget: sum(income, "budget"),
      incomeAnnualBudget: sum(income, "annualBudget"),
      incomeActual: sum(income, "actual"),
      incomeCmp: sum(income, "comparisonActual"),
      incomeCmpBudget: sum(income, "comparisonBudget"),
      expenseBudget: sum(expense, "budget"),
      expenseAnnualBudget: sum(expense, "annualBudget"),
      expenseActual: sum(expense, "actual"),
      expenseCmp: sum(expense, "comparisonActual"),
      expenseCmpBudget: sum(expense, "comparisonBudget"),
    };
  }, [reportRows]);

  const netBudget = totals.incomeBudget - totals.expenseBudget;
  const netActual = totals.incomeActual - totals.expenseActual;
  const netCmp = totals.incomeCmp - totals.expenseCmp;
  const netCmpBudget = totals.incomeCmpBudget - totals.expenseCmpBudget;

  const downloadCsv = () => {
    const headers = ["Type", "Category", "Budget", "Actual", "Variance", "% of Budget"];
    if (comparison !== "none") headers.push("Comparison Actual", "Comparison Budget", "Comparison Variance", "Comparison % of Budget");
    const lines = [headers.join(",")];
    reportRows.forEach((r) => {
      const row = [
        r.accountType,
        `"${r.category.replace(/"/g, '""')}"`,
        r.budget.toFixed(2),
        r.actual.toFixed(2),
        r.variance.toFixed(2),
        r.pctOfBudget.toFixed(1) + "%",
      ];
      if (comparison !== "none") {
        row.push(
          r.comparisonActual.toFixed(2),
          r.comparisonBudget.toFixed(2),
          r.comparisonVariance.toFixed(2),
          r.comparisonBudget > 0 ? r.comparisonPctOfBudget.toFixed(1) + "%" : "—",
        );
      }
      lines.push(row.join(","));
    });
    const blob = new Blob([lines.join("\n")], { type: "text/csv" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `budget-vs-actual-FY${fiscalYear}.csv`;
    a.click();
    URL.revokeObjectURL(url);
  };

  const downloadPdf = async () => {
    if (!association) return;
    setPrinting(true);
    try {
      const comparisonLabel =
        comparison === "prior_year"
          ? "Prior Year (Same Period)"
          : comparison === "prior_year_full"
            ? "Prior Year (Full Year)"
            : comparison === "prior_period"
              ? "Prior Period"
              : comparison === "custom"
                ? "Custom Range"
                : null;
      await generateBudgetVsActualPdf({
        association,
        fiscalYear,
        rangeLabel: `${ranges.curStart.toLocaleDateString()} – ${ranges.curEnd.toLocaleDateString()}`,
        comparisonLabel,
        comparisonRangeLabel:
          ranges.cmpStart && ranges.cmpEnd
            ? `${ranges.cmpStart.toLocaleDateString()} – ${ranges.cmpEnd.toLocaleDateString()}`
            : null,
        rows: reportRows,
        totals,
        comparisonBudgetMonths,
      });
    } finally {
      setPrinting(false);
    }
  };

  const rangeLabel = `${ranges.curStart.toLocaleDateString()} – ${ranges.curEnd.toLocaleDateString()}`;
  const cmpRangeLabel =
    ranges.cmpStart && ranges.cmpEnd
      ? `${ranges.cmpStart.toLocaleDateString()} – ${ranges.cmpEnd.toLocaleDateString()}`
      : null;

  return (
    <div className="space-y-6">
      {/* Filters */}
      <Card>
        <CardContent className="pt-6 grid grid-cols-1 md:grid-cols-4 gap-4">
          <div>
            <Label className="text-xs uppercase text-muted-foreground">Timeframe</Label>
            <Select value={timeframe} onValueChange={(v) => setTimeframe(v as Timeframe)}>
              <SelectTrigger className="mt-1">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="ytd">Year to Date</SelectItem>
                <SelectItem value="year">Full Year</SelectItem>
                <SelectItem value="quarter">Quarter</SelectItem>
                <SelectItem value="month">Month</SelectItem>
                <SelectItem value="custom">Custom Range</SelectItem>
              </SelectContent>
            </Select>
          </div>

          {timeframe === "month" && (
            <div>
              <Label className="text-xs uppercase text-muted-foreground">Month</Label>
              <Select value={month} onValueChange={setMonth}>
                <SelectTrigger className="mt-1">
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  {Array.from({ length: 12 }).map((_, i) => (
                    <SelectItem key={i} value={(i + 1).toString()}>
                      {new Date(fiscalYear, i, 1).toLocaleString("en-US", { month: "long" })}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
          )}

          {timeframe === "quarter" && (
            <div>
              <Label className="text-xs uppercase text-muted-foreground">Quarter</Label>
              <Select value={quarter} onValueChange={setQuarter}>
                <SelectTrigger className="mt-1">
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="1">Q1 (Jan–Mar)</SelectItem>
                  <SelectItem value="2">Q2 (Apr–Jun)</SelectItem>
                  <SelectItem value="3">Q3 (Jul–Sep)</SelectItem>
                  <SelectItem value="4">Q4 (Oct–Dec)</SelectItem>
                </SelectContent>
              </Select>
            </div>
          )}

          {timeframe === "custom" && (
            <>
              <div>
                <Label className="text-xs uppercase text-muted-foreground">From</Label>
                <Input type="date" className="mt-1" value={customStart} onChange={(e) => setCustomStart(e.target.value)} />
              </div>
              <div>
                <Label className="text-xs uppercase text-muted-foreground">To</Label>
                <Input type="date" className="mt-1" value={customEnd} onChange={(e) => setCustomEnd(e.target.value)} />
              </div>
            </>
          )}

          <div>
            <Label className="text-xs uppercase text-muted-foreground">Compare To</Label>
            <Select value={comparison} onValueChange={(v) => setComparison(v as any)}>
              <SelectTrigger className="mt-1">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="none">No comparison</SelectItem>
                <SelectItem value="prior_year">Prior Year YTD</SelectItem>
                <SelectItem value="prior_year_full">Prior Year (Full Year)</SelectItem>
                <SelectItem value="prior_period">Prior Period</SelectItem>
                <SelectItem value="custom">Custom Range</SelectItem>
              </SelectContent>
            </Select>
          </div>

          {comparison === "custom" && (
            <>
              <div>
                <Label className="text-xs uppercase text-muted-foreground">Compare Start</Label>
                <Input type="date" className="mt-1" value={cmpCustomStart} onChange={(e) => setCmpCustomStart(e.target.value)} />
              </div>
              <div>
                <Label className="text-xs uppercase text-muted-foreground">Compare End</Label>
                <Input type="date" className="mt-1" value={cmpCustomEnd} onChange={(e) => setCmpCustomEnd(e.target.value)} />
              </div>
            </>
          )}

          <div className="flex items-end gap-2">
            <Button variant="outline" className="gap-2 flex-1" onClick={downloadCsv}>
              <Download className="h-4 w-4" /> CSV
            </Button>
            <Button className="gap-2 flex-1" onClick={downloadPdf} disabled={printing || !association}>
              <Printer className="h-4 w-4" /> {printing ? "…" : "PDF"}
            </Button>
          </div>
          <div className="flex items-center gap-2 md:col-span-4">
            <Switch id="hide-unbudgeted" checked={hideUnbudgeted} onCheckedChange={setHideUnbudgeted} />
            <Label htmlFor="hide-unbudgeted" className="text-sm cursor-pointer">
              Hide unbudgeted line items
            </Label>
          </div>
        </CardContent>
      </Card>

      {/* Range badges */}
      <div className="flex flex-wrap gap-2">
        <Badge variant="secondary">Period: {rangeLabel}</Badge>
        {cmpRangeLabel && <Badge variant="outline">Compare: {cmpRangeLabel}</Badge>}
        {budgetMonths < 12 && <Badge variant="outline">Budget pro-rated to {budgetMonths} of 12 months</Badge>}
      </div>

      {/* KPI cards */}
      <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
        <Card>
          <CardContent className="pt-6">
            <div className="flex items-center gap-2 text-muted-foreground text-sm mb-1">
              <TrendingUp className="h-4 w-4 text-emerald-600" /> Income — Actual / Budget
            </div>
            <p className="text-2xl font-bold text-foreground">{fmt(totals.incomeActual)}</p>
            <p className="text-xs text-muted-foreground mt-1">Budget: {fmt(totals.incomeBudget)}</p>
            {comparison !== "none" && (
              <p className="text-xs mt-1">
                vs prior:{" "}
                <span className={totals.incomeActual >= totals.incomeCmp ? "text-emerald-600" : "text-destructive"}>
                  {fmtPct(totals.incomeCmp !== 0 ? ((totals.incomeActual - totals.incomeCmp) / Math.abs(totals.incomeCmp)) * 100 : NaN)}
                </span>
              </p>
            )}
          </CardContent>
        </Card>
        <Card>
          <CardContent className="pt-6">
            <div className="flex items-center gap-2 text-muted-foreground text-sm mb-1">
              <TrendingDown className="h-4 w-4 text-destructive" /> Expense — Actual / Budget
            </div>
            <p className="text-2xl font-bold text-foreground">{fmt(totals.expenseActual)}</p>
            <p className="text-xs text-muted-foreground mt-1">Budget: {fmt(totals.expenseBudget)}</p>
            {comparison !== "none" && (
              <p className="text-xs mt-1">
                vs prior:{" "}
                <span className={totals.expenseActual <= totals.expenseCmp ? "text-emerald-600" : "text-destructive"}>
                  {fmtPct(totals.expenseCmp !== 0 ? ((totals.expenseActual - totals.expenseCmp) / Math.abs(totals.expenseCmp)) * 100 : NaN)}
                </span>
              </p>
            )}
          </CardContent>
        </Card>
        <Card>
          <CardContent className="pt-6">
            <div className="flex items-center gap-2 text-muted-foreground text-sm mb-1">
              <DollarSign className="h-4 w-4" /> Net — Actual / Budget
            </div>
            <p className={`text-2xl font-bold ${netActual >= 0 ? "text-emerald-600" : "text-destructive"}`}>{fmt(netActual)}</p>
            <p className="text-xs text-muted-foreground mt-1">Budget: {fmt(netBudget)}</p>
            {comparison !== "none" && (
              <p className="text-xs mt-1">
                Prior: <span className={netCmp >= 0 ? "text-emerald-600" : "text-destructive"}>{fmt(netCmp)}</span>
              </p>
            )}
          </CardContent>
        </Card>
      </div>

      {/* Detail table */}
      <Card>
        <CardHeader>
          <CardTitle className="text-base">Budget vs Actual Detail</CardTitle>
        </CardHeader>
        <CardContent>
          {loading ? (
            <div className="flex justify-center py-12">
              <Loader2 className="h-8 w-8 animate-spin text-primary" />
            </div>
          ) : reportRows.length === 0 ? (
            <div className="text-center py-12 text-muted-foreground text-sm">
              No budget lines or transactions found for this period.
            </div>
          ) : (
            <Table>
              <TableHeader>
                <TableRow>
                  <TableHead>Account</TableHead>
                  <TableHead>Account Code</TableHead>
                  <TableHead className="text-right">Budget</TableHead>
                  <TableHead className="text-right">Annual Budget</TableHead>
                  <TableHead className="text-right">Actual</TableHead>
                  <TableHead className="text-right">Variance</TableHead>
                  <TableHead className="text-right">% Used</TableHead>
                  {comparison !== "none" && (
                    <>
                      <TableHead className="text-right">Comparison Actual</TableHead>
                      <TableHead className="text-right">Comparison Budget</TableHead>
                      <TableHead className="text-right">Comparison Variance</TableHead>
                      <TableHead className="text-right">Comparison %</TableHead>
                    </>
                  )}
                </TableRow>
              </TableHeader>
              <TableBody>
                {(() => {
                  const extraCols = comparison !== "none" ? 4 : 0;
                  const totalCols = 7 + extraCols;
                  // Zoho P&L–style hierarchy: Section → Parent (with code) →
                  // child accounts → "Total for <Parent> <code>" → section total
                  const out: JSX.Element[] = [];
                  const numFmt = (n: number, bold = false) => (
                    <span className={bold ? "font-semibold" : ""}>{fmt(n)}</span>
                  );
                  const renderLeaf = (r: typeof reportRows[number], indent = "pl-10") => {
                    const isIncome = r.accountType === "income";
                    const varianceGood = isIncome ? r.variance >= 0 : r.variance <= 0;
                    out.push(
                      <TableRow key={r.id}>
                        <TableCell className={indent}>{r.category}</TableCell>
                        <TableCell className="text-muted-foreground">{r.accountCode || "—"}</TableCell>
                        <TableCell className="text-right">{fmt(r.budget)}</TableCell>
                        <TableCell className="text-right text-muted-foreground">{fmt(r.annualBudget)}</TableCell>
                        <TableCell className="text-right">{fmt(r.actual)}</TableCell>
                        <TableCell className={`text-right ${varianceGood ? "text-emerald-600" : "text-destructive"}`}>
                          {fmt(r.variance)}
                        </TableCell>
                        <TableCell className="text-right text-muted-foreground">
                          {r.budget > 0 ? `${r.pctOfBudget.toFixed(0)}%` : "—"}
                        </TableCell>
                        {comparison !== "none" && (
                          <>
                            <TableCell className="text-right">{fmt(r.comparisonActual)}</TableCell>
                            <TableCell className="text-right">{fmt(r.comparisonBudget)}</TableCell>
                            <TableCell className={`text-right ${isIncome ? (r.comparisonVariance >= 0 ? "text-emerald-600" : "text-destructive") : (r.comparisonVariance <= 0 ? "text-emerald-600" : "text-destructive")}`}>{fmt(r.comparisonVariance)}</TableCell>
                            <TableCell className="text-right text-muted-foreground">{r.comparisonBudget > 0 ? `${r.comparisonPctOfBudget.toFixed(0)}%` : "—"}</TableCell>
                          </>
                        )}
                      </TableRow>,
                    );
                  };
                  const renderTotalRow = (
                    key: string,
                    label: string,
                    code: string,
                    list: typeof reportRows,
                    opts: { strong?: boolean; indent?: string; bg?: string } = {},
                  ) => {
                    const subActual = list.reduce((s, r) => s + r.actual, 0);
                    const subBudget = list.reduce((s, r) => s + r.budget, 0);
                    const subAnnual = list.reduce((s, r) => s + (r.annualBudget || 0), 0);
                    const subVar = subActual - subBudget;
                    const subType = list[0]?.accountType;
                    const subVarianceGood = subType === "income" ? subVar >= 0 : subVar <= 0;
                    const subCmp = list.reduce((s, r) => s + r.comparisonActual, 0);
                    const subCmpBudget = list.reduce((s, r) => s + r.comparisonBudget, 0);
                    const subCmpVar = subCmp - subCmpBudget;
                    const cls = `${opts.bg || "bg-muted/10"} ${opts.strong ? "font-bold" : "font-semibold"}`;
                    out.push(
                      <TableRow key={key} className={cls}>
                        <TableCell className={opts.indent || ""}>{label}</TableCell>
                        <TableCell className="text-muted-foreground">{code || ""}</TableCell>
                        <TableCell className="text-right">{numFmt(subBudget, true)}</TableCell>
                        <TableCell className="text-right text-muted-foreground">{numFmt(subAnnual, true)}</TableCell>
                        <TableCell className="text-right">{numFmt(subActual, true)}</TableCell>
                        <TableCell className={`text-right ${subVarianceGood ? "text-emerald-600" : "text-destructive"}`}>
                          {numFmt(subVar, true)}
                        </TableCell>
                        <TableCell className="text-right text-muted-foreground">
                          {subBudget > 0 ? `${((subActual / subBudget) * 100).toFixed(0)}%` : "—"}
                        </TableCell>
                        {comparison !== "none" && (
                          <>
                            <TableCell className="text-right">{numFmt(subCmp, true)}</TableCell>
                            <TableCell className="text-right">{numFmt(subCmpBudget, true)}</TableCell>
                            <TableCell className={`text-right ${subType === "income" ? (subCmpVar >= 0 ? "text-emerald-600" : "text-destructive") : (subCmpVar <= 0 ? "text-emerald-600" : "text-destructive")}`}>{numFmt(subCmpVar, true)}</TableCell>
                            <TableCell className="text-right text-muted-foreground">{subCmpBudget > 0 ? `${((subCmp / subCmpBudget) * 100).toFixed(0)}%` : "—"}</TableCell>
                          </>
                        )}
                      </TableRow>,
                    );
                  };
                  const renderSectionHeader = (label: string) =>
                    out.push(
                      <TableRow key={`sec-${label}`} className="bg-muted/40">
                        <TableCell colSpan={totalCols} className="font-bold text-sm uppercase tracking-wide">
                          {label}
                        </TableCell>
                      </TableRow>,
                    );
                  const renderSummaryRow = (label: string, budget: number, annualBudget: number, actual: number, cmp: number) => {
                    out.push(
                      <TableRow key={`sum-${label}`} className="bg-primary/5 border-t-2 border-primary/30">
                        <TableCell colSpan={2} className="font-bold text-center uppercase tracking-wide">
                          {label}
                        </TableCell>
                        <TableCell className="text-right font-bold">{fmt(budget)}</TableCell>
                        <TableCell className="text-right font-bold text-muted-foreground">{fmt(annualBudget)}</TableCell>
                        <TableCell className="text-right font-bold">{fmt(actual)}</TableCell>
                        <TableCell className="text-right font-bold">{fmt(actual - budget)}</TableCell>
                        <TableCell />
                        {comparison !== "none" && (
                          <>
                            <TableCell className="text-right font-bold">{fmt(cmp)}</TableCell>
                            <TableCell />
                          </>
                        )}
                      </TableRow>,
                    );
                  };
                  const renderTypeSection = (type: "income" | "expense", sectionLabel: string) => {
                    const sectionRows = reportRows.filter((r) =>
                      type === "income" ? r.accountType === "income" : r.accountType !== "income",
                    );
                    if (sectionRows.length === 0) {
                      renderSectionHeader(sectionLabel);
                      renderTotalRow(
                        `total-${type}`,
                        `Total for ${sectionLabel}`,
                        "",
                        [],
                        { strong: true, bg: "bg-muted/20" },
                      );
                       return { budget: 0, annualBudget: 0, actual: 0, cmp: 0 };
                    }
                    renderSectionHeader(sectionLabel);
                    const groups = new Map<string, typeof reportRows>();
                    for (const r of sectionRows) {
                      const key = r.parentCategory || "__orphan__";
                      const list = (groups.get(key) || []) as typeof reportRows;
                      list.push(r);
                      groups.set(key, list);
                    }
                    const keys = Array.from(groups.keys()).sort((a, b) => {
                      if (a === "__orphan__") return 1;
                      if (b === "__orphan__") return -1;
                      const acode = groups.get(a)![0].parentCode || "~~~";
                      const bcode = groups.get(b)![0].parentCode || "~~~";
                      if (acode !== bcode) return acode.localeCompare(bcode, undefined, { numeric: true });
                      return a.localeCompare(b);
                    });
                    for (const key of keys) {
                      const list = groups.get(key)!;
                      const isOrphan = key === "__orphan__";
                      const parentCode = isOrphan ? "" : list[0].parentCode || "";
                      if (!isOrphan) {
                        // Parent row (shows the parent account label + code, no values — they roll up below)
                        out.push(
                          <TableRow key={`grp-${type}-${key}`}>
                            <TableCell className="pl-4 font-semibold">{key}</TableCell>
                            <TableCell className="text-muted-foreground">{parentCode || "—"}</TableCell>
                            <TableCell colSpan={totalCols - 2} />
                          </TableRow>,
                        );
                      }
                      for (const r of list) renderLeaf(r, isOrphan ? "pl-4" : "pl-10");
                      if (!isOrphan) {
                        renderTotalRow(
                          `sub-${type}-${key}`,
                          `Total for ${key}`,
                          parentCode,
                          list,
                          { bg: "bg-muted/20", indent: "pl-4" },
                        );
                      }
                    }
                    const budget = sectionRows.reduce((s, r) => s + r.budget, 0);
                    const annualBudget = sectionRows.reduce((s, r) => s + (r.annualBudget || 0), 0);
                    const actual = sectionRows.reduce((s, r) => s + r.actual, 0);
                    const cmp = sectionRows.reduce((s, r) => s + r.comparisonActual, 0);
                    renderTotalRow(
                      `total-${type}`,
                      `Total for ${sectionLabel}`,
                      "",
                      sectionRows,
                      { strong: true, bg: "bg-muted/30" },
                    );
                    return { budget, annualBudget, actual, cmp };
                  };
                  const income = renderTypeSection("income", "Operating Income");
                  renderSummaryRow("Gross Profit", income.budget, income.annualBudget, income.actual, income.cmp);
                  const expense = renderTypeSection("expense", "Operating Expense");
                  renderSummaryRow(
                    "Net Profit / Loss",
                    income.budget - expense.budget,
                    income.annualBudget - expense.annualBudget,
                    income.actual - expense.actual,
                    income.cmp - expense.cmp,
                  );
                  return out;
                })()}
              </TableBody>
            </Table>
          )}
        </CardContent>
      </Card>
    </div>
  );
}
