"use client";

/**
 * Customers (PRD 4.1, 4.3, 4.4), on the Medora design system (Stage 7) and the Customers API, Part 1 (DECISIONS.md,
 * 24 Oct 2026). The list (CU-02: search, type filter, paging, CSV import that flags duplicates, export) and the
 * profile page from the design's detail-page pattern (`/design-system/layout-patterns`): Profile, Purchases,
 * Prescriptions & medical (CU-06, CU-07; health data behind its own permission, CU-08) and Account (CU-03, CU-05).
 *
 * The profile opens at `?id=` rather than a route segment, so the static export (desktop) needs no list of ids.
 * Plan D (26 Oct 2026): a customer's group (CU-04, it prices their basket at the till) and scheme membership (CU-09,
 * the design's `SchemeCard`); the groups and schemes themselves are on Groups and schemes. Loyalty and complaints
 * come after plan E (Q2).
 */

import * as React from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { DownloadIcon, PencilIcon, PlusIcon, SearchIcon, Trash2Icon, UploadIcon } from "lucide-react";

import { PanelError } from "@/components/medora/errors";
import { StatusTag } from "@/components/medora/record-lists";
import { MedoraPageHeader } from "@/components/medora/page-header";
import { RecordDrawer } from "@/components/medora/drawer";
import { DescriptionList } from "@/components/medora/description-list";
import { SensitiveNotice } from "@/components/medora/sensitive";
import { MedicalEntryForm, MedicalTimeline, SchemeCard, type MedicalEntry } from "@/components/medora/parties-ops";
import { PrescriptionCard, type RxStatus } from "@/components/medora/records/parties";
import { AgingBuckets, BalanceSummary, PaymentForm, StatementTable, type PaymentFormStatus } from "@/components/medora/records/finance";
import { ConfirmDialog } from "@/components/medora/confirm-dialog";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";
import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select";
import { Spinner } from "@/components/ui/spinner";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";
import { toast } from "@/components/ui/toast";
import { CustomerGroupsApi, CustomersApi, PrescriptionsApi, SchemesApi, type CustomerFields, type CustomerGroupRow, type CustomerRow, type CustomerScheme, type MedicalRow, type SchemeRow } from "@/lib/api";
import { useTabParam, validTab } from "@/lib/use-tab-param";
import { formatPhone, count, formatDate, formatKES, localToday } from "@/lib/format";
import { useMenuPermissions } from "@/lib/permissions";
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 day = (iso?: string | null) => (iso ? formatDate(iso) : "");
const usePerm = () => {
  const { can, loaded } = useMenuPermissions();
  return (page: string, action: string) => loaded && can(page, action);
};

export default function CustomersPage() {
  // useSearchParams needs a Suspense boundary in the static export
  return (
    <React.Suspense fallback={<Loading what="customers" columns={6} rows={8} toolbar={2} numeric={[3, 4]} />}>
      <Customers />
    </React.Suspense>
  );
}

function Customers() {
  const params = useSearchParams();
  const id = Number(params.get("id")) || null;
  return id ? <CustomerProfile id={id} /> : <CustomerList />;
}

/* ------------------------------------ The form (CU-01) ------------------------------------ */

type Form = { id?: number; d: CustomerFields & { name: string }; original: CustomerFields & { name: string } };
const blank = (): CustomerFields & { name: string } => ({ name: "", type: "Individual", vatStatus: "Vatable", phone: "", email: "", kraPin: "", etimsId: "", dateOfBirth: "", postalAddress: "", physicalAddress: "", country: "Kenya", city: "", contactPerson: "", bankName: "", bankAccount: "", notes: "", creditLimit: 0, groupId: null, schemeId: null, schemeMemberNo: "", schemeRelation: null, schemePrincipal: "", schemeLimit: "", schemeValidUntil: "", schemeStatus: null });
const fromRecord = (c: CustomerFields & { name: string }) => Object.fromEntries(Object.entries({ ...blank(), ...c }).filter(([k]) => k in blank()).map(([k, v]) => [k, v ?? ""])) as CustomerFields & { name: string };

function CustomerForm({ form, onClose, onSaved }: { form: Form; onClose: () => void; onSaved: (c: { id: number; name: string }) => void }) {
  const [d, setD] = React.useState(form.d);
  const [problems, setProblems] = React.useState<Record<string, string>>({});
  const [saving, setSaving] = React.useState(false);
  const [duplicate, setDuplicate] = React.useState<{ id: number; name: string } | null>(null);
  const set = <K extends keyof typeof d>(k: K, v: (typeof d)[K]) => setD((x) => ({ ...x, [k]: v }));
  const company = d.type === "Company";
  // Plan D: the groups and schemes a customer can join (deactivated ones only if already theirs)
  const [groups, setGroups] = React.useState<CustomerGroupRow[]>([]);
  const [schemes, setSchemes] = React.useState<SchemeRow[]>([]);
  React.useEffect(() => {
    CustomerGroupsApi.list().then(setGroups).catch(() => setGroups([]));
    SchemesApi.list().then(setSchemes).catch(() => setSchemes([]));
  }, []);
  const scheme = schemes.find((s) => String(s.id) === String(d.schemeId ?? ""));
  const dependant = !!d.schemeRelation && d.schemeRelation !== "Principal";

  const save = async (allowDuplicate = false) => {
    setSaving(true);
    setProblems({});
    try {
      // Only what changed on an edit, so a field the API refuses isn't sent back unchanged
      const body = form.id ? Object.fromEntries(Object.entries(d).filter(([k, v]) => v !== (form.original as Record<string, unknown>)[k])) : d;
      const saved = form.id ? await CustomersApi.update(form.id, { ...body, creditLimit: Number(d.creditLimit) || 0, allowDuplicate }) : await CustomersApi.create({ ...d, creditLimit: Number(d.creditLimit) || 0, allowDuplicate });
      toast.add({ title: form.id ? "Customer saved" : "Customer added" });
      onSaved(saved);
    } catch (e) {
      const res = (e as { response?: { status?: number; data?: { problems?: Record<string, string>; duplicate?: { id: number; name: string } } } })?.response;
      if (res?.status === 409 && res.data?.duplicate) setDuplicate(res.data.duplicate);
      else if (res?.data?.problems) setProblems(res.data.problems);
      else toast.add({ type: "error", title: "Not saved", description: apiError(e, "Try again.") });
    } finally {
      setSaving(false);
    }
  };

  const text = (k: keyof typeof d, label: string, opts: { hint?: string; type?: string; mono?: boolean; span?: boolean } = {}) => (
    <Field data-invalid={!!problems[k as string] || undefined} className={opts.span ? "sm:col-span-2" : undefined}>
      <FieldLabel htmlFor={`cf-${k}`}>{label}</FieldLabel>
      <Input id={`cf-${k}`} type={opts.type} className={opts.mono ? "font-mono" : undefined} value={String(d[k] ?? "")} onChange={(e) => set(k, e.target.value as never)} aria-invalid={!!problems[k as string]} />
      {problems[k as string] ? <FieldError>{problems[k as string]}</FieldError> : opts.hint ? <FieldDescription>{opts.hint}</FieldDescription> : null}
    </Field>
  );

  return (
    <>
      <RecordDrawer
        open
        onOpenChange={(o) => !o && onClose()}
        variant="edit"
        width="lg"
        saving={saving}
        dirty={JSON.stringify(d) !== JSON.stringify(form.original)}
        title={form.id ? `Edit ${form.original.name}` : "New customer"}
        footer={
          <>
            <Button variant="outline" disabled={saving} onClick={onClose}>
              Cancel
            </Button>
            <Button disabled={saving} onClick={() => void save()}>
              {saving ? <Spinner /> : null}
              Save
            </Button>
          </>
        }
      >
        <div className="grid gap-4">
          <Field>
            <FieldLabel htmlFor="cf-type">Customer is</FieldLabel>
            <NativeSelect id="cf-type" value={d.type} onChange={(e) => set("type", e.target.value as "Individual")}>
              <NativeSelectOption value="Individual">An individual or patient</NativeSelectOption>
              <NativeSelectOption value="Company">A company</NativeSelectOption>
            </NativeSelect>
          </Field>
          {text("name", company ? "Company name" : "Full name")}
          {text("phone", "Phone", { hint: "For example 0712 345 678. Used to find duplicates." })}
          {text("email", "Email", { type: "email" })}
          {text("kraPin", "KRA PIN", { mono: true, hint: "A or P, nine digits and a letter." })}
          {text("etimsId", "eTIMS ID", { mono: true })}
          <Field>
            <FieldLabel htmlFor="cf-vat">VAT status</FieldLabel>
            <NativeSelect id="cf-vat" value={d.vatStatus} onChange={(e) => set("vatStatus", e.target.value as "Vatable")}>
              <NativeSelectOption value="Vatable">Vatable</NativeSelectOption>
              <NativeSelectOption value="Exempt">Exempt</NativeSelectOption>
            </NativeSelect>
            <FieldDescription>Recorded for invoices; the till doesn&apos;t apply it yet.</FieldDescription>
          </Field>
          {company ? text("contactPerson", "Contact person") : text("dateOfBirth", "Date of birth", { type: "date" })}
          {text("physicalAddress", "Physical address", { span: true })}
          {text("postalAddress", "Postal address")}
          {text("city", "City or town")}
          {text("country", "Country")}
          {text("bankName", "Bank")}
          {text("bankAccount", "Bank account number", { mono: true })}
          {text("creditLimit", "Credit limit (KES)", { hint: "0 means no credit at the till." })}
          <Field data-invalid={!!problems.groupId || undefined}>
            <FieldLabel htmlFor="cf-group">Customer group</FieldLabel>
            <NativeSelect id="cf-group" value={String(d.groupId ?? "")} onChange={(e) => set("groupId", (e.target.value ? Number(e.target.value) : "") as never)}>
              <NativeSelectOption value="">None (retail prices)</NativeSelectOption>
              {groups
                .filter((g) => g.active || String(g.id) === String(form.original.groupId ?? ""))
                .map((g) => (
                  <NativeSelectOption key={g.id} value={String(g.id)}>
                    {g.name}: {g.priceGroup ? `${g.priceGroup} prices` : "retail"}, {g.discountPct}% off
                  </NativeSelectOption>
                ))}
            </NativeSelect>
            {problems.groupId ? <FieldError>{problems.groupId}</FieldError> : <FieldDescription>Sets their prices and discount at the till.</FieldDescription>}
          </Field>
          <Field data-invalid={!!problems.schemeId || undefined}>
            <FieldLabel htmlFor="cf-scheme">Insurance or medical scheme</FieldLabel>
            <NativeSelect id="cf-scheme" value={String(d.schemeId ?? "")} onChange={(e) => set("schemeId", (e.target.value ? Number(e.target.value) : "") as never)}>
              <NativeSelectOption value="">None</NativeSelectOption>
              {schemes
                .filter((s) => s.active || String(s.id) === String(form.original.schemeId ?? ""))
                .map((s) => (
                  <NativeSelectOption key={s.id} value={String(s.id)}>
                    {s.name} ({s.copayPct}% co-pay)
                  </NativeSelectOption>
                ))}
            </NativeSelect>
            {problems.schemeId ? <FieldError>{problems.schemeId}</FieldError> : null}
          </Field>
          {scheme ? (
            <>
              {text("schemeMemberNo", "Member number", { mono: true, hint: "As on the scheme card." })}
              <Field>
                <FieldLabel htmlFor="cf-relation">Member is</FieldLabel>
                <NativeSelect id="cf-relation" value={String(d.schemeRelation || "Principal")} onChange={(e) => set("schemeRelation", e.target.value as "Principal")}>
                  <NativeSelectOption value="Principal">The principal member</NativeSelectOption>
                  <NativeSelectOption value="Spouse">A spouse</NativeSelectOption>
                  <NativeSelectOption value="Child">A child</NativeSelectOption>
                </NativeSelect>
              </Field>
              {dependant ? text("schemePrincipal", "Principal member's name") : null}
              {text("schemeLimit", "Annual cover (KES)", { hint: scheme.defaultLimit != null ? `Blank takes the scheme's ${formatKES(scheme.defaultLimit)}.` : "Blank for no limit." })}
              {text("schemeValidUntil", "Cover ends", { type: "date" })}
              <Field>
                <FieldLabel htmlFor="cf-mstatus">Membership</FieldLabel>
                <NativeSelect id="cf-mstatus" value={String(d.schemeStatus || "Active")} onChange={(e) => set("schemeStatus", e.target.value as "Active")}>
                  <NativeSelectOption value="Active">Active</NativeSelectOption>
                  <NativeSelectOption value="Suspended">Suspended</NativeSelectOption>
                  <NativeSelectOption value="Expired">Expired</NativeSelectOption>
                </NativeSelect>
              </Field>
            </>
          ) : null}
          <Field className="sm:col-span-2">
            <FieldLabel htmlFor="cf-notes">Notes</FieldLabel>
            <Textarea id="cf-notes" rows={2} value={String(d.notes ?? "")} onChange={(e) => set("notes", e.target.value)} />
            <FieldDescription>Not for health information: allergies and diagnoses go in Prescriptions &amp; medical, where only permitted roles see them.</FieldDescription>
          </Field>
        </div>
      </RecordDrawer>
      <Dialog open={!!duplicate} onOpenChange={(o) => !o && setDuplicate(null)}>
        <DialogContent>
          <DialogHeader>
            <DialogTitle>{duplicate?.name} already has this phone number or KRA PIN</DialogTitle>
            <DialogDescription>Two records for one person split their history and credit. Open the existing customer, or save this one if it really is someone else.</DialogDescription>
          </DialogHeader>
          <DialogFooter>
            <Button variant="outline" onClick={() => (window.location.href = `/customers?id=${duplicate?.id}`)}>
              Open {duplicate?.name}
            </Button>
            <Button
              onClick={() => {
                setDuplicate(null);
                void save(true);
              }}
            >
              It&apos;s someone else: save
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </>
  );
}

/* ------------------------------------ The list (CU-02) ------------------------------------ */

function CustomerList() {
  const router = useRouter();
  const may = usePerm();
  const [q, setQ] = React.useState({ search: "", type: "All", group: "", page: 1 });
  const [groups, setGroups] = React.useState<CustomerGroupRow[]>([]);
  React.useEffect(() => {
    CustomerGroupsApi.list().then(setGroups).catch(() => setGroups([]));
  }, []);
  const [data, setData] = React.useState<{ total: number; rows: CustomerRow[] } | null>(null);
  const [failed, setFailed] = React.useState(false); // R12 (card 037): a failed load says so, with Try again
  const [form, setForm] = React.useState<Form | null>(null);
  const [importing, setImporting] = React.useState(false);
  const pageSize = 25;
  const req = React.useRef(0);

  const load = React.useCallback(async () => {
    const id = ++req.current;
    try {
      const res = await CustomersApi.list({ search: q.search || undefined, type: q.type === "All" ? undefined : q.type, groupId: q.group === "none" ? "none" : Number(q.group) || undefined, page: q.page, pageSize });
      if (id === req.current) {
        setData(res);
        setFailed(false);
      }
    } catch (e) {
      if (id === req.current) {
        setData({ total: 0, rows: [] });
        setFailed(true);
      }
      toast.add({ type: "error", title: "Couldn't load customers", description: apiError(e, "Try again.") });
    }
  }, [q]);
  React.useEffect(() => {
    const t = setTimeout(() => void load(), 200);
    return () => clearTimeout(t);
  }, [load]);

  const pages = data ? Math.max(1, Math.ceil(data.total / pageSize)) : 1;
  return (
    <div className="medora mx-auto w-full max-w-6xl space-y-4">
      <MedoraPageHeader
        title="Customers"
        description={data ? count(data.total, "customer") : "Loading…"}
        primary={may("Customers/Customer list", "Create") ? { label: "Add customer", icon: PlusIcon, onSelect: () => setForm({ d: blank(), original: blank() }) } : undefined}
        secondary={[
          ...(may("Customers/Customer list", "Create") ? [{ label: "Import CSV", icon: UploadIcon, onSelect: () => setImporting(true) }] : []),
          ...(may("Customers/Customer list", "Export") ? [{ label: "Export", icon: DownloadIcon, onSelect: () => void CustomersApi.exportCsv().catch((e) => toast.add({ type: "error", title: "Export failed", description: apiError(e, "Try again.") })) }] : []),
        ]}
      />
      {/* One container (data table spec §1): filters, table and pager share the white surface */}
      <div className="overflow-hidden rounded-md border border-[var(--color-border-panel)] bg-card">
      <div className="flex flex-wrap items-center gap-3 border-b border-[var(--color-border-subtle)] p-3">
        <InputGroup className="max-w-sm">
          <InputGroupAddon>
            <SearchIcon />
          </InputGroupAddon>
          <InputGroupInput placeholder="Name, phone, email or KRA PIN" value={q.search} onChange={(e) => setQ({ ...q, search: e.target.value, page: 1 })} aria-label="Search customers" />
        </InputGroup>
        <Tabs value={q.type} onValueChange={(v) => setQ({ ...q, type: String(v), page: 1 })}>
          <TabsList variant="line">
            <TabsTrigger value="All">All</TabsTrigger>
            <TabsTrigger value="Individual">Individuals</TabsTrigger>
            <TabsTrigger value="Company">Companies</TabsTrigger>
          </TabsList>
        </Tabs>
        {groups.length ? (
          <NativeSelect aria-label="Customer group" value={q.group} onChange={(e) => setQ({ ...q, group: e.target.value, page: 1 })}>
            <NativeSelectOption value="">All groups</NativeSelectOption>
            {groups.map((g) => (
              <NativeSelectOption key={g.id} value={String(g.id)}>
                {g.name}
              </NativeSelectOption>
            ))}
            <NativeSelectOption value="none">No group</NativeSelectOption>
          </NativeSelect>
        ) : null}
      </div>
      <div className="overflow-x-auto">
        <Table>
          <TableHeader>
            <TableRow>
              <TableHead>Customer</TableHead>
              <TableHead>Phone</TableHead>
              <TableHead>KRA PIN</TableHead>
              <TableHead className="text-right">Visits</TableHead>
              <TableHead className="text-right">Spent</TableHead>
              <TableHead>Last visit</TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            {failed ? (
              <TableRow>
                <TableCell colSpan={6}>
                  <PanelError what="customers" onRetry={() => void load()} />
                </TableCell>
              </TableRow>
            ) : !data ? (
              <TableRow>
                <TableCell colSpan={6} className="py-10 text-center">
                  <Loading what="customers" rows={5} columns={6} numeric={[3, 4]} bare />
                </TableCell>
              </TableRow>
            ) : (
              data.rows.map((c) => (
                <TableRow key={c.id} className="cursor-pointer" onClick={() => router.push(`/customers?id=${c.id}`)}>
                  <TableCell>
                    <span className="font-medium text-primary">{c.name}</span>
                    <span className="block text-xs text-muted-foreground">
                      {c.type === "Company" ? `Company${c.contactPerson ? `, ${c.contactPerson}` : ""}` : "Individual"}
                      {c.group ? ` · ${c.group}` : ""}
                      {c.scheme ? ` · ${c.scheme.name}` : ""}
                    </span>
                  </TableCell>
                  <TableCell className="tabular-nums">{c.phone ? formatPhone(c.phone) : "–"}</TableCell>
                  <TableCell className="font-mono text-xs">{c.kraPin || "–"}</TableCell>
                  <TableCell className="text-right tabular-nums">{c.visits}</TableCell>
                  <TableCell className="text-right tabular-nums">{formatKES(c.spend)}</TableCell>
                  <TableCell>{c.lastVisit ? day(c.lastVisit) : "Never"}</TableCell>
                </TableRow>
              ))
            )}
            {data && !data.rows.length ? (
              <TableRow>
                <TableCell colSpan={6} className="py-10 text-center text-muted-foreground">
                  {q.search ? "No customers match." : "No customers yet."}
                </TableCell>
              </TableRow>
            ) : null}
          </TableBody>
        </Table>
      </div>
      <div className="flex items-center justify-between border-t border-[var(--color-border-subtle)] p-3 text-xs text-muted-foreground">
        <span>{data?.total ? `${(q.page - 1) * pageSize + 1}–${Math.min(data.total, q.page * pageSize)} of ${data.total.toLocaleString("en-US")}` : ""}</span>
        <span className="flex items-center gap-1">
          <Button size="sm" variant="outline" disabled={q.page <= 1} onClick={() => setQ({ ...q, page: q.page - 1 })}>
            Previous
          </Button>
          <span className="px-2 tabular-nums">
            Page {q.page} of {pages}
          </span>
          <Button size="sm" variant="outline" disabled={q.page >= pages} onClick={() => setQ({ ...q, page: q.page + 1 })}>
            Next
          </Button>
        </span>
      </div>
      </div>
      {form ? (
        <CustomerForm
          form={form}
          onClose={() => setForm(null)}
          onSaved={(c) => {
            setForm(null);
            router.push(`/customers?id=${c.id}`);
          }}
        />
      ) : null}
      {importing ? (
        <ImportDialog
          onClose={() => setImporting(false)}
          onDone={() => {
            void load();
          }}
        />
      ) : null}
    </div>
  );
}

function ImportDialog({ onClose, onDone }: { onClose: () => void; onDone: () => void }) {
  const [busy, setBusy] = React.useState(false);
  const [result, setResult] = React.useState<Awaited<ReturnType<typeof CustomersApi.importCsv>> | null>(null);
  const read = async (file: File) => {
    setBusy(true);
    try {
      const r = await CustomersApi.importCsv(await file.text());
      setResult(r);
      onDone();
    } catch (e) {
      toast.add({ type: "error", title: "Import failed", description: apiError(e, "Check the file and try again.") });
    } finally {
      setBusy(false);
    }
  };
  return (
    <Dialog open onOpenChange={(o) => !o && !busy && onClose()}>
      <DialogContent className="sm:max-w-2xl">
        <DialogHeader>
          <DialogTitle>Import customers</DialogTitle>
          <DialogDescription>
            A CSV whose first line names the columns: name, type, phone, email, KRA PIN, eTIMS ID, VAT status, date of birth, postal address, physical address, country, city, contact person, bank name, bank account,
            notes, credit limit. Only name is required. A row whose phone or KRA PIN is already on a customer is skipped and listed, never created twice. At most 5,000 rows.
          </DialogDescription>
        </DialogHeader>
        {!result ? (
          <Field>
            <FieldLabel htmlFor="imp-file">CSV file</FieldLabel>
            <Input id="imp-file" type="file" accept=".csv,text/csv" disabled={busy} onChange={(e) => e.target.files?.[0] && void read(e.target.files[0])} />
            {busy ? (
              <FieldDescription className="flex items-center gap-2">
                <Spinner className="size-3.5" /> Importing…
              </FieldDescription>
            ) : null}
          </Field>
        ) : (
          <div className="space-y-3">
            <p className="text-sm">
              <strong>{result.created}</strong> added, <strong>{result.duplicates}</strong> skipped as duplicates, <strong>{result.invalid}</strong> with problems.
            </p>
            {result.results.some((r) => r.outcome !== "Created") ? (
              <div className="max-h-72 overflow-auto rounded-md border border-[var(--color-border-panel)] bg-card">
                <Table>
                  <TableHeader>
                    <TableRow>
                      <TableHead>Line</TableHead>
                      <TableHead>Name</TableHead>
                      <TableHead>Skipped because</TableHead>
                    </TableRow>
                  </TableHeader>
                  <TableBody>
                    {result.results
                      .filter((r) => r.outcome !== "Created")
                      .map((r) => (
                        <TableRow key={r.row}>
                          <TableCell className="tabular-nums">{r.row}</TableCell>
                          <TableCell>{r.name || "–"}</TableCell>
                          <TableCell className="whitespace-normal">{r.message}</TableCell>
                        </TableRow>
                      ))}
                  </TableBody>
                </Table>
              </div>
            ) : null}
          </div>
        )}
        <DialogFooter>
          <Button variant="outline" disabled={busy} onClick={onClose}>
            {result ? "Done" : "Cancel"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

/* ------------------------------------ The profile ------------------------------------ */

type Profile = {
  customer: CustomerFields & { id: number; name: string; createdAt: string };
  group: string | null;
  scheme: CustomerScheme | null;
  totals: { spend: number; visits: number; lastVisit: string | null };
  balance: { balance: number; creditLimit: number; availableCredit: number; overLimit: boolean };
  purchases: { page: number; pageSize: number; total: number; rows: { id: number; ref: string; date: string; items: number; total: number; method: string; status: string }[] };
};
type Rx = { id: number; patientName: string; prescriber?: string; dateIssued: string; status: string; customerId?: number | null; items?: { productName: string; dosage?: string; frequency?: string; quantity: number }[] };

const toEntry = (m: MedicalRow): MedicalEntry => {
  const base = { id: String(m.id), date: m.entryDate, by: m.recordedByName, error: m.enteredInError ? { reason: m.errorReason ?? "", by: m.errorByName ?? "", at: day(m.errorAt) } : undefined };
  const d = m.data as Record<string, never>;
  if (m.kind === "Allergy") return { ...base, kind: "Allergy", text: d.text, severity: d.severity };
  if (m.kind === "Lab result") return { ...base, kind: "Lab result", lab: d.lab, values: d.values };
  if (m.kind === "Sick-off note") return { ...base, kind: "Sick-off note", from: d.from, to: d.to, reason: d.reason };
  return { ...base, kind: "Diagnosis", text: d.text };
};
const fromEntry = (e: MedicalEntry): { kind: string; date: string; data: Record<string, unknown> } => {
  if (e.kind === "Allergy") return { kind: e.kind, date: e.date, data: { text: e.text, severity: e.severity } };
  if (e.kind === "Lab result") return { kind: e.kind, date: e.date, data: { lab: e.lab, values: e.values } };
  if (e.kind === "Sick-off note") return { kind: e.kind, date: e.date, data: { from: e.from, to: e.to, reason: e.reason } };
  return { kind: e.kind, date: e.date, data: { text: e.text } };
};

function CustomerProfile({ id }: { id: number }) {
  const [tabParam, setTabParam] = useTabParam(); // page tabs change the URL (tabs spec 7.1)
  const router = useRouter();
  const may = usePerm();
  const [p, setP] = React.useState<Profile | null | "missing">(null);
  const [page, setPage] = React.useState(1);
  const [form, setForm] = React.useState<Form | null>(null);
  const [deleting, setDeleting] = React.useState(false);
  const user = React.useMemo(() => {
    try {
      return JSON.parse(localStorage.getItem("user") || "{}") as { name?: string; username?: string };
    } catch {
      return {};
    }
  }, []);

  const load = React.useCallback(async () => {
    try {
      setP(await CustomersApi.profile(id, page));
    } catch (e) {
      const status = (e as { response?: { status?: number } })?.response?.status;
      if (status === 404) setP("missing");
      else toast.add({ type: "error", title: "Couldn't load the customer", description: apiError(e, "Try again.") });
    }
  }, [id, page]);
  React.useEffect(() => {
    void load();
  }, [load]);

  const canMedical = may("Customers/Medical records", "Read");
  const canRx = may("Customers/Prescriptions", "Read");
  const canAccount = may("Accounts/Receivables", "Read");

  if (p === "missing")
    return (
      <div className="medora mx-auto w-full max-w-6xl space-y-4 py-10 text-center">
        <p className="text-sm text-muted-foreground">This customer doesn&apos;t exist, or belongs to another pharmacy.</p>
        <Button variant="outline" onClick={() => router.push("/customers")}>
          Back to customers
        </Button>
      </div>
    );
  if (!p)
    return (
      <Loading what="the customer" shape="detail" rows={8} />
    );
  const c = p.customer;

  return (
    <div className="medora mx-auto w-full max-w-6xl space-y-6">
      <MedoraPageHeader
        variant="record"
        title={c.name}
        meta={[`Customer since ${formatDate(c.createdAt)}`, ...(c.kraPin ? [`KRA PIN ${c.kraPin}`] : []), `Account C-${String(c.id).padStart(4, "0")}`]}
        breadcrumbs={[{ label: "Customers" }, { label: "Customer list", href: "/customers" }, { label: c.name }]}
        secondary={may("Customers/Customer list", "Update") ? [{ label: "Edit", icon: PencilIcon, onSelect: () => setForm({ id: c.id, d: fromRecord(c), original: fromRecord(c) }) }] : []}
        more={may("Customers/Customer list", "Delete") ? [{ label: "Delete customer…", icon: Trash2Icon, destructive: true, onSelect: () => setDeleting(true) }] : []}
      />
      <Tabs value={validTab(tabParam, ["profile", "purchases", "medical", "account"], "profile")} onValueChange={(v) => setTabParam(String(v))}>
        <TabsList variant="line" className="w-full justify-start border-b border-border">
          <TabsTrigger value="profile">Profile</TabsTrigger>
          <TabsTrigger value="purchases">Purchases</TabsTrigger>
          {canMedical || canRx ? <TabsTrigger value="medical">Prescriptions &amp; medical</TabsTrigger> : null}
          {canAccount ? <TabsTrigger value="account">Account</TabsTrigger> : null}
        </TabsList>
        <div className="mt-5 grid gap-6 xl:grid-cols-12">
          <div className="min-w-0 xl:col-span-8">
            <TabsContent value="profile">
              <div className="rounded-lg border border-[var(--color-border-panel)] bg-card p-4">
                <DescriptionList
                  columns={2}
                  items={[
                    { label: "Customer is", value: c.type === "Company" ? "A company" : "An individual or patient" },
                    { label: "Phone", value: c.phone ? formatPhone(c.phone) : "" },
                    { label: "Email", value: c.email || "" },
                    { label: "KRA PIN", value: c.kraPin || "" },
                    { label: "eTIMS ID", value: c.etimsId || "" },
                    { label: "VAT status", value: c.vatStatus || "Vatable" },
                    c.type === "Company" ? { label: "Contact person", value: c.contactPerson || "" } : { label: "Date of birth", value: c.dateOfBirth ? formatDate(c.dateOfBirth) : "" },
                    { label: "City", value: [c.city, c.country].filter(Boolean).join(", ") },
                    { label: "Physical address", value: c.physicalAddress || "", span: 2 },
                    { label: "Postal address", value: c.postalAddress || "" },
                    { label: "Bank", value: [c.bankName, c.bankAccount].filter(Boolean).join(", ") },
                    { label: "Customer group", value: p.group ?? "None (retail prices)" },
                    { label: "Notes", value: c.notes || "", span: 2 },
                  ]}
                />
              </div>
              {p.scheme ? (
                <div className="mt-5 max-w-md">
                  {c.schemeLimit != null ? (
                    <SchemeCard
                      formatDate={(iso) => formatDate(iso)}
                      member={{
                        scheme: `${p.scheme.name}${p.scheme.kind !== "Insurance" ? ` (${p.scheme.kind.toLowerCase()})` : ""}`,
                        memberNo: c.schemeMemberNo ?? "",
                        relation: c.schemeRelation ?? "Principal",
                        principal: c.schemePrincipal ?? undefined,
                        limit: Number(c.schemeLimit),
                        used: 0, // claims against the cover come with Accounts (plan H)
                        copayPct: p.scheme.copayPct,
                        validUntil: c.schemeValidUntil ?? "",
                        status: p.scheme.status ?? "Active",
                      }}
                    />
                  ) : (
                    <p className="rounded-lg border border-[var(--color-border-panel)] p-4 text-sm bg-card">
                      <span className="font-semibold">{p.scheme.name}</span> · member <span className="font-mono">{c.schemeMemberNo}</span> · {p.scheme.copayPct}% co-pay · no cover limit · {p.scheme.status}
                    </p>
                  )}
                  <p className="mt-1 text-xs text-muted-foreground">Cover used is counted from claims, which come with Accounts.</p>
                </div>
              ) : null}
            </TabsContent>
            <TabsContent value="purchases" className="space-y-3">
              <div className="overflow-x-auto rounded-md border border-[var(--color-border-panel)] bg-card">
                <Table>
                  <TableHeader>
                    <TableRow>
                      <TableHead>Receipt</TableHead>
                      <TableHead>Date</TableHead>
                      <TableHead className="text-right">Items</TableHead>
                      <TableHead>Paid by</TableHead>
                      <TableHead className="text-right">Total</TableHead>
                    </TableRow>
                  </TableHeader>
                  <TableBody>
                    {p.purchases.rows.map((s) => (
                      <TableRow key={s.id}>
                        <TableCell className="font-mono text-xs">
                          {s.ref}
                          {s.status !== "Completed" ? <span className="ml-2 font-sans"><StatusTag status={s.status} /></span> : null}
                        </TableCell>
                        <TableCell>{day(s.date)}</TableCell>
                        <TableCell className="text-right tabular-nums">{s.items}</TableCell>
                        <TableCell>{s.method}</TableCell>
                        <TableCell className="text-right tabular-nums">{formatKES(s.total)}</TableCell>
                      </TableRow>
                    ))}
                    {!p.purchases.rows.length ? (
                      <TableRow>
                        <TableCell colSpan={5} className="py-8 text-center text-muted-foreground">
                          No purchases yet.
                        </TableCell>
                      </TableRow>
                    ) : null}
                  </TableBody>
                </Table>
              </div>
              {p.purchases.total > p.purchases.pageSize ? (
                <div className="flex items-center justify-end gap-1 text-xs text-muted-foreground">
                  <Button size="sm" variant="outline" disabled={page <= 1} onClick={() => setPage(page - 1)}>
                    Previous
                  </Button>
                  <span className="px-2 tabular-nums">
                    Page {page} of {Math.ceil(p.purchases.total / p.purchases.pageSize)}
                  </span>
                  <Button size="sm" variant="outline" disabled={page >= Math.ceil(p.purchases.total / p.purchases.pageSize)} onClick={() => setPage(page + 1)}>
                    Next
                  </Button>
                </div>
              ) : null}
            </TabsContent>
            {canMedical || canRx ? (
              <TabsContent value="medical" className="space-y-6">
                <SensitiveNotice kind="medical" />
                {canRx ? <CustomerPrescriptions customer={c} /> : null}
                {canMedical ? <MedicalSection customerId={c.id} by={user.name || user.username || "You"} canAdd={may("Customers/Medical records", "Create")} canMark={may("Customers/Medical records", "Update")} /> : null}
              </TabsContent>
            ) : null}
            {canAccount ? (
              <TabsContent value="account">
                <AccountSection customerId={c.id} name={c.name} canPay={may("Accounts/Receivables", "Create")} onPaid={() => void load()} />
              </TabsContent>
            ) : null}
          </div>
          <aside aria-label="Summary" className="rounded-lg border border-[var(--color-border-panel)] bg-card p-4 xl:col-span-4 xl:self-start">
            <DescriptionList
              variant="inline"
              density="compact"
              title="At a glance"
              items={[
                { label: "Last visit", value: p.totals.lastVisit ? day(p.totals.lastVisit) : "Never" },
                { label: "Visits", value: String(p.totals.visits) },
                { label: "Spent", value: formatKES(p.totals.spend), money: true },
                ...(canAccount ? [{ label: "Balance", value: formatKES(p.balance.balance), money: true }, { label: "Credit limit", value: p.balance.creditLimit ? formatKES(p.balance.creditLimit) : "No credit", money: !!p.balance.creditLimit }] : []),
              ]}
            />
          </aside>
        </div>
      </Tabs>
      {form ? (
        <CustomerForm
          form={form}
          onClose={() => setForm(null)}
          onSaved={() => {
            setForm(null);
            void load();
          }}
        />
      ) : null}
      <ConfirmDialog
        open={deleting}
        onOpenChange={setDeleting}
        level="A"
        title={`Delete ${c.name}?`}
        consequences={[`${p.totals.visits} past sale${p.totals.visits === 1 ? "" : "s"} become walk-in sales: they keep their figures but lose the link to this customer.`, "Their medical records are kept. This can't be undone."]}
        actionLabel="Delete customer"
        busyLabel="Deleting…"
        onConfirm={async () => {
          try {
            await CustomersApi.delete(c.id);
          } catch (e) {
            throw new Error(apiError(e, "Not deleted. Try again."));
          }
          toast.add({ title: `${c.name} deleted` });
          router.push("/customers");
        }}
      />
    </div>
  );
}

function CustomerPrescriptions({ customer }: { customer: { id: number; name: string } }) {
  const [rows, setRows] = React.useState<Rx[] | null>(null);
  React.useEffect(() => {
    PrescriptionsApi.getAll(customer.name)
      .then((all: Rx[]) => setRows(all.filter((r) => r.customerId === customer.id || (!r.customerId && r.patientName === customer.name))))
      .catch(() => setRows([]));
  }, [customer.id, customer.name]);
  return (
    // Prescription cards are white themselves; the section is a card only while it shows the empty message (no card in a card)
    <section className={rows?.length ? "space-y-3" : "rounded-lg border border-[var(--color-border-panel)] bg-card p-4 space-y-3"}>
      <h2 className="text-sm font-semibold">Prescriptions</h2>
      {rows === null ? (
        <Loading what="prescriptions" shape="cards" rows={2} />
      ) : rows.length ? (
        <div className="grid gap-3 lg:grid-cols-2">
          {rows.map((r) => (
            <PrescriptionCard
              key={r.id}
              reference={`RX-${String(r.id).padStart(5, "0")}`}
              patient={r.patientName}
              prescriber={r.prescriber || "Prescriber not recorded"}
              facility="Facility not recorded"
              date={r.dateIssued}
              status={(["Pending", "Dispensed", "Cancelled", "Expired"].includes(r.status) ? r.status : "Pending") as RxStatus}
              items={(r.items ?? []).map((i) => ({ name: i.productName, dose: [i.dosage, i.frequency].filter(Boolean).join(", "), quantity: i.quantity }))}
            />
          ))}
        </div>
      ) : (
        <p className="text-sm text-muted-foreground">No prescriptions for this customer.</p>
      )}
    </section>
  );
}

function MedicalSection({ customerId, by, canAdd, canMark }: { customerId: number; by: string; canAdd: boolean; canMark: boolean }) {
  const [rows, setRows] = React.useState<MedicalRow[] | null>(null);
  const [adding, setAdding] = React.useState(false);
  const [marking, setMarking] = React.useState<MedicalRow | null>(null);
  const load = React.useCallback(() => {
    CustomersApi.medical(customerId)
      .then(setRows)
      .catch((e) => {
        setRows([]);
        toast.add({ type: "error", title: "Couldn't load medical records", description: apiError(e, "Try again.") });
      });
  }, [customerId]);
  React.useEffect(load, [load]);
  return (
    <section className="rounded-lg border border-[var(--color-border-panel)] bg-card p-4 space-y-3">
      <div className="flex items-center justify-between gap-2">
        <h2 className="text-sm font-semibold">Medical record</h2>
        {canAdd && !adding ? (
          <Button size="sm" variant="outline" onClick={() => setAdding(true)}>
            <PlusIcon data-icon="inline-start" />
            Add entry
          </Button>
        ) : null}
      </div>
      {adding ? (
        <div className="rounded-lg border border-[var(--color-border-panel)] p-3 bg-card">
          <MedicalEntryForm
            by={by}
            today={localToday()}
            onSave={async (e) => {
              try {
                await CustomersApi.addMedical(customerId, fromEntry(e));
                toast.add({ title: `${e.kind} added` });
                setAdding(false);
                load();
              } catch (err) {
                toast.add({ type: "error", title: "Not added", description: apiError(err, "Try again.") });
              }
            }}
          />
          <div className="mt-2 flex justify-end">
            <Button size="sm" variant="ghost" onClick={() => setAdding(false)}>
              Cancel
            </Button>
          </div>
        </div>
      ) : null}
      {rows === null ? (
        <Loading what="medical records" shape="list" rows={2} bare />
      ) : (
        <MedicalTimeline entries={rows.map(toEntry)} formatDate={(iso) => formatDate(iso)} onMarkError={canMark ? (eid) => setMarking(rows.find((r) => String(r.id) === eid) ?? null) : undefined} />
      )}
      <ConfirmDialog
        open={!!marking}
        onOpenChange={(o) => !o && setMarking(null)}
        level="B"
        tone="primary"
        title={`Mark this ${marking?.kind.toLowerCase() ?? "entry"} entered in error?`}
        consequences={["It stays in the history, struck through, with your reason. Entries are never edited or deleted."]}
        actionLabel="Mark entered in error"
        busyLabel="Saving…"
        reasonHelp="Shown on the entry to everyone who can see this record."
        onConfirm={async ({ reason }) => {
          if (!marking) return;
          try {
            await CustomersApi.markMedicalError(customerId, marking.id, reason ?? "");
          } catch (e) {
            throw new Error(apiError(e, "Not saved. Try again."));
          }
          setMarking(null);
          load();
        }}
      />
    </section>
  );
}

function AccountSection({ customerId, name, canPay, onPaid }: { customerId: number; name: string; canPay: boolean; onPaid: () => void }) {
  type Statement = {
    summary: { balance: number; creditLimit: number; creditOnAccount: number };
    aging: { current: number; days31: number; days61: number; days90plus: number };
    transactions: { type: string; date: string; reference: string; charge: number; credit: number }[];
  };
  const [s, setS] = React.useState<Statement | null>(null);
  const [paying, setPaying] = React.useState<PaymentFormStatus | null>(null);
  const [payError, setPayError] = React.useState<string>();
  const load = React.useCallback(() => {
    CustomersApi.statement(customerId)
      .then(setS)
      .catch((e) => toast.add({ type: "error", title: "Couldn't load the account", description: apiError(e, "Try again.") }));
  }, [customerId]);
  React.useEffect(load, [load]);
  if (!s) return <Loading what="the account" shape="stats" rows={3} />;
  const overdue = s.aging.days31 + s.aging.days61 + s.aging.days90plus;
  return (
    <div className="space-y-5">
      <div className="flex flex-wrap items-center justify-between gap-2">
        <p className="text-xs text-muted-foreground">Credit sales, invoices and payments received. All customers&apos; balances and ageing are in Accounts → Receivables.</p>
        {canPay && s.summary.balance > 0 ? (
          <Button size="sm" onClick={() => setPaying("idle")}>
            Receive payment
          </Button>
        ) : null}
      </div>
      <BalanceSummary kind="receivable" balance={s.summary.balance} creditLimit={s.summary.creditLimit || undefined} overdue={overdue || undefined} />
      <AgingBuckets data={{ current: s.aging.current, d31to60: s.aging.days31, d61to90: s.aging.days61, over90: s.aging.days90plus }} />
      {s.summary.creditOnAccount ? <p className="text-sm">Paid ahead: {formatKES(s.summary.creditOnAccount)} on account.</p> : null}
      {/* The statement covers the whole history, so there is no balance brought forward to show */}
      <StatementTable rows={s.transactions.map((t) => ({ date: String(t.date).slice(0, 10), ref: t.reference, description: t.type === "Invoice" ? "Credit sale" : "Payment received", charge: t.charge || undefined, payment: t.credit || undefined }))} />
      <Dialog open={paying !== null} onOpenChange={(o) => !o && paying !== "submitting" && setPaying(null)}>
        <DialogContent>
          <DialogHeader>
            <DialogTitle>Receive payment from {name}</DialogTitle>
            <DialogDescription>Applied to the oldest unpaid credit sales first.</DialogDescription>
          </DialogHeader>
          <PaymentForm
            party={name}
            outstanding={s.summary.balance}
            today={localToday()}
            status={paying ?? "idle"}
            error={payError}
            onSubmit={async (pay) => {
              setPaying("submitting");
              setPayError(undefined);
              try {
                await CustomersApi.recordPayment(customerId, { amount: pay.amount, method: pay.method, reference: pay.reference, paymentDate: pay.date });
                toast.add({ title: `${formatKES(pay.amount)} received` });
                setPaying(null);
                load();
                onPaid();
              } catch (e) {
                setPaying("error");
                setPayError(apiError(e, "Not recorded. Try again."));
              }
            }}
          />
        </DialogContent>
      </Dialog>
    </div>
  );
}
