"use client";

import * as React from "react";
import { CircleCheckIcon, InfoIcon } from "lucide-react";

import { Example } from "@/components/design-system/example";
import { Section } from "@/components/design-system/section";
import { ConfirmDialog } from "@/components/medora/confirm-dialog";
import { AccountsOpsSection } from "./accounts-ops-demo";
import { HrMoreSection } from "./hr-more-demo";
import { HrOpsSection } from "./hr-ops-demo";
import { LedgerSection } from "./ledger-demo";
import { EditableGrid, gridErrors, type GridColumn, type GridRow } from "@/components/medora/editable-grid";
import { JournalEditor, journalTotals, lineProblems, newLine, type Account, type JournalLine } from "@/components/medora/journal";
import { AdvanceForm, AdvanceList, advancesToRecover, type PayingAccount, type SalaryAdvance } from "@/components/medora/advance";
import { computePay, KE_PAYROLL, kes, Payslip, type Employee } from "@/components/medora/payroll";
import { PrintButton } from "@/components/medora/print";
import { SensitiveNotice } from "@/components/medora/sensitive";
import { TONE_BOX } from "@/components/medora/tones";
import { Button } from "@/components/ui/button";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select";
import { toast } from "@/components/ui/toast";
import { formatDate, TODAY } from "@/lib/mock-data";
import { cn } from "@/lib/utils";

/* -------------------------------------- Journal -------------------------------------- */

const ACCOUNTS: Account[] = [
  { code: "1000", name: "Cash at till", type: "Asset" },
  { code: "1010", name: "M-Pesa till 247247", type: "Asset" },
  { code: "1020", name: "Equity Bank", type: "Asset" },
  { code: "1200", name: "Inventory", type: "Asset" },
  { code: "2000", name: "Accounts payable", type: "Liability" },
  { code: "2100", name: "VAT payable", type: "Liability" },
  { code: "3000", name: "Owner's equity", type: "Equity" },
  { code: "4000", name: "Sales", type: "Income" },
  { code: "5000", name: "Cost of goods sold", type: "Expense" },
  { code: "5100", name: "Stock write-offs", type: "Expense" },
  { code: "6000", name: "Rent", type: "Expense" },
  { code: "6100", name: "Bank charges", type: "Expense" },
];

const start = (): JournalLine[] => {
  const a = newLine();
  const b = newLine();
  return [
    { ...a, account: "6100", memo: "October bank charges", debit: "1,250.00" },
    { ...b, account: "1020", memo: "October bank charges", credit: "1,200.00" },
  ];
};

function JournalDemo() {
  const [lines, setLines] = React.useState<JournalLine[]>(start);
  const [date, setDate] = React.useState(TODAY);
  const [narration, setNarration] = React.useState("Bank charges for October, per statement");
  const [tried, setTried] = React.useState(false);
  const [confirming, setConfirming] = React.useState(false);
  const [posted, setPosted] = React.useState<string | null>(null);
  const summaryRef = React.useRef<HTMLDivElement>(null);

  const t = journalTotals(lines);
  const problems = [
    ...(narration.trim() ? [] : ["Add a narration that says what this entry is for."]),
    ...(lines.filter((l) => l.account || l.debit || l.credit).length < 2 ? ["A journal needs at least two lines."] : []),
    ...lines.flatMap((l, i) => (lineProblems(l).length ? [`Line ${i + 1}: ${lineProblems(l).join(" ")}`] : [])),
    ...(t.difference !== 0 ? [`Debits and credits differ by ${kes(Math.abs(t.difference) / 100)}.`] : []),
  ];

  const post = () => {
    setTried(true);
    if (problems.length) {
      setTimeout(() => summaryRef.current?.focus(), 0);
      return;
    }
    setConfirming(true);
  };

  if (posted) {
    return (
      <div className={cn("space-y-2 rounded-lg border p-4", TONE_BOX.success)} role="status">
        <p className="flex items-center gap-2 font-semibold">
          <CircleCheckIcon className="size-4" aria-hidden /> Journal {posted} posted
        </p>
        <p className="text-sm">{kes(t.debit / 100)} on {formatDate(date)}. To correct it, post a reversing entry; posted journals can&apos;t be edited.</p>
        <Button
          size="sm"
          variant="outline"
          className="bg-background"
          onClick={() => {
            setPosted(null);
            setTried(false);
            setLines(start());
          }}
        >
          New journal
        </Button>
      </div>
    );
  }

  return (
    <div className="space-y-4">
      <div className="grid gap-4 sm:grid-cols-[10rem_1fr]">
        <Field>
          <FieldLabel htmlFor="jv-date">Date</FieldLabel>
          <Input id="jv-date" type="date" value={date} onChange={(e) => setDate(e.target.value)} />
        </Field>
        <Field>
          <FieldLabel htmlFor="jv-narr">Narration</FieldLabel>
          <Input id="jv-narr" value={narration} onChange={(e) => setNarration(e.target.value)} aria-invalid={tried && !narration.trim()} />
        </Field>
      </div>
      {tried && problems.length ? (
        <div ref={summaryRef} tabIndex={-1} role="alert" className={cn("space-y-1 rounded-lg border px-3 py-2 text-sm outline-none", TONE_BOX.error)}>
          <p className="font-semibold">Fix {problems.length === 1 ? "this" : `these ${problems.length}`} before posting</p>
          <ul className="list-disc pl-5">
            {problems.map((p) => (
              <li key={p}>{p}</li>
            ))}
          </ul>
        </div>
      ) : null}
      <JournalEditor accounts={ACCOUNTS} lines={lines} onChange={setLines} showProblems={tried} />
      <div className="flex flex-wrap justify-end gap-2">
        <Button variant="ghost" onClick={() => toast.add({ title: "Draft saved", description: "Drafts don't affect the ledger." })}>
          Save draft
        </Button>
        <Button onClick={post}>Post journal</Button>
      </div>
      <ConfirmDialog
        open={confirming}
        onOpenChange={setConfirming}
        level="A"
        title="Post this journal?"
        consequences={[`${lines.length} lines, ${kes(t.debit / 100)} each side, dated ${formatDate(date)}.`, "Posted journals can't be edited; a mistake is corrected with a reversing entry."]}
        actionLabel="Post journal"
        busyLabel="Posting…"
        onConfirm={async () => {
          await new Promise((r) => setTimeout(r, 800));
          setPosted("JV-2026-0412");
        }}
      />
    </div>
  );
}

/* -------------------------------------- Payroll -------------------------------------- */

const EMPLOYEES: (Employee & { basic: number; allowances: number })[] = [
  { id: "e1", name: "Grace Wanjiru", number: "EMP-001", role: "Pharmacist", kraPin: "A001234567B", nssfNo: "2004561", shaNo: "SHA-77120", bank: "Equity ••• 4412", basic: 95000, allowances: 15000 },
  { id: "e2", name: "Kevin Mutua", number: "EMP-004", role: "Cashier", kraPin: "A009876543C", nssfNo: "2008812", shaNo: "SHA-88031", bank: "M-Pesa 07•• ••• 214", basic: 32000, allowances: 4000 },
  { id: "e3", name: "Peter Kamau", number: "EMP-002", role: "Branch admin", kraPin: "A004455667D", nssfNo: "2001190", shaNo: "SHA-66014", bank: "KCB ••• 9020", basic: 120000, allowances: 20000 },
  { id: "e4", name: "Faith Njeri", number: "EMP-007", role: "Pharm tech", kraPin: "A007788990E", nssfNo: "2010032", shaNo: "SHA-90127", bank: "Co-op ••• 3381", basic: 45000, allowances: 5000 },
];

const PERIOD = "October 2026";
const RUN = "PAY-2026-10";

const PAYING_ACCOUNTS: PayingAccount[] = [
  { code: "1010", name: "M-Pesa till 247247", method: "M-Pesa" },
  { code: "1020", name: "Equity Bank", method: "Bank transfer" },
  { code: "1025", name: "KCB payroll account", method: "Bank transfer" },
  { code: "1000", name: "Cash at till", method: "Cash" },
  { code: "1005", name: "Petty cash", method: "Cash" },
];

const SEED_ADVANCES: SalaryAdvance[] = [
  { id: "ADV-2026-0030", ref: "ADV-2026-0030", date: "2026-10-08", employeeId: "e2", employeeName: "Kevin Mutua", amount: 5000, method: "M-Pesa", paidFrom: "1010 M-Pesa till 247247", reference: "SJA2K9P1LQ", period: PERIOD },
];

const amountOk = (v: string) => (v === "" || /^\d+(\.\d{1,2})?$/.test(v.replace(/,/g, "")) ? undefined : "Enter an amount in shillings, e.g. 5000.");
const num = (v: string | undefined) => Number((v ?? "0").replace(/,/g, "")) || 0;

const PAY_EXTRAS: Record<string, { commission?: number; bonus?: number; loans?: number; loanRefs?: string[] }> = {
  e2: { commission: 4120, bonus: 3000 },
  e3: { loans: 2500, loanRefs: ["HELB"] },
};

const PAY_ROWS: GridRow[] = EMPLOYEES.map((e) => ({ id: e.id, header: e.name, sub: `${e.number} · ${e.role}`, values: { basic: String(e.basic), allowances: String(e.allowances), overtime: "", other: "" } }));


function HrSections() {
  const [advances, setAdvances] = React.useState<SalaryAdvance[]>(SEED_ADVANCES);
  const [draft, setDraft] = React.useState<Record<string, Record<string, string>>>({ e2: { overtime: "3500" } });
  const [selected, setSelected] = React.useState("e2");
  const [approved, setApproved] = React.useState(false);
  const slipRef = React.useRef<HTMLDivElement>(null);

  const values = (id: string) => ({ ...PAY_ROWS.find((r) => r.id === id)!.values, ...(draft[id] ?? {}) });
  const payFor = (id: string, withAdvances = true) => {
    const v = values(id);
    const a = advancesToRecover(advances, id, RUN);
    return computePay({
      basic: num(v.basic),
      allowances: num(v.allowances),
      overtime: num(v.overtime),
      otherDeductions: num(v.other),
      advance: withAdvances ? a.amount : 0,
      advanceRefs: withAdvances ? a.refs : [],
      // October's commission (HR-18), bonuses (HR-17, the referral bonus on the Bonuses demo) and loan instalments (HR-12).
      commission: PAY_EXTRAS[id]?.commission,
      bonus: PAY_EXTRAS[id]?.bonus,
      loans: PAY_EXTRAS[id]?.loans,
      loanRefs: PAY_EXTRAS[id]?.loanRefs,
    });
  };
  const readMoney = (key: string) => (approved ? (r: Record<string, string>) => kes(num(r[key])) : undefined);

  const columns: GridColumn[] = [
    { key: "basic", header: "Basic", kind: "read", align: "right", render: (r) => kes(num(r.basic)) },
    { key: "allowances", header: "Allowances", kind: approved ? "read" : "number", align: "right", validate: amountOk, render: readMoney("allowances") },
    { key: "overtime", header: "Overtime", kind: approved ? "read" : "number", align: "right", validate: amountOk, placeholder: "0", render: readMoney("overtime") },
    { key: "other", header: "Other deductions", kind: approved ? "read" : "number", align: "right", validate: amountOk, placeholder: "0", render: readMoney("other") },
    { key: "gross", header: "Gross", kind: "derived", align: "right", render: (_r, g) => kes(payFor(g.id).gross) },
    {
      key: "statutory",
      header: "NSSF + SHIF + levy",
      kind: "derived",
      align: "right",
      render: (_r, g) => {
        const p = payFor(g.id);
        return kes(p.nssf + p.shif + p.housingLevy);
      },
    },
    { key: "paye", header: "PAYE", kind: "derived", align: "right", render: (_r, g) => kes(payFor(g.id).paye) },
    {
      key: "advance",
      header: "Advance",
      kind: "derived",
      align: "right",
      // From the advance records, never typed here: each amount traces back to its ADV reference.
      render: (_r, g) => {
        const a = advancesToRecover(advances, g.id, RUN);
        return a.amount ? (
          <span>
            {kes(a.amount)}
            <span className="block font-mono text-[11px] text-muted-foreground">{a.refs.join(", ")}</span>
          </span>
        ) : (
          <span className="text-muted-foreground">–</span>
        );
      },
    },
    {
      key: "net",
      header: "Net pay",
      kind: "derived",
      align: "right",
      render: (_r, g) => {
        const p = payFor(g.id);
        return <span className={cn("font-semibold", p.net < 0 && "text-status-error-text")}>{kes(p.net)}</span>;
      },
    },
  ];

  const pays = PAY_ROWS.map((r) => payFor(r.id));
  const total = pays.reduce(
    (s, p) => ({ gross: s.gross + p.gross, statutory: s.statutory + p.nssf + p.shif + p.housingLevy, paye: s.paye + p.paye, advance: s.advance + (p.advance ?? 0), net: s.net + p.net }),
    { gross: 0, statutory: 0, paye: 0, advance: 0, net: 0 },
  );
  const errs = gridErrors(columns, PAY_ROWS, draft);
  const negative = PAY_ROWS.filter((r) => payFor(r.id).net < 0);
  const blocked = approved || errs.size > 0 || negative.length > 0;
  const emp = EMPLOYEES.find((e) => e.id === selected)!;
  const nextRef = `ADV-2026-${String(30 + advances.length).padStart(4, "0")}`;

  return (
    <>
      <Section
        id="advance"
        title="Salary advance"
        description="An advance is paid now and recovered in full from the next payslip (HR-16). The form says how much can be advanced before anything is typed: this month's net pay, less advances not yet recovered. The paying accounts follow the method, and recording states the ledger posting. Each advance stays To recover until an approved payroll run deducts it, then shows which run recovered it, so it can't be deducted twice."
      >
        <Example title="Record an advance" description="Try 40,000 for Faith Njeri; then record one and see it in the payroll below">
          {approved ? (
            <p className="text-sm text-muted-foreground">{PERIOD} payroll is approved. New advances are recovered from the November payslip.</p>
          ) : (
            <AdvanceForm
              employees={EMPLOYEES.map((e) => ({ id: e.id, name: e.name, netPay: payFor(e.id, false).net }))}
              advances={advances}
              accounts={PAYING_ACCOUNTS}
              period={PERIOD}
              today={TODAY}
              nextRef={nextRef}
              onRecord={async (a) => {
                await new Promise((r) => setTimeout(r, 700));
                setAdvances((xs) => [...xs, a]);
                setSelected(a.employeeId);
                toast.add({ title: `Advance ${a.ref} recorded`, description: `${kes(a.amount)} to ${a.employeeName}, recovered from the ${PERIOD} payslip.` });
              }}
            />
          )}
        </Example>
        <Example title="Advances this period" bare>
          <div className="p-4">
            <AdvanceList advances={advances} formatDate={formatDate} />
          </div>
        </Example>
      </Section>
      <Section
        id="payroll"
        title="Payroll run and payslip"
        description="The editable grid with pay columns: allowances, overtime and other deductions are entered, everything else is derived (gross, NSSF, SHIF, housing levy, PAYE, advances, net). Advances come from their records, never typed here, and are deducted after tax. The run is submitted for approval; approval marks the advances recovered and locks the run. Payslips print on A4."
      >
        <div className="space-y-4">
          <SensitiveNotice kind="pay" />
          <p className={cn("flex items-start gap-2 rounded-md border px-3 py-2 text-sm", TONE_BOX.info)}>
            <InfoIcon className="mt-0.5 size-4 shrink-0" aria-hidden />
            Statutory deductions use the rates as at {KE_PAYROLL.effective} (NSSF 6% up to KES {KE_PAYROLL.nssf.upperLimit.toLocaleString("en-US")}, SHIF 2.75%, housing levy 1.5%, PAYE bands with KES{" "}
            {KE_PAYROLL.paye.personalRelief.toLocaleString("en-US")} relief). Confirm them with your accountant before the first real run.
          </p>
          {approved ? (
            <p className={cn("flex items-center gap-2 rounded-md border px-3 py-2 text-sm", TONE_BOX.success)}>
              <CircleCheckIcon className="size-4 shrink-0" aria-hidden /> {RUN} approved. Payslips are locked;{" "}
              {total.advance ? `${kes(total.advance)} of advances marked recovered.` : "there were no advances to recover."}
            </p>
          ) : null}
          <EditableGrid
            caption={`${PERIOD} payroll, Westlands Main`}
            rowLabel="Employee"
            columns={columns}
            rows={PAY_ROWS}
            draft={draft}
            onDraftChange={(rowId, key, value) =>
              setDraft((d) => {
                const row = { ...(d[rowId] ?? {}) };
                if (value === undefined) delete row[key];
                else row[key] = value;
                const next = { ...d, [rowId]: row };
                if (!Object.keys(row).length) delete next[rowId];
                return next;
              })
            }
            onDiscard={approved ? undefined : () => setDraft({})}
            footer={
              <tr className="border-t-2 border-[var(--color-border-panel)] bg-muted/50 text-sm font-semibold tabular-nums">
                <th scope="row" colSpan={5} className="px-3 py-2 text-left">
                  Totals, {PAY_ROWS.length} employees
                </th>
                <td className="px-3 py-2 text-right">{kes(total.gross)}</td>
                <td className="px-3 py-2 text-right">{kes(total.statutory)}</td>
                <td className="px-3 py-2 text-right">{kes(total.paye)}</td>
                <td className="px-3 py-2 text-right">{kes(total.advance)}</td>
                <td className="px-3 py-2 text-right">{kes(total.net)}</td>
              </tr>
            }
          />
          {errs.size ? (
            <p className="text-sm text-status-error-text">
              {errs.size} {errs.size === 1 ? "cell needs" : "cells need"} attention before the run can be submitted.
            </p>
          ) : null}
          {negative.length ? (
            <p className="text-sm text-status-error-text">
              Net pay is below zero for {negative.map((r) => r.header).join(", ")}. Reduce the other deductions; advances are recovered in full.
            </p>
          ) : null}
          <div className="flex flex-wrap items-end gap-3">
            <Field className="w-56">
              <FieldLabel htmlFor="slip-emp">Payslip for</FieldLabel>
              <NativeSelect id="slip-emp" value={selected} onChange={(e) => setSelected(e.target.value)}>
                {EMPLOYEES.map((e) => (
                  <NativeSelectOption key={e.id} value={e.id}>
                    {e.name}
                  </NativeSelectOption>
                ))}
              </NativeSelect>
            </Field>
            <PrintButton target={slipRef} paper="a4" label="Print payslip" />
            <div className="ml-auto flex gap-2">
              <Button variant="outline" disabled={blocked} onClick={() => toast.add({ title: "Payroll submitted for approval", description: "An Admin approves before payslips are released." })}>
                Submit for approval
              </Button>
              <Button
                variant="ghost"
                disabled={blocked}
                onClick={() => {
                  setApproved(true);
                  setAdvances((xs) => xs.map((a) => (a.recoveredBy ? a : { ...a, recoveredBy: RUN })));
                  toast.add({ title: `${RUN} approved (demo)`, description: "Advances deducted on this run are marked recovered." });
                }}
              >
                Simulate approval
              </Button>
            </div>
          </div>
          <div className="overflow-x-auto bg-muted/50 p-6">
            <Payslip
              ref={slipRef}
              employer={{ name: "Medora Pharmacy Ltd", kraPin: "P051924221B", address: "Sarit Centre, Westlands, Nairobi" }}
              employee={emp}
              period={PERIOD}
              payDate="31 Oct 2026"
              pay={payFor(emp.id)}
            />
          </div>
        </div>
      </Section>
    </>
  );
}

export function AccountingSection() {
  return (
    <>
      <LedgerSection />
      <AccountsOpsSection />
      <Section
        id="journal"
        title="Journal entry"
        description="Manual journals for the ledger: account, memo, and a debit or a credit on each line. Typing in one side clears the other. Totals and the difference are always shown; Post lists every problem and only goes ahead when debits equal credits. Posted journals are corrected by reversing, never edited."
      >
        <Example title="New journal" description="Starts KES 50.00 out of balance: try Post, then fix the credit">
          <JournalDemo />
        </Example>
      </Section>
    </>
  );
}

export function HrPayrollSection() {
  return (
    <>
      <HrSections />
      <HrOpsSection />
      <HrMoreSection />
    </>
  );
}
