"use client";

/**
 * Groups and schemes (PRD 4.2, 4.5: CU-04, CU-09), on the Medora design system and plan D (DECISIONS.md, 26 Oct 2026).
 * Customer groups as the design's group list (price group and default discount, checked by `groupProblem`), and
 * schemes with the design's `SchemesManager`, which edits a copy that Save turns into API calls. A customer joins a
 * group or scheme on their own record (Customers). Claims against schemes come with Accounts (plan H).
 */

import * as React from "react";
import { PlusIcon } from "lucide-react";

import { groupProblem } from "@/components/medora/catalog-ops";
import { PanelError } from "@/components/medora/errors";
import { MedoraPageHeader } from "@/components/medora/page-header";
import { SchemesManager, schemeProblems, type Scheme } from "@/components/medora/registers";
import { RowActions } from "@/components/medora/row-actions";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select";
import { Spinner } from "@/components/ui/spinner";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { toast } from "@/components/ui/toast";
import { CustomerGroupsApi, PriceGroupsApi, SchemesApi, type CustomerGroupRow, type PriceGroupRow, type SchemeRow } from "@/lib/api";
import { useTabParam, validTab } from "@/lib/use-tab-param";
import { useMenuPermissions } from "@/lib/permissions";
import { count } from "@/lib/format";
import { Loading } from "@/components/medora/loading";

const apiError = (e: unknown, fallback: string): string => {
  const m = (e as { response?: { data?: { message?: string | string[] } } })?.response?.data?.message;
  return Array.isArray(m) ? m.join(" ") : m || fallback;
};
export default function GroupsAndSchemesPage() {
  const [tabParam, setTabParam] = useTabParam(); // page tabs change the URL (tabs spec 7.1)
  const { can, loaded } = useMenuPermissions();
  const groupsOk = loaded && (can("Customers/Customer groups", "Read") || can("Customers/Customer list", "Read"));
  const schemesOk = loaded && (can("Customers/Schemes", "Read") || can("Customers/Customer list", "Read"));
  if (!loaded) return <Loading what="customer groups" shape="list" rows={3} />;
  return (
    <div className="medora mx-auto w-full max-w-6xl space-y-4">
      <MedoraPageHeader title="Groups and schemes" description="Customer groups' prices and discounts, and the insurance schemes customers belong to." />
      <Tabs value={validTab(tabParam, ["groups", "schemes"], groupsOk ? "groups" : "schemes")} onValueChange={(v) => setTabParam(String(v))}>
        <TabsList variant="line" className="w-full justify-start border-b border-border">
          {groupsOk ? <TabsTrigger value="groups">Customer groups</TabsTrigger> : null}
          {schemesOk ? <TabsTrigger value="schemes">Schemes</TabsTrigger> : null}
        </TabsList>
        {groupsOk ? (
          <TabsContent value="groups" className="pt-3">
            <Groups can={(a) => can("Customers/Customer groups", a)} />
          </TabsContent>
        ) : null}
        {schemesOk ? (
          <TabsContent value="schemes" className="pt-3">
            <Schemes can={(a) => can("Customers/Schemes", a)} />
          </TabsContent>
        ) : null}
      </Tabs>
    </div>
  );
}

/* ------------------------------------------- CU-04 Customer groups ------------------------------------------- */

type Edit = { id?: number; name: string; priceGroupId: string; discountPct: string };

function Groups({ can }: { can: (action: string) => boolean }) {
  const [rows, setRows] = React.useState<CustomerGroupRow[] | null>(null);
  const [failed, setFailed] = React.useState(false); // R12 (card 037): a failed load says so, with Try again
  const [priceGroups, setPriceGroups] = React.useState<PriceGroupRow[]>([]);
  const [edit, setEdit] = React.useState<Edit | null>(null);
  const [saving, setSaving] = React.useState(false);

  const load = React.useCallback(async () => {
    const [g, p] = await Promise.all([CustomerGroupsApi.list(), PriceGroupsApi.list().catch(() => [] as PriceGroupRow[])]);
    setRows(g);
    setPriceGroups(p);
    setFailed(false);
  }, []);
  React.useEffect(() => {
    load().catch((e) => {
      setRows([]);
      setFailed(true);
      toast.add({ type: "error", title: "Couldn't load customer groups", description: apiError(e, "Try again.") });
    });
  }, [load]);
  if (failed) return <PanelError framed what="customer groups" onRetry={() => void load().catch(() => setFailed(true))} />;
  if (!rows) return <Loading what="customer groups" shape="list" rows={3} />;

  const editing = edit?.id ? rows.find((r) => r.id === edit.id)?.name : undefined;
  const problem = edit
    ? groupProblem({ name: edit.name, priceGroup: "", discountPct: Number(edit.discountPct), members: 0 }, rows.map((r) => ({ name: r.name, priceGroup: r.priceGroup ?? "", discountPct: r.discountPct, members: r.members })), editing) ??
      (!/^\d+(\.\d{1,2})?$/.test(edit.discountPct.trim()) ? "The discount is a number, e.g. 5 or 7.5." : undefined)
    : undefined;

  const run = async (fn: () => Promise<unknown>, done: string) => {
    setSaving(true);
    try {
      await fn();
      toast.add({ title: done });
      await load();
      return true;
    } catch (e) {
      toast.add({ type: "error", title: "Not saved", description: apiError(e, "Try again.") });
      return false;
    } finally {
      setSaving(false);
    }
  };
  const save = async () => {
    if (!edit || problem) return;
    const body = { name: edit.name.trim(), priceGroupId: edit.priceGroupId ? Number(edit.priceGroupId) : null, discountPct: Number(edit.discountPct) };
    if (await run(() => (edit.id ? CustomerGroupsApi.update(edit.id, body) : CustomerGroupsApi.create(body)), edit.id ? "Group saved" : `${body.name} added`)) setEdit(null);
  };

  return (
    <div className="space-y-3">
      {rows.length ? (
        <ul className="divide-y divide-[var(--color-border-subtle)] rounded-lg border border-[var(--color-border-panel)] text-sm bg-card">
          {rows.map((g) => (
            <li key={g.id} className="flex flex-wrap items-center gap-3 px-3 py-2">
              <span className={g.active ? "min-w-0 flex-1" : "min-w-0 flex-1 text-muted-foreground"}>
                <span className="font-medium">{g.name}</span>
                {!g.active ? " (deactivated: members pay retail)" : ""}
                <span className="block text-xs text-muted-foreground">
                  {g.priceGroup ? `${g.priceGroup} prices` : "Retail prices"} · {g.discountPct}% off · {count(g.members, "customer")}
                </span>
              </span>
              <RowActions
                label={g.name}
                actions={[
                  ...(can("Update")
                    ? [
                        { label: "Edit", onSelect: () => setEdit({ id: g.id, name: g.name, priceGroupId: g.priceGroupId ? String(g.priceGroupId) : "", discountPct: String(g.discountPct) }) },
                        { label: g.active ? "Deactivate" : "Reactivate", destructive: g.active, onSelect: () => void (saving || run(() => CustomerGroupsApi.update(g.id, { active: !g.active }), g.active ? `${g.name} deactivated` : `${g.name} reactivated`)) },
                      ]
                    : []),
                  ...(can("Delete") && !g.members ? [{ label: "Delete", destructive: true, onSelect: () => void (saving || run(() => CustomerGroupsApi.remove(g.id), `${g.name} deleted`)) }] : []),
                ]}
              />
            </li>
          ))}
        </ul>
      ) : (
        <p className="text-sm text-muted-foreground">No customer groups yet. Everyone pays retail.</p>
      )}
      {edit ? (
        <div className="space-y-2 rounded-lg border border-[var(--color-border-panel)] p-3 text-sm bg-card">
          <div className="grid gap-2">
            <Input aria-label="Group name" placeholder="Group name, e.g. Staff" value={edit.name} onChange={(e) => setEdit({ ...edit, name: e.target.value })} />
            <NativeSelect aria-label="Price group" value={edit.priceGroupId} onChange={(e) => setEdit({ ...edit, priceGroupId: e.target.value })}>
              <NativeSelectOption value="">Retail prices</NativeSelectOption>
              {priceGroups
                .filter((p) => p.active || String(p.id) === edit.priceGroupId)
                .map((p) => (
                  <NativeSelectOption key={p.id} value={String(p.id)}>
                    {p.name} prices
                  </NativeSelectOption>
                ))}
            </NativeSelect>
            <Input aria-label="Default discount %" inputMode="decimal" placeholder="Discount %" value={edit.discountPct} onChange={(e) => setEdit({ ...edit, discountPct: e.target.value })} />
          </div>
          <p className="text-xs text-muted-foreground">At the till a member&apos;s items are priced from the price group (retail where it has none), then the discount is taken off. Prices the cashier changes by hand stay as typed.</p>
          {edit.name.trim() && problem ? <p className="text-xs text-status-error-text">{problem}</p> : null}
          <div className="flex justify-end gap-2">
            <Button size="sm" variant="ghost" onClick={() => setEdit(null)}>
              Cancel
            </Button>
            <Button size="sm" disabled={!!problem || saving} onClick={() => void save()}>
              {saving ? <Spinner /> : null}
              Save group
            </Button>
          </div>
        </div>
      ) : can("Create") ? (
        <Button size="sm" variant="outline" onClick={() => setEdit({ name: "", priceGroupId: "", discountPct: "0" })}>
          <PlusIcon data-icon="inline-start" />
          New group
        </Button>
      ) : null}
    </div>
  );
}

/* ------------------------------------------- CU-09 Schemes ------------------------------------------- */

const toScheme = (s: SchemeRow): Scheme => ({ id: String(s.id), name: s.name, kind: s.kind, copayPct: String(s.copayPct), defaultLimit: s.defaultLimit == null ? "" : String(s.defaultLimit), claimWithinDays: String(s.claimWithinDays), active: s.active, members: s.members });
const bodyOf = (s: Scheme) => ({ name: s.name.trim(), kind: s.kind, copayPct: s.copayPct, defaultLimit: s.defaultLimit, claimWithinDays: s.claimWithinDays, active: s.active });

function Schemes({ can }: { can: (action: string) => boolean }) {
  const [saved, setSaved] = React.useState<Scheme[] | null>(null);
  const [failed, setFailed] = React.useState(false); // R12 (card 037): a failed load says so, with Try again
  const [draft, setDraft] = React.useState<Scheme[]>([]);
  const [saving, setSaving] = React.useState(false);

  const load = React.useCallback(async () => {
    const s = (await SchemesApi.list()).map(toScheme);
    setSaved(s);
    setDraft(s);
    setFailed(false);
  }, []);
  React.useEffect(() => {
    load().catch((e) => {
      setSaved([]);
      setFailed(true);
      toast.add({ type: "error", title: "Couldn't load schemes", description: apiError(e, "Try again.") });
    });
  }, [load]);
  if (failed) return <PanelError framed what="schemes" onRetry={() => void load().catch(() => setFailed(true))} />;
  if (!saved) return <Loading what="schemes" columns={7} rows={4} numeric={[2, 3, 4, 5]} />;

  const dirty = JSON.stringify(saved) !== JSON.stringify(draft);
  const problems = draft.flatMap((s) => schemeProblems(s, draft));
  const editable = can("Update") || can("Create");

  const save = async () => {
    if (problems.length) return toast.add({ type: "error", title: "Fix the schemes marked in red first" });
    setSaving(true);
    const before = new Map(saved.map((s) => [s.id, s]));
    try {
      // Deletes first, so a name freed by a delete can be reused
      for (const s of saved) if (!draft.some((d) => d.id === s.id)) await SchemesApi.remove(Number(s.id));
      for (const d of draft) {
        const was = before.get(d.id);
        if (!was) await SchemesApi.create(bodyOf(d));
        else if (JSON.stringify(bodyOf(was)) !== JSON.stringify(bodyOf(d))) await SchemesApi.update(Number(d.id), bodyOf(d));
      }
      toast.add({ title: "Schemes saved" });
    } catch (e) {
      toast.add({ type: "error", title: "Schemes not all saved", description: apiError(e, "Try again.") });
    } finally {
      setSaving(false);
      await load().catch(() => undefined);
    }
  };

  return (
    <div className="space-y-3">
      <fieldset disabled={!editable || saving} className="min-w-0">
        <SchemesManager schemes={draft} onChange={(next) => setDraft(can("Create") ? next : next.filter((s) => saved.some((x) => x.id === s.id)))} />
      </fieldset>
      <p className="text-xs text-muted-foreground">Co-pay is what the member pays at the till; the default limit fills in each new member&apos;s annual cover; the claim deadline sets when claims must be sent. Claims are raised in Accounts.</p>
      {editable ? (
        <div className="flex gap-2">
          <Button size="sm" disabled={!dirty || saving} onClick={() => void save()}>
            {saving ? <Spinner /> : null}
            Save schemes
          </Button>
          {dirty ? (
            <Button size="sm" variant="ghost" disabled={saving} onClick={() => setDraft(saved)}>
              Discard
            </Button>
          ) : null}
        </div>
      ) : null}
    </div>
  );
}
