"use client";

/**
 * Branch transfers (PRD 3.8: IN-24, IN-25), on the Medora design system and the branch transfers plan (DECISIONS.md,
 * 26 Oct 2026). The design's send lines and receiving count (`TransferSendLines`, `TransferReceive`, `TransferBadge` in
 * stock-ops.tsx), the three transfer reports and the printable transfer note (`TransferReports`, `TransferNote` in
 * inventory-reports.tsx), on the Transfers API.
 *
 * Transfers start once stock is kept per branch (Q3). Stock in transit can't be sold (Q1); a shortfall is written off at
 * batch cost when received (Q2); only the sender cancels, before receipt (Q4). The design has no cancelled state, so
 * cancelled transfers are listed apart from the reports.
 */

import * as React from "react";
import Link from "next/link";
import { PrinterIcon, SendIcon, 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 { TransferBadge, TransferReceive, TransferSendLines, type TransferStockOption } from "@/components/medora/stock-ops";
import { TransferNote, TransferReports, type TransferRecord } from "@/components/medora/inventory-reports";
import { PrintButton } from "@/components/medora/print";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
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 { toast } from "@/components/ui/toast";
import { BranchesApi, InventoryApi, TransfersApi, type TransferOption, type TransferRow } from "@/lib/api";
import { addDays, count, formatDate, formatKES, localDate, localToday } from "@/lib/format";
import { useMenuPermissions } from "@/lib/permissions";
import { useSettings } from "@/lib/SettingsContext";
import { Loading } from "@/components/medora/loading";

const PAGE = "Inventory/Branch transfers";
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");
/** Local date and time, as the inventory log shows it. */
const formatAt = (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 })}, ${pad(d.getHours())}:${pad(d.getMinutes())}`;
};

/** The design's `TransferRecord`; a line carries its batch so two batches of one product read apart. */
const toRecord = (t: TransferRow): TransferRecord => ({
  ref: t.number,
  date: formatAt(t.date),
  from: t.from,
  to: t.to,
  sentBy: t.sentBy || "—",
  receivedBy: t.receivedBy,
  receivedOn: t.receivedOn ? formatAt(t.receivedOn) : undefined,
  note: t.note,
  shortNote: t.shortNote,
  lines: t.lines.map((l) => ({ item: l.batch ? `${l.item} (${l.batch})` : l.item, unit: l.unit, sent: l.sent, received: l.received ?? undefined, unitCost: l.unitCost ?? 0 })),
});

type Branch = { id: number; name: string; status?: string };
type Me = { id?: number; username?: string; branchId?: number | null };

/** Every page of a list (the reports filter on the client, as the design does). */
async function loadAll(p: Parameters<typeof TransfersApi.list>[0]) {
  const first = await TransfersApi.list({ ...p, page: 1, pageSize: 200 });
  const rows = [...first.rows];
  for (let page = 2; rows.length < first.total; page++) {
    const next = await TransfersApi.list({ ...p, page, pageSize: 200 });
    if (!next.rows.length) break;
    rows.push(...next.rows);
  }
  return rows;
}

export default function BranchTransfersPage() {
  const { can, loaded } = useMenuPermissions();
  const may = (a: string) => loaded && can(PAGE, a);
  const allBranches = may("All branches");
  const { s } = useSettings();

  const [me, setMe] = React.useState<Me>({});
  const [branches, setBranches] = React.useState<Branch[] | null>(null);
  const [switchOn, setSwitchOn] = React.useState<boolean | null>(null);
  const [mainId, setMainId] = React.useState<number | null>(null);
  const [picked, setPicked] = React.useState<number | null>(null);
  const today = localToday();
  const [from, setFrom] = React.useState(addDays(today, -29));
  const [to, setTo] = React.useState(today);
  const [rows, setRows] = React.useState<TransferRow[] | null>(null);
  const [moving, setMoving] = React.useState<TransferRow[]>([]);
  const [version, setVersion] = React.useState(0);
  const [failed, setFailed] = React.useState(false); // R12 (card 037): a failed load says so, with Try again
  const [sending, setSending] = React.useState(false);
  const [receiving, setReceiving] = React.useState<TransferRow | null>(null);
  const [cancelling, setCancelling] = React.useState<TransferRow | null>(null);
  const [printing, setPrinting] = React.useState<TransferRow | null>(null);

  React.useEffect(() => {
    try {
      setMe(JSON.parse(localStorage.getItem("user") || "{}"));
    } catch {
      setMe({});
    }
    BranchesApi.getAll()
      .then((b: Branch[]) => setBranches((b ?? []).filter((x) => (x.status ?? "Active") === "Active")))
      .catch(() => setBranches([]));
    InventoryApi.status()
      .then((st) => {
        setSwitchOn(st.stockByBranch);
        setMainId(st.mainBranchId);
      })
      .catch(() => setSwitchOn(false));
  }, []);

  // The branch the reports speak for: the user's own (or the main branch without one); any branch for "All branches"
  const own = branches?.find((b) => b.id === Number(me.branchId))?.id ?? mainId;
  const branchId = allBranches ? (picked ?? own) : own;
  const branchName = branches?.find((b) => b.id === branchId)?.name ?? "this branch";

  const canRead = may("Read");
  React.useEffect(() => {
    if (!canRead) return;
    let live = true;
    setRows(null);
    Promise.all([loadAll({ view: "all", from, to }), loadAll({ view: "in-transit" })])
      .then(([all, transit]) => {
        if (!live) return;
        setRows(all);
        setMoving(transit);
        setFailed(false);
      })
      .catch((e) => {
        if (!live) return;
        setRows([]);
        setFailed(true);
        toast.add({ type: "error", title: "Couldn't load transfers", description: apiError(e, "Try again.") });
      });
    return () => {
      live = false;
    };
  }, [canRead, from, to, version]);

  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 branch transfers.</div>;

  const reload = () => setVersion((v) => v + 1);
  const shown = (rows ?? []).filter((t) => t.status !== "Cancelled");
  const cancelled = (rows ?? []).filter((t) => t.status === "Cancelled");
  const coming = moving.filter((t) => t.toBranchId === branchId);
  const sentByMe = moving.filter((t) => t.sentById != null && t.sentById === me.id && !coming.includes(t));
  const mayReceive = (t: TransferRow) => may("Update") && (allBranches || t.toBranchId === own);

  return (
    <div className="medora mx-auto w-full max-w-6xl space-y-4">
      <MedoraPageHeader
        title="Branch transfers"
        description="Stock moving between branches. It can't be sold while it's in transit."
        meta={allBranches ? [] : [branchName]}
        primary={may("Create") && switchOn ? { label: "Send stock", icon: SendIcon, onSelect: () => setSending(true) } : undefined}
      />

      {switchOn === false ? (
        <p className="rounded-lg border border-[var(--color-border-panel)] bg-card px-4 py-3 text-sm text-muted-foreground">
          Branch transfers start once stock is kept per branch. Until then, an Admin allocates stock from the main branch on{" "}
          <Link href="/inventory" className="font-medium text-foreground underline underline-offset-2">
            Products and stock → By branch
          </Link>
          .
        </p>
      ) : null}

      {coming.length || sentByMe.length ? (
        <section className="space-y-2">
          <h2 className="text-sm font-semibold">On the way</h2>
          <ul className="divide-y divide-[var(--color-border-subtle)] rounded-lg border border-[var(--color-border-panel)] bg-card text-sm">
            {[...coming, ...sentByMe].map((t) => (
              <li key={t.id} className="flex flex-wrap items-center gap-3 px-4 py-3">
                <div className="min-w-0 flex-1">
                  <p className="flex flex-wrap items-center gap-2">
                    <span className="font-mono font-semibold">{t.number}</span>
                    <TransferBadge stage="in-transit" />
                  </p>
                  <p className="text-muted-foreground">
                    {t.from} → {t.to} · {t.units.toLocaleString("en-US")} units on {t.lines.length} {t.lines.length === 1 ? "line" : "lines"} · sent {formatAt(t.date)} by {t.sentBy || "—"}
                    {t.note ? ` · ${t.note}` : ""}
                  </p>
                </div>
                <div className="flex flex-wrap gap-2">
                  <Button size="sm" variant="ghost" onClick={() => setPrinting(t)}>
                    <PrinterIcon data-icon="inline-start" />
                    Note
                  </Button>
                  {t.sentById != null && t.sentById === me.id ? (
                    <Button size="sm" variant="outline" onClick={() => setCancelling(t)}>
                      <XIcon data-icon="inline-start" />
                      Cancel
                    </Button>
                  ) : null}
                  {mayReceive(t) ? (
                    <Button size="sm" onClick={() => setReceiving(t)}>
                      Receive
                    </Button>
                  ) : null}
                </div>
              </li>
            ))}
          </ul>
        </section>
      ) : null}

      <section className="space-y-3">
        {/* One container (data table spec §1): the dates and branch sit in the list's own toolbar */}
        <TransferReports
          transfers={rows === null || failed ? [] : shown.map(toRecord)}
          branch={branchName}
          onPrint={(ref) => setPrinting(shown.find((t) => t.number === ref) ?? null)}
          toolbar={
            <>
              <Input aria-label="Sent from" type="date" className="w-40" value={from} max={to} onChange={(e) => e.target.value && setFrom(e.target.value)} />
              <Input aria-label="To" type="date" className="w-40" value={to} min={from} max={today} onChange={(e) => e.target.value && setTo(e.target.value)} />
              {allBranches && branches?.length ? (
                <NativeSelect aria-label="Receivable at" value={branchId ?? ""} onChange={(e) => setPicked(Number(e.target.value) || null)}>
                  {branches.map((b) => (
                    <NativeSelectOption key={b.id} value={b.id}>
                      Receivable at {b.name}
                    </NativeSelectOption>
                  ))}
                </NativeSelect>
              ) : null}
            </>
          }
          body={failed ? <PanelError what="transfers" onRetry={reload} /> : rows === null ? <Loading what="transfers" columns={6} rows={5} numeric={[4]} bare /> : undefined}
        />
        {cancelled.length ? (
          <details className="rounded-lg border border-[var(--color-border-panel)] text-sm bg-card">
            <summary className="cursor-pointer px-4 py-2 font-medium">Cancelled ({cancelled.length})</summary>
            <ul className="divide-y divide-[var(--color-border-subtle)] border-t border-[var(--color-border-subtle)]">
              {cancelled.map((t) => (
                <li key={t.id} className="px-4 py-2">
                  <span className="font-mono">{t.number}</span> <Badge variant="outline">Cancelled</Badge>{" "}
                  <span className="text-muted-foreground">
                    {t.from} → {t.to}, {t.units.toLocaleString("en-US")} units back at {t.from} · {formatAt(t.cancelledAt)}
                    {t.cancelReason ? ` · ${t.cancelReason}` : ""}
                  </span>
                </li>
              ))}
            </ul>
          </details>
        ) : null}
      </section>

      {sending && branches ? (
        <SendDrawer
          branches={branches}
          fromId={allBranches ? branchId : own}
          canPickFrom={allBranches}
          onClose={() => setSending(false)}
          onSent={(t) => {
            setSending(false);
            setPrinting(t);
            reload();
          }}
        />
      ) : null}

      {receiving ? (
        <ReceiveDrawer
          transfer={receiving}
          onClose={() => setReceiving(null)}
          onDone={() => {
            setReceiving(null);
            reload();
          }}
        />
      ) : null}

      {cancelling ? (
        <ConfirmDialog
          open
          onOpenChange={(o) => !o && setCancelling(null)}
          level="B"
          title={`Cancel transfer ${cancelling.number}?`}
          consequences={[
            `${cancelling.units.toLocaleString("en-US")} units go back into the batches they left at ${cancelling.from}.`,
            `${cancelling.to} can no longer receive it. The transfer stays on record as cancelled.`,
          ]}
          reasonHelp="Saved with the transfer and in the activity log."
          actionLabel="Cancel transfer"
          busyLabel="Cancelling…"
          onConfirm={async ({ reason }) => {
            try {
              await TransfersApi.cancel(cancelling.id, reason ?? "");
            } catch (e) {
              throw new Error(apiError(e, "Not cancelled. Try again."));
            }
            toast.add({ title: `${cancelling.number} cancelled`, description: `Stock is back at ${cancelling.from}.` });
            setCancelling(null);
            reload();
          }}
        />
      ) : null}

      {printing ? <PrintDrawer transfer={printing} company={s("store_name", "Pharmacy")} printedBy={me.username ?? ""} onClose={() => setPrinting(null)} /> : null}
    </div>
  );
}

/* ------------------------------------- Send ------------------------------------- */

function SendDrawer({ branches, fromId, canPickFrom, onClose, onSent }: { branches: Branch[]; fromId: number | null; canPickFrom: boolean; onClose: () => void; onSent: (t: TransferRow) => void }) {
  const [from, setFrom] = React.useState<number | null>(fromId);
  const [to, setTo] = React.useState<number | null>(branches.find((b) => b.id !== fromId)?.id ?? null);
  const [note, setNote] = React.useState("");
  const [options, setOptions] = React.useState<TransferOption[] | null>(null);
  const [search, setSearch] = React.useState("");
  const [qty, setQty] = React.useState<Record<string, number>>({});
  const [tried, setTried] = React.useState(false);
  const [saving, setSaving] = React.useState(false);

  React.useEffect(() => {
    let live = true;
    setOptions(null);
    setQty({});
    TransfersApi.options({ fromBranchId: from ?? undefined })
      .then((o) => live && setOptions(o))
      .catch((e) => {
        if (!live) return;
        setOptions([]);
        toast.add({ type: "error", title: "Couldn't load the stock to send", description: apiError(e, "Try again.") });
      });
    return () => {
      live = false;
    };
  }, [from]);

  const lines = Object.entries(qty).filter(([, n]) => n > 0).map(([id, n]) => ({ batchId: Number(id), quantity: n }));
  const units = lines.reduce((s, l) => s + l.quantity, 0);
  const target = branches.find((b) => b.id === to);
  const problem = to == null ? "Choose a branch to send to." : to === from ? "A transfer has to go to a different branch." : !lines.length ? "Add at least one line to send." : undefined;
  const q = search.trim().toLowerCase();
  // Expiry as a date, so the design's list keeps the oldest stock first; lines being sent always stay visible
  const visible: TransferStockOption[] = (options ?? [])
    .filter((o) => !q || (qty[String(o.batchId)] ?? 0) > 0 || `${o.name} ${o.code ?? ""} ${o.batch ?? ""}`.toLowerCase().includes(q))
    .map((o) => ({ id: String(o.batchId), name: o.name, batch: [o.code, o.batch ? `batch ${o.batch}` : "no batch number"].filter(Boolean).join(" · "), expiry: o.expiryDate ? localDate(o.expiryDate) : "", available: o.available }));

  const save = async () => {
    setTried(true);
    if (problem || !target) return;
    setSaving(true);
    try {
      const t = await TransfersApi.send({ fromBranchId: canPickFrom ? from ?? undefined : undefined, toBranchId: target.id, note: note.trim() || undefined, lines });
      toast.add({ title: `${t.number} sent`, description: `${count(units, "unit")} left ${t.from}; in transit to ${t.to}.` });
      onSent(t);
    } catch (e) {
      toast.add({ type: "error", title: "Not sent", description: apiError(e, "Try again.") });
    } finally {
      setSaving(false);
    }
  };

  return (
    <RecordDrawer
      open
      onOpenChange={(o) => !o && onClose()}
      variant="edit"
      width="lg"
      saving={saving}
      dirty={lines.length > 0 || !!note}
      title="Send stock to another branch"
      subtitle={`From ${branches.find((b) => b.id === from)?.name ?? "your branch"}`}
      footer={
        <>
          <Button variant="outline" disabled={saving} onClick={onClose}>
            Cancel
          </Button>
          <Button disabled={saving} onClick={() => void save()}>
            {saving ? <Spinner /> : <SendIcon data-icon="inline-start" />}
            {target ? `Dispatch to ${target.name}` : "Dispatch"}
          </Button>
        </>
      }
    >
      <div className="space-y-4">
        <div className="grid gap-4">
          {canPickFrom ? (
            <Field>
              <FieldLabel htmlFor="send-from">Send from</FieldLabel>
              <NativeSelect id="send-from" value={from ?? ""} onChange={(e) => setFrom(Number(e.target.value) || null)}>
                {branches.map((b) => (
                  <NativeSelectOption key={b.id} value={b.id}>
                    {b.name}
                  </NativeSelectOption>
                ))}
              </NativeSelect>
            </Field>
          ) : null}
          <Field>
            <FieldLabel htmlFor="send-to">Send to</FieldLabel>
            <NativeSelect id="send-to" value={to ?? ""} onChange={(e) => setTo(Number(e.target.value) || null)}>
              <NativeSelectOption value="">Choose a branch…</NativeSelectOption>
              {branches.filter((b) => b.id !== from).map((b) => (
                <NativeSelectOption key={b.id} value={b.id}>
                  {b.name}
                </NativeSelectOption>
              ))}
            </NativeSelect>
          </Field>
          <Field className={canPickFrom ? "sm:col-span-2" : undefined}>
            <FieldLabel htmlFor="send-note">Note (optional)</FieldLabel>
            <Input id="send-note" value={note} maxLength={255} onChange={(e) => setNote(e.target.value)} placeholder="e.g. Cover for the weekend rush" />
          </Field>
        </div>
        <Input aria-label="Find stock to send" placeholder="Find a product or batch" value={search} onChange={(e) => setSearch(e.target.value)} />
        {options === null ? (
          <Loading what="stock to send" columns={4} rows={6} numeric={[2, 3]} />
        ) : visible.length ? (
          <TransferSendLines options={visible} qty={qty} onQty={(id, n) => setQty((x) => ({ ...x, [id]: n }))} />
        ) : (
          <p className="py-6 text-center text-sm text-muted-foreground">{options.length ? "Nothing matches." : "This branch holds no stock to send."}</p>
        )}
        {tried && problem ? <p className="text-sm text-status-error-text">{problem}</p> : null}
        <FieldDescription>Stock leaves this branch when you dispatch it and is in transit until the other branch counts it in. Print the transfer note to send with the goods.</FieldDescription>
      </div>
    </RecordDrawer>
  );
}

/* ------------------------------------ Receive ------------------------------------ */

function ReceiveDrawer({ transfer: t, onClose, onDone }: { transfer: TransferRow; onClose: () => void; onDone: () => void }) {
  const [counts, setCounts] = React.useState<Record<string, number>>({});
  const [notes, setNotes] = React.useState<Record<string, string>>({});
  const [tried, setTried] = React.useState(false);
  const [saving, setSaving] = React.useState(false);
  const lines = t.lines.map((l) => ({ id: String(l.id), name: l.item, batch: [l.batch ? `batch ${l.batch}` : "no batch number", l.expiry ? `expires ${formatDate(l.expiry)}` : null].filter(Boolean).join(" · "), sent: l.sent }));
  const got = (id: string, sent: number) => counts[id] ?? sent;
  const missingNote = lines.some((l) => got(l.id, l.sent) < l.sent && !notes[l.id]?.trim());
  const units = lines.reduce((s, l) => s + got(l.id, l.sent), 0);
  const lost = t.lines.reduce((s, l) => s + (l.sent - got(String(l.id), l.sent)) * (l.unitCost ?? 0), 0);

  const save = async () => {
    setTried(true);
    if (missingNote) return;
    setSaving(true);
    try {
      const done = await TransfersApi.receive(t.id, lines.map((l) => ({ lineId: Number(l.id), received: got(l.id, l.sent), reason: got(l.id, l.sent) < l.sent ? notes[l.id]?.trim() : undefined })));
      toast.add({ title: done.status === "Received short" ? `${t.number} received short` : `${t.number} received`, description: `${count(units, "unit")} added to ${t.to}.` });
      onDone();
    } catch (e) {
      toast.add({ type: "error", title: "Not received", description: apiError(e, "Try again.") });
    } finally {
      setSaving(false);
    }
  };

  return (
    <RecordDrawer
      open
      onOpenChange={(o) => !o && onClose()}
      variant="edit"
      width="lg"
      saving={saving}
      dirty={Object.keys(counts).length > 0}
      title={`Receive ${t.number}`}
      subtitle={`From ${t.from}, sent ${formatAt(t.date)} by ${t.sentBy || "—"}`}
      footer={
        <>
          <Button variant="outline" disabled={saving} onClick={onClose}>
            Cancel
          </Button>
          <Button disabled={saving} onClick={() => void save()}>
            {saving ? <Spinner /> : null}
            Accept {units.toLocaleString("en-US")} units into {t.to}
          </Button>
        </>
      }
    >
      <div className="space-y-3">
        {t.note ? <p className="text-sm text-muted-foreground">Note from {t.from}: {t.note}</p> : null}
        <TransferReceive lines={lines} counts={counts} notes={notes} onCount={(id, n) => setCounts((c) => ({ ...c, [id]: n }))} onNote={(id, v) => setNotes((x) => ({ ...x, [id]: v }))} />
        {tried && missingNote ? <p className="text-sm text-status-error-text">Say what happened to each missing item.</p> : null}
        {lost > 0 ? <p className="text-sm text-muted-foreground">Written off as lost in transit: {formatKES(lost, { decimals: true })} at cost.</p> : null}
      </div>
    </RecordDrawer>
  );
}

/* ------------------------------------- Print ------------------------------------- */

function PrintDrawer({ transfer, company, printedBy, onClose }: { transfer: TransferRow; company: string; printedBy: string; onClose: () => void }) {
  const ref = React.useRef<HTMLDivElement>(null);
  const [printedAt] = React.useState(() => formatAt(new Date().toISOString()));
  return (
    <RecordDrawer open onOpenChange={(o) => !o && onClose()} width="lg" title={`Transfer note ${transfer.number}`} subtitle="One copy travels with the goods and comes back signed.">
      <div className="space-y-3">
        <div className="flex justify-end">
          <PrintButton target={ref} paper="a4" label={`Print ${transfer.number}`} />
        </div>
        <div className="overflow-x-auto bg-muted/40 p-2">
          <TransferNote ref={ref} transfer={toRecord(transfer)} company={company} printedBy={printedBy || "—"} printedAt={printedAt} />
        </div>
      </div>
    </RecordDrawer>
  );
}
