"use client";

import * as React from "react";
import { ChevronDownIcon, ChevronRightIcon, CircleCheckIcon, LockIcon, SearchIcon, TriangleAlertIcon } from "lucide-react";

import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { cn } from "@/lib/utils";

import { RowActions, type RowAction } from "./row-actions";
import { TONE_BOX } from "./tones";

/*
 * General Ledger (PRD 6.5, AC-01 to AC-05). Amounts are signed with debits positive throughout; each
 * account type has a normal side (assets and expenses debit, the rest credit), and a balance on the other
 * side is shown as "Unusual" because it usually means a posting went to the wrong account.
 */

export type AccountType = "Asset" | "Liability" | "Equity" | "Income" | "Expense";

export const ACCOUNT_TYPES: { type: AccountType; label: string; range: [number, number]; normal: "Dr" | "Cr" }[] = [
  { type: "Asset", label: "Assets", range: [1000, 1999], normal: "Dr" },
  { type: "Liability", label: "Liabilities", range: [2000, 2999], normal: "Cr" },
  { type: "Equity", label: "Equity", range: [3000, 3999], normal: "Cr" },
  { type: "Income", label: "Income", range: [4000, 4999], normal: "Cr" },
  { type: "Expense", label: "Expenses", range: [5000, 6999], normal: "Dr" },
];
export const typeInfo = (t: AccountType) => ACCOUNT_TYPES.find((x) => x.type === t)!;

export type LedgerAccount = {
  code: string;
  name: string;
  type: AccountType;
  parent?: string;
  /** Branch money accounts (cash drawer, M-Pesa till, card terminal, bank) belong to one branch. */
  branch?: string;
  description?: string;
  /** Used by automatic postings (sales, GRN, payroll…): can be renamed but not deactivated. */
  system?: boolean;
  active: boolean;
  /** Signed, debit positive, as at the chosen date: this account's own postings (`rolledUp()` adds its children). */
  balance: number;
  /** Has postings: deactivate instead of delete. */
  hasPostings?: boolean;
};

const fmt = new Intl.NumberFormat("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });

/** "12,400.00 Dr" / "3,000.00 Cr" / "0.00". */
export function DrCr({ amount, type, strong = false }: { amount: number; type?: AccountType; strong?: boolean }) {
  const side = amount > 0 ? "Dr" : amount < 0 ? "Cr" : "";
  const unusual = !!type && !!side && side !== typeInfo(type).normal;
  return (
    <span className="inline-flex flex-col items-end leading-tight">
      <span className={cn("tabular-nums whitespace-nowrap", strong && "font-semibold")}>
        {fmt.format(Math.abs(amount))}
        {side ? <span className="ml-1 text-xs text-muted-foreground">{side}</span> : null}
      </span>
      {unusual ? <span className="text-[11px] font-medium text-status-warning-text">Unusual for {type!.toLowerCase()}</span> : null}
    </span>
  );
}

/**
 * Balance of an account including its sub-accounts: its own postings plus each child's. A parent can carry postings
 * of its own (4000 Sales has 4010 Sales returns under it), so its own balance is never dropped.
 */
export function rolledUp(accounts: LedgerAccount[], code: string): number {
  const kids = accounts.filter((a) => a.parent === code);
  const own = accounts.find((a) => a.code === code)?.balance ?? 0;
  return kids.reduce((s, k) => s + rolledUp(accounts, k.code), own);
}

/** Problems with a new or edited account, in words (AC-01). */
export function accountProblems(draft: Partial<LedgerAccount>, accounts: LedgerAccount[], editing?: string): Partial<Record<"code" | "name" | "parent", string>> {
  const p: Partial<Record<"code" | "name" | "parent", string>> = {};
  const info = draft.type ? typeInfo(draft.type) : undefined;
  const n = Number(draft.code);
  if (!draft.code?.trim()) p.code = "Enter an account number.";
  else if (!/^\d{4}$/.test(draft.code)) p.code = "Account numbers have 4 digits, e.g. 1015.";
  else if (info && (n < info.range[0] || n > info.range[1])) p.code = `${draft.type} accounts are numbered ${info.range[0]}–${info.range[1]}.`;
  else if (accounts.some((a) => a.code === draft.code && a.code !== editing)) p.code = `${draft.code} is already ${accounts.find((a) => a.code === draft.code)!.name}.`;
  else if (draft.parent && draft.code.slice(0, 2) !== draft.parent.slice(0, 2)) p.code = `Sub-accounts of ${draft.parent} are numbered ${draft.parent.slice(0, 2)}xx.`;
  if (!draft.name?.trim()) p.name = "Enter the account name.";
  // Branch money accounts share names ("Cash drawer"), so a name only has to be unique within its branch.
  else if (accounts.some((a) => a.name.toLowerCase() === draft.name!.trim().toLowerCase() && (a.branch ?? "") === (draft.branch ?? "") && a.code !== editing))
    p.name = draft.branch ? `${draft.branch} already has an account with this name.` : "Another account already has this name.";
  if (draft.parent) {
    const parent = accounts.find((a) => a.code === draft.parent);
    if (parent && parent.type !== draft.type) p.parent = `${parent.code} ${parent.name} is a ${parent.type.toLowerCase()} account.`;
  }
  return p;
}

/**
 * Chart of accounts (AC-01): grouped by type, parents expand to their sub-accounts, balances as Dr/Cr with
 * a total per type. System accounts carry a lock and can't be deactivated; accounts with postings are
 * deactivated, never deleted. Inactive accounts are hidden unless asked for. Row actions come from the
 * caller (`actionsFor`), so permissions decide what's offered.
 */
export function ChartOfAccounts({ accounts, actionsFor, asAt }: { accounts: LedgerAccount[]; actionsFor: (a: LedgerAccount) => RowAction[]; asAt: string }) {
  const [query, setQuery] = React.useState("");
  const [showInactive, setShowInactive] = React.useState(false);
  const [collapsed, setCollapsed] = React.useState<Set<string>>(new Set());
  const q = query.trim().toLowerCase();
  const matches = (a: LedgerAccount) => !q || `${a.code} ${a.name} ${a.branch ?? ""} ${a.description ?? ""}`.toLowerCase().includes(q);
  const visible = (a: LedgerAccount): boolean => (showInactive || a.active) && (matches(a) || accounts.some((k) => k.parent === a.code && visible(k)));
  const toggle = (code: string) =>
    setCollapsed((s) => {
      const n = new Set(s);
      if (n.has(code)) n.delete(code);
      else n.add(code);
      return n;
    });

  const rows: React.ReactNode[] = [];
  const walk = (a: LedgerAccount, level: number) => {
    if (!visible(a)) return;
    const kids = accounts.filter((k) => k.parent === a.code).sort((x, y) => x.code.localeCompare(y.code));
    const open = !collapsed.has(a.code) || !!q;
    rows.push(
      <tr key={a.code} className={cn("hover:bg-muted/40", !a.active && "text-muted-foreground")} aria-level={level}>
        <td className="px-3 py-2 font-mono text-xs tabular-nums">{a.code}</td>
        <th scope="row" className="px-3 py-2 text-left font-normal">
          <span className="flex items-center gap-1.5" style={{ paddingLeft: `${(level - 1) * 1.25}rem` }}>
            {kids.length ? (
              <button type="button" onClick={() => toggle(a.code)} aria-expanded={open} aria-label={`${open ? "Collapse" : "Expand"} ${a.name}`} className="flex size-5 cursor-pointer items-center justify-center rounded-sm text-muted-foreground hover:bg-muted">
                {open ? <ChevronDownIcon className="size-3.5" /> : <ChevronRightIcon className="size-3.5" />}
              </button>
            ) : (
              <span className="size-5" aria-hidden />
            )}
            <span className={cn(kids.length && "font-semibold")}>{a.name}</span>
            {a.branch ? <span className="rounded-full border border-[var(--color-border-panel)] bg-[var(--color-bg-surface-subtle)] px-1.5 text-[11px] text-[var(--color-fg-secondary-strong)]">{a.branch}</span> : null}
            {a.system ? (
              <span className="inline-flex items-center gap-0.5 text-[11px] text-muted-foreground" title="Used by automatic postings">
                <LockIcon className="size-3" aria-hidden /> System
              </span>
            ) : null}
            {!a.active ? <span className="text-[11px] font-medium">Inactive</span> : null}
          </span>
          {a.description ? <span className="block text-xs text-muted-foreground" style={{ paddingLeft: `${(level - 1) * 1.25 + 1.625}rem` }}>{a.description}</span> : null}
        </th>
        <td className="px-3 py-2 text-right">
          <DrCr amount={kids.length ? rolledUp(accounts, a.code) : a.balance} type={a.type} strong={kids.length > 0} />
        </td>
        <td className="w-12 px-2 py-2 text-right">
          <RowActions label={`${a.code} ${a.name}`} actions={actionsFor(a)} />
        </td>
      </tr>,
    );
    if (open) kids.forEach((k) => walk(k, level + 1));
  };

  return (
    // One container (data table spec §1): toolbar, table and pager share the white surface and its border
    <div className="overflow-hidden rounded-md border border-[var(--color-border-panel)] bg-card">
      <div className="border-b border-[var(--color-border-subtle)] p-3 flex flex-wrap items-center gap-3">
        <div className="relative w-full sm:w-72">
          <SearchIcon className="pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground" aria-hidden />
          <Input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Find by number, name or branch" aria-label="Find an account" className="pl-8" />
        </div>
        <label className="flex items-center gap-2 text-sm">
          <Switch checked={showInactive} onCheckedChange={setShowInactive} /> Show inactive
        </label>
        <span className="ml-auto text-xs text-muted-foreground">Balances as at {asAt}</span>
      </div>
      <div className="overflow-x-auto">
        <table className="w-full border-collapse text-sm">
          <caption className="sr-only">Chart of accounts</caption>
          <thead className="bg-[var(--color-bg-surface-subtle)] text-left text-xs text-muted-foreground">
            <tr>
              <th scope="col" className="w-20 px-3 py-2 font-medium">
                Number
              </th>
              <th scope="col" className="px-3 py-2 font-medium">
                Account
              </th>
              <th scope="col" className="px-3 py-2 text-right font-medium">
                Balance
              </th>
              <th scope="col" className="px-2 py-2">
                <span className="sr-only">Actions</span>
              </th>
            </tr>
          </thead>
          {ACCOUNT_TYPES.map(({ type, label, range, normal }) => {
            const roots = accounts.filter((a) => a.type === type && !a.parent).sort((x, y) => x.code.localeCompare(y.code));
            rows.length = 0;
            roots.forEach((r) => walk(r, 1));
            const body = [...rows];
            const total = roots.reduce((s, r) => s + rolledUp(accounts, r.code), 0);
            if (!body.length) return null;
            return (
              <tbody key={type} className="divide-y divide-[var(--color-border-subtle)] border-t-2 border-[var(--color-border-panel)]">
                <tr className="bg-muted/30">
                  <th colSpan={2} scope="rowgroup" className="px-3 py-1.5 text-left text-xs font-semibold tracking-wide uppercase">
                    {label} <span className="font-normal text-muted-foreground normal-case">
                      {range[0]}–{range[1]} · normally {normal === "Dr" ? "debit" : "credit"}
                    </span>
                  </th>
                  <td className="px-3 py-1.5 text-right text-xs font-semibold">
                    <DrCr amount={total} />
                  </td>
                  <td />
                </tr>
                {body}
              </tbody>
            );
          })}
        </table>
      </div>
    </div>
  );
}

/* --------------------------------- Account statement --------------------------------- */

export type Posting = { id: string; date: string; ref: string; details: string; debit: number; credit: number };

/**
 * Account statement (AC-05): opening balance, each posting with a running balance, closing balance. The
 * reference is the source document (SALE-, GRN-, JV-), so every line can be traced (PRD: accuracy).
 */
export function AccountStatement({ account, opening, postings, formatDate, from, to }: { account: LedgerAccount; opening: number; postings: Posting[]; formatDate: (iso: string) => string; from: string; to: string }) {
  // Balance after each posting, worked out before rendering.
  const running = postings.reduce<number[]>((acc, p) => [...acc, (acc.length ? acc[acc.length - 1] : opening) + p.debit - p.credit], []);
  const dr = postings.reduce((s, p) => s + p.debit, 0);
  const cr = postings.reduce((s, p) => s + p.credit, 0);
  const cell = "px-3 py-2";
  return (
    <div className="overflow-x-auto rounded-lg border border-[var(--color-border-panel)] bg-card">
      <table className="w-full border-collapse text-sm tabular-nums">
        <caption className="sr-only">
          Statement for {account.code} {account.name}, {from} to {to}
        </caption>
        <thead className="bg-[var(--color-bg-surface-subtle)] text-left text-xs text-muted-foreground">
          <tr>
            {["Date", "Reference", "Details", "Debit", "Credit", "Balance"].map((h, i) => (
              <th key={h} scope="col" className={cn(cell, "font-medium", i >= 3 && "text-right")}>
                {h}
              </th>
            ))}
          </tr>
        </thead>
        <tbody className="divide-y divide-[var(--color-border-subtle)]">
          <tr className="bg-muted/30">
            <td className={cell}>{from}</td>
            <td className={cell} colSpan={4}>
              Opening balance
            </td>
            <td className={cn(cell, "text-right")}>
              <DrCr amount={opening} type={account.type} />
            </td>
          </tr>
          {postings.map((p, i) => {
            return (
              <tr key={p.id}>
                <td className={cn(cell, "whitespace-nowrap")}>{formatDate(p.date)}</td>
                <td className={cn(cell, "font-mono text-xs")}>{p.ref}</td>
                <td className={cell}>{p.details}</td>
                <td className={cn(cell, "text-right")}>{p.debit ? fmt.format(p.debit) : ""}</td>
                <td className={cn(cell, "text-right")}>{p.credit ? fmt.format(p.credit) : ""}</td>
                <td className={cn(cell, "text-right")}>
                  <DrCr amount={running[i]} type={account.type} />
                </td>
              </tr>
            );
          })}
        </tbody>
        <tfoot className="border-t-2 border-[var(--color-border-panel)] font-semibold">
          <tr>
            <th scope="row" colSpan={3} className={cn(cell, "text-left")}>
              Closing balance, {to}
            </th>
            <td className={cn(cell, "text-right")}>{fmt.format(dr)}</td>
            <td className={cn(cell, "text-right")}>{fmt.format(cr)}</td>
            <td className={cn(cell, "text-right")}>
              <DrCr amount={opening + dr - cr} type={account.type} strong />
            </td>
          </tr>
        </tfoot>
      </table>
    </div>
  );
}

/* ----------------------------------- Trial balance ----------------------------------- */

/**
 * Trial balance (AC-05): every account with a balance, in a debit or credit column, totals, and whether
 * they agree. Each account's own balance, parents included (a parent such as 4000 Sales has postings of its own);
 * nothing is counted twice because no balance includes another account's.
 */
export function TrialBalance({ accounts, asAt }: { accounts: LedgerAccount[]; asAt: string }) {
  const leaves = accounts.filter((a) => a.balance !== 0).sort((x, y) => x.code.localeCompare(y.code));
  const dr = leaves.reduce((s, a) => s + Math.max(0, a.balance), 0);
  const cr = leaves.reduce((s, a) => s + Math.max(0, -a.balance), 0);
  const diff = Math.round((dr - cr) * 100) / 100;
  const cell = "px-3 py-1.5";
  return (
    <div className="space-y-3">
      <p className={cn("flex items-center gap-2 rounded-md border px-3 py-2 text-sm", diff === 0 ? TONE_BOX.success : TONE_BOX.error)}>
        {diff === 0 ? <CircleCheckIcon className="size-4 shrink-0" aria-hidden /> : <TriangleAlertIcon className="size-4 shrink-0" aria-hidden />}
        {diff === 0 ? `Balanced as at ${asAt}: debits and credits are both KES ${fmt.format(dr)}.` : `Out of balance by KES ${fmt.format(Math.abs(diff))}. Every posting balances, so this means data was changed outside Medora: report it.`}
      </p>
      <div className="overflow-x-auto rounded-lg border border-[var(--color-border-panel)] bg-card">
        <table className="w-full border-collapse text-sm tabular-nums">
          <caption className="sr-only">Trial balance as at {asAt}</caption>
          <thead className="bg-[var(--color-bg-surface-subtle)] text-left text-xs text-muted-foreground">
            <tr>
              <th scope="col" className={cn(cell, "w-20 font-medium")}>
                Number
              </th>
              <th scope="col" className={cn(cell, "font-medium")}>
                Account
              </th>
              <th scope="col" className={cn(cell, "text-right font-medium")}>
                Debit
              </th>
              <th scope="col" className={cn(cell, "text-right font-medium")}>
                Credit
              </th>
            </tr>
          </thead>
          <tbody className="divide-y divide-[var(--color-border-subtle)]">
            {leaves.map((a) => (
              <tr key={a.code}>
                <td className={cn(cell, "font-mono text-xs")}>{a.code}</td>
                <th scope="row" className={cn(cell, "text-left font-normal")}>
                  {a.name}
                  {a.branch ? <span className="ml-1.5 text-xs text-muted-foreground">{a.branch}</span> : null}
                </th>
                <td className={cn(cell, "text-right")}>{a.balance > 0 ? fmt.format(a.balance) : ""}</td>
                <td className={cn(cell, "text-right")}>{a.balance < 0 ? fmt.format(-a.balance) : ""}</td>
              </tr>
            ))}
          </tbody>
          <tfoot className="border-t-2 border-[var(--color-border-panel)] font-semibold">
            <tr>
              <th scope="row" colSpan={2} className={cn(cell, "text-left")}>
                Totals, {leaves.length} accounts
              </th>
              <td className={cn(cell, "text-right")}>{fmt.format(dr)}</td>
              <td className={cn(cell, "text-right")}>{fmt.format(cr)}</td>
            </tr>
          </tfoot>
        </table>
      </div>
    </div>
  );
}

/* ------------------------------- P&L and balance sheet ------------------------------- */

/** One account's figure per period, shown the natural way round (income and expenses both positive). */
export type StatementRow = { code: string; name: string; amounts: number[] };

const pctChange = (now: number, before?: number) => (before ? ((now - before) / Math.abs(before)) * 100 : undefined);

function YearCells({ values, strong = false, negativeBad = false }: { values: number[]; strong?: boolean; negativeBad?: boolean }) {
  const ch = pctChange(values[0], values[1]);
  return (
    <>
      {values.map((v, i) => (
        <td key={i} className={cn("px-3 py-1.5 text-right tabular-nums", strong && "font-semibold", i > 0 && "text-muted-foreground", negativeBad && v < 0 && "text-status-error-text")}>
          {v < 0 ? `(${fmt.format(-v)})` : fmt.format(v)}
        </td>
      ))}
      <td className="px-3 py-1.5 text-right text-xs whitespace-nowrap tabular-nums text-muted-foreground">{ch === undefined ? "–" : `${ch > 0 ? "+" : "−"}${Math.abs(ch).toFixed(1)}%`}</td>
    </>
  );
}

const sumCols = (rows: StatementRow[], n: number) => Array.from({ length: n }, (_, i) => rows.reduce((s, r) => s + (r.amounts[i] ?? 0), 0));

/**
 * Profit & loss (AC-05) with up to three years side by side and the change on the latest. Net revenue
 * excludes VAT; cost of sales (5xxx) gives gross profit; operating expenses (6xxx) give net profit. Losses
 * show in brackets.
 */
export function ProfitAndLoss({ periods, income, costOfSales, expenses }: { periods: string[]; income: StatementRow[]; costOfSales: StatementRow[]; expenses: StatementRow[] }) {
  const n = periods.length;
  const inc = sumCols(income, n);
  const cos = sumCols(costOfSales, n);
  const gp = inc.map((v, i) => v - cos[i]);
  const ex = sumCols(expenses, n);
  const np = gp.map((v, i) => v - ex[i]);
  const block = (title: string, rows: StatementRow[], total: number[], totalLabel: string) => (
    <>
      <tr className="bg-muted/30">
        <th colSpan={n + 2} scope="rowgroup" className="px-3 py-1.5 text-left text-xs font-semibold tracking-wide uppercase">
          {title}
        </th>
      </tr>
      {rows.map((r) => (
        <tr key={r.code}>
          <th scope="row" className="px-3 py-1.5 pl-6 text-left font-normal">
            <span className="mr-2 font-mono text-xs text-muted-foreground">{r.code}</span>
            {r.name}
          </th>
          <YearCells values={r.amounts} />
        </tr>
      ))}
      <tr className="border-t border-[var(--color-border-panel)]">
        <th scope="row" className="px-3 py-1.5 text-left font-semibold">
          {totalLabel}
        </th>
        <YearCells values={total} strong />
      </tr>
    </>
  );
  return (
    <div className="overflow-x-auto rounded-lg border border-[var(--color-border-panel)] bg-card">
      <table className="w-full border-collapse text-sm">
        <caption className="sr-only">Profit and loss</caption>
        <thead className="bg-[var(--color-bg-surface-subtle)] text-xs text-muted-foreground">
          <tr>
            <th scope="col" className="px-3 py-2 text-left font-medium">
              KES
            </th>
            {periods.map((p) => (
              <th key={p} scope="col" className="px-3 py-2 text-right font-medium">
                {p}
              </th>
            ))}
            <th scope="col" className="px-3 py-2 text-right font-medium">
              Change
            </th>
          </tr>
        </thead>
        <tbody className="divide-y divide-[var(--color-border-subtle)]">
          {block("Income (excluding VAT)", income, inc, "Net revenue")}
          {block("Cost of sales", costOfSales, cos, "Total cost of sales")}
          <tr className="border-t-2 border-[var(--color-border-panel)] bg-[var(--color-bg-selected)]">
            <th scope="row" className="px-3 py-2 text-left font-semibold">
              Gross profit
              <span className="block text-xs font-normal text-muted-foreground tabular-nums">Margin {inc[0] ? ((gp[0] / inc[0]) * 100).toFixed(1) : "0"}%</span>
            </th>
            <YearCells values={gp} strong negativeBad />
          </tr>
          {block("Operating expenses", expenses, ex, "Total expenses")}
          <tr className="border-t-2 border-[var(--color-border-panel)] bg-[var(--color-bg-selected)]">
            <th scope="row" className="px-3 py-2 text-left text-base font-semibold">
              Net profit
            </th>
            <YearCells values={np} strong negativeBad />
          </tr>
        </tbody>
      </table>
    </div>
  );
}

/**
 * Balance sheet (AC-05) at each year end: assets against liabilities and equity, where equity includes the
 * year's profit. The two sides must agree; if they don't, the check says so rather than hiding it.
 */
export function BalanceSheet({ periods, assets, liabilities, equity }: { periods: string[]; assets: StatementRow[]; liabilities: StatementRow[]; equity: StatementRow[] }) {
  const n = periods.length;
  const a = sumCols(assets, n);
  const l = sumCols(liabilities, n);
  const e = sumCols(equity, n);
  const gap = a.map((v, i) => Math.round((v - l[i] - e[i]) * 100) / 100);
  const ok = gap.every((g) => g === 0);
  const block = (title: string, rows: StatementRow[], total: number[]) => (
    <>
      <tr className="bg-muted/30">
        <th colSpan={n + 2} scope="rowgroup" className="px-3 py-1.5 text-left text-xs font-semibold tracking-wide uppercase">
          {title}
        </th>
      </tr>
      {rows.map((r) => (
        <tr key={r.code}>
          <th scope="row" className="px-3 py-1.5 pl-6 text-left font-normal">
            <span className="mr-2 font-mono text-xs text-muted-foreground">{r.code}</span>
            {r.name}
          </th>
          <YearCells values={r.amounts} />
        </tr>
      ))}
      <tr className="border-t border-[var(--color-border-panel)]">
        <th scope="row" className="px-3 py-1.5 text-left font-semibold">
          Total {title.toLowerCase()}
        </th>
        <YearCells values={total} strong />
      </tr>
    </>
  );
  return (
    <div className="space-y-3">
      <p className={cn("flex items-center gap-2 rounded-md border px-3 py-2 text-sm", ok ? TONE_BOX.success : TONE_BOX.error)}>
        {ok ? <CircleCheckIcon className="size-4 shrink-0" aria-hidden /> : <TriangleAlertIcon className="size-4 shrink-0" aria-hidden />}
        {ok ? "Assets equal liabilities plus equity in every year." : `Out by ${periods.map((p, i) => (gap[i] ? `${fmt.format(Math.abs(gap[i]))} in ${p}` : null)).filter(Boolean).join(", ")}. Report it: every posting balances, so this means data changed outside Medora.`}
      </p>
      <div className="overflow-x-auto rounded-lg border border-[var(--color-border-panel)] bg-card">
        <table className="w-full border-collapse text-sm">
          <caption className="sr-only">Balance sheet</caption>
          <thead className="bg-[var(--color-bg-surface-subtle)] text-xs text-muted-foreground">
            <tr>
              <th scope="col" className="px-3 py-2 text-left font-medium">
                KES, at year end
              </th>
              {periods.map((p) => (
                <th key={p} scope="col" className="px-3 py-2 text-right font-medium">
                  {p}
                </th>
              ))}
              <th scope="col" className="px-3 py-2 text-right font-medium">
                Change
              </th>
            </tr>
          </thead>
          <tbody className="divide-y divide-[var(--color-border-subtle)]">
            {block("Assets", assets, a)}
            {block("Liabilities", liabilities, l)}
            {block("Equity", equity, e)}
            <tr className="border-t-2 border-[var(--color-border-panel)] bg-[var(--color-bg-selected)]">
              <th scope="row" className="px-3 py-2 text-left font-semibold">
                Liabilities and equity
              </th>
              <YearCells values={l.map((v, i) => v + e[i])} strong />
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  );
}

/* --------------------------------- Opening balances --------------------------------- */

export type OpeningLine = { code: string; debit: string; credit: string };

/**
 * Opening balances (AC-06): the balance of each account on the day Medora starts, from the old books.
 * Entered on screen or imported from CSV. They post as one journal and must balance; the difference is
 * shown until they do, and an account can't have both a debit and a credit.
 */
export function OpeningBalances({ accounts, lines, onChange }: { accounts: LedgerAccount[]; lines: Record<string, OpeningLine>; onChange: (code: string, l: OpeningLine) => void }) {
  const leaves = accounts.filter((a) => !accounts.some((k) => k.parent === a.code) && a.active);
  const num = (s: string) => Number(s.replace(/,/g, "")) || 0;
  const dr = Object.values(lines).reduce((s, l) => s + num(l.debit), 0);
  const cr = Object.values(lines).reduce((s, l) => s + num(l.credit), 0);
  const diff = Math.round((dr - cr) * 100) / 100;
  const both = Object.values(lines).filter((l) => num(l.debit) && num(l.credit)).map((l) => l.code);
  return (
    <div className="space-y-3">
      <div className="max-h-96 overflow-y-auto rounded-lg border border-[var(--color-border-panel)] bg-card">
        <table className="w-full border-collapse text-sm">
          <caption className="sr-only">Opening balances</caption>
          <thead className="sticky top-0 z-[1] bg-[var(--color-bg-surface-subtle)] text-left text-xs text-muted-foreground">
            <tr>
              <th scope="col" className="px-3 py-2 font-medium">
                Account
              </th>
              <th scope="col" className="w-36 px-3 py-2 text-right font-medium">
                Debit
              </th>
              <th scope="col" className="w-36 px-3 py-2 text-right font-medium">
                Credit
              </th>
            </tr>
          </thead>
          <tbody className="divide-y divide-[var(--color-border-subtle)]">
            {leaves.map((a) => {
              const l = lines[a.code] ?? { code: a.code, debit: "", credit: "" };
              const bad = both.includes(a.code);
              return (
                <tr key={a.code}>
                  <th scope="row" className="px-3 py-1.5 text-left font-normal">
                    <span className="mr-2 font-mono text-xs text-muted-foreground">{a.code}</span>
                    {a.name}
                    {a.branch ? <span className="ml-1.5 text-xs text-muted-foreground">{a.branch}</span> : null}
                    {bad ? <span className="block text-xs text-status-error-text">Enter a debit or a credit, not both.</span> : null}
                  </th>
                  {(["debit", "credit"] as const).map((side) => (
                    <td key={side} className="px-3 py-1">
                      <Input
                        className="h-8 text-right tabular-nums"
                        inputMode="decimal"
                        aria-label={`${side === "debit" ? "Debit" : "Credit"}, ${a.code} ${a.name}`}
                        aria-invalid={bad}
                        value={l[side]}
                        onChange={(e) => onChange(a.code, { ...l, [side]: e.target.value })}
                      />
                    </td>
                  ))}
                </tr>
              );
            })}
          </tbody>
          <tfoot className="sticky bottom-0 border-t-2 border-[var(--color-border-panel)] bg-card font-semibold tabular-nums">
            <tr>
              <th scope="row" className="px-3 py-2 text-left">
                Totals
              </th>
              <td className="px-3 py-2 text-right">{fmt.format(dr)}</td>
              <td className="px-3 py-2 text-right">{fmt.format(cr)}</td>
            </tr>
          </tfoot>
        </table>
      </div>
      <p className={cn("flex items-center gap-2 rounded-md border px-3 py-2 text-sm", diff === 0 && dr > 0 && !both.length ? TONE_BOX.success : TONE_BOX.warning)}>
        {diff === 0 && dr > 0 && !both.length ? <CircleCheckIcon className="size-4 shrink-0" aria-hidden /> : <TriangleAlertIcon className="size-4 shrink-0" aria-hidden />}
        {dr === 0 && cr === 0
          ? "Enter each account's balance from the old books, or import them."
          : diff === 0 && !both.length
            ? `Balanced at KES ${fmt.format(dr)}. Ready to post as the opening journal.`
            : diff !== 0
              ? `Debits are KES ${fmt.format(Math.abs(diff))} ${diff > 0 ? "more" : "less"} than credits. Check the old trial balance; the difference can't be posted.`
              : "Fix the accounts that have both a debit and a credit."}
      </p>
    </div>
  );
}
