"use client";

import * as React from "react";
import Image from "next/image";
import { PrinterIcon } from "lucide-react";

import { Button } from "@/components/ui/button";
import { formatDate } from "@/lib/format";
import { cn } from "@/lib/utils";

/*
 * Print layouts (spec 03-structure-and-navigation §11.8, C77). Formats are still open under D10, so the sizes
 * below are working defaults: A4 documents, 80 / 58 mm thermal receipts, an A4 21-up label sheet
 * (70 × 42.3 mm), 38 × 25 mm barcode labels and 70 × 40 mm dispensing labels. Everything prints black on
 * white; status is always a word, never a colour.
 */

/* ------------------------------------ Printing ------------------------------------- */

/** Paper names match the `@page` rules in styles/medora-theme.css. */
export type Paper = "a4" | "receipt-80" | "receipt-58" | "sheet-21" | "label-38x25" | "label-70x40";

/**
 * Prints one element on its own paper. The element is copied into #medora-print-root (a direct child of
 * <body>) and everything else is hidden for the print, so the sidebar, top bar and action bars never print.
 */
export function printElement(el: HTMLElement, paper: Paper) {
  document.getElementById("medora-print-root")?.remove();
  const root = document.createElement("div");
  root.id = "medora-print-root";
  root.dataset.paper = paper;
  root.innerHTML = el.outerHTML;
  document.body.appendChild(root);
  document.documentElement.dataset.printing = paper;
  const done = () => {
    root.remove();
    delete document.documentElement.dataset.printing;
    window.removeEventListener("afterprint", done);
  };
  window.addEventListener("afterprint", done);
  window.print();
}

export function PrintButton({ target, paper, label, variant = "outline" }: { target: React.RefObject<HTMLElement | null>; paper: Paper; label: string; variant?: "outline" | "default" | "secondary" }) {
  return (
    <Button variant={variant} onClick={() => target.current && printElement(target.current, paper)}>
      <PrinterIcon data-icon="inline-start" />
      {label}
    </Button>
  );
}

/* ------------------------------------- Formats ------------------------------------- */

const fmt2 = new Intl.NumberFormat("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const amt = (n: number) => fmt2.format(n);

/** eTIMS tax types. Most medicines are exempt (A); cosmetics and consumables are usually 16% (B). */
export const VAT_CLASS = {
  A: { label: "A Exempt", rate: 0 },
  B: { label: "B 16%", rate: 0.16 },
  C: { label: "C Zero-rated", rate: 0 },
  D: { label: "D Non-VAT", rate: 0 },
  E: { label: "E 8%", rate: 0.08 },
} as const;
export type VatClass = keyof typeof VAT_CLASS;

/** Card 015: phone and KRA PIN print only when known (there's no KRA PIN setting yet), never as a placeholder. */
export type Issuer = { name: string; branch: string; address: string[]; kraPin?: string; phone?: string; email?: string };
export type Party = { name: string; kraPin?: string; phone?: string; address?: string[] };
export type DocLine = {
  code: string;
  name: string;
  qty: number;
  unit?: string;
  /** Unit price excluding VAT (prices in this app are VAT-exclusive). */
  price?: number;
  /** Per-unit discount, as stored on sale_items. */
  discount?: number;
  vat?: VatClass;
  /** Delivery notes. */
  ordered?: number;
  batch?: string;
  expiry?: string;
};

export type Etims =
  | { state: "signed"; scuId: string; cuInvoiceNo: string; internalData: string; signature: string; signedAt: string; qrUrl: string }
  | { state: "queued" };

/* ------------------------------------- Pieces -------------------------------------- */

/** Logo for print: greyscale with extra contrast so the M stays readable. Swap for the black file when it exists (OQ-51). */
export function PrintLogo({ className }: { className?: string }) {
  return <Image src="/medora-logo.svg" alt="Medora" width={884} height={238} unoptimized className={cn("w-auto grayscale contrast-[1.8]", className)} />;
}

/**
 * KRA QR code. A placeholder until a QR library is added: it reserves the printed size (20 mm by default,
 * the smallest that scans reliably on thermal paper) and shows the link it will encode.
 */
export function KraQr({ value, size = 20 }: { value: string; size?: number }) {
  return (
    <div className="flex flex-col items-center gap-0.5" data-qr-placeholder>
      <svg viewBox="0 0 21 21" style={{ width: `${size}mm`, height: `${size}mm` }} role="img" aria-label={`KRA QR code for ${value}`} className="text-black">
        <rect x="0.5" y="0.5" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="0.3" strokeDasharray="0.8 0.6" />
        {[
          [1, 1],
          [14, 1],
          [1, 14],
        ].map(([x, y]) => (
          <g key={`${x}-${y}`}>
            <rect x={x} y={y} width="6" height="6" fill="currentColor" />
            <rect x={x + 1} y={y + 1} width="4" height="4" fill="#fff" />
            <rect x={x + 2} y={y + 2} width="2" height="2" fill="currentColor" />
          </g>
        ))}
        <text x="14.5" y="17.6" fontSize="3" textAnchor="middle" fill="currentColor" fontFamily="sans-serif">
          QR
        </text>
      </svg>
    </div>
  );
}

const EAN_L = ["0001101", "0011001", "0010011", "0111101", "0100011", "0110001", "0101111", "0111011", "0110111", "0001011"];
const EAN_R = EAN_L.map((p) => [...p].map((b) => (b === "0" ? "1" : "0")).join(""));
const EAN_G = EAN_R.map((p) => [...p].reverse().join(""));
const EAN_PARITY = ["LLLLLL", "LLGLGG", "LLGGLG", "LLGGGL", "LGLLGG", "LGGLLG", "LGGGLL", "LGLGLG", "LGLGGL", "LGGLGL"];

/** GS1 check digit for the first 12 digits. */
export function ean13Check(d12: string) {
  let s = 0;
  for (let i = 0; i < 12; i++) s += Number(d12[i]) * (i % 2 ? 3 : 1);
  return (10 - (s % 10)) % 10;
}

/** The 95 bar modules of an EAN-13, or null when the value isn't a valid EAN-13 (12 digits get a check digit added). */
export function ean13(value: string): { digits: string; bits: string } | null {
  const v = value.replace(/\s/g, "");
  if (!/^\d{12,13}$/.test(v)) return null;
  const check = ean13Check(v.slice(0, 12));
  if (v.length === 13 && Number(v[12]) !== check) return null;
  const digits = v.slice(0, 12) + check;
  const parity = EAN_PARITY[Number(digits[0])];
  let bits = "101";
  for (let i = 1; i <= 6; i++) bits += (parity[i - 1] === "L" ? EAN_L : EAN_G)[Number(digits[i])];
  bits += "01010";
  for (let i = 7; i <= 12; i++) bits += EAN_R[Number(digits[i])];
  return { digits, bits: bits + "101" };
}

/**
 * EAN-13 barcode as SVG, with the human-readable digits. `width` is the printed width in mm including the
 * quiet zones (nominal 37.3 mm; below about 30 mm scanners start to struggle). Codes that are not EAN-13
 * (internal codes such as "2130") print as text with no bars rather than as an unscannable barcode.
 */
export function Ean13({ value, width = 32, className }: { value: string; width?: number; className?: string }) {
  const code = ean13(value);
  if (!code) {
    return (
      <p className={cn("font-mono text-[7pt]", className)}>
        Code {value} <span className="font-sans">(no barcode)</span>
      </p>
    );
  }
  const Q = 11; // left quiet zone in modules (7 on the right)
  const guard = new Set([0, 1, 2, 45, 46, 47, 48, 49, 92, 93, 94]);
  const bars: React.ReactNode[] = [];
  for (let i = 0; i < 95; i++) {
    if (code.bits[i] !== "1") continue;
    bars.push(<rect key={i} x={Q + i} y={0} width={1} height={guard.has(i) ? 55 : 50} />);
  }
  const d = code.digits;
  return (
    <svg viewBox="0 0 113 63" style={{ width: `${width}mm` }} className={cn("block text-black", className)} role="img" aria-label={`Barcode ${d}`} fill="currentColor" shapeRendering="crispEdges">
      {bars}
      <g fontFamily="ui-monospace, monospace" fontSize="9" textAnchor="middle">
        <text x={5} y={62}>
          {d[0]}
        </text>
        {[...d.slice(1, 7)].map((c, i) => (
          <text key={`l${i}`} x={Q + 3 + 7 * i + 3.5} y={62}>
            {c}
          </text>
        ))}
        {[...d.slice(7)].map((c, i) => (
          <text key={`r${i}`} x={Q + 50 + 7 * i + 3.5} y={62}>
            {c}
          </text>
        ))}
      </g>
    </svg>
  );
}

/* ---------------------------------- A4 documents ----------------------------------- */

export type DocumentKind = "invoice" | "quotation" | "delivery-note";

const TITLE: Record<DocumentKind, string> = { invoice: "Tax invoice", quotation: "Quotation", "delivery-note": "Delivery note" };

/** Totals per VAT class, prices VAT-exclusive (total = taxable + VAT, as on the till). */
export function documentTotals(lines: DocLine[]) {
  const byClass = new Map<VatClass, { taxable: number; vat: number }>();
  let discount = 0;
  for (const l of lines) {
    const net = l.qty * ((l.price ?? 0) - (l.discount ?? 0));
    discount += l.qty * (l.discount ?? 0);
    const c = l.vat ?? "A";
    const row = byClass.get(c) ?? { taxable: 0, vat: 0 };
    row.taxable += net;
    row.vat += net * VAT_CLASS[c].rate;
    byClass.set(c, row);
  }
  const rows = [...byClass.entries()].sort(([a], [b]) => a.localeCompare(b));
  const subtotal = rows.reduce((s, [, r]) => s + r.taxable, 0);
  const vat = rows.reduce((s, [, r]) => s + r.vat, 0);
  return { rows, subtotal, vat, discount, total: subtotal + vat };
}

/**
 * A4 document: tax invoice, quotation or delivery note. Header with one-colour logo and the branch details
 * from Settings (ST-01), title and references, the other party, a line table whose header row repeats on
 * every page (with the reference and printed-at time), totals by VAT class, and the eTIMS block.
 * Page x of y is added by the @page footer when printed.
 */
export const DocumentSheet = React.forwardRef<
  HTMLDivElement,
  {
    kind: DocumentKind;
    issuer: Issuer;
    party: Party;
    reference: string;
    date: string;
    lines: DocLine[];
    printedBy: string;
    printedAt: string;
    /** Invoice: payment status as a word. */
    status?: "Paid" | "Unpaid" | "Part paid";
    dueDate?: string;
    paid?: number;
    /** Quotation. */
    validUntil?: string;
    /** Links: sale, PO, quotation the document came from. */
    related?: { label: string; value: string }[];
    etims?: Etims;
    /** A reprint says so on every copy. */
    reprint?: boolean;
    notes?: string[];
    paymentDetails?: string[];
  }
>(function DocumentSheet({ kind, issuer, party, reference, date, lines, printedBy, printedAt, status, dueDate, paid, validUntil, related = [], etims, reprint, notes = [], paymentDetails = [] }, ref) {
  const t = documentTotals(lines);
  const priced = kind !== "delivery-note";
  const cols = 7; // both layouts have seven columns
  const balance = paid != null ? t.total - paid : undefined;

  return (
    <div
      ref={ref}
      className="relative mx-auto box-border w-[210mm] min-h-[297mm] bg-white p-[12mm] text-[9pt] leading-[1.35] text-black shadow-md print:min-h-0 print:w-auto print:p-0 print:shadow-none"
      aria-label={`${TITLE[kind]} ${reference}`}
    >
      {reprint ? (
        <p className="mb-3 border border-black py-1 text-center text-[9pt] font-bold tracking-wide uppercase">
          Copy. Reprinted {formatDate(printedAt.slice(0, 10))} {printedAt.slice(11, 16)} by {printedBy}
        </p>
      ) : null}

      {/* Header */}
      <header className="flex items-start justify-between gap-6">
        <div className="space-y-0.5">
          <PrintLogo className="mb-2 h-[9mm]" />
          <p className="text-[10pt] font-bold">{issuer.name}</p>
          <p>{issuer.branch}</p>
          {issuer.address.map((a) => (
            <p key={a}>{a}</p>
          ))}
          {issuer.phone || issuer.email ? <p>{[issuer.phone ? `Tel ${issuer.phone}` : "", issuer.email ?? ""].filter(Boolean).join(" · ")}</p> : null}
          {issuer.kraPin ? <p className="font-semibold">KRA PIN {issuer.kraPin}</p> : null}
        </div>
        <div className="text-right">
          <h1 className="text-[18pt] leading-none font-bold tracking-tight uppercase">{TITLE[kind]}</h1>
          {status ? <p className="mt-2 inline-block border border-black px-2 py-0.5 text-[9pt] font-bold uppercase">{status}</p> : null}
          <dl className="mt-3 grid grid-cols-[auto_auto] justify-end gap-x-3 gap-y-0.5 text-left tabular-nums">
            <dt>{kind === "invoice" ? "Invoice no" : kind === "quotation" ? "Quotation no" : "Delivery note no"}</dt>
            <dd className="font-semibold">{reference}</dd>
            <dt>Date</dt>
            <dd>{formatDate(date)}</dd>
            {dueDate ? (
              <>
                <dt>Due</dt>
                <dd>{formatDate(dueDate)}</dd>
              </>
            ) : null}
            {validUntil ? (
              <>
                <dt>Valid until</dt>
                <dd className="font-semibold">{formatDate(validUntil)}</dd>
              </>
            ) : null}
            {related.map((r) => (
              <React.Fragment key={r.label}>
                <dt>{r.label}</dt>
                <dd>{r.value}</dd>
              </React.Fragment>
            ))}
          </dl>
        </div>
      </header>

      {/* Parties */}
      <section className="mt-6 grid grid-cols-2 gap-6 border-y border-black py-3">
        <div className="space-y-0.5">
          <p className="text-[8pt] font-bold tracking-wide uppercase">{kind === "delivery-note" ? "Deliver to" : kind === "quotation" ? "Prepared for" : "Bill to"}</p>
          <p className="font-semibold">{party.name}</p>
          {party.address?.map((a) => (
            <p key={a}>{a}</p>
          ))}
          {party.phone ? <p>Tel {party.phone}</p> : null}
          {party.kraPin ? <p>KRA PIN {party.kraPin}</p> : null}
        </div>
        <div className="space-y-0.5">
          <p className="text-[8pt] font-bold tracking-wide uppercase">Prepared by</p>
          <p>{printedBy}</p>
          <p className="tabular-nums">
            Printed {formatDate(printedAt.slice(0, 10))} {printedAt.slice(11, 16)}
          </p>
        </div>
      </section>

      {kind === "quotation" ? (
        <p className="mt-3 border border-black px-2 py-1 font-semibold">
          This is a quotation, not a tax invoice. {validUntil ? `Prices are held until ${formatDate(validUntil)}; stock` : "Stock"} is not reserved.
        </p>
      ) : null}

      {/* Lines */}
      <table className="mt-4 w-full border-collapse tabular-nums">
        <thead className="[display:table-header-group]">
          {/* Repeats on every printed page: which document this page belongs to (spec: printed-at on every page). */}
          <tr className="hidden print:table-row">
            <th colSpan={cols} className="pb-1 text-left text-[7.5pt] font-normal">
              {TITLE[kind]} {reference} · {issuer.name} · printed {formatDate(printedAt.slice(0, 10))} {printedAt.slice(11, 16)}
            </th>
          </tr>
          <tr className="border-b border-black text-left text-[8pt] uppercase">
            <th className="w-[6%] py-1 pr-2 font-bold">#</th>
            <th className="w-[12%] py-1 pr-2 font-bold">Code</th>
            <th className="py-1 pr-2 font-bold">Item</th>
            {priced ? (
              <>
                <th className="w-[9%] py-1 pr-2 text-right font-bold">Qty</th>
                <th className="w-[13%] py-1 pr-2 text-right font-bold">Unit price</th>
                <th className="w-[8%] py-1 pr-2 text-center font-bold">VAT</th>
                <th className="w-[14%] py-1 text-right font-bold">Amount</th>
              </>
            ) : (
              <>
                <th className="w-[16%] py-1 pr-2 font-bold">Batch · expiry</th>
                <th className="w-[10%] py-1 pr-2 text-right font-bold">Ordered</th>
                <th className="w-[10%] py-1 pr-2 text-right font-bold">Delivered</th>
                <th className="w-[12%] py-1 text-right font-bold">Received</th>
              </>
            )}
          </tr>
        </thead>
        <tbody>
          {lines.map((l, i) => {
            const unit = (l.price ?? 0) - (l.discount ?? 0);
            return (
              <tr key={`${l.code}-${i}`} className="break-inside-avoid border-b border-neutral-300 align-top">
                <td className="py-1 pr-2">{i + 1}</td>
                <td className="py-1 pr-2 font-mono text-[8pt]">{l.code}</td>
                <td className="py-1 pr-2">
                  {l.name}
                  {priced && l.discount ? <span className="block text-[7.5pt]">Less {amt(l.discount)} each</span> : null}
                </td>
                {priced ? (
                  <>
                    <td className="py-1 pr-2 text-right">
                      {l.qty}
                      {l.unit ? ` ${l.unit}` : ""}
                    </td>
                    <td className="py-1 pr-2 text-right">{amt(unit)}</td>
                    <td className="py-1 pr-2 text-center">{l.vat ?? "A"}</td>
                    <td className="py-1 text-right">{amt(unit * l.qty)}</td>
                  </>
                ) : (
                  <>
                    <td className="py-1 pr-2 font-mono text-[8pt]">
                      {l.batch ?? "–"}
                      {l.expiry ? <span className="block font-sans">exp {formatDate(l.expiry)}</span> : null}
                    </td>
                    <td className="py-1 pr-2 text-right">{l.ordered ?? l.qty}</td>
                    <td className="py-1 pr-2 text-right font-semibold">{l.qty}</td>
                    <td className="py-1 text-right">________</td>
                  </>
                )}
              </tr>
            );
          })}
        </tbody>
      </table>

      {/* Totals and eTIMS */}
      {priced ? (
        <section className="mt-4 grid break-inside-avoid grid-cols-[1fr_auto] gap-8">
          <div className="space-y-3">
            <table className="w-full max-w-[95mm] border-collapse text-[8pt] tabular-nums">
              <thead>
                <tr className="border-b border-black text-left uppercase">
                  <th className="py-0.5 font-bold">VAT class</th>
                  <th className="py-0.5 text-right font-bold">Taxable</th>
                  <th className="py-0.5 text-right font-bold">VAT</th>
                </tr>
              </thead>
              <tbody>
                {t.rows.map(([c, r]) => (
                  <tr key={c} className="border-b border-neutral-300">
                    <td className="py-0.5">{VAT_CLASS[c].label}</td>
                    <td className="py-0.5 text-right">{amt(r.taxable)}</td>
                    <td className="py-0.5 text-right">{amt(r.vat)}</td>
                  </tr>
                ))}
              </tbody>
            </table>
            {kind === "invoice" && etims ? <EtimsBlock etims={etims} /> : null}
          </div>
          <dl className="grid min-w-[62mm] grid-cols-[1fr_auto] content-start gap-x-4 gap-y-0.5 tabular-nums">
            {t.discount ? (
              <>
                <dt>Discount given</dt>
                <dd className="text-right">{amt(t.discount)}</dd>
              </>
            ) : null}
            <dt>Subtotal (excl. VAT)</dt>
            <dd className="text-right">{amt(t.subtotal)}</dd>
            <dt>VAT</dt>
            <dd className="text-right">{amt(t.vat)}</dd>
            <dt className="mt-1 border-t border-black pt-1 text-[11pt] font-bold">Total KES</dt>
            <dd className="mt-1 border-t border-black pt-1 text-right text-[11pt] font-bold">{amt(t.total)}</dd>
            {paid != null ? (
              <>
                <dt>Paid</dt>
                <dd className="text-right">{amt(paid)}</dd>
                <dt className="font-bold">Balance due</dt>
                <dd className="text-right font-bold">{amt(balance ?? 0)}</dd>
              </>
            ) : null}
          </dl>
        </section>
      ) : (
        <section className="mt-8 grid break-inside-avoid grid-cols-2 gap-10">
          {["Delivered by", "Received by"].map((who) => (
            <div key={who} className="space-y-5">
              <p className="text-[8pt] font-bold tracking-wide uppercase">{who}</p>
              {["Name", "Signature", "Date and time"].map((f) => (
                <p key={f} className="flex items-end gap-2">
                  <span className="w-24 shrink-0">{f}</span>
                  <span className="h-px flex-1 bg-black" />
                </p>
              ))}
            </div>
          ))}
          <p className="col-span-2 text-[8pt]">Check quantities, batches and expiry dates on receipt. Note any shortfall or damage on this copy before signing.</p>
        </section>
      )}

      {notes.length || paymentDetails.length ? (
        <section className="mt-6 grid break-inside-avoid grid-cols-2 gap-6 border-t border-neutral-400 pt-3 text-[8pt]">
          {paymentDetails.length ? (
            <div className="space-y-0.5">
              <p className="font-bold tracking-wide uppercase">How to pay</p>
              {paymentDetails.map((p) => (
                <p key={p}>{p}</p>
              ))}
            </div>
          ) : (
            <div />
          )}
          {notes.length ? (
            <div className="space-y-0.5">
              <p className="font-bold tracking-wide uppercase">Notes</p>
              {notes.map((n) => (
                <p key={n}>{n}</p>
              ))}
            </div>
          ) : null}
        </section>
      ) : null}

      {/* Screen-only stand-in for the @page footer that prints "Page x of y". */}
      <p className="absolute right-[12mm] bottom-[6mm] text-[8pt] print:hidden">Page 1 of 1 · Medora</p>
    </div>
  );
});

/** eTIMS details as KRA requires them on the invoice, or a "will follow" note while the sale is queued (IG-09). */
export function EtimsBlock({ etims, compact = false }: { etims: Etims; compact?: boolean }) {
  if (etims.state === "queued") {
    return (
      <div className={cn("border border-dashed border-black p-2", compact ? "text-[7.5pt]" : "max-w-[95mm] text-[8pt]")}>
        <p className="font-bold uppercase">KRA details to follow</p>
        <p>This sale is queued for eTIMS. The KRA invoice number and QR code will be on the signed copy.</p>
      </div>
    );
  }
  const rows: [string, string][] = [
    ["SCU ID", etims.scuId],
    ["CU invoice no", etims.cuInvoiceNo],
    ["Internal data", etims.internalData],
    ["Signature", etims.signature],
    ["Signed", `${formatDate(etims.signedAt.slice(0, 10))} ${etims.signedAt.slice(11, 19)}`],
  ];
  return (
    <div className={cn("flex gap-3", compact ? "flex-col items-center text-center text-[7pt]" : "max-w-[95mm] items-start text-[7.5pt]")}>
      <KraQr value={etims.qrUrl} size={compact ? 22 : 20} />
      <div>
        <p className="font-bold uppercase">eTIMS, KRA</p>
        <dl className={cn("grid gap-x-2 tabular-nums", compact ? "grid-cols-1" : "grid-cols-[auto_1fr]")}>
          {rows.map(([k, v]) => (
            <React.Fragment key={k}>
              <dt className={compact ? "hidden" : undefined}>{k}</dt>
              <dd className="font-mono break-all">{compact ? `${k}: ${v}` : v}</dd>
            </React.Fragment>
          ))}
        </dl>
      </div>
    </div>
  );
}

/* -------------------------------------- Labels ------------------------------------- */

export type LabelProduct = { name: string; strength?: string; pack?: string; price: number; unitPrice?: { amount: number; unit: string }; barcode: string; shelf?: string; rx?: boolean };

/** Shelf-edge label, 70 × 42.3 mm (21 per A4 sheet). Price is the largest thing on it; per-unit price for comparison. */
export function ShelfLabel({ product, printedOn }: { product: LabelProduct; printedOn: string }) {
  return (
    <div className="box-border flex h-[42.3mm] w-[70mm] flex-col overflow-hidden bg-white px-[3mm] py-[2.5mm] text-black">
      <div className="flex items-start justify-between gap-2">
        <p className="line-clamp-2 text-[9pt] leading-tight font-bold">{product.name}</p>
        {product.rx ? <span className="shrink-0 border border-black px-1 text-[6.5pt] font-bold uppercase">Rx only</span> : null}
      </div>
      <p className="text-[7pt]">{[product.strength, product.pack].filter(Boolean).join(" · ")}</p>
      <div className="mt-auto flex items-end justify-between gap-2">
        <div>
          <p className="text-[16pt] leading-none font-bold tabular-nums">
            <span className="text-[7pt] font-normal">KES </span>
            {amt(product.price)}
          </p>
          {product.unitPrice ? (
            <p className="mt-0.5 text-[6.5pt] tabular-nums">
              {amt(product.unitPrice.amount)} per {product.unitPrice.unit}
            </p>
          ) : null}
          <p className="mt-0.5 text-[6pt] tabular-nums">
            {product.shelf ? `${product.shelf} · ` : ""}
            {formatDate(printedOn, { short: true })}
          </p>
        </div>
        <Ean13 value={product.barcode} width={30} />
      </div>
    </div>
  );
}

/** Product barcode label, 38 × 25 mm on a roll: for stock that arrives without a barcode. */
export function BarcodeLabel({ product }: { product: LabelProduct }) {
  return (
    <div className="box-border flex h-[25mm] w-[38mm] flex-col items-center justify-between overflow-hidden bg-white px-[1.5mm] py-[1mm] text-black">
      <p className="w-full truncate text-center text-[6.5pt] font-semibold">
        {product.name}
        {product.strength ? ` ${product.strength}` : ""}
      </p>
      <Ean13 value={product.barcode} width={34} />
      <p className="text-[7pt] font-bold tabular-nums">KES {amt(product.price)}</p>
    </div>
  );
}

export type DispenseLabel = {
  patient: string;
  medicine: string;
  quantity: string;
  directions: string;
  warnings: string[];
  rxNo: string;
  date: string;
  pharmacist: string;
};

/**
 * Dispensing label, 70 × 40 mm: what the Pharmacy and Poisons Board expects on a dispensed medicine
 * (patient, medicine and strength, quantity, directions, date, pharmacy name and address).
 * Directions are the largest text: they are what the patient reads at home.
 */
export function DispensingLabel({ label, issuer }: { label: DispenseLabel; issuer: Issuer }) {
  return (
    <div className="box-border flex h-[40mm] w-[70mm] flex-col overflow-hidden bg-white px-[2.5mm] py-[2mm] text-[6.5pt] leading-tight text-black">
      <div className="flex justify-between gap-2 border-b border-black pb-0.5">
        <span className="font-bold">{label.patient}</span>
        <span className="tabular-nums">{formatDate(label.date)}</span>
      </div>
      <p className="mt-1 text-[8pt] font-bold">
        {label.medicine} <span className="font-normal">× {label.quantity}</span>
      </p>
      <p className="mt-0.5 text-[9pt] leading-snug font-bold">{label.directions}</p>
      <ul className="mt-0.5">
        {label.warnings.map((w) => (
          <li key={w}>{w}</li>
        ))}
      </ul>
      <div className="mt-auto flex justify-between gap-2 border-t border-black pt-0.5 text-[6pt]">
        <span className="truncate">
          {issuer.name}, {issuer.branch} · {issuer.phone}
        </span>
        <span className="shrink-0 tabular-nums">
          {label.rxNo} · {label.pharmacist}
        </span>
      </div>
    </div>
  );
}

/**
 * A4 sheet of 21 labels (3 × 7, 70 × 42.3 mm, no margins). `startAt` skips labels already peeled off a
 * part-used sheet, so it isn't wasted. Labels that don't fit continue on further sheets.
 */
export const labelSheetCount = (labels: number, startAt = 1) => Math.max(1, Math.ceil((startAt - 1 + labels) / 21));

export const LabelSheet = React.forwardRef<HTMLDivElement, { children: React.ReactNode[]; startAt?: number; outlines?: boolean }>(function LabelSheet({ children, startAt = 1, outlines = true }, ref) {
  const cells: React.ReactNode[] = [...Array.from({ length: startAt - 1 }, () => null), ...children];
  const sheets = labelSheetCount(children.length, startAt);
  while (cells.length < sheets * 21) cells.push(null);
  return (
    <div ref={ref} className="space-y-6 print:space-y-0">
      {Array.from({ length: sheets }, (_, s) => (
        <div
          key={s}
          className="mx-auto grid w-[210mm] grid-cols-[repeat(3,70mm)] grid-rows-[repeat(7,42.3mm)] bg-white shadow-md break-after-page print:shadow-none"
          aria-label={`Label sheet ${s + 1} of ${sheets}`}
        >
          {cells.slice(s * 21, s * 21 + 21).map((c, j) => {
            const i = s * 21 + j;
            return (
              <div key={i} className={cn("overflow-hidden", outlines && "outline outline-1 -outline-offset-1 outline-neutral-300 print:outline-none", !c && "bg-neutral-50 print:bg-white")}>
                {c ?? <span className="sr-only">Label {j + 1}, {i < startAt - 1 ? "already used" : "blank"}</span>}
              </div>
            );
          })}
        </div>
      ))}
    </div>
  );
});

/** A run of roll labels: one label per printed page. */
export const LabelRoll = React.forwardRef<HTMLDivElement, { children: React.ReactNode[] }>(function LabelRoll({ children }, ref) {
  return (
    <div ref={ref} className="flex flex-wrap gap-3 print:block">
      {children.map((c, i) => (
        <div key={i} className="border border-neutral-300 shadow-sm break-after-page print:border-0 print:shadow-none">
          {c}
        </div>
      ))}
    </div>
  );
});
