"use client";

/**
 * Stocktakes (PRD 3.2: IN-12, IN-13), on the Medora design system and the stocktakes plan (DECISIONS.md, 26 Oct 2026).
 * The design's stock-take count grid (`EditableGrid` as in grid-demo.tsx: System, Counted, Variance, Value at cost,
 * Note, CSV of barcode and count) and its variance report (`StocktakeVariance`), on the Stocktakes API.
 *
 * One location per stocktake (Q1). Counts are a draft; applying compares them with the system quantity at that moment
 * (Q2), so the grid flags lines whose system quantity moved since the count started. Applying needs Approve (Q5). A
 * stocktake opens at `?id=` so the static export needs no list of ids.
 */

import * as React from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { CheckIcon, ChevronDownIcon, ClipboardListIcon, PlusIcon, SearchIcon, XIcon } from "lucide-react";

import { PanelError } from "@/components/medora/errors";
import { MedoraPageHeader } from "@/components/medora/page-header";
import { RecordDrawer } from "@/components/medora/drawer";
import { ConfirmDialog } from "@/components/medora/confirm-dialog";
import { EditableGrid, gridErrors, type GridColumn, type GridRow } from "@/components/medora/editable-grid";
import { StocktakeVariance } from "@/components/medora/stocktake-variance";
import { useUnsavedChangesGuard } from "@/components/medora/unsaved-changes";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";
import { downloadText } from "@/components/medora/file-upload";
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select";
import { Spinner } from "@/components/ui/spinner";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { toast } from "@/components/ui/toast";
import { InventoryApi, SetupListsApi, StocktakesApi, type StocktakeDetail, type StocktakeListRow } from "@/lib/api";
import { formatDate, formatKES } from "@/lib/format";
import { useMenuPermissions } from "@/lib/permissions";
import { cn } from "@/lib/utils";
import { Loading } from "@/components/medora/loading";

const PAGE = "Inventory/Stocktakes";
/** A difference this large needs a note (the design's grid; the API checks it again when applying). */
const NOTE_FROM = 20;
const apiError = (e: unknown, fallback: string): string => {
  const m = (e as { response?: { data?: { message?: string | string[] } } })?.response?.data?.message;
  return Array.isArray(m) ? m.join(" ") : m || fallback;
};
const pad = (n: number) => String(n).padStart(2, "0");
const day = (iso: string | null | undefined) => {
  if (!iso) return "";
  const d = new Date(iso);
  return formatDate(`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`, { short: true });
};
const at = (iso: string | null | undefined) => (iso ? `${day(iso)}, ${pad(new Date(iso).getHours())}:${pad(new Date(iso).getMinutes())}` : "");
const kes = (n: number) => formatKES(n, { decimals: true });
const isWhole = (v: string) => /^\d+$/.test(v.trim());
const csvCell = (v: string | number) => (/[",\n]/.test(String(v)) ? `"${String(v).replace(/"/g, '""')}"` : String(v));
/** Data table spec 1.1: 50 a page by default. */
const PAGE_SIZE = 50;
type Status = "" | "Draft" | "Applied" | "Cancelled";
type ListQuery = { search: string; status: Status; branchId: number | null; page: number };
const asked = (q: ListQuery, page: number, pageSize: number) => ({ search: q.search.trim() || undefined, status: q.status || undefined, branchId: q.branchId ?? undefined, page, pageSize });

export default function StocktakesPage() {
  return (
    <React.Suspense fallback={<Loading what="stocktakes" columns={6} rows={5} numeric={[3, 4]} />}>
      <Stocktakes />
    </React.Suspense>
  );
}

function Stocktakes() {
  const router = useRouter();
  const params = useSearchParams();
  const openId = Number(params.get("id")) || null;
  const { can, loaded } = useMenuPermissions();
  const may = (a: string) => loaded && can(PAGE, a);
  const [data, setData] = React.useState<{ total: number; rows: StocktakeListRow[] } | null>(null);
  const [failed, setFailed] = React.useState(false); // R12 (card 037): a failed load says so, with Try again
  // Search, filters and paging go to the API (data table spec 1.1); changing a filter returns to page 1
  const [q, setQ] = React.useState<ListQuery>({ search: "", status: "", branchId: null, page: 1 });
  const [branches, setBranches] = React.useState<{ id: number; name: string }[]>([]);
  const [exporting, setExporting] = React.useState(false);
  const [starting, setStarting] = React.useState(false);
  const [version, setVersion] = React.useState(0);
  const canRead = may("Read");
  const allBranches = may("All branches");
  const filtered = Boolean(q.search.trim() || q.status || q.branchId);

  React.useEffect(() => {
    if (allBranches) InventoryApi.status().then((st) => setBranches(st.branches.map((b) => ({ id: b.id, name: b.name })))).catch(() => setBranches([]));
  }, [allBranches]);

  React.useEffect(() => {
    if (!canRead || openId) return;
    let live = true;
    const t = setTimeout(() => {
    StocktakesApi.list(asked(q, q.page, PAGE_SIZE))
      .then((r) => {
        if (!live) return;
        setData(r);
        setFailed(false);
      })
      .catch((e) => {
        if (!live) return;
        setData({ total: 0, rows: [] });
        setFailed(true);
        toast.add({ type: "error", title: "Couldn't load stocktakes", description: apiError(e, "Try again.") });
      });
    }, 200);
    return () => {
      live = false;
      clearTimeout(t);
    };
  }, [canRead, openId, version, q]);

  // Export (data table spec 1.8): every stocktake the search and filters match, not only this page
  const exportCsv = async () => {
    setExporting(true);
    try {
      const all: StocktakeListRow[] = [];
      for (let page = 1; ; page++) {
        const r = await StocktakesApi.list(asked(q, page, 200));
        all.push(...r.rows);
        if (all.length >= r.total || !r.rows.length) break;
      }
      const head = ["Stocktake", "Branch", "Location", "Products", "Status", "Started", "Started by", "Applied", "Applied by", "Counted", "Lines", "Lost at cost", "Found at cost"];
      const body = all.map((r) => [r.number, r.branch, r.location, r.category ?? "All products", r.status === "Draft" ? "Counting" : r.status, at(r.startedAt), r.startedBy, at(r.appliedAt), r.appliedBy ?? "", r.counted, r.lines, r.status === "Applied" ? (r.lossCost ?? 0).toFixed(2) : "", r.status === "Applied" ? (r.gainCost ?? 0).toFixed(2) : ""]);
      downloadText("stocktakes.csv", [head, ...body].map((row) => row.map(csvCell).join(",")).join("\n"));
    } catch (e) {
      toast.add({ type: "error", title: "Export failed", description: apiError(e, "Try again.") });
    } finally {
      setExporting(false);
    }
  };
  const pages = data ? Math.max(1, Math.ceil(data.total / PAGE_SIZE)) : 1;

  if (loaded && !canRead) return <div className="medora mx-auto w-full max-w-6xl py-10 text-center text-sm text-muted-foreground">Your role can&apos;t see stocktakes.</div>;
  if (openId) return <StocktakeRecord id={openId} may={may} onBack={() => { router.push("/stocktakes"); setVersion((v) => v + 1); }} />;

  return (
    <div className="medora mx-auto w-full max-w-6xl space-y-4">
      <MedoraPageHeader
        title="Stocktakes"
        description="Count one location, then apply the differences."
        primary={may("Create") ? { label: "Start a stocktake", icon: PlusIcon, onSelect: () => setStarting(true) } : undefined}
      />
      {/* One container (data table spec §1): search, filters, table and pager share the white surface */}
      <div className="overflow-hidden rounded-md border border-[var(--color-border-panel)] bg-card">
        <div className="flex flex-wrap items-center gap-2 border-b border-[var(--color-border-subtle)] p-3">
          <InputGroup className="max-w-xs">
            <InputGroupAddon>
              <SearchIcon />
            </InputGroupAddon>
            <InputGroupInput placeholder="Stocktake number" value={q.search} onChange={(e) => setQ((x) => ({ ...x, search: e.target.value, page: 1 }))} aria-label="Search stocktakes" />
          </InputGroup>
          <NativeSelect aria-label="Status" value={q.status} onChange={(e) => setQ((x) => ({ ...x, status: e.target.value as Status, page: 1 }))}>
            <NativeSelectOption value="">All statuses</NativeSelectOption>
            <NativeSelectOption value="Draft">Counting</NativeSelectOption>
            <NativeSelectOption value="Applied">Applied</NativeSelectOption>
            <NativeSelectOption value="Cancelled">Cancelled</NativeSelectOption>
          </NativeSelect>
          {allBranches && branches.length > 1 ? (
            <NativeSelect aria-label="Branch" value={String(q.branchId ?? "")} onChange={(e) => setQ((x) => ({ ...x, branchId: Number(e.target.value) || null, page: 1 }))}>
              <NativeSelectOption value="">All branches</NativeSelectOption>
              {branches.map((b) => (
                <NativeSelectOption key={b.id} value={String(b.id)}>
                  {b.name}
                </NativeSelectOption>
              ))}
            </NativeSelect>
          ) : null}
          {may("Export") ? (
            <DropdownMenu>
              <DropdownMenuTrigger render={<Button size="sm" variant="outline" className="ml-auto" disabled={!data?.total || exporting} />}>
                {exporting ? <Spinner /> : null}
                {exporting ? "Exporting…" : "Export"}
                <ChevronDownIcon data-icon="inline-end" />
              </DropdownMenuTrigger>
              <DropdownMenuContent align="end">
                <DropdownMenuItem onClick={() => void exportCsv()}>CSV</DropdownMenuItem>
              </DropdownMenuContent>
            </DropdownMenu>
          ) : null}
        </div>
        {failed ? (
          <PanelError what="stocktakes" onRetry={() => setVersion((v) => v + 1)} />
        ) : !data ? (
          <Loading what="stocktakes" columns={6} rows={5} numeric={[3, 4, 5]} bare />
        ) : data.rows.length ? (
          <div className="overflow-x-auto">
            <Table>
              <TableHeader>
                <TableRow>
                  <TableHead>Stocktake</TableHead>
                  <TableHead>Location</TableHead>
                  <TableHead>Status</TableHead>
                  <TableHead className="text-right">Counted</TableHead>
                  <TableHead className="text-right">Lost at cost</TableHead>
                  <TableHead className="text-right">Found at cost</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
              {data.rows.map((r) => (
                <TableRow key={r.id} className="cursor-pointer" onClick={() => router.push(`/stocktakes?id=${r.id}`)}>
                  <TableCell>
                    <span className="font-mono font-medium">{r.number}</span>
                    <span className="block text-xs text-muted-foreground">
                      Started {at(r.startedAt)} by {r.startedBy || "—"}
                    </span>
                  </TableCell>
                  <TableCell>
                    {r.branch}, {r.location}
                    <span className="block text-xs text-muted-foreground">{r.category ?? "All products"}</span>
                  </TableCell>
                  <TableCell>
                    <StatusBadge status={r.status} />
                    {r.appliedAt ? <span className="block text-xs text-muted-foreground">{at(r.appliedAt)} by {r.appliedBy}</span> : null}
                  </TableCell>
                  <TableCell className="text-right tabular-nums">
                    {r.counted.toLocaleString("en-US")} of {r.lines.toLocaleString("en-US")}
                  </TableCell>
                  <TableCell className="text-right tabular-nums">{r.status === "Applied" && r.lossCost != null ? `−${kes(r.lossCost)}` : "–"}</TableCell>
                  <TableCell className="text-right tabular-nums">{r.status === "Applied" && r.gainCost != null ? `+${kes(r.gainCost)}` : "–"}</TableCell>
                </TableRow>
              ))}
              </TableBody>
            </Table>
          </div>
        ) : (
          <p className="py-12 text-center text-sm text-muted-foreground">{filtered ? "No stocktakes match. Try other filters." : "No stocktakes yet."}</p>
        )}
        <div className="flex items-center justify-between border-t border-[var(--color-border-subtle)] p-3 text-xs text-muted-foreground">
          <span role="status">{data?.total ? `${(q.page - 1) * PAGE_SIZE + 1}–${Math.min(data.total, q.page * PAGE_SIZE)} of ${data.total.toLocaleString("en-US")}` : ""}</span>
          <nav aria-label="Pagination" className="flex items-center gap-1">
            <Button size="sm" variant="outline" aria-label="Previous page" disabled={q.page <= 1} onClick={() => setQ((x) => ({ ...x, page: x.page - 1 }))}>
              Previous
            </Button>
            <span className="px-2 tabular-nums" aria-current="page">
              Page {q.page} of {pages}
            </span>
            <Button size="sm" variant="outline" aria-label="Next page" disabled={q.page >= pages} onClick={() => setQ((x) => ({ ...x, page: x.page + 1 }))}>
              Next
            </Button>
          </nav>
        </div>
      </div>
      {starting ? <StartDrawer onClose={() => setStarting(false)} onStarted={(id) => router.push(`/stocktakes?id=${id}`)} /> : null}
    </div>
  );
}

function StatusBadge({ status }: { status: string }) {
  return <Badge variant={status === "Applied" ? "default" : status === "Draft" ? "secondary" : "outline"}>{status === "Draft" ? "Counting" : status}</Badge>;
}

/* ------------------------------------- Start ------------------------------------- */

function StartDrawer({ onClose, onStarted }: { onClose: () => void; onStarted: (id: number) => void }) {
  const [locations, setLocations] = React.useState<{ id: number; label: string }[] | null>(null);
  const [categories, setCategories] = React.useState<string[]>([]);
  const [locationId, setLocationId] = React.useState<number | null>(null);
  const [category, setCategory] = React.useState("");
  const [note, setNote] = React.useState("");
  const [saving, setSaving] = React.useState(false);

  React.useEffect(() => {
    // The locations the till uses (Q1); stock in transit is counted when it's received
    InventoryApi.status()
      .then((st) => {
        const list = st.branches.flatMap((b) => b.locations.filter((l) => l.showInPos && l.active).map((l) => ({ id: l.id, label: `${b.name}, ${l.name}` })));
        setLocations(list);
        setLocationId(list[0]?.id ?? null);
      })
      .catch(() => setLocations([]));
    SetupListsApi.list("categories")
      .then((l: { name: string; active: boolean }[]) => setCategories((l ?? []).filter((c) => c.active).map((c) => c.name)))
      .catch(() => setCategories([]));
  }, []);

  const save = async () => {
    if (locationId == null) return;
    setSaving(true);
    try {
      const s = await StocktakesApi.start({ locationId, category: category || undefined, note: note.trim() || undefined });
      toast.add({ title: `${s.number} started`, description: `${s.lines.length} products to count at ${s.location}.` });
      onStarted(s.id);
    } catch (e) {
      toast.add({ type: "error", title: "Not started", description: apiError(e, "Try again.") });
      setSaving(false);
    }
  };

  return (
    <RecordDrawer
      open
      onOpenChange={(o) => !o && onClose()}
      variant="edit"
      saving={saving}
      title="Start a stocktake"
      subtitle="One location at a time. Lines you don't count are left as they are."
      footer={
        <>
          <Button variant="outline" disabled={saving} onClick={onClose}>
            Cancel
          </Button>
          <Button disabled={saving || locationId == null} onClick={() => void save()}>
            {saving ? <Spinner /> : null}
            Start counting
          </Button>
        </>
      }
    >
      {locations === null ? (
        <Loading what="the stocktake form" shape="form" rows={3} />
      ) : (
        <div className="space-y-4">
          <Field>
            <FieldLabel htmlFor="stk-loc">Location</FieldLabel>
            <NativeSelect id="stk-loc" value={locationId ?? ""} onChange={(e) => setLocationId(Number(e.target.value) || null)}>
              {locations.map((l) => (
                <NativeSelectOption key={l.id} value={l.id}>
                  {l.label}
                </NativeSelectOption>
              ))}
            </NativeSelect>
          </Field>
          <Field>
            <FieldLabel htmlFor="stk-cat">Products</FieldLabel>
            <NativeSelect id="stk-cat" value={category} onChange={(e) => setCategory(e.target.value)}>
              <NativeSelectOption value="">All products</NativeSelectOption>
              {categories.map((c) => (
                <NativeSelectOption key={c} value={c}>
                  {c}
                </NativeSelectOption>
              ))}
            </NativeSelect>
          </Field>
          <Field>
            <FieldLabel htmlFor="stk-note">Note (optional)</FieldLabel>
            <Input id="stk-note" value={note} maxLength={255} onChange={(e) => setNote(e.target.value)} placeholder="e.g. Month-end count" />
            <FieldDescription>Tills keep selling while you count. The count is compared with the stock at the moment it&apos;s applied.</FieldDescription>
          </Field>
        </div>
      )}
    </RecordDrawer>
  );
}

/* ------------------------------------ One stocktake ------------------------------------ */

function StocktakeRecord({ id, may, onBack }: { id: number; may: (a: string) => boolean; onBack: () => void }) {
  const [s, setS] = React.useState<StocktakeDetail | null>(null);
  const [failed, setFailed] = React.useState(false);
  const [draft, setDraft] = React.useState<Record<string, Record<string, string>>>({});
  const [saving, setSaving] = React.useState(false);
  const [applying, setApplying] = React.useState(false);
  const [cancelling, setCancelling] = React.useState(false);

  const load = React.useCallback(async () => {
    try {
      setS(await StocktakesApi.get(id));
    } catch (e) {
      setFailed(true);
      toast.add({ type: "error", title: "Couldn't load the stocktake", description: apiError(e, "Try again.") });
    }
  }, [id]);
  React.useEffect(() => void load(), [load]);

  const dirty = Object.keys(draft).length > 0;
  const saveDraft = async (): Promise<StocktakeDetail | null> => {
    if (!s || !dirty) return s;
    setSaving(true);
    try {
      const lines = Object.entries(draft).map(([lineId, v]) => {
        const l = s.lines.find((x) => x.id === Number(lineId))!;
        const counted = v.counted ?? (l.counted == null ? "" : String(l.counted));
        return { lineId: Number(lineId), counted: counted.trim() === "" ? null : Number(counted), note: v.note ?? l.note };
      });
      const next = await StocktakesApi.saveCounts(s.id, lines);
      setS(next);
      setDraft({});
      toast.add({ title: "Counts saved", description: "Nothing is applied to stock until the stocktake is applied." });
      return next;
    } catch (e) {
      toast.add({ type: "error", title: "Counts not saved", description: apiError(e, "Try again.") });
      return null;
    } finally {
      setSaving(false);
    }
  };
  const { dialog } = useUnsavedChangesGuard({ dirty, changes: ["counts"], save: async () => !!(await saveDraft()), discard: () => setDraft({}) });

  if (failed && !s) {
    return (
      <div className="medora mx-auto w-full max-w-6xl py-10 text-center text-sm text-muted-foreground">
        This stocktake didn&apos;t load.{" "}
        <Button size="sm" variant="outline" onClick={onBack}>
          Back to stocktakes
        </Button>
      </div>
    );
  }
  if (!s) {
    return (
      <Loading what="the stocktake" columns={6} rows={8} toolbar={1} numeric={[2, 3, 4, 5]} />
    );
  }

  const isDraft = s.status === "Draft";
  const rows: GridRow[] = s.lines.map((l) => ({
    id: String(l.id),
    header: l.name,
    sub: [l.code, l.barcode, l.moved ? `system was ${l.startSystem} when counting started` : null].filter(Boolean).join(" · "),
    values: { system: String(l.system), counted: l.counted == null ? "" : String(l.counted), note: l.note ?? "", unit: l.unit, cost: String(l.unitCost ?? 0), moved: l.moved ? "1" : "" },
  }));
  const variance = (r: Record<string, string>) => (isWhole(r.counted ?? "") ? Number(r.counted) - Number(r.system) : null);
  const columns: GridColumn[] = [
    {
      key: "system",
      header: "System",
      kind: "read",
      align: "right",
      render: (r) => (
        <span className={cn(r.moved && "font-semibold text-status-warning-text")} title={r.moved ? "Changed since counting started: recount if unsure" : undefined}>
          {Number(r.system).toLocaleString("en-US")} {r.unit}
        </span>
      ),
    },
    {
      key: "counted",
      header: "Counted",
      kind: "number",
      width: "9rem",
      placeholder: "Count",
      unit: (row) => row.values.unit,
      validate: (v) => (v === "" ? undefined : !isWhole(v) ? "Enter a whole number of units, 0 or more." : Number(v) > 100000 ? "That is more than 100,000. Check the count." : undefined),
    },
    {
      key: "variance",
      header: "Variance",
      kind: "derived",
      render: (r) => {
        const d = variance(r);
        if (d === null) return <span className="text-muted-foreground">–</span>;
        if (d === 0) return <span className="text-muted-foreground">No change</span>;
        return (
          <span className={cn("font-medium", d < 0 ? "text-status-warning-text" : "text-status-info-text")}>
            {d > 0 ? "+" : "−"}
            {Math.abs(d).toLocaleString("en-US")}
            <span className="ml-1 font-normal">{d > 0 ? "over" : "short"}</span>
          </span>
        );
      },
    },
    {
      key: "value",
      header: "Value at cost",
      kind: "derived",
      render: (r) => {
        const d = variance(r);
        return d === null || d === 0 ? <span className="text-muted-foreground">–</span> : `${d < 0 ? "−" : "+"}${kes(Math.abs(d) * Number(r.cost))}`;
      },
    },
    { key: "note", header: "Note", kind: "text", width: "12rem", placeholder: "Optional", validate: (v, row) => (variance(row) !== null && Math.abs(variance(row)!) >= NOTE_FROM && !v.trim() ? `A difference of ${NOTE_FROM} or more needs a note.` : undefined) },
  ];
  const value = (lineId: string, key: string) => draft[lineId]?.[key] ?? rows.find((r) => r.id === lineId)!.values[key];
  const countedCount = rows.filter((r) => value(r.id, "counted") !== "").length;
  const moved = s.lines.filter((l) => l.moved && l.counted != null).length;
  const errors = gridErrors(columns, rows, draft, true);

  const onDraftChange = (rowId: string, key: string, v: string | undefined) =>
    setDraft((d) => {
      const row = { ...(d[rowId] ?? {}) };
      if (v === undefined) delete row[key];
      else row[key] = v;
      const next = { ...d, [rowId]: row };
      if (!Object.keys(row).length) delete next[rowId];
      return next;
    });

  return (
    <div className="medora mx-auto w-full max-w-6xl space-y-4">
      <MedoraPageHeader
        title={s.number}
        breadcrumbs={[{ label: "Stocktakes", href: "/stocktakes" }, { label: s.number }]}
        status={<StatusBadge status={s.status} />}
        meta={[`${s.branch}, ${s.location}`, s.category ?? "All products", `Started ${at(s.startedAt)} by ${s.startedBy || "—"}`]}
        primary={isDraft && may("Approve") ? { label: "Apply counts", icon: CheckIcon, onSelect: () => setApplying(true) } : undefined}
        secondary={isDraft && may("Create") ? [{ label: "Cancel stocktake", icon: XIcon, onSelect: () => setCancelling(true) }] : []}
      />

      {isDraft ? (
        <>
          {!may("Approve") ? <p className="text-sm text-muted-foreground">Save your counts; someone with permission to apply stocktakes (an Admin by default) applies them.</p> : null}
          {moved ? (
            <p className="rounded-md border border-[var(--color-border-panel)] px-3 py-2 text-sm bg-card">
              <ClipboardListIcon className="mr-1 inline size-4" aria-hidden />
              {moved} counted {moved === 1 ? "line has" : "lines have"} a system quantity that changed since counting started (a sale or delivery). The count is compared with the stock when applied; recount {moved === 1 ? "it" : "them"} if unsure.
            </p>
          ) : null}
          <EditableGrid
            caption={`Stocktake counts, ${s.branch}, ${s.location}`}
            columns={columns}
            rows={rows}
            draft={draft}
            saving={saving}
            onDraftChange={onDraftChange}
            onSaveDraft={() => void saveDraft()}
            onDiscard={() => setDraft({})}
            csv={{
              filename: `${s.number}-counts.csv`,
              template: ["barcode or code,counted", ...s.lines.map((l) => `${l.barcode || l.code || ""},`)].join("\n"),
              onUpload: (text) => {
                const skipped: string[] = [];
                let applied = 0;
                text
                  .split(/\r?\n/)
                  .slice(1)
                  .filter((line) => line.trim())
                  .forEach((line, i) => {
                    const [code, count] = line.split(",").map((x) => x.trim());
                    const l = s.lines.find((x) => (x.barcode && x.barcode === code) || (x.code && x.code === code));
                    if (!l) return skipped.push(`line ${i + 2}: unknown barcode or code ${code}`);
                    if (!count) return;
                    onDraftChange(String(l.id), "counted", count);
                    applied++;
                  });
                return { applied, skipped };
              },
            }}
            footer={
              <tr className="border-t border-border bg-muted/50 text-sm">
                <th scope="row" className="px-3 py-2 text-left font-medium" colSpan={6}>
                  {countedCount} of {rows.length} lines counted. Lines left blank are not changed.
                </th>
              </tr>
            }
          />
        </>
      ) : s.status === "Applied" ? (
        <StocktakeVariance
          reference={s.number}
          location={`${s.branch}, ${s.location}`}
          countedOn={day(s.appliedAt)}
          countedBy={s.appliedBy ?? "—"}
          lines={s.lines.filter((l) => l.counted != null).map((l) => ({ item: l.name, unit: l.unit, system: l.system, counted: l.counted!, cost: l.unitCost ?? 0, price: l.unitPrice, note: l.note ?? undefined }))}
        />
      ) : (
        <p className="rounded-lg border border-[var(--color-border-panel)] px-4 py-3 text-sm text-muted-foreground bg-card">
          Cancelled {at(s.cancelledAt)}
          {s.cancelReason ? `: ${s.cancelReason}` : ""}. Nothing was applied to stock.
        </p>
      )}

      {applying ? (
        <ConfirmDialog
          open
          onOpenChange={(o) => !o && setApplying(false)}
          level="neutral"
          title={`Apply ${s.number}?`}
          consequences={[
            `${countedCount} counted ${countedCount === 1 ? "line" : "lines"} at ${s.location}; ${rows.length - countedCount} left blank stay as they are.`,
            "Each difference is compared with the stock now, recorded in the inventory log and posted at batch cost.",
            "A stocktake can't be undone once applied; correct a mistake with a stock adjustment.",
          ]}
          actionLabel="Apply counts"
          busyLabel="Applying…"
          onConfirm={async () => {
            if (errors.size) throw new Error(`Fix the counts first: ${[...errors.values()][0]}`);
            const saved = await saveDraft();
            if (!saved) throw new Error("The counts couldn't be saved, so nothing was applied.");
            try {
              setS(await StocktakesApi.apply(s.id));
            } catch (e) {
              throw new Error(apiError(e, "Not applied. Try again."));
            }
            toast.add({ title: `${s.number} applied`, description: "Stock now matches the count." });
            setApplying(false);
          }}
        />
      ) : null}

      {cancelling ? (
        <ConfirmDialog
          open
          onOpenChange={(o) => !o && setCancelling(false)}
          level="B"
          title={`Cancel ${s.number}?`}
          consequences={["The counts are kept on record, but nothing is applied to stock.", "Start a new stocktake to count this location again."]}
          reasonHelp="Saved with the stocktake and in the activity log."
          actionLabel="Cancel stocktake"
          busyLabel="Cancelling…"
          onConfirm={async ({ reason }) => {
            try {
              setS(await StocktakesApi.cancel(s.id, reason ?? ""));
            } catch (e) {
              throw new Error(apiError(e, "Not cancelled. Try again."));
            }
            setDraft({});
            setCancelling(false);
          }}
        />
      ) : null}
      {dialog}
    </div>
  );
}
