"use client";

/**
 * The till's records, on the design's components and the till records API (DECISIONS.md, 24 Oct 2026):
 * held orders (PS-10, `HeldOrdersLog`), past sales (PS-22, `SalesList`) and returns and reversals (PS-18,
 * `ReturnBuilder`). The API works out every refund; the figure shown here is checked against it.
 *
 * Named, not faked: an exchange is a return and then a new sale at the till (Q3); reversing needs the Approve
 * permission until manager approval exists (Q2); the export button exports the dates only (the CSV has no other filters).
 */

import * as React from "react";
import { FileTextIcon, PrinterIcon, Undo2Icon } from "lucide-react";

import { PanelError } from "@/components/medora/errors";
import { StatusTag } from "@/components/medora/record-lists";
import { HeldOrdersLog, type HeldOrder, type ProcessedHold } from "@/components/medora/held-orders";
import { SalesList, type SalesPage, type SalesQuery } from "@/components/medora/sales-lists";
import { ReturnBuilder, kes, type ReturnMode, type ReturnPick, type SoldLine } from "@/components/medora/pos-ops";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Field, FieldLabel } from "@/components/ui/field";
import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select";
import { Spinner } from "@/components/ui/spinner";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { toast } from "@/components/ui/toast";
import { AuthApi, CreditNotesApi, ExportsApi, SalesApi, type HeldRow, type SalesListRow } from "@/lib/api";
import { localToday } from "@/lib/format";
import { Loading } from "@/components/medora/loading";

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 when = (iso?: string) => (iso ? new Date(iso).toLocaleString("en-GB", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }) : "");
const r2 = (n: number) => Math.round(n * 100) / 100;

/* ------------------------------------ Held orders (PS-10) ------------------------------------ */

export function HeldDialog({ canDelete, onRecall, onClose }: { canDelete: boolean; onRecall: (id: number) => void; onClose: () => void }) {
  const [held, setHeld] = React.useState<HeldRow[] | null>(null);
  const [failed, setFailed] = React.useState(false); // R12 (card 037): a failed load says so, with Try again
  const [processed, setProcessed] = React.useState<HeldRow[]>([]);
  const load = React.useCallback(async () => {
    try {
      const [h, p] = await Promise.all([SalesApi.held("held"), SalesApi.held("processed")]);
      setHeld(h);
      setProcessed(p);
      setFailed(false);
    } catch (e) {
      setHeld([]);
      setFailed(true);
      toast.add({ type: "error", title: "Couldn't load held sales", description: apiError(e, "Try again.") });
    }
  }, []);
  React.useEffect(() => {
    void load();
  }, [load]);
  const idOf = (ref: string) => [...(held ?? []), ...processed].find((h) => h.ref === ref)?.id;
  const asHeld = (h: HeldRow): HeldOrder => ({ ref: h.ref, heldAt: when(h.heldAt), cashier: h.cashier, customer: h.customer, items: h.items, total: h.total });

  return (
    <Dialog open onOpenChange={(o) => !o && onClose()}>
      <DialogContent className="sm:max-w-2xl">
        <DialogHeader>
          <DialogTitle>Held sales</DialogTitle>
          <DialogDescription>Recall a basket to finish it at the till. Processed shows the last 7 days.</DialogDescription>
        </DialogHeader>
        {failed ? (
          <PanelError what="held sales" onRetry={() => void load()} />
        ) : held === null ? (
          <Loading what="held sales" shape="list" rows={4} />
        ) : (
          <div className="max-h-[60svh] overflow-auto">
            <HeldOrdersLog
              held={held.map(asHeld)}
              processed={processed.map((p): ProcessedHold => ({ ...asHeld(p), outcome: p.outcome ?? "Deleted", by: p.by || "An older till", at: when(p.at), saleRef: p.saleRef, reason: p.reason }))}
              canDelete={canDelete}
              onRecall={(ref) => {
                const id = idOf(ref);
                if (id) onRecall(id);
              }}
              onDelete={async (ref, reason) => {
                const id = idOf(ref);
                if (!id) return;
                try {
                  await SalesApi.deleteHeld(id, reason);
                  toast.add({ title: `Held sale ${ref} deleted` });
                  await load();
                } catch (e) {
                  toast.add({ type: "error", title: "Not deleted", description: apiError(e, "Try again.") });
                }
              }}
            />
          </div>
        )}
      </DialogContent>
    </Dialog>
  );
}

/* ------------------------------------ Past sales (PS-22) ------------------------------------ */

export function PastSalesDialog({
  canReturn,
  onReprint,
  onGenerateRx,
  onReturn,
  onClose,
}: {
  canReturn: boolean;
  onReprint: (saleId: number) => void;
  onGenerateRx: (row: SalesListRow) => void;
  onReturn: (row: SalesListRow) => void;
  onClose: () => void;
}) {
  const today = localToday();
  const [people, setPeople] = React.useState<{ id: number; name: string }[]>([]);
  const rows = React.useRef(new Map<string, SalesListRow>());
  const [open, setOpen] = React.useState<SalesListRow | null>(null);

  React.useEffect(() => {
    AuthApi.getCashiers()
      .then((us: { id: number; name?: string; username?: string }[]) => setPeople(us.map((u) => ({ id: u.id, name: u.name || u.username || `#${u.id}` }))))
      .catch(() => setPeople([]));
  }, []);
  const idOf = React.useCallback((name: string) => people.find((p) => p.name === name)?.id, [people]);

  const fetchPage = React.useCallback(
    async (q: SalesQuery): Promise<SalesPage> => {
      try {
        const res = await SalesApi.list({ from: q.from, to: q.to, sellerId: q.seller ? idOf(q.seller) : undefined, cashierId: q.cashier ? idOf(q.cashier) : undefined, q: q.q || undefined, page: q.page, pageSize: q.pageSize });
        res.rows.forEach((r) => rows.current.set(r.receipt, r));
        return {
          total: res.total,
          totalValue: res.totalValue,
          rows: res.rows.map((r) => {
            const at = new Date(r.createdAt);
            // The design list adds VAT per line from its rate; older sales have no stored rate, so their own tax over subtotal
            const oldRate = r.subtotal > 0 ? (r.tax / r.subtotal) * 100 : 0;
            const items = r.items.map((i) => ({ product: i.product, category: i.category, qty: i.qty, price: i.unitPrice - i.discount, cost: 0, vat: i.taxRate ?? oldRate }));
            if (r.deliveryFee) items.push({ product: "Delivery", category: "", qty: 1, price: r.deliveryFee, cost: 0, vat: r.etims ? 16 : oldRate });
            return {
              receipt: r.receipt,
              date: at.toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" }),
              time: at.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" }),
              branch: r.branch,
              seller: r.seller,
              cashier: r.cashier,
              customer: r.customer,
              method: r.method as "Cash",
              items,
              etims: r.etims,
              status: r.status,
              returned: r.refunded,
            };
          }),
        };
      } catch (e) {
        toast.add({ type: "error", title: "Couldn't load sales", description: apiError(e, "Try again.") });
        return { rows: [], total: 0, totalValue: 0 };
      }
    },
    [idOf],
  );

  return (
    <Dialog open onOpenChange={(o) => !o && onClose()}>
      <DialogContent className="sm:max-w-4xl">
        <DialogHeader>
          <DialogTitle>Past sales</DialogTitle>
          <DialogDescription>Open a receipt to reprint it, write its prescription, or return items from it.</DialogDescription>
        </DialogHeader>
        {open ? (
          <div className="flex flex-wrap items-center gap-2 rounded-md border border-[var(--color-border-panel)] bg-muted/40 p-2 text-sm">
            <span className="flex-1">
              <span className="font-mono text-xs">{open.receipt}</span> · {open.customer ?? "Walk-in"} · {kes(open.total)}
              {open.status !== "Completed" ? <span className="ml-2"><StatusTag status={open.status} /></span> : null}
            </span>
            <Button size="sm" variant="outline" onClick={() => onReprint(open.id)}>
              <PrinterIcon data-icon="inline-start" />
              Reprint
            </Button>
            <Button size="sm" variant="outline" onClick={() => onGenerateRx(open)}>
              <FileTextIcon data-icon="inline-start" />
              Prescription
            </Button>
            {canReturn ? (
              <Button size="sm" variant="outline" disabled={open.status === "Returned"} onClick={() => onReturn(open)}>
                <Undo2Icon data-icon="inline-start" />
                Return items
              </Button>
            ) : null}
          </div>
        ) : null}
        <div className="max-h-[60svh] overflow-auto">
          <SalesList
            today={today}
            sellers={people.map((p) => p.name)}
            cashiers={people.map((p) => p.name)}
            fetchPage={fetchPage}
            onOpen={(receipt) => setOpen(rows.current.get(receipt) ?? null)}
            onExport={async (q) => {
              try {
                await ExportsApi.csv(`/sales/export/csv?from=${q.from}&to=${q.to}`, `sales-${q.from}-to-${q.to}.csv`);
                if (q.seller || q.cashier || q.q) toast.add({ title: "Exported every sale in these dates", description: "The file doesn't apply the seller, cashier or search filters." });
              } catch (e) {
                toast.add({ type: "error", title: "Export failed", description: apiError(e, "Try again.") });
              }
            }}
          />
        </div>
      </DialogContent>
    </Dialog>
  );
}

/* ------------------------------------ Returns and reversals (PS-18) ------------------------------------ */

/** One line per product, as the API returns them; paid per unit with the VAT the sale charged. */
function soldLines(row: SalesListRow): SoldLine[] {
  const oldRate = row.subtotal > 0 ? (row.tax / row.subtotal) * 100 : 0;
  const by = new Map<number, SoldLine>();
  for (const i of row.items) {
    const net = i.unitPrice - i.discount;
    const unitPaid = r2(net + r2((net * (i.taxRate ?? oldRate)) / 100));
    const cur = by.get(i.productId);
    if (cur) cur.sold += i.qty;
    // `returned` is already per product, so it's taken once
    else by.set(i.productId, { id: String(i.productId), name: i.product, sold: i.qty, returned: i.returned, unitPaid, rx: i.rx });
  }
  return [...by.values()];
}

export function ReturnDialog({ row, canReverse, onDone, onClose }: { row: SalesListRow; canReverse: boolean; onDone: () => void; onClose: () => void }) {
  const lines = React.useMemo(() => soldLines(row), [row]);
  const [mode, setMode] = React.useState<ReturnMode>("return");
  const [picks, setPicks] = React.useState<Record<string, ReturnPick>>({});
  const [method, setMethod] = React.useState<"Cash" | "M-Pesa" | "Card">(["Cash", "M-Pesa", "Card"].includes(row.method) ? (row.method as "Cash") : "Cash");
  const [saving, setSaving] = React.useState(false);

  const switchMode = (m: ReturnMode) => {
    setMode(m);
    // A reversal takes everything left, and can't be edited (the design's rule)
    setPicks(m === "reverse" ? Object.fromEntries(lines.filter((l) => l.sold > l.returned).map((l) => [l.id, { qty: l.sold - l.returned, reason: "Wrong item" as const, restock: true }])) : {});
  };
  const chosen = lines.filter((l) => (picks[l.id]?.qty ?? 0) > 0);
  const refund = r2(chosen.reduce((t, l) => t + picks[l.id].qty * l.unitPaid, 0));

  const submit = async () => {
    setSaving(true);
    try {
      await CreditNotesApi.create({
        saleId: row.id,
        type: mode === "reverse" ? "Reversal" : "Return",
        returnedItems: chosen.map((l) => ({ productId: Number(l.id), quantity: picks[l.id].qty, reason: picks[l.id].reason, restock: picks[l.id].restock })),
        refundAmount: refund,
        refundMethod: method,
      });
      toast.add({ title: mode === "reverse" ? "Sale reversed" : "Return recorded", description: `Refund ${kes(refund)} by ${method}.` });
      onDone();
    } catch (e) {
      const res = (e as { response?: { status?: number; data?: { refundAmount?: number } } })?.response;
      toast.add({ type: "error", title: res?.status === 409 ? "The refund is different" : "Not recorded", description: apiError(e, "Try again.") });
    } finally {
      setSaving(false);
    }
  };

  return (
    <Dialog open onOpenChange={(o) => !o && !saving && onClose()}>
      <DialogContent className="sm:max-w-3xl">
        <DialogHeader>
          <DialogTitle>Return items from {row.receipt}</DialogTitle>
          <DialogDescription>
            {row.customer ?? "Walk-in"}, {new Date(row.createdAt).toLocaleString("en-GB", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" })}, {kes(row.total)}. For an exchange, return the items here, then sell the new ones at the till.
          </DialogDescription>
        </DialogHeader>
        <Tabs value={mode} onValueChange={(v) => switchMode(v as ReturnMode)}>
          <TabsList variant="line" className="w-full justify-start border-b border-border">
            <TabsTrigger value="return" className="flex-none">
              Return items
            </TabsTrigger>
            <TabsTrigger value="reverse" className="flex-none" disabled={!canReverse}>
              Reverse the whole sale{canReverse ? "" : " (needs Approve)"}
            </TabsTrigger>
          </TabsList>
        </Tabs>
        <div className="max-h-[50svh] overflow-auto">
          <ReturnBuilder lines={lines} mode={mode} picks={picks} onChange={setPicks} />
        </div>
        <Field orientation="horizontal" className="w-fit items-center gap-2">
          <FieldLabel htmlFor="rf-method">Refund by</FieldLabel>
          <NativeSelect id="rf-method" value={method} onChange={(e) => setMethod(e.target.value as "Cash")}>
            {["Cash", "M-Pesa", "Card"].map((m) => (
              <NativeSelectOption key={m} value={m}>
                {m}
              </NativeSelectOption>
            ))}
          </NativeSelect>
        </Field>
        <DialogFooter>
          <Button variant="outline" disabled={saving} onClick={onClose}>
            Cancel
          </Button>
          <Button variant={mode === "reverse" ? "destructive" : "default"} disabled={saving || !chosen.length} onClick={() => void submit()}>
            {saving ? <Spinner /> : null}
            {mode === "reverse" ? `Reverse and refund ${kes(refund)}` : `Refund ${kes(refund)}`}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}
