"use client";

/**
 * Price groups (IN-03), on the Medora design system and plan D (DECISIONS.md, 26 Oct 2026): the price groups
 * themselves, the design's `PriceGroupGrid` (one column per group beside retail, a price under cost flagged) and
 * `PriceGroupCsv` (download, edit, upload, checked row by row). Customer groups choose a price group
 * (Customers → Groups and schemes); the till prices a member's basket from it, then the group's discount.
 */

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

import { PriceGroupGrid } from "@/components/medora/catalog-ops";
import { PanelError } from "@/components/medora/errors";
import { MedoraPageHeader } from "@/components/medora/page-header";
import { PriceGroupCsv } from "@/components/medora/setup-more";
import { ConfirmDialog } from "@/components/medora/confirm-dialog";
import { RowActions } from "@/components/medora/row-actions";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";
import { Spinner } from "@/components/ui/spinner";
import { toast } from "@/components/ui/toast";
import { PriceGroupsApi, type PriceGrid, type PriceGroupRow } from "@/lib/api";
import { useMenuPermissions } from "@/lib/permissions";
import { count } from "@/lib/format";
import { Loading } from "@/components/medora/loading";

const PAGE = "Inventory/Price groups";
const PER_PAGE = 50;

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;
};
/** The grid keys cells `productId:groupName`; the API keys prices `productId:groupId`. */
const asText = (n: number | undefined) => (n == null ? "" : String(n));

export default function PriceGroupsPage() {
  const { can, loaded } = useMenuPermissions();
  const may = (a: string) => loaded && can(PAGE, a);
  const [groups, setGroups] = React.useState<PriceGroupRow[] | null>(null);
  const [failed, setFailed] = React.useState(false); // R12 (card 037): a failed load says so, with Try again
  const [grid, setGrid] = React.useState<PriceGrid | null>(null);
  const [draft, setDraft] = React.useState<Record<string, string>>({});
  const [q, setQ] = React.useState("");
  const [page, setPage] = React.useState(1);
  const [newName, setNewName] = React.useState("");
  const [renaming, setRenaming] = React.useState<{ id: number; name: string } | null>(null);
  const [deleting, setDeleting] = React.useState<PriceGroupRow | null>(null);
  const [saving, setSaving] = React.useState(false);

  const load = React.useCallback(async () => {
    const [g, d] = await Promise.all([PriceGroupsApi.list(), PriceGroupsApi.grid()]);
    setGroups(g);
    setGrid(d);
    setDraft({});
    setFailed(false);
  }, []);
  React.useEffect(() => {
    load().catch((e) => {
      setGroups([]);
      setFailed(true);
      toast.add({ type: "error", title: "Couldn't load price groups", description: apiError(e, "Try again.") });
    });
  }, [load]);

  const active = (grid?.groups ?? []).filter((g) => g.active);
  const nameOf = (id: number) => grid?.groups.find((g) => g.id === id)?.name ?? "";
  const idOf = (name: string) => grid?.groups.find((g) => g.name === name)?.id;
  // Saved prices as the grid shows them, then what has been typed over them
  const saved = React.useMemo(() => {
    const out: Record<string, string> = {};
    for (const [k, v] of Object.entries(grid?.prices ?? {})) {
      const [p, g] = k.split(":");
      out[`${p}:${nameOf(Number(g))}`] = asText(v);
    }
    return out;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [grid]);
  const prices = { ...saved, ...draft };
  const changed = Object.entries(draft).filter(([k, v]) => (saved[k] ?? "") !== v.trim());
  const bad = changed.filter(([, v]) => v.trim() && !(/^\d+(\.\d{1,2})?$/.test(v.replace(/,/g, "").trim()) && Number(v.replace(/,/g, "")) > 0));

  const term = q.trim().toLowerCase();
  const products = (grid?.products ?? []).filter((p) => !term || `${p.name} ${p.code}`.toLowerCase().includes(term));
  const pages = Math.max(1, Math.ceil(products.length / PER_PAGE));
  const shown = products.slice((Math.min(page, pages) - 1) * PER_PAGE, Math.min(page, pages) * PER_PAGE);

  const savePrices = async () => {
    if (bad.length) return toast.add({ type: "error", title: "Some prices aren't amounts", description: "A price is more than 0, e.g. 120 or 120.50. Clear the cell for retail." });
    setSaving(true);
    try {
      const body = changed.map(([k, v]) => {
        const [p, g] = [k.slice(0, k.indexOf(":")), k.slice(k.indexOf(":") + 1)];
        return { productId: Number(p), priceGroupId: idOf(g)!, price: v.trim() ? Number(v.replace(/,/g, "")) : null };
      });
      const r = await PriceGroupsApi.savePrices(body);
      toast.add({ title: "Prices saved", description: `${r.set} set, ${r.removed} back to retail.` });
      await load();
    } catch (e) {
      toast.add({ type: "error", title: "Prices not saved", description: apiError(e, "Try again.") });
    } finally {
      setSaving(false);
    }
  };

  const act = async (fn: () => Promise<unknown>, done: string) => {
    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;
    }
  };

  if (groups === null || !loaded)
    return (
      <Loading what="price groups" columns={5} rows={8} numeric={[1, 2, 3, 4]} />
    );

  return (
    <div className="medora mx-auto w-full max-w-6xl space-y-4">
      <MedoraPageHeader title="Price groups" description="Special prices for groups of customers, such as insurance, corporate or staff." />

      <section aria-label="Price groups" className="space-y-3 rounded-lg border border-[var(--color-border-panel)] p-4 bg-card">
        <h2 className="text-sm font-semibold">Groups</h2>
        {groups.length ? (
          <ul className="divide-y divide-[var(--color-border-subtle)] rounded-lg border border-[var(--color-border-panel)] text-sm bg-card">
            {groups.map((g) => (
              <li key={g.id} className="flex flex-wrap items-center gap-3 px-3 py-2">
                {renaming?.id === g.id ? (
                  <span className="flex min-w-0 flex-1 gap-2">
                    <Input aria-label="Price group name" value={renaming.name} onChange={(e) => setRenaming({ ...renaming, name: e.target.value })} />
                    <Button size="sm" disabled={renaming.name.trim().length < 2} onClick={async () => (await act(() => PriceGroupsApi.update(g.id, { name: renaming.name.trim() }), "Price group renamed")) && setRenaming(null)}>
                      Save
                    </Button>
                    <Button size="sm" variant="ghost" onClick={() => setRenaming(null)}>
                      Cancel
                    </Button>
                  </span>
                ) : (
                  <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)" : ""}
                    <span className="block text-xs text-muted-foreground">
                      {count(g.prices, "price")} · {g.usedBy.length ? `used by ${g.usedBy.join(", ")}` : "no customer group uses it yet"}
                    </span>
                  </span>
                )}
                {renaming?.id !== g.id ? (
                  <RowActions
                    label={g.name}
                    actions={[
                      ...(may("Update")
                        ? [
                            { label: "Rename", onSelect: () => setRenaming({ id: g.id, name: g.name }) },
                            { label: g.active ? "Deactivate" : "Reactivate", destructive: g.active, onSelect: () => void act(() => PriceGroupsApi.update(g.id, { active: !g.active }), g.active ? `${g.name} deactivated: its members pay retail` : `${g.name} reactivated`) },
                          ]
                        : []),
                      ...(may("Delete") && !g.usedBy.length ? [{ label: "Delete…", destructive: true, onSelect: () => setDeleting(g) }] : []),
                    ]}
                  />
                ) : null}
              </li>
            ))}
          </ul>
        ) : (
          failed ? (
            <PanelError what="price groups" onRetry={() => void load().catch(() => setFailed(true))} />
          ) : (
            <p className="text-sm text-muted-foreground">No price groups yet.</p>
          )
        )}
        {may("Create") ? (
          <div className="flex max-w-md gap-2">
            <Input aria-label="New price group" placeholder="e.g. Insurance" value={newName} onChange={(e) => setNewName(e.target.value)} />
            <Button variant="outline" disabled={newName.trim().length < 2} onClick={async () => (await act(() => PriceGroupsApi.create(newName.trim()), `${newName.trim()} added`)) && setNewName("")}>
              <PlusIcon data-icon="inline-start" />
              Add
            </Button>
          </div>
        ) : null}
      </section>

      {active.length && grid ? (
        <section aria-label="Prices" className="space-y-3 rounded-lg border border-[var(--color-border-panel)] p-4 bg-card">
          <div className="flex flex-wrap items-center gap-2">
            <h2 className="mr-auto text-sm font-semibold">Prices</h2>
            <InputGroup className="max-w-xs">
              <InputGroupAddon>
                <SearchIcon />
              </InputGroupAddon>
              <InputGroupInput placeholder="Product name or code" value={q} onChange={(e) => (setQ(e.target.value), setPage(1))} aria-label="Find a product" />
            </InputGroup>
          </div>
          <fieldset disabled={!may("Update") || saving} className="min-w-0">
            <PriceGroupGrid products={shown} groups={active.map((g) => g.name)} prices={prices} onChange={(k, v) => setDraft((d) => ({ ...d, [k]: v }))} />
          </fieldset>
          <div className="flex flex-wrap items-center gap-2 text-sm">
            <span className="text-muted-foreground">
              {products.length.toLocaleString("en-US")} products{pages > 1 ? ` · page ${Math.min(page, pages)} of ${pages}` : ""}
            </span>
            {pages > 1 ? (
              <>
                <Button size="xs" variant="outline" disabled={page <= 1} onClick={() => setPage((p) => p - 1)}>
                  Previous
                </Button>
                <Button size="xs" variant="outline" disabled={page >= pages} onClick={() => setPage((p) => p + 1)}>
                  Next
                </Button>
              </>
            ) : null}
            {may("Update") ? (
              <span className="ml-auto flex gap-2">
                {changed.length ? (
                  <Button size="sm" variant="ghost" disabled={saving} onClick={() => setDraft({})}>
                    Discard
                  </Button>
                ) : null}
                <Button size="sm" disabled={!changed.length || saving} onClick={() => void savePrices()}>
                  {saving ? <Spinner /> : null}
                  Save {changed.length ? `${changed.length} ` : ""}price{changed.length === 1 ? "" : "s"}
                </Button>
              </span>
            ) : null}
          </div>
          {may("Update") ? (
            <div className="border-t border-[var(--color-border-subtle)] pt-4">
              <PriceGroupCsv
                products={grid.products.map((p) => ({ id: p.id, code: p.code, name: p.name, cost: p.cost }))}
                groups={active.map((g) => g.name)}
                prices={prices}
                onApply={(u) => {
                  setDraft((d) => ({ ...d, ...u }));
                  toast.add({ title: `${count(Object.keys(u).length, "price")} read from the file`, description: "Check them, then Save prices." });
                }}
              />
            </div>
          ) : null}
        </section>
      ) : null}

      <ConfirmDialog
        open={!!deleting}
        onOpenChange={(o) => !o && setDeleting(null)}
        level="A"
        title={`Delete ${deleting?.name ?? ""}?`}
        consequences={[`Its ${deleting?.prices ?? 0} prices are deleted with it. This can't be undone.`]}
        actionLabel="Delete price group"
        busyLabel="Deleting…"
        onConfirm={async () => {
          if (deleting) await act(() => PriceGroupsApi.remove(deleting.id), `${deleting.name} deleted`);
          setDeleting(null);
        }}
      />
    </div>
  );
}
