"use client";

import * as React from "react";
import { CircleXIcon, InfoIcon, TargetIcon } from "lucide-react";
import { Area, Bar, BarChart, CartesianGrid, Cell, ComposedChart, LabelList, Line, LineChart, Pie, PieChart, PolarAngleAxis, PolarGrid, PolarRadiusAxis, Radar, RadarChart, ReferenceLine, XAxis, YAxis } from "recharts";

import { kes } from "@/components/medora/payroll";
import { ViewSwitch } from "@/components/medora/sales-reports";
import { VIZ_DIVERGING, VIZ_INK, VIZ_SEQ, VIZ_SERIES } from "@/components/medora/viz";
import { EmptyState } from "@/components/medora/records/states";
import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "@/components/ui/chart";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { cn } from "@/lib/utils";

/*
 * Report charts (charts pass, 22 Oct 2026). Rules every chart here follows:
 * - colours come from the chart tokens through viz.ts; series keep their colour when filters change;
 * - one value axis per chart, bars start at zero, no dual axes;
 * - a Chart / Table switch shows the same figures, so nothing depends on colour or bar length;
 * - the period and basis are stated by the caller in the Example or card title.
 */

const k = (v: number) => {
  const a = Math.abs(v);
  const s = a >= 1_000_000 ? `${(a / 1_000_000).toFixed(a >= 10_000_000 ? 0 : 1)}M` : a >= 1000 ? `${Math.round(a / 1000)}k` : String(Math.round(a));
  return v < 0 ? `−${s}` : s;
};
const pct = (v: number) => `${v.toFixed(1)}%`;
const GRID = { vertical: false, stroke: "var(--color-chart-gridline)" } as const;
const AXIS = { tickLine: false, axisLine: false, tick: { fill: "var(--color-chart-axis-label)", fontSize: 11 } } as const;
const GAP = "var(--color-chart-segment-gap)";

type Cell_ = string | number;
type TableSpec = { head: string[]; rows: Cell_[][]; foot?: Cell_[] };

function Legend({ items }: { items: { label: string; color: string; dashed?: boolean }[] }) {
  return (
    <ul className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground">
      {items.map((i) => (
        <li key={i.label} className="flex items-center gap-1.5">
          {i.dashed ? <span className="w-4 border-t-2 border-dashed" style={{ borderColor: i.color }} aria-hidden /> : <span className="size-2.5 rounded-[2px]" style={{ backgroundColor: i.color }} aria-hidden />}
          {i.label}
        </li>
      ))}
    </ul>
  );
}

function FigureTable({ spec }: { spec: TableSpec }) {
  const cell = (v: Cell_) => (typeof v === "number" ? kes(v) : v);
  return (
    <div className="overflow-x-auto">
      <table className="w-full text-sm tabular-nums">
        <thead className="bg-[var(--color-bg-surface-subtle)] text-xs text-muted-foreground">
          <tr>
            {spec.head.map((h, i) => (
              <th key={h} className={cn("px-2 py-1 font-medium", i ? "text-right" : "text-left")}>
                {h}
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {spec.rows.map((r, ri) => (
            <tr key={ri} className="border-t border-[var(--color-border-subtle)]">
              {r.map((v, i) => (
                <td key={i} className={cn("px-2 py-1", i ? "text-right" : "text-left font-medium")}>
                  {cell(v)}
                </td>
              ))}
            </tr>
          ))}
        </tbody>
        {spec.foot ? (
          <tfoot className="border-t-2 border-[var(--color-border-panel)] font-semibold">
            <tr>
              {spec.foot.map((v, i) => (
                <td key={i} className={cn("px-2 py-1", i ? "text-right" : "text-left")}>
                  {cell(v)}
                </td>
              ))}
            </tr>
          </tfoot>
        ) : null}
      </table>
    </div>
  );
}

/** Chart with its Table twin, a summary line and a legend. */
/**
 * Compact charts (chart spec, compact variant; DECISIONS 29 Oct 2026): on the dashboard the chart is always shown, with
 * no Chart | Table switch. The table stays in the accessibility tree, and each panel links to its report, which has
 * the table view.
 */
export const CompactCharts = React.createContext(false);

function ChartPanel({ label, summary, legend, table, children }: { label: string; summary?: React.ReactNode; legend?: React.ReactNode; table: TableSpec; children: React.ReactNode }) {
  const compact = React.useContext(CompactCharts);
  const [view, setView] = React.useState<"chart" | "table">("chart");
  const shown = compact ? "chart" : view;
  return (
    <div className="space-y-3">
      <div className="flex flex-wrap items-start justify-between gap-2">
        <div className="min-w-0 space-y-1.5">
          {summary ? <p className="text-sm">{summary}</p> : null}
          {shown === "chart" ? legend : null}
        </div>
        {compact ? null : <ViewSwitch value={view} onChange={setView} label={label} />}
      </div>
      {shown === "chart" ? children : <FigureTable spec={table} />}
      {compact ? (
        <div className="sr-only">
          <FigureTable spec={table} />
        </div>
      ) : null}
    </div>
  );
}

/* ------------------------------ Sales trend vs previous period (RP-11, DB-09) ------------------------------ */

export type TrendPoint = { label: string; current: number; previous: number };

/**
 * This period against the one before, day by day. The comparison is grey and dashed so the current period
 * is the one that reads first (emphasis: one hue and grey), and the totals and change are written out.
 */
export function PeriodTrend({ points, currentLabel, previousLabel }: { points: TrendPoint[]; currentLabel: string; previousLabel: string }) {
  const cur = points.reduce((s, p) => s + p.current, 0);
  const prev = points.reduce((s, p) => s + p.previous, 0);
  const change = prev ? ((cur - prev) / prev) * 100 : 0;
  const config = { current: { label: currentLabel, color: VIZ_SERIES[0] }, previous: { label: previousLabel, color: "var(--color-chart-axis-line)" } } satisfies ChartConfig;
  return (
    <ChartPanel
      label="Sales trend"
      summary={
        <>
          <strong className="tabular-nums">{kes(cur)}</strong> against {kes(prev)}: {change >= 0 ? "up" : "down"} <strong>{pct(Math.abs(change))}</strong> {change >= 0 ? "▲" : "▼"}
        </>
      }
      legend={<Legend items={[{ label: currentLabel, color: VIZ_SERIES[0] }, { label: previousLabel, color: "var(--color-chart-axis-line)", dashed: true }]} />}
      table={{ head: ["Day", currentLabel, previousLabel], rows: points.map((p) => [p.label, p.current, p.previous]), foot: ["Total", cur, prev] }}
    >
      <ChartContainer config={config} className="aspect-auto h-56 w-full" initialDimension={{ width: 560, height: 224 }}>
        <ComposedChart data={points} margin={{ left: 0, right: 8, top: 8 }}>
          <CartesianGrid {...GRID} />
          <XAxis dataKey="label" {...AXIS} interval="preserveStartEnd" minTickGap={16} />
          <YAxis {...AXIS} width={40} tickFormatter={k} />
          <ChartTooltip content={<ChartTooltipContent formatter={(v, n) => `${config[n as keyof typeof config]?.label ?? n}: ${kes(Number(v))}`} />} />
          <Area dataKey="current" type="linear" stroke="none" fill="var(--color-current)" fillOpacity={0.1} isAnimationActive={false} />
          <Line dataKey="previous" type="linear" stroke="var(--color-previous)" strokeWidth={2} strokeDasharray="4 4" dot={false} isAnimationActive={false} />
          <Line dataKey="current" type="linear" stroke="var(--color-current)" strokeWidth={2} dot={false} activeDot={{ r: 4, strokeWidth: 2, stroke: GAP }} isAnimationActive={false} />
        </ComposedChart>
      </ChartContainer>
    </ChartPanel>
  );
}

/* ------------------------------ Financial overview (DB-04) ------------------------------ */

export type FinPeriod = { label: string; sales: number; purchases: number; expenses: number };
const FIN = [
  { key: "sales", label: "Sales" },
  { key: "purchases", label: "Purchases" },
  { key: "expenses", label: "Expenses" },
] as const;

/** Sales, purchases and expenses per month as grouped columns on one axis. */
export function FinancialOverviewChart({ months }: { months: FinPeriod[] }) {
  const config = Object.fromEntries(FIN.map((f, i) => [f.key, { label: f.label, color: VIZ_SERIES[i] }])) satisfies ChartConfig;
  return (
    <ChartPanel
      label="Financial overview"
      legend={<Legend items={FIN.map((f, i) => ({ label: f.label, color: VIZ_SERIES[i] }))} />}
      table={{ head: ["Month", "Sales", "Purchases", "Expenses"], rows: months.map((m) => [m.label, m.sales, m.purchases, m.expenses]) }}
    >
      <ChartContainer config={config} className="aspect-auto h-56 w-full" initialDimension={{ width: 480, height: 224 }}>
        <BarChart data={months} margin={{ left: 0, right: 8, top: 8 }} barGap={2} barCategoryGap="24%">
          <CartesianGrid {...GRID} />
          <XAxis dataKey="label" {...AXIS} interval={0} />
          <YAxis {...AXIS} width={40} tickFormatter={k} />
          <ChartTooltip content={<ChartTooltipContent formatter={(v, n) => `${config[n as keyof typeof config]?.label ?? n}: ${kes(Number(v))}`} />} />
          {FIN.map((f) => (
            <Bar key={f.key} dataKey={f.key} fill={`var(--color-${f.key})`} radius={[4, 4, 0, 0]} maxBarSize={24} isAnimationActive={false} />
          ))}
        </BarChart>
      </ChartContainer>
    </ChartPanel>
  );
}

/* ------------------------------ Sales by branch (DB-05) ------------------------------ */

export type BranchSales = { branch: string; paid: number; credit: number };

/** Each branch's sales, split into paid at the till and on credit, as stacked bars with the total written. */
export function BranchSalesChart({ rows, compact }: { rows: BranchSales[]; compact?: boolean }) {
  const data = [...rows].sort((a, b) => b.paid + b.credit - (a.paid + a.credit)).map((r) => ({ ...r, total: r.paid + r.credit }));
  const config = { paid: { label: "Paid at the till", color: VIZ_SERIES[0] }, credit: { label: "On credit", color: VIZ_SERIES[1] } } satisfies ChartConfig;
  const sum = (key: "paid" | "credit") => rows.reduce((s, r) => s + r[key], 0);
  return (
    <ChartPanel
      label="Sales by branch"
      legend={<Legend items={[{ label: "Paid at the till", color: VIZ_SERIES[0] }, { label: "On credit", color: VIZ_SERIES[1] }]} />}
      table={{ head: ["Branch", "Paid at the till", "On credit", "Total"], rows: data.map((r) => [r.branch, r.paid, r.credit, r.total]), foot: ["All branches", sum("paid"), sum("credit"), sum("paid") + sum("credit")] }}
    >
      <ChartContainer config={config} className={cn("aspect-auto w-full", compact ? "h-40" : "h-52")} initialDimension={{ width: 480, height: compact ? 160 : 208 }}>
        <BarChart data={data} layout="vertical" margin={{ left: 0, right: 48, top: 4, bottom: 4 }}>
          <XAxis type="number" hide />
          <YAxis type="category" dataKey="branch" {...AXIS} width={compact ? 92 : 108} />
          <ChartTooltip cursor={false} content={<ChartTooltipContent formatter={(v, n) => `${config[n as keyof typeof config]?.label ?? n}: ${kes(Number(v))}`} />} />
          <Bar dataKey="paid" stackId="s" fill="var(--color-paid)" stroke={GAP} strokeWidth={2} maxBarSize={20} isAnimationActive={false} />
          <Bar dataKey="credit" stackId="s" fill="var(--color-credit)" stroke={GAP} strokeWidth={2} radius={[0, 4, 4, 0]} maxBarSize={20} isAnimationActive={false}>
            <LabelList dataKey="total" position="right" className="fill-foreground text-[11px] tabular-nums" formatter={(v: unknown) => k(Number(v))} />
          </Bar>
        </BarChart>
      </ChartContainer>
    </ChartPanel>
  );
}

/* ------------------------------ Profit and loss waterfall (AC-05, RP-15) ------------------------------ */

export type PLStep = { label: string; amount: number; kind: "total" | "less" };

/** Bars for the walk, and the first total that doesn't equal the line above less the deductions between. */
export function waterfall(steps: PLStep[]) {
  let running = 0;
  let broken: PLStep | undefined;
  const data = steps.map((s) => {
    if (s.kind === "total") {
      if (running !== 0 && !broken && Math.abs(running - s.amount) > 0.005) broken = s;
      running = s.amount;
      return { label: s.label, base: 0, value: s.amount, kind: s.kind, shown: s.amount };
    }
    running -= s.amount;
    return { label: s.label, base: running, value: s.amount, kind: s.kind, shown: -s.amount };
  });
  return { data, broken };
}

/**
 * Gross sales down to net profit. Totals stand on the baseline in the total colour; each deduction hangs
 * from the running total in the diverging "below" colour and is labelled with a minus, so the walk reads
 * without colour. The steps must reconcile: each total equals the one before less what came between.
 */
export function PLWaterfall({ steps }: { steps: PLStep[] }) {
  const { data, broken } = waterfall(steps);
  const config = { value: { label: "Amount" } } satisfies ChartConfig;
  return (
    <ChartPanel
      label="Profit and loss"
      legend={broken ? null : <Legend items={[{ label: "Total", color: VIZ_INK }, { label: "Less", color: VIZ_DIVERGING.negative }]} />}
      table={{ head: ["Line", "Amount"], rows: steps.map((s) => [s.kind === "less" ? `Less ${s.label.toLowerCase()}` : s.label, s.kind === "less" ? `(${kes(s.amount)})` : s.amount]) }}
    >
      {broken ? (
        <p role="alert" className="rounded-md border border-status-error-border bg-status-error-bg p-3 text-sm text-status-error-text">
          {broken.label} doesn&apos;t equal the line above less the deductions in between, so the walk isn&apos;t drawn. Check the figures in the Table view.
        </p>
      ) : (
        <ChartContainer config={config} className="aspect-auto h-60 w-full" initialDimension={{ width: 560, height: 240 }}>
          <BarChart data={data} margin={{ left: 0, right: 8, top: 20 }}>
            <CartesianGrid {...GRID} />
            <XAxis dataKey="label" {...AXIS} interval={0} tick={{ ...AXIS.tick, fontSize: 10 }} />
            <YAxis {...AXIS} width={40} tickFormatter={k} />
            <ReferenceLine y={0} stroke="var(--color-chart-axis-line)" />
            <ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel formatter={(_v, _n, item) => `${(item.payload as { label: string }).label}: ${(item.payload as { shown: number }).shown < 0 ? "−" : ""}${kes(Math.abs((item.payload as { shown: number }).shown))}`} />} />
            <Bar dataKey="base" stackId="w" fill="transparent" isAnimationActive={false} />
            <Bar dataKey="value" stackId="w" radius={[4, 4, 0, 0]} maxBarSize={44} isAnimationActive={false}>
              {data.map((d) => (
                <Cell key={d.label} fill={d.kind === "total" ? VIZ_INK : VIZ_DIVERGING.negative} />
              ))}
              <LabelList dataKey="shown" position="top" className="fill-foreground text-[11px] tabular-nums" formatter={(v: unknown) => k(Number(v))} />
            </Bar>
          </BarChart>
        </ChartContainer>
      )}
    </ChartPanel>
  );
}

/* ------------------------------ Category P&L (AC-19) ------------------------------ */

export type CategoryRow = { category: string; revenue: number; cost: number };

/**
 * Each category's revenue as one bar split into cost and gross profit, with the margin written at the end.
 * Profit and margin share one axis (shillings); margin is a label, never a second scale.
 */
export function CategoryMarginChart({ rows }: { rows: CategoryRow[] }) {
  const data = [...rows].sort((a, b) => b.revenue - a.revenue).map((r) => ({ ...r, profit: r.revenue - r.cost, margin: r.revenue ? ((r.revenue - r.cost) / r.revenue) * 100 : 0 }));
  const config = { cost: { label: "Cost of goods", color: VIZ_DIVERGING.midpoint }, profit: { label: "Gross profit", color: VIZ_SERIES[0] } } satisfies ChartConfig;
  const tot = data.reduce((s, r) => ({ revenue: s.revenue + r.revenue, cost: s.cost + r.cost }), { revenue: 0, cost: 0 });
  const best = data.reduce((a, b) => (b.margin > a.margin ? b : a), data[0]);
  const worst = data.reduce((a, b) => (b.margin < a.margin ? b : a), data[0]);
  return (
    <ChartPanel
      label="Category profit"
      summary={
        <>
          Best margin <strong>{best.category}</strong> ({pct(best.margin)}), lowest <strong>{worst.category}</strong> ({pct(worst.margin)}).
        </>
      }
      legend={<Legend items={[{ label: "Gross profit", color: VIZ_SERIES[0] }, { label: "Cost of goods", color: VIZ_DIVERGING.midpoint }]} />}
      table={{
        head: ["Category", "Revenue", "Cost", "Gross profit", "Margin"],
        rows: data.map((r) => [r.category, r.revenue, r.cost, r.profit, pct(r.margin)]),
        foot: ["All categories", tot.revenue, tot.cost, tot.revenue - tot.cost, pct(((tot.revenue - tot.cost) / tot.revenue) * 100)],
      }}
    >
      <ChartContainer config={config} className="aspect-auto w-full" style={{ height: data.length * 34 + 16 }} initialDimension={{ width: 560, height: data.length * 34 + 16 }}>
        <BarChart data={data} layout="vertical" margin={{ left: 0, right: 56, top: 4, bottom: 4 }}>
          <XAxis type="number" hide />
          <YAxis type="category" dataKey="category" {...AXIS} width={104} />
          <ChartTooltip cursor={false} content={<ChartTooltipContent formatter={(v, n) => `${config[n as keyof typeof config]?.label ?? n}: ${kes(Number(v))}`} />} />
          <Bar dataKey="profit" stackId="c" fill="var(--color-profit)" stroke={GAP} strokeWidth={2} maxBarSize={20} isAnimationActive={false} />
          <Bar dataKey="cost" stackId="c" fill="var(--color-cost)" stroke={GAP} strokeWidth={2} radius={[0, 4, 4, 0]} maxBarSize={20} isAnimationActive={false}>
            <LabelList dataKey="margin" position="right" className="fill-foreground text-[11px] tabular-nums" formatter={(v: unknown) => `${Number(v).toFixed(1)}%`} />
          </Bar>
        </BarChart>
      </ChartContainer>
    </ChartPanel>
  );
}

/* ------------------------------ Staff ranking (AC-19) ------------------------------ */

export type StaffRow = { staff: string; sales: number; returns: number };

/** Net sales per staff member, ranked, with each person's return rate written beside the bar. */
export function StaffRankChart({ rows }: { rows: StaffRow[] }) {
  const data = rows.map((r) => ({ ...r, net: r.sales - r.returns, rate: r.sales ? (r.returns / r.sales) * 100 : 0 })).sort((a, b) => b.net - a.net);
  const config = { net: { label: "Net sales", color: VIZ_SERIES[0] } } satisfies ChartConfig;
  return (
    <ChartPanel
      label="Staff ranking"
      table={{ head: ["Staff", "Sales", "Returns", "Net sales", "Return rate"], rows: data.map((r) => [r.staff, r.sales, r.returns, r.net, pct(r.rate)]) }}
    >
      <ChartContainer config={config} className="aspect-auto w-full" style={{ height: data.length * 34 + 16 }} initialDimension={{ width: 480, height: data.length * 34 + 16 }}>
        <BarChart data={data} layout="vertical" margin={{ left: 0, right: 120, top: 4, bottom: 4 }}>
          <XAxis type="number" hide />
          <YAxis type="category" dataKey="staff" {...AXIS} width={104} />
          <ChartTooltip cursor={false} content={<ChartTooltipContent formatter={(v) => `Net sales: ${kes(Number(v))}`} />} />
          <Bar dataKey="net" fill="var(--color-net)" radius={[0, 4, 4, 0]} maxBarSize={20} isAnimationActive={false}>
            <LabelList dataKey="net" position="right" content={(p) => <StaffLabel {...(p as LabelProps)} rows={data} />} />
          </Bar>
        </BarChart>
      </ChartContainer>
    </ChartPanel>
  );
}

type LabelProps = { x?: number; y?: number; width?: number; height?: number; index?: number };
function StaffLabel({ x = 0, y = 0, width = 0, height = 0, index = 0, rows }: LabelProps & { rows: { net: number; rate: number }[] }) {
  const r = rows[index];
  return (
    <text x={x + width + 6} y={y + height / 2} dominantBaseline="middle" className="fill-foreground text-[11px] tabular-nums">
      {k(r.net)}
      <tspan className="fill-muted-foreground"> · returns {pct(r.rate)}</tspan>
    </text>
  );
}

/* ------------------------------ Stock value per branch (RP-03, DB-06) ------------------------------ */

export type BranchStockValue = { branch: string; cost: number; selling: number };

/** Stock value per branch at cost and at selling price, side by side, with the margin it represents. */
export function StockValueChart({ rows }: { rows: BranchStockValue[] }) {
  const data = rows.map((r) => ({ ...r, margin: r.selling ? ((r.selling - r.cost) / r.selling) * 100 : 0 }));
  const config = { cost: { label: "At cost", color: VIZ_SERIES[0] }, selling: { label: "At selling price", color: VIZ_SERIES[1] } } satisfies ChartConfig;
  const tc = rows.reduce((s, r) => s + r.cost, 0);
  const ts = rows.reduce((s, r) => s + r.selling, 0);
  return (
    <ChartPanel
      label="Stock value by branch"
      summary={
        <>
          <strong className="tabular-nums">{kes(tc)}</strong> at cost, {kes(ts)} at selling price ({pct(((ts - tc) / ts) * 100)} margin), valued from batches.
        </>
      }
      legend={<Legend items={[{ label: "At cost", color: VIZ_SERIES[0] }, { label: "At selling price", color: VIZ_SERIES[1] }]} />}
      table={{ head: ["Branch", "At cost", "At selling price", "Margin"], rows: data.map((r) => [r.branch, r.cost, r.selling, pct(r.margin)]), foot: ["All branches", tc, ts, pct(((ts - tc) / ts) * 100)] }}
    >
      <ChartContainer config={config} className="aspect-auto h-56 w-full" initialDimension={{ width: 480, height: 224 }}>
        <BarChart data={data} margin={{ left: 0, right: 8, top: 8 }} barGap={2} barCategoryGap="28%">
          <CartesianGrid {...GRID} />
          <XAxis dataKey="branch" {...AXIS} />
          <YAxis {...AXIS} width={40} tickFormatter={k} />
          <ChartTooltip content={<ChartTooltipContent formatter={(v, n) => `${config[n as keyof typeof config]?.label ?? n}: ${kes(Number(v))}`} />} />
          <Bar dataKey="cost" fill="var(--color-cost)" radius={[4, 4, 0, 0]} maxBarSize={24} isAnimationActive={false} />
          <Bar dataKey="selling" fill="var(--color-selling)" radius={[4, 4, 0, 0]} maxBarSize={24} isAnimationActive={false} />
        </BarChart>
      </ChartContainer>
    </ChartPanel>
  );
}

/* ------------------------------ Expiring stock by month (RP-02, IN-10) ------------------------------ */

export type ExpiryMonth = { label: string; value: number; batches: number; expired?: boolean };

/**
 * Value at cost of stock expiring each month. Already expired and the next three months are the ones to act
 * on, so they carry the colour and later months are grey (emphasis, not a scale). Words say which is which.
 */
export function ExpiryChart({ months, actWithin = 3 }: { months: ExpiryMonth[]; actWithin?: number }) {
  const data = months.map((m, i) => ({ ...m, act: m.expired || i < actWithin + (months[0]?.expired ? 1 : 0) }));
  const config = { value: { label: "Value at cost" } } satisfies ChartConfig;
  const urgent = data.filter((d) => d.act).reduce((s, d) => s + d.value, 0);
  return (
    <ChartPanel
      label="Expiring stock"
      summary={
        <>
          <strong className="tabular-nums">{kes(urgent)}</strong> expired or expiring in the next {actWithin} months: return, discount or move it first.
        </>
      }
      legend={<Legend items={[{ label: "Act now", color: VIZ_DIVERGING.negative }, { label: "Later", color: VIZ_DIVERGING.midpoint }]} />}
      table={{ head: ["Month", "Batches", "Value at cost", "Act now"], rows: data.map((d) => [d.label, String(d.batches), d.value, d.act ? "Yes" : "–"]), foot: ["Total", String(data.reduce((s, d) => s + d.batches, 0)), data.reduce((s, d) => s + d.value, 0), ""] }}
    >
      <ChartContainer config={config} className="aspect-auto h-52 w-full" initialDimension={{ width: 480, height: 208 }}>
        <BarChart data={data} margin={{ left: 0, right: 8, top: 18 }}>
          <CartesianGrid {...GRID} />
          <XAxis dataKey="label" {...AXIS} interval={0} />
          <YAxis {...AXIS} width={40} tickFormatter={k} />
          <ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel formatter={(v, _n, item) => `${(item.payload as ExpiryMonth).label}: ${kes(Number(v))} in ${(item.payload as ExpiryMonth).batches} batches`} />} />
          <Bar dataKey="value" radius={[4, 4, 0, 0]} maxBarSize={28} isAnimationActive={false}>
            {data.map((d) => (
              <Cell key={d.label} fill={d.act ? VIZ_DIVERGING.negative : VIZ_DIVERGING.midpoint} />
            ))}
            <LabelList dataKey="value" position="top" className="fill-muted-foreground text-[10px] tabular-nums" formatter={(v: unknown) => k(Number(v))} />
          </Bar>
        </BarChart>
      </ChartContainer>
    </ChartPanel>
  );
}

/* ------------------------------ Receivables and payables ageing (AC-09, AC-11) ------------------------------ */

export const AGEING = ["0–30 days", "31–60 days", "61–90 days", "Over 90 days"] as const;
export type AgeingBar = { label: string; buckets: [number, number, number, number] };

/**
 * Money owed to us and by us, each as one bar split by age. Age is ordered, so it uses the teal ramp:
 * darker is older. Each segment's amount is in the tooltip and the Table view; the share over 90 days is written.
 */
export function AgeingChart({ rows }: { rows: AgeingBar[] }) {
  const shades = VIZ_SEQ.slice(1);
  const data = rows.map((r) => ({ label: r.label, b0: r.buckets[0], b1: r.buckets[1], b2: r.buckets[2], b3: r.buckets[3], total: r.buckets.reduce((s, v) => s + v, 0) }));
  const config = Object.fromEntries(AGEING.map((a, i) => [`b${i}`, { label: a, color: shades[i] }])) satisfies ChartConfig;
  return (
    <ChartPanel
      label="Ageing"
      summary={data.map((d, i) => (
        <span key={d.label} className="block">
          {d.label}: <strong className="tabular-nums">{kes(d.total)}</strong>, {pct(d.total ? (rows[i].buckets[3] / d.total) * 100 : 0)} over 90 days
        </span>
      ))}
      legend={<Legend items={AGEING.map((a, i) => ({ label: a, color: shades[i] }))} />}
      table={{ head: ["", ...AGEING, "Total"], rows: data.map((d) => [d.label, d.b0, d.b1, d.b2, d.b3, d.total]) }}
    >
      <ChartContainer config={config} className="aspect-auto w-full" style={{ height: data.length * 44 + 16 }} initialDimension={{ width: 480, height: data.length * 44 + 16 }}>
        <BarChart data={data} layout="vertical" margin={{ left: 0, right: 48, top: 4, bottom: 4 }}>
          <XAxis type="number" hide />
          <YAxis type="category" dataKey="label" {...AXIS} width={96} />
          <ChartTooltip cursor={false} content={<ChartTooltipContent formatter={(v, n) => `${config[n as keyof typeof config]?.label ?? n}: ${kes(Number(v))}`} />} />
          {AGEING.map((a, i) => (
            <Bar key={a} dataKey={`b${i}`} stackId="a" fill={`var(--color-b${i})`} stroke={GAP} strokeWidth={2} radius={i === AGEING.length - 1 ? [0, 4, 4, 0] : 0} maxBarSize={24} isAnimationActive={false}>
              {i === AGEING.length - 1 ? <LabelList dataKey="total" position="right" className="fill-foreground text-[11px] tabular-nums" formatter={(v: unknown) => k(Number(v))} /> : null}
            </Bar>
          ))}
        </BarChart>
      </ChartContainer>
    </ChartPanel>
  );
}

/* ------------------------------ Payment method split (DB-08) ------------------------------ */

export type MethodShare = { method: string; amount: number };

/**
 * Part-to-whole as one 100% bar rather than a donut: lengths on a line are easier to compare than angles,
 * and the amounts and shares are written beside the key.
 */
export function MethodSplit({ rows }: { rows: MethodShare[] }) {
  const total = rows.reduce((s, r) => s + r.amount, 0);
  return (
    <div className="space-y-2">
      <div className="flex h-6 w-full gap-[2px]" role="img" aria-label={rows.map((r) => `${r.method} ${pct((r.amount / total) * 100)}`).join(", ")}>
        {rows.map((r, i) => (
          <span key={r.method} className="h-full first:rounded-l-[4px] last:rounded-r-[4px]" style={{ width: `${(r.amount / total) * 100}%`, backgroundColor: VIZ_SERIES[i] }} />
        ))}
      </div>
      <ul className="grid grid-cols-2 gap-x-4 gap-y-1 text-xs">
        {rows.map((r, i) => (
          <li key={r.method} className="flex items-baseline justify-between gap-2">
            <span className="flex items-center gap-1.5">
              <span className="size-2.5 rounded-[2px]" style={{ backgroundColor: VIZ_SERIES[i] }} aria-hidden />
              {r.method} <span className="text-muted-foreground">{pct((r.amount / total) * 100)}</span>
            </span>
            <span className="tabular-nums">{kes(r.amount)}</span>
          </li>
        ))}
      </ul>
    </div>
  );
}

/**
 * Payment methods as a donut with the total in the centre (the charts demo's "Payment methods", DECISIONS 29 Oct
 * 2026), every part listed beside it with its share and amount, so the figures can be checked (pair every chart
 * with its table). Four parts at most: Cash, M-Pesa, Card and Credit.
 */
export function MethodDonut({ rows: all, caption }: { rows: MethodShare[]; caption?: string }) {
  // Four parts at most (charts spec, colour): beyond four, the largest three stay and the rest become "Other"
  const sorted = [...all].sort((x, y) => y.amount - x.amount);
  const rows = sorted.length > 4 ? [...sorted.slice(0, 3), { method: `Other (${sorted.slice(3).map((r) => r.method).join(", ")})`, amount: sorted.slice(3).reduce((s, r) => s + r.amount, 0) }] : sorted;
  const total = rows.reduce((s, r) => s + r.amount, 0);
  const data = rows.map((r, i) => ({ key: `m${i}`, method: r.method, amount: r.amount }));
  const config = Object.fromEntries(data.map((d, i) => [d.key, { label: d.method, color: VIZ_SERIES[i] }])) satisfies ChartConfig;
  // The list sits under the donut in a narrow panel and beside it when the panel has room (container query)
  return (
    <div className="@container">
    <div className="grid items-center gap-4 @md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
      <div className="relative">
        <ChartContainer config={config} className="mx-auto aspect-square h-48 w-full max-w-48" initialDimension={{ width: 192, height: 192 }}>
          <PieChart>
            <ChartTooltip content={<ChartTooltipContent nameKey="key" hideLabel formatter={(v, n) => `${config[n as keyof typeof config]?.label ?? n}: ${kes(Number(v))}`} />} />
            <Pie data={data} dataKey="amount" nameKey="key" innerRadius="68%" outerRadius="100%" paddingAngle={1} stroke="var(--background)" strokeWidth={2} isAnimationActive={false}>
              {data.map((d) => (
                <Cell key={d.key} fill={`var(--color-${d.key})`} />
              ))}
            </Pie>
          </PieChart>
        </ChartContainer>
        <div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center">
          {caption ? <span className="text-xs text-muted-foreground">{caption}</span> : null}
          <span className="text-sm font-semibold tabular-nums">{kes(total)}</span>
        </div>
      </div>
      <ul className="space-y-1.5 text-xs">
        {rows.map((r, i) => (
          <li key={r.method} className="flex items-baseline justify-between gap-2">
            <span className="flex items-center gap-1.5">
              <span className="size-2.5 rounded-[2px]" style={{ backgroundColor: VIZ_SERIES[i] }} aria-hidden />
              {r.method} <span className="text-muted-foreground">{total ? pct((r.amount / total) * 100) : "–"}</span>
            </span>
            <span className="tabular-nums">{kes(r.amount)}</span>
          </li>
        ))}
      </ul>
    </div>
    </div>
  );
}

/* ------------------------------ Interactive line chart (C90, DB-16, RP-17) ------------------------------ */

export type LineSeries = { key: string; label: string };
export type LinePoint = { label: string } & Record<string, number | string>;

/** Line marks (G2 §6): series 1 solid, 2 dashed, 3 dotted, 4 solid with markers, so lines differ without colour. */
const LINE_DASH = [undefined, "4 2", "1 3", undefined];

/**
 * Interactive line chart (C90, card 040): one button per series in the header, each with the series' name and its
 * figure (the period's total, or the latest value for a balance such as stock), and "All" first. Choosing a series
 * draws only that line; the Table view always shows every series. The buttons are a segmented control: one tab stop,
 * arrow keys between them, the chosen one pressed. `caveat` states the data's limits under the chart.
 */
export function SeriesLineChart({
  label,
  points,
  series,
  measure = "sum",
  format = kes,
  caveat,
}: {
  label: string;
  points: LinePoint[];
  series: LineSeries[];
  /** "sum" for flows (sales), "last" for balances (stock value) */
  measure?: "sum" | "last";
  format?: (v: number) => string;
  caveat?: React.ReactNode;
}) {
  const compact = React.useContext(CompactCharts);
  const shown = series.slice(0, 4);
  const [chosen, setChosen] = React.useState<string>("all");
  const [view, setView] = React.useState<"chart" | "table">("chart");
  const figure = (key: string) => (measure === "last" ? Number(points[points.length - 1]?.[key] ?? 0) : points.reduce((s, p) => s + Number(p[key] ?? 0), 0));
  const config = Object.fromEntries(shown.map((s, i) => [s.key, { label: s.label, color: VIZ_SERIES[i] }])) satisfies ChartConfig;
  const drawn = chosen === "all" ? shown : shown.filter((s) => s.key === chosen);
  const caption = measure === "last" ? "Latest" : "Total";
  return (
    <div className="@container space-y-3">
      {/* The view switch on the right; the series buttons a full-width row of equal columns, so they never wrap apart */}
      <div className="flex justify-end">
        {compact ? null : <ViewSwitch value={view} onChange={setView} label={label} />}
      </div>
      <div>
        <ToggleGroup variant="outline" size="sm" spacing={0} aria-label={`${label}: series shown`} value={[chosen]} onValueChange={(v: string[]) => v[0] && setChosen(v[0])} className="flex w-full">
          {shown.length > 1 ? (
            <ToggleGroupItem value="all" className="h-auto shrink-0 flex-col items-start px-3 py-1.5">
              <span className="text-xs text-muted-foreground">All</span>
              <span className="text-xs font-medium">{shown.length} series</span>
            </ToggleGroupItem>
          ) : null}
          {shown.map((s, i) => (
            <ToggleGroupItem key={s.key} value={s.key} className="h-auto min-w-0 flex-1 flex-col items-start px-3 py-1.5">
              <span className="flex items-center gap-1.5 text-xs text-muted-foreground">
                <span className="size-2 rounded-[2px]" style={{ backgroundColor: VIZ_SERIES[i] }} aria-hidden />
                {s.label}
              </span>
              <span className="text-xs font-semibold whitespace-nowrap tabular-nums @md:text-sm">
                <span className="sr-only">{caption} </span>
                {format(figure(s.key))}
              </span>
            </ToggleGroupItem>
          ))}
        </ToggleGroup>
      </div>
      {view === "chart" ? (
        <ChartContainer config={config} className="aspect-auto h-56 w-full" initialDimension={{ width: 560, height: 224 }}>
          <LineChart data={points} margin={{ left: 0, right: 8, top: 8 }} accessibilityLayer>
            <CartesianGrid {...GRID} />
            <XAxis dataKey="label" {...AXIS} interval="preserveStartEnd" minTickGap={16} />
            <YAxis {...AXIS} width={44} tickFormatter={k} />
            <ChartTooltip content={<ChartTooltipContent formatter={(v, n) => `${config[n as keyof typeof config]?.label ?? n}: ${format(Number(v))}`} />} />
            {drawn.map((s) => {
              const i = shown.indexOf(s);
              return <Line key={s.key} dataKey={s.key} type="linear" stroke={`var(--color-${s.key})`} strokeWidth={2} strokeDasharray={LINE_DASH[i]} dot={i === 3 || points.length <= 12 ? { r: 2.5 } : false} isAnimationActive={false} />;
            })}
          </LineChart>
        </ChartContainer>
      ) : (
        <FigureTable
          spec={{
            head: ["Day", ...shown.map((s) => s.label)],
            rows: points.map((p) => [p.label, ...shown.map((s) => format(Number(p[s.key] ?? 0)))]),
            foot: measure === "sum" ? [caption, ...shown.map((s) => format(figure(s.key)))] : undefined,
          }}
        />
      )}
      {caveat ? (
        <p className="flex items-start gap-1.5 text-xs text-muted-foreground">
          <InfoIcon className="mt-0.5 size-3.5 shrink-0" aria-hidden />
          <span>{caveat}</span>
        </p>
      ) : null}
    </div>
  );
}

/* ------------------------------ Radial progress against a target (C91, DB-17) ------------------------------ */

/**
 * Radial progress chart (C91, card 040): a ring of progress towards a target, the share in the centre and the
 * amounts under it. Not a gauge: no needle, no red or green bands. Over the target the ring is full and the line
 * says by how much. With no target it shows the empty state and where to set one.
 */
export function TargetRing({ value, target, label = "Sales to date", period, onSetTarget }: { value: number; target: number | null; label?: string; period?: string; onSetTarget?: () => void }) {
  if (!target)
    return (
      <EmptyState
        icon={TargetIcon}
        title={`No sales target${period ? ` for ${period}` : ""}`}
        description="Set one in Settings, Sales targets, to see progress here."
        action={onSetTarget ? { label: "Set a target", onClick: onSetTarget } : undefined}
        className="py-6"
      />
    );
  const share = (value / target) * 100;
  const r = 52;
  const c = 2 * Math.PI * r;
  const filled = Math.min(share, 100) / 100;
  return (
    <div className="flex flex-col items-center gap-2 text-center">
      <svg viewBox="0 0 128 128" className="size-40" role="img" aria-label={`${label}: ${pct(share)} of the target, ${kes(value)} of ${kes(target)}`}>
        <circle cx="64" cy="64" r={r} fill="none" stroke="var(--color-chart-diverging-midpoint)" strokeWidth="12" />
        <circle cx="64" cy="64" r={r} fill="none" stroke="var(--color-chart-series-1)" strokeWidth="12" strokeDasharray={`${c * filled} ${c}`} strokeLinecap="butt" transform="rotate(-90 64 64)" />
        <text x="64" y="62" textAnchor="middle" className="fill-foreground text-[22px] font-semibold tabular-nums">
          {pct(share)}
        </text>
        <text x="64" y="80" textAnchor="middle" className="fill-muted-foreground text-[10px]">
          of target
        </text>
      </svg>
      <p className="text-xs text-muted-foreground tabular-nums">
        {label}: <span className="font-medium text-foreground">{kes(value)}</span> of {kes(target)}
        {period ? `, ${period}` : ""}
      </p>
      <p className="text-xs tabular-nums">{share >= 100 ? `Over target by ${kes(value - target)}` : `${kes(target - value)} to go`}</p>
    </div>
  );
}

/* ------------------------------ Radar chart (C92, RP-16) ------------------------------ */

export type RadarMeasure = { key: string; label: string; lowerIsBetter?: boolean; format: (v: number) => string };
export type RadarItem = { name: string; values: Record<string, number> };

/**
 * Radar chart (C92, card 040): branches compared over shared measures, each scaled to a share of the best branch so
 * every axis runs 0–100% (a measure where less is better uses lowest ÷ this). Four branches at most are drawn (four
 * series, F6); with more, the header picks which, the four highest on the first measure by default. The tooltip and
 * the Table view give the real figures, and the table lists every branch.
 */
export function RadarCompare({ label, measures, items }: { label: string; measures: RadarMeasure[]; items: RadarItem[] }) {
  const ranked = [...items].sort((a, b) => (b.values[measures[0].key] ?? 0) - (a.values[measures[0].key] ?? 0));
  const compact = React.useContext(CompactCharts);
  const [picked, setPicked] = React.useState<string[]>(() => ranked.slice(0, 4).map((i) => i.name));
  const [view, setView] = React.useState<"chart" | "table">("chart");
  const chosen = ranked.filter((i) => picked.includes(i.name)).slice(0, 4);
  const share = (m: RadarMeasure, v: number) => {
    const all = items.map((i) => i.values[m.key] ?? 0);
    if (m.lowerIsBetter) {
      const low = Math.min(...all);
      return v <= 0 ? 100 : (low / v) * 100;
    }
    const best = Math.max(...all);
    return best > 0 ? (v / best) * 100 : 0;
  };
  const keyOf = (n: string) => `b${items.findIndex((i) => i.name === n)}`;
  const data = measures.map((m) => ({ measure: m.label, ...Object.fromEntries(chosen.map((i) => [keyOf(i.name), Math.round(share(m, i.values[m.key] ?? 0) * 10) / 10])) }));
  const config = Object.fromEntries(chosen.map((i, n) => [keyOf(i.name), { label: i.name, color: VIZ_SERIES[n] }])) satisfies ChartConfig;
  const real = (measure: string, key: string) => {
    const m = measures.find((x) => x.label === measure);
    const it = items[Number(key.slice(1))];
    return m && it ? m.format(it.values[m.key] ?? 0) : "";
  };
  return (
    <div className="space-y-3">
      <div className="flex flex-wrap items-start justify-between gap-2">
        {items.length > 4 ? (
          <div className="space-y-1">
            <ToggleGroup variant="outline" size="sm" spacing={0} multiple aria-label={`${label}: branches drawn, four at most`} value={picked} onValueChange={(v: string[]) => v.length && v.length <= 4 && setPicked(v)} className="flex-wrap">
              {ranked.map((i) => (
                <ToggleGroupItem key={i.name} value={i.name}>
                  {i.name}
                </ToggleGroupItem>
              ))}
            </ToggleGroup>
            <p className="text-xs text-muted-foreground">Four branches at most are drawn; the table lists them all.</p>
          </div>
        ) : null}
        {compact ? null : <ViewSwitch value={view} onChange={setView} label={label} />}
      </div>
      {view === "chart" ? (
        <>
          <Legend items={chosen.map((i, n) => ({ label: i.name, color: VIZ_SERIES[n] }))} />
          <ChartContainer config={config} className="mx-auto aspect-square h-72 w-full max-w-md" initialDimension={{ width: 288, height: 288 }}>
            <RadarChart data={data} outerRadius="62%" margin={{ top: 8, right: 40, bottom: 8, left: 40 }}>
              <PolarGrid stroke="var(--color-chart-gridline)" />
              <PolarAngleAxis dataKey="measure" tick={{ fill: "var(--color-chart-axis-label)", fontSize: 11 }} />
              <PolarRadiusAxis angle={45} domain={[0, 100]} tickCount={5} tick={{ fill: "var(--color-chart-axis-label)", fontSize: 10 }} tickFormatter={(v: number) => `${v}%`} axisLine={false} />
              <ChartTooltip content={<ChartTooltipContent formatter={(v, n, item) => `${config[n as keyof typeof config]?.label ?? n}: ${real(String((item?.payload as { measure?: string })?.measure ?? ""), String(n))} (${pct(Number(v))} of the best)`} />} />
              {chosen.map((i) => (
                <Radar key={i.name} dataKey={keyOf(i.name)} stroke={`var(--color-${keyOf(i.name)})`} strokeWidth={2} fill={`var(--color-${keyOf(i.name)})`} fillOpacity={0.1} isAnimationActive={false} />
              ))}
            </RadarChart>
          </ChartContainer>
        </>
      ) : (
        <FigureTable spec={{ head: ["Branch", ...measures.map((m) => m.label)], rows: ranked.map((i) => [i.name, ...measures.map((m) => m.format(i.values[m.key] ?? 0))]) }} />
      )}
    </div>
  );
}

/* ------------------------------ Branch money chart (C93, DB-13) ------------------------------ */

export type BranchMoney = { branch: string; drawer: number; mpesa: number; card: number };
/** Money card rule: a negative balance reads "−KES 1,200.00", the minus before the currency. */
const signed = (v: number) => (v < 0 ? `−${kes(-v)}` : kes(v));
const MONEY_PARTS = [
  { key: "drawer", label: "Cash drawer" },
  { key: "mpesa", label: "M-Pesa till" },
  { key: "card", label: "Card terminal" },
] as const;

/**
 * Branch money chart (C93, card 040): one bar per branch split by cash drawer, M-Pesa till and card terminal.
 * A negative balance is drawn left of zero, outlined in the error colour, and explained under the chart (money card
 * rule: a negative stands out and says why). Banks are pharmacy-wide, so they are listed beside it, not drawn.
 * `explain` holds the reason for a negative, keyed "branch:part" ("Kilimani:drawer").
 */
export function BranchMoneyChart({ rows, explain = {} }: { rows: BranchMoney[]; explain?: Record<string, string> }) {
  const config = Object.fromEntries(MONEY_PARTS.map((p, i) => [p.key, { label: p.label, color: VIZ_SERIES[i] }])) satisfies ChartConfig;
  const negatives = rows.flatMap((r) => MONEY_PARTS.filter((p) => r[p.key] < 0).map((p) => ({ r, p })));
  const total = (r: BranchMoney) => r.drawer + r.mpesa + r.card;
  // The axis is set here: round steps up to the largest positive bar, and only as much room left of zero as the
  // largest negative needs, so a small negative never squeezes every other branch.
  const pos = Math.max(1, ...rows.map((r) => MONEY_PARTS.reduce((s, p) => s + Math.max(0, r[p.key]), 0)));
  const neg = Math.min(0, ...rows.map((r) => MONEY_PARTS.reduce((s, p) => s + Math.min(0, r[p.key]), 0)));
  const mag = 10 ** Math.floor(Math.log10(pos / 3));
  const step = [1, 2, 2.5, 5, 10].map((m) => m * mag).find((x) => x * 3 >= pos) ?? 10 * mag;
  const ticks = Array.from({ length: Math.ceil(pos / step) + 1 }, (_, i) => i * step);
  const domain: [number, number] = [neg < 0 ? neg * 1.4 : 0, ticks[ticks.length - 1]];
  return (
    <ChartPanel
      label="Cash and bank by branch"
      legend={<Legend items={MONEY_PARTS.map((p, i) => ({ label: p.label, color: VIZ_SERIES[i] }))} />}
      table={{
        head: ["Branch", ...MONEY_PARTS.map((p) => p.label), "Total"],
        rows: rows.map((r) => [r.branch, signed(r.drawer), signed(r.mpesa), signed(r.card), signed(total(r))]),
        foot: ["All branches", ...MONEY_PARTS.map((p) => signed(rows.reduce((s, r) => s + r[p.key], 0))), signed(rows.reduce((s, r) => s + total(r), 0))],
      }}
    >
      <ChartContainer config={config} className="aspect-auto w-full" style={{ height: `${Math.max(120, rows.length * 44 + 32)}px` }} initialDimension={{ width: 560, height: rows.length * 44 + 32 }}>
        <BarChart data={rows} layout="vertical" stackOffset="sign" margin={{ left: 0, right: 16 }} barCategoryGap="30%">
          <CartesianGrid horizontal={false} stroke="var(--color-chart-gridline)" />
          <YAxis dataKey="branch" type="category" {...AXIS} width={112} />
          <XAxis type="number" {...AXIS} tickFormatter={(v: number) => (Math.abs(v) >= 1000 ? `${Number((v / 1000).toFixed(1))}k` : String(v))} domain={domain} ticks={ticks} allowDataOverflow />
          <ReferenceLine x={0} stroke="var(--color-chart-axis-line)" />
          <ChartTooltip cursor={false} content={<ChartTooltipContent formatter={(v, n) => `${config[n as keyof typeof config]?.label ?? n}: ${signed(Number(v))}`} />} />
          {MONEY_PARTS.map((p) => (
            <Bar key={p.key} dataKey={p.key} stackId="money" fill={`var(--color-${p.key})`} stroke={GAP} strokeWidth={1} isAnimationActive={false}>
              {rows.map((r) => (
                <Cell key={r.branch} fill={r[p.key] < 0 ? "transparent" : `var(--color-${p.key})`} stroke={r[p.key] < 0 ? "var(--color-status-error-text)" : GAP} strokeWidth={r[p.key] < 0 ? 2 : 1} strokeDasharray={r[p.key] < 0 ? "3 2" : undefined} />
              ))}
            </Bar>
          ))}
        </BarChart>
      </ChartContainer>
      {negatives.length ? (
        <ul className="space-y-1 text-xs">
          {negatives.map(({ r, p }) => (
            <li key={`${r.branch}:${p.key}`} className="flex items-start gap-1.5">
              <CircleXIcon className="mt-0.5 size-3.5 shrink-0 text-status-error-text" aria-hidden />
              <span>
                <span className="font-medium text-status-error-text tabular-nums">
                  {r.branch} {p.label.toLowerCase()} {signed(r[p.key])}
                </span>
                {explain[`${r.branch}:${p.key}`] ? `: ${explain[`${r.branch}:${p.key}`]}` : ": check the drops and closes for this till."}
              </span>
            </li>
          ))}
        </ul>
      ) : null}
    </ChartPanel>
  );
}
