"use client";

/**
 * Promotions (ST-03, PS-07), on the Medora design system and plan E (DECISIONS.md, 26 Oct 2026): the design's
 * promotion form (`promoProblems`, `PromoBadge`, the shilling preview) with a basket limit and an optional customer
 * group, and the list with what each has given. The till applies running promotions through the API's pricing:
 * the best one per line, then the best basket promotion; a price set by hand replaces them (Q2), and a customer
 * group's prices win when they are lower (plan D, Q1).
 */

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

import { ConfirmDialog } from "@/components/medora/confirm-dialog";
import { PanelError } from "@/components/medora/errors";
import { MedoraPageHeader } from "@/components/medora/page-header";
import { RowActions } from "@/components/medora/row-actions";
import { PromoBadge, promoDiscount, promoProblems, type Promo } from "@/components/medora/settings-ops";
import { RecordDrawer } from "@/components/medora/drawer";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Field, FieldDescription, 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 { toast } from "@/components/ui/toast";
import { PromotionsApi, type PromotionInput, type PromotionRow } from "@/lib/api";
import { count, formatDate, formatKES, localToday } from "@/lib/format";
import { useMenuPermissions } from "@/lib/permissions";
import { Loading } from "@/components/medora/loading";

const PAGE = "Settings/Promotions";

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 kes = (n: number) => formatKES(n, { decimals: true });

type Draft = { id?: number; name: string; kind: "Percent" | "Fixed"; value: string; scope: "Basket" | "Category" | "Product"; target: string; targetName: string; minBasket: string; maxBasket: string; start: string; end: string; branchIds: number[]; groupId: string; active: boolean };
const num = (v: string) => Number(v.replace(/,/g, "")) || 0;

export default function PromotionsPage() {
  const { can, loaded } = useMenuPermissions();
  const may = (a: string) => loaded && can(PAGE, a);
  const today = localToday();
  const [data, setData] = React.useState<Awaited<ReturnType<typeof PromotionsApi.list>> | null>(null);
  const [failed, setFailed] = React.useState(false); // R12 (card 037): a failed load says so, with Try again
  const [draft, setDraft] = React.useState<Draft | null>(null);
  const [tried, setTried] = React.useState(false);
  const [saving, setSaving] = React.useState(false);
  const [deleting, setDeleting] = React.useState<PromotionRow | null>(null);
  const [productQ, setProductQ] = React.useState("");
  const [found, setFound] = React.useState<{ id: number; name: string; code: string | null; price: number }[]>([]);

  const load = React.useCallback(async () => {
    try {
      setData(await PromotionsApi.list());
      setFailed(false);
    } catch (e) {
      setFailed(true);
      toast.add({ type: "error", title: "Couldn't load promotions", description: apiError(e, "Try again.") });
      setData({ promotions: [], branches: [], categories: [], groups: [] });
    }
  }, []);
  React.useEffect(() => {
    void load();
  }, [load]);

  // Products to pick for a product promotion, searched as the person types
  React.useEffect(() => {
    if (draft?.scope !== "Product" || productQ.trim().length < 2) return setFound([]);
    const t = setTimeout(() => {
      PromotionsApi.products(productQ.trim())
        .then(setFound)
        .catch(() => setFound([]));
    }, 250);
    return () => clearTimeout(t);
  }, [productQ, draft?.scope]);

  const startNew = () => {
    setTried(false);
    setProductQ("");
    setDraft({ name: "", kind: "Percent", value: "", scope: "Basket", target: "", targetName: "", minBasket: "0", maxBasket: "", start: today, end: today, branchIds: (data?.branches ?? []).map((b) => b.id), groupId: "", active: true });
  };
  const edit = (p: PromotionRow) => {
    setTried(false);
    setProductQ("");
    setDraft({ id: p.id, name: p.name, kind: p.kind, value: String(p.value), scope: p.scope, target: p.target ?? "", targetName: p.targetName ?? "", minBasket: String(p.minBasket), maxBasket: p.maxBasket == null ? "" : String(p.maxBasket), start: p.start, end: p.end, branchIds: p.branchIds, groupId: p.groupId == null ? "" : String(p.groupId), active: p.active });
  };

  // The design's checks, on the design's shape (branches by name)
  const branchName = (id: number) => data?.branches.find((b) => b.id === id)?.name ?? `Branch ${id}`;
  const asPromo = (d: Draft): Promo => ({ name: d.name, kind: d.kind, value: num(d.value), scope: d.scope, target: d.target || undefined, minBasket: num(d.minBasket), start: d.start, end: d.end, branches: d.branchIds.map(branchName), active: d.active });
  const problems = draft
    ? [
        ...promoProblems(asPromo(draft)),
        ...(draft.maxBasket.trim() && num(draft.maxBasket) < num(draft.minBasket) ? ["The basket limit is below the minimum basket, so the promotion would never apply."] : []),
      ]
    : [];

  const save = async () => {
    if (!draft) return;
    setTried(true);
    if (problems.length) return;
    setSaving(true);
    const body: PromotionInput = {
      name: draft.name.trim(),
      kind: draft.kind,
      value: num(draft.value),
      scope: draft.scope,
      target: draft.scope === "Basket" ? null : draft.target,
      minBasket: num(draft.minBasket),
      maxBasket: draft.maxBasket.trim() ? num(draft.maxBasket) : null,
      start: draft.start,
      end: draft.end,
      branchIds: draft.branchIds,
      groupId: draft.groupId ? Number(draft.groupId) : null,
      active: draft.active,
    };
    try {
      if (draft.id) await PromotionsApi.update(draft.id, body);
      else await PromotionsApi.create(body);
      toast.add({ title: `${body.name} saved`, description: draft.branchIds.map(branchName).join(", ") });
      setDraft(null);
      await load();
    } catch (e) {
      toast.add({ type: "error", title: "Promotion not saved", description: apiError(e, "Try again.") });
    } finally {
      setSaving(false);
    }
  };

  const switchOver = async (p: PromotionRow) => {
    try {
      await PromotionsApi.update(p.id, { active: !p.active });
      toast.add({ title: p.active ? `${p.name} switched off` : `${p.name} switched on` });
      await load();
    } catch (e) {
      toast.add({ type: "error", title: "Not changed", description: apiError(e, "Try again.") });
    }
  };

  if (!data || !loaded)
    return (
      <Loading what="promotions" columns={6} rows={5} numeric={[4]} />
    );

  const set = (patch: Partial<Draft>) => setDraft((d) => (d ? { ...d, ...patch } : d));
  const off = draft ? promoDiscount(asPromo(draft), 1500, 900) : 0;

  return (
    <div className="medora mx-auto w-full max-w-6xl space-y-4">
      <MedoraPageHeader
        title="Promotions"
        description="Discounts the till applies by itself between two dates."
        primary={may("Create") ? { label: "Add promotion", icon: PlusIcon, onSelect: startNew } : undefined}
      />

      <div className="overflow-x-auto rounded-lg border border-[var(--color-border-panel)] bg-card">
        <Table>
          <TableHeader>
            <TableRow>
              <TableHead>Promotion</TableHead>
              <TableHead>Applies to</TableHead>
              <TableHead>Dates</TableHead>
              <TableHead>Status</TableHead>
              <TableHead className="text-right">Given</TableHead>
              <TableHead className="w-10">
                <span className="sr-only">Actions</span>
              </TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            {data.promotions.map((p) => (
              <TableRow key={p.id}>
                <TableCell className="whitespace-normal">
                  <span className="font-medium">{p.name}</span>
                  <span className="block text-xs text-muted-foreground">
                    {p.kind === "Percent" ? `${p.value}% off` : `${kes(p.value)} off`}
                    {p.minBasket ? ` · basket from ${kes(p.minBasket)}` : ""}
                    {p.maxBasket != null ? ` up to ${kes(p.maxBasket)}` : ""}
                    {p.groupId != null ? ` · ${data.groups.find((g) => g.id === p.groupId)?.name ?? "one customer group"} only` : ""}
                  </span>
                </TableCell>
                <TableCell className="whitespace-normal">
                  {p.scope === "Basket" ? "Whole basket" : `${p.scope}: ${p.targetName}`}
                  <span className="block text-xs text-muted-foreground">{p.branchIds.map(branchName).join(", ")}</span>
                </TableCell>
                <TableCell className="whitespace-nowrap tabular-nums">
                  {formatDate(p.start)} to {formatDate(p.end)}
                </TableCell>
                <TableCell>
                  <PromoBadge status={p.status} />
                </TableCell>
                <TableCell className="text-right tabular-nums">
                  {kes(p.given)}
                  <span className="block text-xs text-muted-foreground">{count(p.sales, "sale")}</span>
                </TableCell>
                <TableCell>
                  <RowActions
                    label={p.name}
                    actions={[
                      ...(may("Update") ? [{ label: "Edit", onSelect: () => edit(p) }, { label: p.active ? "Switch off" : "Switch on", onSelect: () => void switchOver(p) }] : []),
                      ...(may("Delete") && !p.sales ? [{ label: "Delete", destructive: true, onSelect: () => setDeleting(p) }] : []),
                    ]}
                  />
                </TableCell>
              </TableRow>
            ))}
            {!data.promotions.length ? (
              <TableRow>
                <TableCell colSpan={6} className="py-10 text-center text-muted-foreground">
                  {failed ? <PanelError what="promotions" onRetry={() => void load()} /> : "No promotions yet."}
                </TableCell>
              </TableRow>
            ) : null}
          </TableBody>
        </Table>
      </div>

      {draft ? (
        <RecordDrawer open onOpenChange={(o) => !o && !saving && setDraft(null)} variant="edit" width="lg" saving={saving} title={draft.id ? `Edit ${draft.name}` : "New promotion"}
          footer={
            <>
              <Button variant="outline" disabled={saving} onClick={() => setDraft(null)}>
                Cancel
              </Button>
              <Button disabled={saving} onClick={() => void save()}>
                {saving ? <Spinner /> : null}
                Save promotion
              </Button>
            </>
          }
        >
          <div className="space-y-4">
            <div className="flex flex-wrap items-center gap-2">
              <PromoBadge status={!draft.active ? "Off" : today < draft.start ? "Scheduled" : today > draft.end ? "Ended" : "Running"} />
              <span className="text-xs text-muted-foreground">Only the best promotion applies to each line; promotions don&apos;t stack.</span>
            </div>
            <div className="grid gap-3">
              <Field>
                <FieldLabel htmlFor="pr-name">Name</FieldLabel>
                <Input id="pr-name" value={draft.name} onChange={(e) => set({ name: e.target.value })} />
              </Field>
              <Field>
                <FieldLabel htmlFor="pr-kind">Discount</FieldLabel>
                <div className="flex gap-2">
                  <NativeSelect id="pr-kind" value={draft.kind} onChange={(e) => set({ kind: e.target.value as Draft["kind"] })}>
                    <NativeSelectOption value="Percent">%</NativeSelectOption>
                    <NativeSelectOption value="Fixed">KES</NativeSelectOption>
                  </NativeSelect>
                  <Input aria-label="Discount amount" inputMode="decimal" className="tabular-nums" value={draft.value} onChange={(e) => set({ value: e.target.value.replace(/[^0-9.]/g, "") })} />
                </div>
              </Field>
              <Field>
                <FieldLabel htmlFor="pr-scope">Applies to</FieldLabel>
                <div className="flex gap-2">
                  <NativeSelect id="pr-scope" value={draft.scope} onChange={(e) => set({ scope: e.target.value as Draft["scope"], target: "", targetName: "" })}>
                    <NativeSelectOption>Basket</NativeSelectOption>
                    <NativeSelectOption>Category</NativeSelectOption>
                    <NativeSelectOption>Product</NativeSelectOption>
                  </NativeSelect>
                  {draft.scope === "Category" ? (
                    <NativeSelect aria-label="Category" value={draft.target} onChange={(e) => set({ target: e.target.value, targetName: e.target.value })}>
                      <NativeSelectOption value="">Choose…</NativeSelectOption>
                      {data.categories.map((c) => (
                        <NativeSelectOption key={c}>{c}</NativeSelectOption>
                      ))}
                    </NativeSelect>
                  ) : null}
                </div>
              </Field>
              {draft.scope === "Product" ? (
                <Field>
                  <FieldLabel htmlFor="pr-product">Product</FieldLabel>
                  {draft.target ? (
                    <div className="flex items-center justify-between gap-2 rounded-md border border-border px-3 py-1.5 text-sm">
                      <span>{draft.targetName}</span>
                      <Button size="xs" variant="ghost" onClick={() => set({ target: "", targetName: "" })}>
                        Change
                      </Button>
                    </div>
                  ) : (
                    <>
                      <InputGroup>
                        <InputGroupAddon>
                          <SearchIcon />
                        </InputGroupAddon>
                        <InputGroupInput id="pr-product" placeholder="Name, code or barcode" value={productQ} onChange={(e) => setProductQ(e.target.value)} />
                      </InputGroup>
                      {found.length ? (
                        <ul className="divide-y divide-[var(--color-border-subtle)] rounded-md border border-border text-sm">
                          {found.map((f) => (
                            <li key={f.id}>
                              <button type="button" className="flex w-full cursor-pointer justify-between gap-2 px-3 py-1.5 text-left hover:bg-muted/60" onClick={() => (set({ target: String(f.id), targetName: f.name }), setProductQ(""))}>
                                <span>{f.name}</span>
                                <span className="text-xs text-muted-foreground tabular-nums">{kes(f.price)}</span>
                              </button>
                            </li>
                          ))}
                        </ul>
                      ) : null}
                    </>
                  )}
                </Field>
              ) : null}
              <div className="grid gap-3 sm:grid-cols-2">
                <Field>
                  <FieldLabel htmlFor="pr-min">Minimum basket (KES)</FieldLabel>
                  <Input id="pr-min" inputMode="numeric" className="tabular-nums" value={draft.minBasket} onChange={(e) => set({ minBasket: e.target.value.replace(/[^0-9.]/g, "") })} />
                </Field>
                <Field>
                  <FieldLabel htmlFor="pr-max">Basket up to (KES)</FieldLabel>
                  <Input id="pr-max" inputMode="numeric" className="tabular-nums" value={draft.maxBasket} onChange={(e) => set({ maxBasket: e.target.value.replace(/[^0-9.]/g, "") })} />
                  <FieldDescription>Blank for no limit. Above it the promotion doesn&apos;t apply.</FieldDescription>
                </Field>
              </div>
              <div className="grid gap-3 sm:grid-cols-2">
                <Field>
                  <FieldLabel htmlFor="pr-start">From</FieldLabel>
                  <Input id="pr-start" type="date" value={draft.start} onChange={(e) => set({ start: e.target.value })} />
                </Field>
                <Field>
                  <FieldLabel htmlFor="pr-end">Until</FieldLabel>
                  <Input id="pr-end" type="date" value={draft.end} onChange={(e) => set({ end: e.target.value })} />
                </Field>
              </div>
              <Field>
                <FieldLabel htmlFor="pr-group">Customers</FieldLabel>
                <NativeSelect id="pr-group" value={draft.groupId} onChange={(e) => set({ groupId: e.target.value })}>
                  <NativeSelectOption value="">Everyone</NativeSelectOption>
                  {data.groups.map((g) => (
                    <NativeSelectOption key={g.id} value={String(g.id)}>
                      {g.name} only
                    </NativeSelectOption>
                  ))}
                </NativeSelect>
              </Field>
            </div>
            <fieldset className="flex flex-wrap gap-x-4 gap-y-1 text-sm">
              <legend className="mb-1 text-sm font-medium">Branches</legend>
              {data.branches.map((b) => (
                <label key={b.id} className="inline-flex items-center gap-1.5">
                  <Checkbox checked={draft.branchIds.includes(b.id)} onCheckedChange={(v) => set({ branchIds: v ? [...draft.branchIds, b.id] : draft.branchIds.filter((x) => x !== b.id) })} />
                  {b.name}
                </label>
              ))}
            </fieldset>
            <label className="inline-flex items-center gap-1.5 text-sm">
              <Checkbox checked={draft.active} onCheckedChange={(v) => set({ active: !!v })} />
              On (runs between the dates)
            </label>
            <p className="rounded-md border border-[var(--color-border-panel)] px-3 py-2 text-sm bg-card">
              On a KES 1,500.00 basket with KES 900.00 of {draft.scope === "Basket" ? "items" : draft.targetName || draft.scope.toLowerCase()}: <span className="font-semibold">{off ? `${kes(off)} off` : "no discount"}</span>
              {num(draft.minBasket) > 1500 ? ` (minimum basket is ${kes(num(draft.minBasket))})` : ""}
              {draft.maxBasket.trim() && num(draft.maxBasket) < 1500 ? ` (basket limit is ${kes(num(draft.maxBasket))}, so it wouldn't apply)` : ""}.
            </p>
            {tried && problems.length ? (
              <ul role="alert" className="list-disc rounded-md border border-status-error-border bg-status-error-bg py-2 pr-3 pl-8 text-sm text-status-error-text">
                {problems.map((x) => (
                  <li key={x}>{x}</li>
                ))}
              </ul>
            ) : null}
          </div>
        </RecordDrawer>
      ) : null}

      <ConfirmDialog
        open={!!deleting}
        onOpenChange={(o) => !o && setDeleting(null)}
        level="A"
        title={`Delete ${deleting?.name ?? ""}?`}
        consequences={["It has never been given on a sale, so nothing else changes. This can't be undone."]}
        actionLabel="Delete promotion"
        busyLabel="Deleting…"
        onConfirm={async () => {
          if (deleting) {
            try {
              await PromotionsApi.remove(deleting.id);
              toast.add({ title: `${deleting.name} deleted` });
              await load();
            } catch (e) {
              toast.add({ type: "error", title: "Not deleted", description: apiError(e, "Try again.") });
            }
          }
          setDeleting(null);
        }}
      />
    </div>
  );
}
