"use client";

import * as React from "react";
import { CheckIcon, FileImageIcon, MinusIcon, PlusIcon, XIcon } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select";
import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";

import { AsyncBadge } from "./async-status";
import { TONE_BOX } from "./tones";

const fmt = new Intl.NumberFormat("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
export const kes = (n: number) => `KES ${fmt.format(n)}`;
const r2 = (n: number) => Math.round(n * 100) / 100;

/** A small controlled − n + stepper. */
export function Stepper({ value, min = 0, max, onChange, label, disabled }: { value: number; min?: number; max: number; onChange: (v: number) => void; label: string; disabled?: boolean }) {
  return (
    <span className="inline-flex items-center gap-0.5 rounded-lg border border-border bg-background p-0.5">
      <Button type="button" variant="ghost" size="icon-xs" aria-label={`Fewer, ${label}`} disabled={disabled || value <= min} onClick={() => onChange(Math.max(min, value - 1))}>
        <MinusIcon />
      </Button>
      <span className="min-w-7 text-center text-sm font-medium tabular-nums" aria-label={`${label}: ${value}`}>
        {value}
      </span>
      <Button type="button" variant="ghost" size="icon-xs" aria-label={`More, ${label}`} disabled={disabled || value >= max} onClick={() => onChange(Math.min(max, value + 1))}>
        <PlusIcon />
      </Button>
    </span>
  );
}

/* ------------------------------- 1. Returns and exchanges ------------------------------- */

export type SoldLine = { id: string; name: string; sold: number; /** Already returned on earlier credit notes. */ returned: number; /** Paid per unit, VAT included, after discount. */ unitPaid: number; rx?: boolean };
export type ReturnReason = "Wrong item" | "Customer changed mind" | "Expired" | "Damaged" | "Adverse reaction";
const NO_RESTOCK: ReturnReason[] = ["Expired", "Damaged", "Adverse reaction"];
export const RETURN_REASONS: ReturnReason[] = ["Wrong item", "Customer changed mind", "Expired", "Damaged", "Adverse reaction"];

export type ReturnMode = "return" | "exchange" | "reverse";
export type ReturnPick = { qty: number; reason: ReturnReason; restock: boolean };

/** Can this line go back on the shelf? Expired, damaged or reacted-to stock can't; opened Rx items can't either. */
export const canRestock = (line: SoldLine, reason: ReturnReason) => !NO_RESTOCK.includes(reason) && !(line.rx && reason !== "Wrong item");

/**
 * Return builder (PRD 2.7): pick lines from the original sale. Quantity can't exceed what was sold less what
 * was already returned; each line has a reason, and whether it goes back to stock follows the reason (the
 * user can't restock expired or damaged items). Reverse sale takes everything and can't be edited.
 * Shows the refund, the stock effect, and for an exchange the credit to put towards the new basket.
 */
export function ReturnBuilder({ lines, mode, picks, onChange }: { lines: SoldLine[]; mode: ReturnMode; picks: Record<string, ReturnPick>; onChange: (p: Record<string, ReturnPick>) => void }) {
  const set = (id: string, patch: Partial<ReturnPick>) => {
    const line = lines.find((l) => l.id === id)!;
    const cur = picks[id] ?? { qty: 0, reason: "Customer changed mind" as ReturnReason, restock: true };
    const next = { ...cur, ...patch };
    if (!canRestock(line, next.reason)) next.restock = false;
    else if (patch.reason) next.restock = true;
    onChange({ ...picks, [id]: next });
  };
  const reverse = mode === "reverse";
  const chosen = lines.filter((l) => (picks[l.id]?.qty ?? 0) > 0);
  const refund = r2(chosen.reduce((s, l) => s + picks[l.id].qty * l.unitPaid, 0));
  const restocked = chosen.filter((l) => picks[l.id].restock).reduce((s, l) => s + picks[l.id].qty, 0);
  const writtenOff = chosen.filter((l) => !picks[l.id].restock).reduce((s, l) => s + picks[l.id].qty, 0);

  return (
    <div className="space-y-3">
      <div className="overflow-x-auto rounded-lg border border-[var(--color-border-panel)] bg-card">
        <table className="w-full border-collapse text-sm">
          <caption className="sr-only">Lines on the original sale</caption>
          <thead className="bg-[var(--color-bg-surface-subtle)] text-left text-xs text-muted-foreground">
            <tr>
              <th scope="col" className="px-3 py-2 font-medium">
                Item
              </th>
              <th scope="col" className="px-3 py-2 text-right font-medium">
                Can return
              </th>
              <th scope="col" className="px-3 py-2 font-medium">
                {mode === "exchange" ? "Exchange" : "Return"}
              </th>
              <th scope="col" className="px-3 py-2 font-medium">
                Reason
              </th>
              <th scope="col" className="px-3 py-2 font-medium">
                Stock
              </th>
              <th scope="col" className="px-3 py-2 text-right font-medium">
                Refund
              </th>
            </tr>
          </thead>
          <tbody className="divide-y divide-[var(--color-border-subtle)]">
            {lines.map((l) => {
              const left = l.sold - l.returned;
              const p = picks[l.id] ?? { qty: 0, reason: "Customer changed mind" as ReturnReason, restock: true };
              const restockable = canRestock(l, p.reason);
              return (
                <tr key={l.id} className={cn(left === 0 && "text-muted-foreground")}>
                  <th scope="row" className="px-3 py-2 text-left font-medium">
                    {l.name}
                    {l.rx ? <span className="ml-1.5 text-xs font-normal text-muted-foreground">Rx</span> : null}
                    <span className="block text-xs font-normal text-muted-foreground tabular-nums">
                      Sold {l.sold} at {fmt.format(l.unitPaid)}
                      {l.returned ? `, ${l.returned} already returned` : ""}
                    </span>
                  </th>
                  <td className="px-3 py-2 text-right tabular-nums">{left}</td>
                  <td className="px-3 py-2">{left === 0 ? <span className="text-xs">All returned</span> : <Stepper value={p.qty} max={left} disabled={reverse} label={l.name} onChange={(v) => set(l.id, { qty: v })} />}</td>
                  <td className="px-3 py-2">
                    {p.qty > 0 ? (
                      <NativeSelect size="sm" aria-label={`Reason, ${l.name}`} value={p.reason} onChange={(e) => set(l.id, { reason: e.target.value as ReturnReason })}>
                        {RETURN_REASONS.map((r) => (
                          <NativeSelectOption key={r} value={r}>
                            {r}
                          </NativeSelectOption>
                        ))}
                      </NativeSelect>
                    ) : null}
                  </td>
                  <td className="px-3 py-2 text-xs">
                    {p.qty > 0 ? (
                      restockable ? (
                        <label className="inline-flex items-center gap-1.5">
                          <input type="checkbox" className="size-4 accent-[var(--color-brand-primary,currentColor)]" checked={p.restock} onChange={(e) => set(l.id, { restock: e.target.checked })} /> Back to stock
                        </label>
                      ) : (
                        <span className="font-medium text-status-warning-text">Written off{l.rx && p.reason !== "Wrong item" && !NO_RESTOCK.includes(p.reason) ? " (Rx)" : ""}</span>
                      )
                    ) : null}
                  </td>
                  <td className="px-3 py-2 text-right tabular-nums">{p.qty ? fmt.format(p.qty * l.unitPaid) : ""}</td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
      <dl className="grid grid-cols-2 gap-px overflow-hidden rounded-lg border border-[var(--color-border-panel)] bg-[var(--color-border-panel)] text-sm sm:grid-cols-3">
        {[
          [mode === "exchange" ? "Credit towards new items" : "Refund", kes(refund)],
          ["Back to stock", `${restocked} ${restocked === 1 ? "unit" : "units"}`],
          ["Written off", `${writtenOff} ${writtenOff === 1 ? "unit" : "units"}`],
        ].map(([k, v]) => (
          <div key={k} className="bg-card px-3 py-2">
            <dt className="text-xs text-muted-foreground">{k}</dt>
            <dd className="font-semibold tabular-nums">{v}</dd>
          </div>
        ))}
      </dl>
    </div>
  );
}

/* ------------------------------- 2. Online order with Rx ------------------------------- */

export type OnlineOrderStage = "new" | "rx-review" | "rx-rejected" | "packing" | "ready" | "dispatched" | "delivered";
export type OnlineOrder = {
  ref: string;
  customer: string;
  phone: string;
  placedAt: string;
  fulfilment: "Delivery" | "Pickup";
  address?: string;
  paid: { method: string; amount: number; status: "Paid" | "Pay on delivery" };
  items: { name: string; qty: number; rx?: boolean }[];
  prescription?: { file: string; doctor?: string; decision?: { approved: boolean; by: string; at: string; reason?: string } };
  stage: OnlineOrderStage;
};

/**
 * Prescription review for an online order (PRD 2.3, PS-12): the uploaded image beside the Rx items. Approve or
 * reject (a reason is required to reject); the decision is stored with who and when. Rx items can't be packed
 * until the prescription is approved.
 */
export function RxReview({ order, reviewer, onDecide }: { order: OnlineOrder; reviewer: string; onDecide: (d: { approved: boolean; reason?: string }) => void }) {
  const [rejecting, setRejecting] = React.useState(false);
  const [reason, setReason] = React.useState("");
  const [tried, setTried] = React.useState(false);
  const d = order.prescription?.decision;
  const rxItems = order.items.filter((i) => i.rx);
  return (
    <div className="grid gap-4 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
      <figure className="flex min-h-56 flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-[var(--color-border-panel)] bg-muted/40 p-4 text-center">
        <FileImageIcon className="size-8 text-muted-foreground" aria-hidden />
        <figcaption className="text-sm">
          <span className="font-medium">{order.prescription?.file ?? "No prescription uploaded"}</span>
          {order.prescription?.doctor ? <span className="block text-xs text-muted-foreground">{order.prescription.doctor}</span> : null}
          <span className="mt-1 block text-xs text-muted-foreground">Open full size to check the patient, medicine, dose and date.</span>
        </figcaption>
      </figure>
      <div className="space-y-3">
        <div>
          <p className="text-sm font-semibold">Prescription items</p>
          <ul className="mt-1 list-disc pl-5 text-sm">
            {rxItems.map((i) => (
              <li key={i.name}>
                {i.name} × {i.qty}
              </li>
            ))}
          </ul>
        </div>
        {d ? (
          <p className={cn("rounded-md border px-3 py-2 text-sm", d.approved ? TONE_BOX.success : TONE_BOX.error)}>
            <span className="font-semibold">{d.approved ? "Approved" : "Rejected"}</span> by {d.by}, {d.at}
            {d.reason ? <span className="block">{d.reason}</span> : null}
          </p>
        ) : rejecting ? (
          <div className="space-y-2">
            <Field data-invalid={tried && !reason.trim()}>
              <FieldLabel htmlFor={`rx-reason-${order.ref}`}>Why is it rejected?</FieldLabel>
              <Textarea id={`rx-reason-${order.ref}`} rows={2} value={reason} onChange={(e) => setReason(e.target.value)} placeholder="e.g. The prescription is older than 30 days." aria-invalid={tried && !reason.trim()} />
              {tried && !reason.trim() ? <FieldError>Say why, so the customer knows what to send.</FieldError> : <FieldDescription>Sent to the customer with the order update.</FieldDescription>}
            </Field>
            <div className="flex gap-2">
              <Button size="sm" variant="ghost" onClick={() => setRejecting(false)}>
                Back
              </Button>
              <Button
                size="sm"
                variant="destructive"
                onClick={() => {
                  setTried(true);
                  if (reason.trim()) onDecide({ approved: false, reason: reason.trim() });
                }}
              >
                Reject prescription
              </Button>
            </div>
          </div>
        ) : (
          <div className="flex flex-wrap gap-2">
            <Button size="sm" onClick={() => onDecide({ approved: true })}>
              <CheckIcon data-icon="inline-start" />
              Approve as {reviewer}
            </Button>
            <Button size="sm" variant="outline" onClick={() => setRejecting(true)}>
              <XIcon data-icon="inline-start" />
              Reject…
            </Button>
          </div>
        )}
      </div>
    </div>
  );
}

const STAGE_LABEL: Record<OnlineOrderStage, string> = {
  new: "New",
  "rx-review": "Prescription to check",
  "rx-rejected": "Prescription rejected",
  packing: "Packing",
  ready: "Ready for pickup",
  dispatched: "Out for delivery",
  delivered: "Delivered",
};
export const onlineStageBadge = (s: OnlineOrderStage) => (
  <AsyncBadge
    state={s === "delivered" ? "done" : s === "rx-rejected" ? "attention" : s === "rx-review" ? "queued" : "waiting"}
    severe={s === "rx-rejected"}
    label={STAGE_LABEL[s]}
    className={cn(s !== "rx-review" && s !== "delivered" && s !== "rx-rejected" && "[&>svg]:animate-none")}
  />
);

/* ------------------------------------- 3. Preorders ------------------------------------- */

export type Preorder = { ref: string; customer: string; phone: string; item: string; qty: number; deposit: number; status: "Waiting for stock" | "Stock arrived" | "Collected" | "Cancelled"; placed: string; notified?: string };

export function preorderBadge(status: Preorder["status"]) {
  const state = status === "Collected" ? "done" : status === "Stock arrived" ? "attention" : status === "Cancelled" ? "failed" : "queued";
  return <AsyncBadge state={state} label={status} />;
}

/* --------------------------------------- 4. Drops --------------------------------------- */

export type DropKind = "Cash" | "M-Pesa";

/**
 * Cash or M-Pesa drop (PRD 2.5): move money out of the drawer or till to an account (banking the cash,
 * sweeping the till). Can't exceed what's there; the drawer balance after the drop is shown before saving.
 * A cash drop needs a witness and a bag or deposit slip number.
 */
export function DropForm({
  balances,
  accounts,
  onSubmit,
}: {
  balances: Record<DropKind, number>;
  accounts: { code: string; name: string; kind: DropKind }[];
  onSubmit: (d: { kind: DropKind; amount: number; to: string; reference: string; witness?: string }) => void;
}) {
  const [kind, setKind] = React.useState<DropKind>("Cash");
  const [amount, setAmount] = React.useState("");
  const [to, setTo] = React.useState("");
  const [reference, setReference] = React.useState("");
  const [witness, setWitness] = React.useState("");
  const [tried, setTried] = React.useState(false);
  const n = Number(amount.replace(/,/g, ""));
  const bal = balances[kind];
  const errs = {
    amount: !(n > 0) ? "Enter the amount dropped." : n > bal ? `The ${kind === "Cash" ? "drawer" : "till"} only has ${kes(bal)}.` : undefined,
    to: to ? undefined : "Choose where the money goes.",
    reference: reference.trim() ? undefined : kind === "Cash" ? "Enter the bag or deposit slip number." : "Enter the M-Pesa transaction code.",
    witness: kind === "Cash" && !witness.trim() ? "A second person must witness a cash drop." : undefined,
  };
  const err = (k: keyof typeof errs) => (tried ? errs[k] : undefined);
  return (
    <div className="max-w-xl space-y-4">
      <div className="grid gap-4">
        <Field>
          <FieldLabel htmlFor="drop-kind">Drop from</FieldLabel>
          <NativeSelect
            id="drop-kind"
            value={kind}
            onChange={(e) => {
              setKind(e.target.value as DropKind);
              setTo("");
            }}
          >
            <NativeSelectOption value="Cash">Cash drawer ({kes(balances.Cash)})</NativeSelectOption>
            <NativeSelectOption value="M-Pesa">M-Pesa till ({kes(balances["M-Pesa"])})</NativeSelectOption>
          </NativeSelect>
        </Field>
        <Field data-invalid={!!err("amount")}>
          <FieldLabel htmlFor="drop-amount">Amount (KES)</FieldLabel>
          <Input id="drop-amount" inputMode="decimal" className="tabular-nums" value={amount} onChange={(e) => setAmount(e.target.value)} aria-invalid={!!err("amount")} />
          {err("amount") ? <FieldError>{err("amount")}</FieldError> : <FieldDescription>{n > 0 && n <= bal ? `${kes(r2(bal - n))} stays in the ${kind === "Cash" ? "drawer" : "till"}.` : `Up to ${kes(bal)}.`}</FieldDescription>}
        </Field>
        <Field data-invalid={!!err("to")}>
          <FieldLabel htmlFor="drop-to">To</FieldLabel>
          <NativeSelect id="drop-to" value={to} onChange={(e) => setTo(e.target.value)} aria-invalid={!!err("to")}>
            <NativeSelectOption value="">Choose an account</NativeSelectOption>
            {accounts
              .filter((a) => a.kind === kind)
              .map((a) => (
                <NativeSelectOption key={a.code} value={`${a.code} ${a.name}`}>
                  {a.code} {a.name}
                </NativeSelectOption>
              ))}
          </NativeSelect>
          {err("to") ? <FieldError>{err("to")}</FieldError> : null}
        </Field>
        <Field data-invalid={!!err("reference")}>
          <FieldLabel htmlFor="drop-ref">{kind === "Cash" ? "Bag or deposit slip number" : "M-Pesa code"}</FieldLabel>
          <Input id="drop-ref" className="font-mono" value={reference} onChange={(e) => setReference(kind === "M-Pesa" ? e.target.value.toUpperCase() : e.target.value)} aria-invalid={!!err("reference")} />
          {err("reference") ? <FieldError>{err("reference")}</FieldError> : null}
        </Field>
        {kind === "Cash" ? (
          <Field data-invalid={!!err("witness")}>
            <FieldLabel htmlFor="drop-witness">Witnessed by</FieldLabel>
            <Input id="drop-witness" value={witness} onChange={(e) => setWitness(e.target.value)} aria-invalid={!!err("witness")} />
            {err("witness") ? <FieldError>{err("witness")}</FieldError> : null}
          </Field>
        ) : null}
      </div>
      <div className="flex justify-end">
        <Button
          onClick={() => {
            setTried(true);
            if (!Object.values(errs).some(Boolean)) onSubmit({ kind, amount: n, to, reference: reference.trim(), witness: witness.trim() || undefined });
          }}
        >
          Record {kind === "Cash" ? "cash" : "M-Pesa"} drop
        </Button>
      </div>
    </div>
  );
}

/* ------------------------------------- 5. Open shift ------------------------------------- */

/**
 * Open a shift (PRD 2.8): cash counted into the drawer and the M-Pesa till balance at the start. Compared with
 * what the last shift left; a difference needs a note before the shift can open, so it's explained while
 * people still remember.
 */
export function OpenShiftForm({
  lastClose,
  onOpen,
  busy,
}: {
  /** Card 014: null for a branch's first shift; a figure is null when that close didn't record it. */
  lastClose: { cashLeft: number | null; mpesa: number | null; by: string; at: string } | null;
  onOpen: (o: { cash: number; mpesa: number; note?: string }) => void;
  busy?: boolean;
}) {
  const [cash, setCash] = React.useState("");
  const [mpesa, setMpesa] = React.useState("");
  const [note, setNote] = React.useState("");
  const [tried, setTried] = React.useState(false);
  const c = Number(cash.replace(/,/g, ""));
  const m = Number(mpesa.replace(/,/g, ""));
  const cashDiff = cash.trim() && lastClose?.cashLeft != null ? r2(c - lastClose.cashLeft) : 0;
  const mDiff = mpesa.trim() && lastClose?.mpesa != null ? r2(m - lastClose.mpesa) : 0;
  const differs = cashDiff !== 0 || mDiff !== 0;
  const errs = {
    cash: !cash.trim() || !(c >= 0) || Number.isNaN(c) ? "Count the cash in the drawer and enter the total." : undefined,
    mpesa: !mpesa.trim() || Number.isNaN(m) ? "Enter the till balance from the M-Pesa statement." : undefined,
    note: differs && !note.trim() ? "Explain the difference before opening." : undefined,
  };
  const diffText = (d: number) => (d === 0 ? "matches" : `${d > 0 ? "over" : "short"} by ${kes(Math.abs(d))}`);
  return (
    <div className="max-w-xl space-y-4">
      <p className="text-sm text-muted-foreground">
        {!lastClose
          ? "This is the first shift here, so there's nothing to compare the count with."
          : `Last shift closed by ${lastClose.by}, ${lastClose.at}: ${lastClose.cashLeft != null ? `${kes(lastClose.cashLeft)} left in the drawer` : "cash left in the drawer not recorded"}, ${lastClose.mpesa != null ? `M-Pesa till at ${kes(lastClose.mpesa)}` : "M-Pesa till not recorded"}.`}
      </p>
      <div className="grid gap-4">
        <Field data-invalid={tried && !!errs.cash}>
          <FieldLabel htmlFor="os-cash">Cash counted (KES)</FieldLabel>
          <Input id="os-cash" inputMode="decimal" className="tabular-nums" value={cash} onChange={(e) => setCash(e.target.value)} aria-invalid={tried && !!errs.cash} />
          {tried && errs.cash ? <FieldError>{errs.cash}</FieldError> : cash.trim() && lastClose?.cashLeft != null ? <FieldDescription className={cn(cashDiff !== 0 && "text-status-warning-text")}>{`Last close ${diffText(cashDiff)}.`}</FieldDescription> : null}
        </Field>
        <Field data-invalid={tried && !!errs.mpesa}>
          <FieldLabel htmlFor="os-mpesa">M-Pesa till balance (KES)</FieldLabel>
          <Input id="os-mpesa" inputMode="decimal" className="tabular-nums" value={mpesa} onChange={(e) => setMpesa(e.target.value)} aria-invalid={tried && !!errs.mpesa} />
          {tried && errs.mpesa ? <FieldError>{errs.mpesa}</FieldError> : mpesa.trim() && lastClose?.mpesa != null ? <FieldDescription className={cn(mDiff !== 0 && "text-status-warning-text")}>{`Last close ${diffText(mDiff)}.`}</FieldDescription> : null}
        </Field>
      </div>
      {differs ? (
        <Field data-invalid={tried && !!errs.note}>
          <FieldLabel htmlFor="os-note">What explains the difference?</FieldLabel>
          <Textarea id="os-note" rows={2} value={note} onChange={(e) => setNote(e.target.value)} aria-invalid={tried && !!errs.note} />
          {tried && errs.note ? <FieldError>{errs.note}</FieldError> : <FieldDescription>Shown on the Z-report and to the branch manager.</FieldDescription>}
        </Field>
      ) : null}
      <div className="flex justify-end">
        <Button
          disabled={busy}
          onClick={() => {
            setTried(true);
            if (!Object.values(errs).some(Boolean)) onOpen({ cash: c, mpesa: m, note: note.trim() || undefined });
          }}
        >
          Open shift
        </Button>
      </div>
    </div>
  );
}

/* ---------------------------------- 6. Loyalty at the till ---------------------------------- */

/**
 * Redeem points at the till (PRD 4.6): points are spent in blocks; the discount can't exceed the basket or
 * the customer's balance. Shows the KES value before applying, and the balance left after.
 */
export function LoyaltyRedeem({ points, kesPerPoint, block, basket, applied, onApply }: { points: number; kesPerPoint: number; block: number; basket: number; applied: number; onApply: (points: number) => void }) {
  const maxByBasket = Math.floor(basket / kesPerPoint / block) * block;
  const max = Math.min(Math.floor(points / block) * block, maxByBasket);
  const [value, setValue] = React.useState(String(applied || max));
  const n = Number(value);
  const err = !Number.isInteger(n) || n < 0 ? "Enter whole points." : n % block ? `Points are used in blocks of ${block}.` : n > max ? `At most ${max.toLocaleString("en-US")} points on this sale.` : undefined;
  if (points < block) return <p className="text-sm text-muted-foreground">{points} points: redeemable from {block}.</p>;
  return (
    <div className="space-y-2">
      <p className="text-sm">
        <span className="font-semibold tabular-nums">{points.toLocaleString("en-US")}</span> points available, worth {kes(points * kesPerPoint)}.
      </p>
      <div className="flex flex-wrap items-end gap-2">
        <Field className="w-36" data-invalid={!!err}>
          <FieldLabel htmlFor="loy-pts">Points to use</FieldLabel>
          <Input id="loy-pts" inputMode="numeric" className="tabular-nums" value={value} onChange={(e) => setValue(e.target.value.replace(/\D/g, ""))} aria-invalid={!!err} />
        </Field>
        <Button size="sm" variant="outline" disabled={!!err || n === 0} onClick={() => onApply(n)}>
          Take off {kes(n * kesPerPoint)}
        </Button>
        {applied ? (
          <Button size="sm" variant="ghost" onClick={() => onApply(0)}>
            Remove
          </Button>
        ) : null}
      </div>
      {err ? <p className="text-xs text-status-error-text">{err}</p> : <p className="text-xs text-muted-foreground">{(points - n).toLocaleString("en-US")} points left after this sale. Up to {max.toLocaleString("en-US")} can be used on this basket.</p>}
    </div>
  );
}
/* --------------------------------- 3b. New preorder --------------------------------- */

/**
 * Take a preorder (PRD 2.4): who, what, how many, and an optional deposit that can't exceed the expected
 * price. The customer's phone is needed so they can be told when stock arrives.
 */
export function PreorderForm({ items, onSave }: { items: { name: string; price: number }[]; onSave: (p: { customer: string; phone: string; item: string; qty: number; deposit: number }) => void }) {
  const [customer, setCustomer] = React.useState("");
  const [phone, setPhone] = React.useState("");
  const [item, setItem] = React.useState("");
  const [qty, setQty] = React.useState(1);
  const [deposit, setDeposit] = React.useState("");
  const [tried, setTried] = React.useState(false);
  const price = items.find((i) => i.name === item)?.price ?? 0;
  const dep = Number(deposit.replace(/,/g, "")) || 0;
  const errs = {
    customer: customer.trim() ? undefined : "Enter the customer's name.",
    phone: /^(\+?254|0)[17]\d{8}$/.test(phone.replace(/\s/g, "")) ? undefined : "Enter a mobile number to text when stock arrives, e.g. 0712 345 678.",
    item: item ? undefined : "Choose the item.",
    deposit: dep > price * qty ? `The deposit can't be more than the expected price, ${kes(price * qty)}.` : undefined,
  };
  const err = (k: keyof typeof errs) => (tried ? errs[k] : undefined);
  return (
    <div className="space-y-3">
      <div className="grid gap-3">
        <Field data-invalid={!!err("customer")}>
          <FieldLabel htmlFor="pre-cust">Customer</FieldLabel>
          <Input id="pre-cust" value={customer} onChange={(e) => setCustomer(e.target.value)} aria-invalid={!!err("customer")} />
          {err("customer") ? <FieldError>{err("customer")}</FieldError> : null}
        </Field>
        <Field data-invalid={!!err("phone")}>
          <FieldLabel htmlFor="pre-phone">Mobile number</FieldLabel>
          <Input id="pre-phone" inputMode="tel" value={phone} onChange={(e) => setPhone(e.target.value)} aria-invalid={!!err("phone")} />
          {err("phone") ? <FieldError>{err("phone")}</FieldError> : null}
        </Field>
        <Field data-invalid={!!err("item")}>
          <FieldLabel htmlFor="pre-item">Item</FieldLabel>
          <NativeSelect id="pre-item" value={item} onChange={(e) => setItem(e.target.value)} aria-invalid={!!err("item")}>
            <NativeSelectOption value="">Choose an item</NativeSelectOption>
            {items.map((i) => (
              <NativeSelectOption key={i.name} value={i.name}>
                {i.name} ({kes(i.price)})
              </NativeSelectOption>
            ))}
          </NativeSelect>
          {err("item") ? <FieldError>{err("item")}</FieldError> : null}
        </Field>
        <Field>
          <FieldLabel>Quantity</FieldLabel>
          <Stepper value={qty} min={1} max={99} label="Quantity" onChange={setQty} />
        </Field>
        <Field data-invalid={!!err("deposit")}>
          <FieldLabel htmlFor="pre-dep">Deposit (KES, optional)</FieldLabel>
          <Input id="pre-dep" inputMode="decimal" className="tabular-nums" value={deposit} onChange={(e) => setDeposit(e.target.value)} aria-invalid={!!err("deposit")} />
          {err("deposit") ? <FieldError>{err("deposit")}</FieldError> : price ? <FieldDescription>Expected total {kes(price * qty)}.</FieldDescription> : null}
        </Field>
      </div>
      <div className="flex justify-end">
        <Button
          onClick={() => {
            setTried(true);
            if (Object.values(errs).some(Boolean)) return;
            onSave({ customer: customer.trim(), phone, item, qty, deposit: dep });
            setCustomer("");
            setPhone("");
            setItem("");
            setQty(1);
            setDeposit("");
            setTried(false);
          }}
        >
          Save preorder
        </Button>
      </div>
    </div>
  );
}

/* ------------------------------- PS-05. Customer at the till ------------------------------- */

export type TillCustomer = {
  id: number;
  name: string;
  phone: string;
  memberNo?: string;
  priceGroup?: { name: string; discountPct: number };
  vatExempt?: boolean;
  points?: number;
  creditHeadroom?: number;
  scheme?: string;
  allergy?: string;
};

const digits = (s: string) => s.replace(/\D/g, "").replace(/^254/, "0");

/**
 * Customer at the till (PS-05): find by name, phone or member number without leaving the till, or add one
 * with just a name and phone. A phone that already belongs to someone offers that customer instead of a
 * duplicate. Once chosen, what applies automatically is listed: price group, VAT status, loyalty, credit,
 * scheme, and any severe allergy.
 */
export function TillCustomerPicker({ customers, selected, onSelect, onAdd }: { customers: TillCustomer[]; selected: TillCustomer | null; onSelect: (c: TillCustomer | null) => void; onAdd: (c: { name: string; phone: string }) => TillCustomer }) {
  const [q, setQ] = React.useState("");
  const [adding, setAdding] = React.useState(false);
  const [name, setName] = React.useState("");
  const [phone, setPhone] = React.useState("");
  const [tried, setTried] = React.useState(false);
  const t = q.trim().toLowerCase();
  const matches = t.length >= 2 ? customers.filter((c) => c.name.toLowerCase().includes(t) || (digits(t).length >= 3 && digits(c.phone).includes(digits(t))) || c.memberNo?.toLowerCase().includes(t)).slice(0, 6) : [];
  const dupe = customers.find((c) => digits(c.phone) === digits(phone) && digits(phone).length >= 10);
  const phoneOk = /^(\+?254|0)[17]\d{8}$/.test(phone.replace(/\s/g, ""));

  if (selected) {
    const applied = [
      selected.priceGroup ? `${selected.priceGroup.name} prices${selected.priceGroup.discountPct ? `, ${selected.priceGroup.discountPct}% off` : ""}` : null,
      selected.vatExempt ? "VAT exempt" : null,
      selected.points != null ? `${selected.points.toLocaleString("en-US")} loyalty points` : null,
      selected.creditHeadroom != null ? `${kes(selected.creditHeadroom)} credit available` : null,
      selected.scheme ? `Scheme: ${selected.scheme}` : null,
    ].filter(Boolean);
    return (
      <div className="space-y-2 rounded-lg border border-[var(--color-border-panel)] p-3 bg-card">
        <div className="flex items-start justify-between gap-2">
          <p className="text-sm">
            <span className="font-semibold">{selected.name}</span>
            <span className="block text-xs text-muted-foreground tabular-nums">{selected.phone}</span>
          </p>
          <Button size="xs" variant="ghost" onClick={() => onSelect(null)}>
            Change
          </Button>
        </div>
        {selected.allergy ? <p className={cn("rounded-md border px-2 py-1 text-xs font-medium", TONE_BOX.error)}>Severe allergy: {selected.allergy}</p> : null}
        {applied.length ? (
          <ul className="list-disc pl-5 text-xs text-muted-foreground">
            {applied.map((a) => (
              <li key={a}>{a}</li>
            ))}
          </ul>
        ) : (
          <p className="text-xs text-muted-foreground">Standard prices; no loyalty or credit.</p>
        )}
      </div>
    );
  }

  return (
    <div className="space-y-2">
      {!adding ? (
        <>
          <Input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Customer: name, phone or member number" aria-label="Find a customer" aria-controls="till-customer-results" />
          {t.length >= 2 ? (
            <ul id="till-customer-results" className="divide-y divide-[var(--color-border-subtle)] rounded-lg border border-[var(--color-border-panel)] text-sm bg-card" aria-label="Matching customers">
              {matches.map((c) => (
                <li key={c.id}>
                  <button
                    type="button"
                    className="flex w-full cursor-pointer items-baseline justify-between gap-2 px-3 py-2 text-left hover:bg-muted/60"
                    onClick={() => {
                      onSelect(c);
                      setQ("");
                    }}
                  >
                    <span>
                      <span className="font-medium">{c.name}</span>
                      {c.memberNo ? <span className="ml-1.5 font-mono text-xs text-muted-foreground">{c.memberNo}</span> : null}
                    </span>
                    <span className="text-xs text-muted-foreground tabular-nums">{c.phone}</span>
                  </button>
                </li>
              ))}
              {!matches.length ? <li className="px-3 py-2 text-muted-foreground">No customer matches &ldquo;{q.trim()}&rdquo;.</li> : null}
            </ul>
          ) : null}
          <div className="flex gap-2">
            <Button
              size="sm"
              variant="outline"
              onClick={() => {
                setAdding(true);
                if (/\d/.test(q)) setPhone(q.trim());
                else setName(q.trim());
              }}
            >
              <PlusIcon data-icon="inline-start" />
              Add new customer
            </Button>
            <span className="self-center text-xs text-muted-foreground">or sell to a walk-in</span>
          </div>
        </>
      ) : (
        <div className="space-y-2 rounded-lg border border-[var(--color-border-panel)] p-3 bg-card">
          <div className="grid gap-2">
            <Field data-invalid={tried && !name.trim()}>
              <FieldLabel htmlFor="tc-name">Name</FieldLabel>
              <Input id="tc-name" value={name} onChange={(e) => setName(e.target.value)} aria-invalid={tried && !name.trim()} autoFocus />
              {tried && !name.trim() ? <FieldError>Enter the customer&apos;s name.</FieldError> : null}
            </Field>
            <Field data-invalid={tried && !phoneOk}>
              <FieldLabel htmlFor="tc-phone">Mobile number</FieldLabel>
              <Input id="tc-phone" inputMode="tel" value={phone} onChange={(e) => setPhone(e.target.value)} aria-invalid={tried && !phoneOk} />
              {tried && !phoneOk ? <FieldError>Enter a mobile number, e.g. 0712 345 678.</FieldError> : null}
            </Field>
          </div>
          {dupe ? (
            <p className={cn("flex flex-wrap items-center gap-2 rounded-md border px-2 py-1.5 text-xs", TONE_BOX.warning)}>
              {dupe.phone} is already {dupe.name}.
              <Button
                size="xs"
                variant="outline"
                className="bg-background"
                onClick={() => {
                  onSelect(dupe);
                  setAdding(false);
                }}
              >
                Use {dupe.name}
              </Button>
            </p>
          ) : null}
          <p className="text-xs text-muted-foreground">The rest of their details can be added later from Customers.</p>
          <div className="flex justify-end gap-2">
            <Button size="sm" variant="ghost" onClick={() => setAdding(false)}>
              Cancel
            </Button>
            <Button
              size="sm"
              disabled={!!dupe}
              onClick={() => {
                setTried(true);
                if (!name.trim() || !phoneOk) return;
                onSelect(onAdd({ name: name.trim(), phone: phone.trim() }));
                setAdding(false);
                setName("");
                setPhone("");
                setTried(false);
              }}
            >
              Add and use
            </Button>
          </div>
        </div>
      )}
    </div>
  );
}

/* ---------------------------- PS-16. Opening/closing balances ---------------------------- */

export type ShiftBalance = { id: string; date: string; till: string; cashier: string; tenders: { tender: "Cash" | "M-Pesa" | "Card"; opening: number; system: number; counted: number }[] };

/**
 * Opening/closing balance report (PS-16): each shift's opening and closing amounts against what the system
 * expected, per tender. A difference beyond the tolerance is flagged in words with the amount.
 */
export function ShiftBalanceReport({ shifts, tolerance }: { shifts: ShiftBalance[]; tolerance: number }) {
  const n = (v: number) => v.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  return (
    <div className="overflow-x-auto rounded-lg border border-[var(--color-border-panel)] bg-card">
      <table className="w-full border-collapse text-sm tabular-nums">
        <caption className="sr-only">Opening and closing balances by shift</caption>
        <thead className="bg-[var(--color-bg-surface-subtle)] text-left text-xs text-muted-foreground">
          <tr>
            {["Shift", "Tender", "Opening", "Expected at close", "Counted", "Difference"].map((h) => (
              <th key={h} scope="col" className={cn("px-3 py-2 font-medium", !["Shift", "Tender"].includes(h) && "text-right")}>
                {h}
              </th>
            ))}
          </tr>
        </thead>
        {shifts.map((s) => {
          const bad = s.tenders.some((t) => Math.abs(t.counted - t.system) > tolerance);
          return (
            <tbody key={s.id} className="divide-y divide-[var(--color-border-subtle)] border-t-2 border-[var(--color-border-panel)]">
              {s.tenders.map((t, i) => {
                const d = Math.round((t.counted - t.system) * 100) / 100;
                const flag = Math.abs(d) > tolerance;
                return (
                  <tr key={t.tender}>
                    {i === 0 ? (
                      <th scope="rowgroup" rowSpan={s.tenders.length} className="px-3 py-2 text-left align-top font-medium">
                        {s.date} · {s.till}
                        <span className="block text-xs font-normal text-muted-foreground">{s.cashier}</span>
                        {bad ? <span className="mt-0.5 block text-xs font-semibold text-status-error-text">Mismatch</span> : <span className="mt-0.5 block text-xs text-status-success-text">Balanced</span>}
                      </th>
                    ) : null}
                    <td className="px-3 py-2">{t.tender}</td>
                    <td className="px-3 py-2 text-right">{n(t.opening)}</td>
                    <td className="px-3 py-2 text-right">{n(t.system)}</td>
                    <td className="px-3 py-2 text-right">{n(t.counted)}</td>
                    <td className={cn("px-3 py-2 text-right", flag && "font-semibold text-status-error-text")}>
                      {d === 0 ? "–" : `${d > 0 ? "Over" : "Short"} ${n(Math.abs(d))}`}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          );
        })}
      </table>
    </div>
  );
}

/* ------------------------------ PS-13. Delivery details ------------------------------ */

export type DeliveryDetails = { on: boolean; zone: string; fee: string; destination: string; pickup: string; by: "Own rider" | "Courier"; rider: string; riderPhone: string; company: string; cost: string };

/**
 * Delivery at the till (PS-13, ST-06): the zone pre-fills the fee, which the cashier can change; free
 * delivery above a basket value beats both. Records who carries it (own rider or a courier company), pickup
 * and destination, and what the delivery costs the pharmacy, so delivery margin can be reported.
 */
export function DeliveryPanel({ value, onChange, zones, basket, freeAbove }: { value: DeliveryDetails; onChange: (d: DeliveryDetails) => void; zones: { name: string; fee: number }[]; basket: number; freeAbove?: number }) {
  const set = <K extends keyof DeliveryDetails>(k: K, v: DeliveryDetails[K]) => onChange({ ...value, [k]: v });
  const free = freeAbove != null && basket >= freeAbove;
  const fee = free ? 0 : Number(value.fee) || 0;
  const cost = Number(value.cost) || 0;
  if (!value.on)
    return (
      <Button size="sm" variant="outline" onClick={() => set("on", true)}>
        Add delivery
      </Button>
    );
  return (
    <div className="space-y-3 rounded-lg border border-[var(--color-border-panel)] p-3 text-sm bg-card">
      <div className="flex items-center justify-between">
        <p className="font-semibold">Delivery</p>
        <Button size="xs" variant="ghost" onClick={() => set("on", false)}>
          Remove
        </Button>
      </div>
      <div className="grid gap-3">
        <Field>
          <FieldLabel htmlFor="dl-zone">Zone</FieldLabel>
          <NativeSelect id="dl-zone" value={value.zone} onChange={(e) => onChange({ ...value, zone: e.target.value, fee: String(zones.find((z) => z.name === e.target.value)?.fee ?? value.fee) })}>
            <NativeSelectOption value="">Choose a zone</NativeSelectOption>
            {zones.map((z) => (
              <NativeSelectOption key={z.name} value={z.name}>
                {z.name} ({kes(z.fee)})
              </NativeSelectOption>
            ))}
          </NativeSelect>
        </Field>
        <Field>
          <FieldLabel htmlFor="dl-fee">Fee charged (KES)</FieldLabel>
          <Input id="dl-fee" inputMode="decimal" className="tabular-nums" value={free ? "0" : value.fee} disabled={free} onChange={(e) => set("fee", e.target.value)} />
          <FieldDescription>{free ? `Free: the basket is over ${kes(freeAbove!)}.` : "Taxed with the basket (it's part of the subtotal)."}</FieldDescription>
        </Field>
        <Field>
          <FieldLabel htmlFor="dl-pickup">Pick up from</FieldLabel>
          <Input id="dl-pickup" value={value.pickup} onChange={(e) => set("pickup", e.target.value)} />
        </Field>
        <Field>
          <FieldLabel htmlFor="dl-dest">Deliver to</FieldLabel>
          <Input id="dl-dest" value={value.destination} onChange={(e) => set("destination", e.target.value)} placeholder="Estate, street, house or landmark" />
        </Field>
        <Field>
          <FieldLabel htmlFor="dl-by">Carried by</FieldLabel>
          <NativeSelect id="dl-by" value={value.by} onChange={(e) => set("by", e.target.value as DeliveryDetails["by"])}>
            <NativeSelectOption>Own rider</NativeSelectOption>
            <NativeSelectOption>Courier</NativeSelectOption>
          </NativeSelect>
        </Field>
        {value.by === "Own rider" ? (
          <Field>
            <FieldLabel htmlFor="dl-rider">Rider and phone</FieldLabel>
            <div className="flex gap-2">
              <Input id="dl-rider" value={value.rider} onChange={(e) => set("rider", e.target.value)} placeholder="Name" />
              <Input aria-label="Rider phone" inputMode="tel" value={value.riderPhone} onChange={(e) => set("riderPhone", e.target.value)} placeholder="07…" />
            </div>
          </Field>
        ) : (
          <Field>
            <FieldLabel htmlFor="dl-company">Courier company</FieldLabel>
            <Input id="dl-company" value={value.company} onChange={(e) => set("company", e.target.value)} placeholder="e.g. Sendy, G4S" />
          </Field>
        )}
        <Field>
          <FieldLabel htmlFor="dl-cost">Costs us (KES)</FieldLabel>
          <Input id="dl-cost" inputMode="decimal" className="tabular-nums" value={value.cost} onChange={(e) => set("cost", e.target.value)} />
          <FieldDescription>{value.cost ? `${fee - cost >= 0 ? "Makes" : "Loses"} ${kes(Math.abs(fee - cost))} on this delivery.` : "What the rider or courier is paid, for the delivery report."}</FieldDescription>
        </Field>
      </div>
    </div>
  );
}

/* ------------------------------- PS-17. Direct credit note ------------------------------- */

/** Reasons a direct credit note may be raised; "Other" needs details. */
export const DIRECT_CN_REASONS = ["Price correction", "Goodwill", "Overcharged on an earlier visit", "Promotion not applied", "Other"];

/** Problems with a direct credit note, in words (PS-17: registered customer, reason, approval). */
export function directCreditProblems(d: { customer?: string; amount: string; reason: string; details: string }, max: number) {
  const n = Number(d.amount.replace(/,/g, ""));
  const p: string[] = [];
  if (!d.customer) p.push("Choose a registered customer: a credit note can't be given to a walk-in.");
  if (!(n > 0)) p.push("Enter the amount.");
  else if (n > max) p.push(`Above ${kes(max)} it needs an Admin, not a branch approval. Ask an Admin to raise it.`);
  if (!d.reason) p.push("Choose a reason.");
  if (d.reason === "Other" && !d.details.trim()) p.push("Say what the credit is for.");
  return p;
}
