"use client";

/**
 * Dashboard (PRD 6.1), on the Medora design system and the Dashboard plan (DECISIONS.md, 26 Oct 2026). The design's
 * dashboard filters (applied together with Apply, DB-01), launcher tiles (DB-11; the desktop app's home), and the panel
 * grid (DB-14): each panel is a permission held on the role, fetched on its own from `GET /dashboard/panels/:id`, so
 * one failing never blanks the others. Figures come from the same API calculations as Reports and Accounts.
 *
 * The detailed sales charts and the full Stock Insights panel are in Reports, the activity log in Settings → Users and
 * roles. The AI stock summary stays here as its own panel (card 017, DB-15).
 */

import * as React from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { ArrowLeftRightIcon, BadgePercentIcon, BarChart3Icon, ShieldPlusIcon, BookOpenIcon, ClipboardListIcon, ClipboardPlusIcon, BarcodeIcon, PackageOpenIcon, PillIcon, ReceiptIcon, Trash2Icon, SettingsIcon, ShoppingCartIcon, TruckIcon, UserCogIcon, UsersIcon, WalletIcon, KeyRoundIcon, CalendarClockIcon, ReceiptTextIcon, TagIcon, SmartphoneIcon, FileTextIcon, HandCoinsIcon, IdCardIcon, BanknoteIcon } from "lucide-react";

import { MedoraPageHeader } from "@/components/medora/page-header";
import { DashboardFilters, type DashFilter } from "@/components/medora/reports-more";
import { DashboardGrid, type DashboardPanel } from "@/components/medora/dashboard-panels";
import { Launcher, type LauncherTile } from "@/components/medora/launcher";
import { AgeingChart, BranchMoneyChart, BranchSalesChart, CompactCharts, FinancialOverviewChart, MethodDonut, StockValueChart, type BranchMoney } from "@/components/medora/report-charts";
import { StatTile } from "@/components/medora/records/finance";
import { MoneyCard } from "@/components/medora/money-card";
import { PanelError } from "@/components/medora/errors";
import { PeriodTrend } from "@/components/medora/report-charts";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Skeleton } from "@/components/ui/skeleton";
import { InsightNarrative } from "@/components/medora/insights";
import { BranchesApi, DashboardApi, type DashboardPanelDef } from "@/lib/api";
import { addDays, formatDate, localToday } from "@/lib/format";
import { useMenuPermissions } from "@/lib/permissions";
import { cn } from "@/lib/utils";

/** Money card rule: a negative reads "−KES 1,200.00", the minus before the currency. */
const signed = (n: number) => (n < 0 ? `−${kes(-n)}` : kes(n));
const change = (now: number, before: number | undefined) => (before ? ((now - before) / Math.abs(before)) * 100 : undefined);
const kes = (n: number) => `KES ${Number(n || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
const apiError = (e: unknown): { status?: number; message: string } => {
  const r = (e as { response?: { status?: number; data?: { message?: string | string[] } } })?.response;
  const m = r?.data?.message;
  return { status: r?.status, message: (Array.isArray(m) ? m.join(" ") : m) || "Couldn't load this panel." };
};

/** DB-11: one tile per main menu the role may use (the desktop app's home, where there is no sidebar). */
const desktopTiles: (LauncherTile & { roles: string[] })[] = [
  { title: "POS", description: "Sell, take payment and print receipts", icon: ShoppingCartIcon, href: "/pos", roles: ["Admin", "Cashier", "Pharmacist"] },
  { title: "Open and close shift", description: "Open the register and close it with the Z-report", icon: KeyRoundIcon, href: "/shift", roles: ["Admin", "Cashier", "Pharmacist"] },
  { title: "Cashier balances", description: "The drawer and till balance, drops and past Z-reports", icon: WalletIcon, href: "/shifts", roles: ["Admin"] },
  { title: "Preorders", description: "Items ordered for customers, with deposits", icon: CalendarClockIcon, href: "/preorders", roles: ["Admin", "Cashier", "Pharmacist"] },
  { title: "Credit notes", description: "Credit a customer's account without a sale", icon: ReceiptTextIcon, href: "/credit-notes", roles: ["Admin", "Cashier", "Pharmacist"] },
  { title: "Products and stock", description: "Products, batches, stock and receiving", icon: PillIcon, href: "/inventory", roles: ["Admin", "Pharmacist", "Inventory Manager"] },
  { title: "Customers", description: "Customer records, accounts and health records", icon: UsersIcon, href: "/customers", roles: ["Admin", "Cashier", "Pharmacist"] },
  { title: "Prescriptions", description: "Prescriptions received and dispensed", icon: ClipboardPlusIcon, href: "/prescriptions", roles: ["Admin", "Cashier", "Pharmacist"] },
  { title: "Groups and schemes", description: "Customer groups, their prices and insurance schemes", icon: ShieldPlusIcon, href: "/customer-groups", roles: ["Admin"] },
  { title: "Purchase orders", description: "Order, approve and receive stock", icon: TruckIcon, href: "/purchase-orders", roles: ["Admin", "Inventory Manager"] },
  { title: "Branch transfers", description: "Send stock to another branch and receive it", icon: ArrowLeftRightIcon, href: "/branch-transfers", roles: ["Admin", "Pharmacist", "Inventory Manager"] },
  { title: "Stocktakes", description: "Count a location and apply the differences", icon: ClipboardListIcon, href: "/stocktakes", roles: ["Admin", "Pharmacist", "Inventory Manager"] },
  { title: "Waste", description: "Write off expired or damaged stock", icon: Trash2Icon, href: "/waste", roles: ["Admin", "Pharmacist", "Inventory Manager"] },
  { title: "Internal issues", description: "Move stock between locations or use it internally", icon: PackageOpenIcon, href: "/issues", roles: ["Admin", "Pharmacist", "Inventory Manager"] },
  { title: "Barcode labels", description: "Print shelf and barcode labels", icon: BarcodeIcon, href: "/labels", roles: ["Admin", "Pharmacist", "Inventory Manager"] },
  { title: "Price groups", description: "Special prices for insurance, corporate and staff", icon: BadgePercentIcon, href: "/price-groups", roles: ["Admin", "Pharmacist", "Inventory Manager"] },
  { title: "Suppliers", description: "Suppliers, price lists, returns and statements", icon: TruckIcon, href: "/suppliers", roles: ["Admin", "Inventory Manager"] },
  { title: "General ledger", description: "Accounts, journals and statements", icon: BookOpenIcon, href: "/accounts", roles: ["Admin"] },
  { title: "Expenses", description: "Record and report what was spent", icon: ReceiptIcon, href: "/expenses", roles: ["Admin"] },
  { title: "Receivables", description: "Invoices, quotations, scheme claims and payments", icon: FileTextIcon, href: "/receivables", roles: ["Admin"] },
  { title: "Payables", description: "Supplier bills, credit notes, payments and ageing", icon: HandCoinsIcon, href: "/payables", roles: ["Admin"] },
  { title: "Employees", description: "Staff records, leave and balances", icon: IdCardIcon, href: "/employees", roles: ["Admin"] },
  { title: "Payroll", description: "Payroll runs, payslips, loans and P9s", icon: BanknoteIcon, href: "/payroll", roles: ["Admin"] },
  { title: "Reports", description: "Sales, stock, customers, suppliers and more", icon: BarChart3Icon, href: "/reports", roles: ["Admin", "Pharmacist", "Inventory Manager"] },
  { title: "Users and roles", description: "Staff, roles, permissions and dashboard panels", icon: UserCogIcon, href: "/users", roles: ["Admin"] },
  { title: "Settings", description: "Pharmacy, branches, tax and devices", icon: SettingsIcon, href: "/settings", roles: ["Admin", "Cashier", "Pharmacist", "Inventory Manager"] },
  { title: "Promotions", description: "Discounts the till applies by itself", icon: TagIcon, href: "/promotions", roles: ["Admin"] },
  { title: "M-Pesa", description: "Keys, live or sandbox, and paybill payments", icon: SmartphoneIcon, href: "/integrations/mpesa", roles: ["Admin"] },
];

/** The applied period for a preset, in the server's local days. */
function rangeOf(f: DashFilter): { from: string; to: string } {
  const today = localToday();
  if (f.preset === "today") return { from: today, to: today };
  if (f.preset === "7d") return { from: addDays(today, -6), to: today };
  if (f.preset === "mtd") return { from: `${today.slice(0, 7)}-01`, to: today };
  return { from: f.from, to: f.to };
}

export default function DashboardPage() {
  const { showsInMenu, loaded, can } = useMenuPermissions();
  // Only a role with "All branches" on the dashboard may choose a branch; the API keeps everyone else on their own
  const allBranches = loaded && can("Dashboard/Overview", "All branches");
  const [user, setUser] = React.useState<{ name?: string; role?: string } | null>(null);
  const [isDesktop, setIsDesktop] = React.useState(false);
  const [panels, setPanels] = React.useState<DashboardPanelDef[] | null>(null);
  const [branches, setBranches] = React.useState<{ id: number; name: string }[]>([]);
  const [applied, setApplied] = React.useState<DashFilter>({ preset: "mtd", from: `${localToday().slice(0, 7)}-01`, to: localToday(), branch: "" });

  React.useEffect(() => {
    try {
      setUser(JSON.parse(localStorage.getItem("user") || "null"));
    } catch {
      setUser(null);
    }
    setIsDesktop(!!(window as { electronAPI?: unknown }).electronAPI || navigator.userAgent.includes("Electron"));
    DashboardApi.myPanels()
      .then((r) => setPanels(r.panels))
      .catch(() => setPanels([]));
    BranchesApi.getAll()
      .then((b: { id: number; name: string }[]) => setBranches(b.map((x) => ({ id: x.id, name: x.name }))))
      .catch(() => setBranches([]));
  }, []);

  const tiles = desktopTiles.filter((t) => showsInMenu(t.href, user?.role, t.roles));
  const branchId = applied.branch ? (branches.find((b) => b.name === applied.branch)?.id ?? null) : null;
  // One object per Apply, so panels reload only when the applied filters change (DB-01)
  const filters = React.useMemo(() => ({ ...rangeOf(applied), branchId }), [applied, branchId]);
  const describe = (f: DashFilter) => {
    const r = rangeOf(f);
    return `${r.from === r.to ? formatDate(r.from) : `${formatDate(r.from)} to ${formatDate(r.to)}`}, ${f.branch || (allBranches ? "all branches" : "your branch")}`;
  };
  // The AI summary sits at the top, apart from the grid: it reads the last 90 days, not the dates below (card 017),
  // but follows the branch filter (plan O)
  const summary = (panels ?? []).find((p) => p.id === "ai-summary");
  const shown: DashboardPanel[] = (panels ?? []).filter((p) => p.id !== "ai-summary").map((p) => ({ ...p }));

  return (
    <div className="medora mx-auto w-full max-w-7xl space-y-6">
      <MedoraPageHeader title={user?.name ? `Welcome, ${user.name.split(" ")[0]}` : "Dashboard"} description="An overview for the period and branch below." />
      {summary ? <Panel def={summary} filters={filters} /> : null}
      {isDesktop && loaded ? <Launcher tiles={tiles} heading="Main menu" /> : null}
      {panels === null ? (
        <Skeleton className="h-40 w-full" />
      ) : shown.length ? (
        <>
          <DashboardFilters applied={applied} branches={allBranches && branches.length > 1 ? branches.map((b) => b.name) : []} onApply={setApplied} describe={describe} />
          {/* Compact charts: no Chart | Table switch on the dashboard; each panel links to its report */}
          <CompactCharts.Provider value>
          <DashboardGrid
            panels={shown.filter((p) => !(p.id === "branch-sales" && branchId != null))}
            allowed={shown.map((p) => p.id)}
            role={user?.role ?? "your role"}
            render={(p) => <Panel def={p as DashboardPanelDef} filters={filters} />}
          />
          </CompactCharts.Provider>
        </>
      ) : summary ? null : (
        <DashboardGrid panels={[]} allowed={[]} role={user?.role ?? "your role"} render={() => null} />
      )}
      {!isDesktop && loaded ? <Launcher tiles={tiles} compact heading="Go to" /> : null}
    </div>
  );
}

/* ------------------------------------ Panels ------------------------------------ */

type Filters = { from: string; to: string; branchId: number | null };

function usePanel<T>(id: string, filters: Filters) {
  const [state, setState] = React.useState<{ data?: T; error?: string; loading: boolean }>({ loading: true });
  const [attempt, setAttempt] = React.useState(0);
  React.useEffect(() => {
    let live = true;
    setState({ loading: true });
    DashboardApi.panel<T>(id, filters)
      .then((data) => live && setState({ data, loading: false }))
      .catch((e) => live && setState({ error: apiError(e).message, loading: false }));
    return () => {
      live = false;
    };
  }, [id, filters, attempt]);
  return { ...state, retry: () => setAttempt((a) => a + 1) };
}

function Frame({ title, children, href, hrefLabel, asAt }: { title: string; children: React.ReactNode; href?: string; hrefLabel?: string; asAt?: string }) {
  return (
    <div className="flex h-full flex-col rounded-lg border border-[var(--color-border-panel)] bg-card p-4">
      <div className="mb-2 flex items-baseline justify-between gap-2">
        <h2 className="text-sm font-semibold">{title}</h2>
        {asAt ? <span className="text-xs text-muted-foreground">As at {formatDate(asAt)}</span> : null}
      </div>
      <div className="flex-1">{children}</div>
      {href ? (
        <Link href={href} className="mt-3 text-xs font-medium text-primary hover:underline">
          {hrefLabel ?? "Open the detail"}
        </Link>
      ) : null}
    </div>
  );
}

function Figure({ value, sub, tone }: { value: string; sub?: React.ReactNode; tone?: "bad" }) {
  return (
    <>
      <p className={cn("text-2xl font-semibold tabular-nums", tone === "bad" && "text-status-error-text")}>{value}</p>
      {sub ? <p className="mt-1 text-xs text-muted-foreground">{sub}</p> : null}
    </>
  );
}

function Panel({ def, filters }: { def: DashboardPanelDef; filters: Filters }) {
  const s = usePanel<Record<string, any>>(def.id, filters); // eslint-disable-line @typescript-eslint/no-explicit-any
  if (s.loading)
    return def.group === "Headline figures" ? (
      <StatTile label={def.title} value="" loading />
    ) : (
      <Frame title={def.title}>
        <Skeleton className="h-16 w-full" />
      </Frame>
    );
  if (s.error || !s.data)
    return (
      <Frame title={def.title}>
        <PanelError what={def.title.toLowerCase()} onRetry={s.retry} />
      </Frame>
    );
  return <PanelBody def={def} d={s.data} />;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function PanelBody({ def, d }: { def: DashboardPanelDef; d: Record<string, any> }) {
  const router = useRouter();
  const [list, setList] = React.useState<{ title: string; rows: { id: number; name: string; stock: number; daysOfStock: number | null }[] } | null>(null);
  // The stat card keeps one short line under the figure (02-data-display §4): where the figure comes from
  const source = d.source === "ledger" ? "From the books" : "From sales and expenses";
  switch (def.id) {
    case "revenue":
      return <StatTile label="Revenue" value={kes(d.gross)} delta={change(d.gross, d.previous?.gross)} hint={`${kes(d.net)} before VAT`} trend={d.trend} href="/reports" linkLabel="Sales reports" />;
    case "transactions":
      return <StatTile label="Transactions" value={Number(d.receipts).toLocaleString("en-US")} delta={change(d.receipts, d.previous?.receipts)} hint={d.receipts ? `Average sale ${kes(d.gross / d.receipts)}` : "No sales in this period"} trend={d.trend} href="/reports" linkLabel="Sales reports" />;
    case "gross-profit":
      return <StatTile label="Gross profit" value={kes(d.grossProfit)} delta={change(d.grossProfit, d.previous?.grossProfit)} hint={d.revenueExVat ? `Margin ${((d.grossProfit / d.revenueExVat) * 100).toFixed(1)}%. ${source}` : source} href={d.source === "ledger" ? "/accounts" : "/reports"} linkLabel={d.source === "ledger" ? "General ledger" : "Profit and loss"} />;
    case "expenses":
      return <StatTile label="Expenses" value={kes(d.expenses)} delta={change(d.expenses, d.previous?.expenses)} deltaGoodWhen="down" hint={source} href="/expenses" linkLabel="Expenses" />;
    case "net-profit":
      return <StatTile label="Net profit" value={kes(d.netProfit)} delta={change(d.netProfit, d.previous?.netProfit)} hint={d.netProfit < 0 ? "Expenses were more than gross profit" : source} href={d.source === "ledger" ? "/accounts" : "/reports"} linkLabel={d.source === "ledger" ? "General ledger" : "Profit and loss"} />;
    case "financial-overview":
      return (
        <Frame title="Financial overview" href="/reports" hrefLabel="General report">
          <FinancialOverviewChart months={d.months?.length > 1 ? d.months : [{ label: `${formatDate(d.period.from, { short: true })} to ${formatDate(d.period.to, { short: true })}`, sales: d.sales, purchases: d.purchases, expenses: d.expenses }]} />
          {d.note ? <p className="mt-2 text-xs text-muted-foreground">{d.note}</p> : null}
        </Frame>
      );
    case "money-summary":
      // Card 040 (Gate 3, 29 Oct 2026): owed to us and we owe by age, then Tax, Stock and Costs as figures
      return (
        <MoneyCard
          title="Money summary"
          asAt={formatDate(d.asAt)}
          chart={
            <AgeingChart
              rows={[
                { label: "Owed to us", buckets: d.owedToUs.buckets ?? [d.owedToUs.amount, 0, 0, 0] },
                { label: "We owe", buckets: d.weOwe.buckets ?? [d.weOwe.amount, 0, 0, 0] },
              ]}
            />
          }
          groups={[
            { name: "Tax", rows: [{ label: "VAT charged in the period", value: kes(d.tax.outputVat), href: d.tax.href }] },
            { name: "Stock", rows: [{ label: "Stock at cost", value: kes(d.stock.costValue), href: d.stock.href }] },
            { name: "Costs", rows: [{ label: "Expenses in the period", value: kes(d.costs.expenses), href: d.costs.href }] },
          ]}
          footer={
            <div className="mt-3 flex flex-wrap gap-4 border-t border-border pt-2 text-xs font-medium">
              <Link href={d.owedToUs.href} className="text-primary hover:underline">Customers who owe us</Link>
              <Link href={d.weOwe.href} className="text-primary hover:underline">Suppliers we owe</Link>
            </div>
          }
        />
      );
    case "cash-bank": {
      if (!d.started)
        return (
          <Frame title="Cash and bank" href="/accounts" hrefLabel="General ledger">
            <p className="text-sm text-muted-foreground">{d.reason}</p>
          </Frame>
        );
      // Card 040 (Gate 3, 29 Oct 2026): each branch's drawer, M-Pesa till and card terminal drawn; banks are
      // pharmacy-wide, so listed beside the chart
      type Account = { id: number; name: string; branch: string; balance: number; role?: string };
      const part = { "Cash drawer": "drawer", "M-Pesa till": "mpesa", "Card terminal": "card" } as const;
      const branches = new Map<string, BranchMoney>();
      const banks: Account[] = [];
      const safes: Account[] = [];
      for (const a of d.accounts as Account[]) {
        const key = part[a.role as keyof typeof part];
        if (a.role === "Safe") {
          safes.push(a);
          continue;
        }
        if (!key || a.branch === "All branches") {
          banks.push(a);
          continue;
        }
        const row = branches.get(a.branch) ?? { branch: a.branch, drawer: 0, mpesa: 0, card: 0 };
        row[key] += a.balance;
        branches.set(a.branch, row);
      }
      return (
        <MoneyCard
          title="Cash and bank"
          asAt={formatDate(d.asAt)}
          chart={branches.size ? <BranchMoneyChart rows={[...branches.values()]} /> : undefined}
          groups={[
            ...(safes.length ? [{ name: "Safes", rows: safes.map((a) => ({ label: `${a.name}, ${a.branch}`, value: signed(a.balance), negative: a.balance < 0 })) }] : []),
            ...(banks.length ? [{ name: "Banks", rows: banks.map((a) => ({ label: a.name, value: signed(a.balance), negative: a.balance < 0 })) }] : []),
          ]}
          footer={
            <div className="mt-3 flex items-baseline justify-between border-t border-border pt-2 text-sm font-semibold">
              <Link href="/accounts" className="font-medium text-primary hover:underline">General ledger</Link>
              <span className={cn("tabular-nums", d.total < 0 && "text-status-error-text")}>Total {signed(d.total)}</span>
            </div>
          }
        />
      );
    }
    case "payment-methods":
      return <Frame title="Sales by payment method" href="/reports">{d.rows.length ? <MethodDonut rows={d.rows} caption="Total sales" /> : <p className="text-sm text-muted-foreground">No sales in this period.</p>}</Frame>;
    case "branch-sales":
      if (d.hidden) return null;
      return <Frame title="Sales by branch" href="/reports">{d.rows.length ? <BranchSalesChart rows={d.rows} compact /> : <p className="text-sm text-muted-foreground">No sales in this period.</p>}</Frame>;
    case "sales-trend":
      return (
        <Frame title="Sales trend" href="/reports" hrefLabel="Sales summary in Reports">
          <PeriodTrend points={d.points.map((p: { date: string; gross: number; previous?: number }) => ({ label: formatDate(p.date, { short: true }), current: p.gross, previous: p.previous ?? 0 }))} currentLabel="This period" previousLabel="Period before" />
        </Frame>
      );
    case "recent-activity":
      return (
        <Frame title="Recent activity" href="/users" hrefLabel="The full activity log">
          <ul className="space-y-1.5 text-sm">
            {d.rows.map((r: { id: number; at: string; who: string; action: string; details: string }) => (
              <li key={r.id}>
                <span className="font-medium">{r.who}</span> <span className="text-muted-foreground">{r.details || r.action.toLowerCase().replace(/_/g, " ")}</span>
                <span className="block text-xs text-muted-foreground">{new Date(r.at).toLocaleString("en-GB", { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" })}</span>
              </li>
            ))}
            {!d.rows.length ? <li className="text-muted-foreground">Nothing yet.</li> : null}
          </ul>
        </Frame>
      );
    case "stock-alerts": {
      // Count with status (02-data-display §4): the count, its badge from the status map, and the list behind it;
      // a zero is a figure with a plain line, not an alert
      const items: { key: keyof typeof d.lists; label: string; status: string; rule: string; none: string }[] = [
        { key: "reorder", label: "Need reordering", status: "Low stock", rule: "Under 30 days of stock", none: "Nothing needs reordering" },
        { key: "outOfStock", label: "Out of stock but selling", status: "Out of stock", rule: "Zero stock, sold in the last 90 days", none: "Nothing selling is out of stock" },
        { key: "overstocked", label: "Overstocked", status: "Overstocked", rule: "Over a year of stock", none: "Nothing overstocked" },
        { key: "expiring", label: "Expiring before they sell", status: "Expiring", rule: "Batches at risk", none: "Nothing expiring before it sells" },
      ];
      return (
        <div className="space-y-2">
          <div className="flex items-baseline justify-between gap-2">
            <h2 className="text-sm font-semibold">Stock alerts</h2>
            <Link href="/reports" className="text-xs font-medium text-primary hover:underline">
              Stock insights in Reports
            </Link>
          </div>
          <div className="grid auto-rows-fr gap-6 sm:grid-cols-2 xl:grid-cols-4">
            {items.map((it) => {
              const n = Number(d.counts[it.key] ?? 0);
              return (
                <StatTile
                  key={String(it.key)}
                  label={it.label}
                  value={n.toLocaleString("en-US")}
                  status={n ? it.status : undefined}
                  hint={n ? (it.key === "overstocked" && d.cashInOverstock ? `${kes(d.cashInOverstock)} tied up` : it.rule) : it.none}
                  onSelect={n ? () => setList({ title: it.label, rows: d.lists[it.key] }) : undefined}
                  linkLabel={`View the ${n === 1 ? "item" : `${n} items`}`}
                />
              );
            })}
          </div>
          <Dialog open={!!list} onOpenChange={(o) => !o && setList(null)}>
            <DialogContent>
              <DialogHeader>
                <DialogTitle>
                  {list?.title}: {list?.rows.length}
                </DialogTitle>
                <DialogDescription>From the same calculation as Stock insights, over the last 90 days&apos; sales.</DialogDescription>
              </DialogHeader>
              <ul className="max-h-80 divide-y divide-border overflow-y-auto text-sm">
                {list?.rows.map((r) => (
                  <li key={r.id} className="flex justify-between gap-3 py-1.5">
                    <span>{r.name}</span>
                    <span className="tabular-nums text-muted-foreground">
                      {r.stock} in stock{r.daysOfStock != null ? `, about ${r.daysOfStock} days` : ""}
                    </span>
                  </li>
                ))}
              </ul>
              <Button variant="outline" onClick={() => router.push("/inventory")}>
                Open Products and stock
              </Button>
            </DialogContent>
          </Dialog>
        </div>
      );
    }
    case "ai-summary":
      // Card 017 / DB-15: hides itself when no model is available, offline, or the role can't see cost
      if (!d.available || !d.narrative) return null;
      return (
        <Frame title="Stock recommendation" href="/reports" hrefLabel="Stock insights in Reports">
          <InsightNarrative narrative={{ state: "ready", text: d.narrative, at: d.generatedAt ?? new Date().toISOString() }} />
        </Frame>
      );
    case "stock-value":
      return (
        <Frame title="Stock value" asAt={d.asAt} href="/reports" hrefLabel="Stock report">
          <Figure value={kes(d.costValue)} sub={<>At cost, from batches. {kes(d.retailValue)} at selling prices. {d.note}</>} />
          {d.branches?.length > 1 ? <StockValueChart rows={d.branches} /> : null}
        </Frame>
      );
  }
  return null;
}
