"use client";

import * as React from "react";

import { Button } from "@/components/ui/button";
import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";

import { AsyncBadge } from "./async-status";
import { ConfirmDialog } from "./confirm-dialog";
import { kes } from "./payroll";

/*
 * Salary advance (PRD 7.10, HR-16): recorded with date, employee, amount, payment method, the account it
 * was paid from and a reference; recovered in full from the next payslip and marked with the payroll run
 * that recovered it, so it can't be deducted twice. Posts Dr Staff advances / Cr the paying account (AC-02).
 */

export type AdvanceMethod = "M-Pesa" | "Bank transfer" | "Cash";

export type PayingAccount = { code: string; name: string; method: AdvanceMethod };

export type SalaryAdvance = {
  id: string;
  /** ADV-2026-0031 */
  ref: string;
  date: string;
  employeeId: string;
  employeeName: string;
  amount: number;
  method: AdvanceMethod;
  paidFrom: string;
  /** M-Pesa or bank transaction code; a voucher number for cash. */
  reference: string;
  /** The payroll period it is recovered in ("October 2026"). */
  period: string;
  /** Set when an approved payroll run deducted it. */
  recoveredBy?: string;
};

export type AdvanceEmployee = { id: string; name: string; /** This period's net pay before advances. */ netPay: number };

/** What can still be advanced: this period's net pay less advances not yet recovered. Never below zero. */
export function advanceAvailable(emp: AdvanceEmployee, advances: SalaryAdvance[]) {
  const open = advances.filter((a) => a.employeeId === emp.id && !a.recoveredBy).reduce((s, a) => s + a.amount, 0);
  return { open, available: Math.max(0, Math.round((emp.netPay - open) * 100) / 100) };
}

/** Advances a payroll run deducts for one employee: those not yet recovered, plus any this run already recovered (an approved run keeps showing what it deducted). */
export function advancesToRecover(advances: SalaryAdvance[], employeeId: string, run?: string) {
  const list = advances.filter((a) => a.employeeId === employeeId && (!a.recoveredBy || a.recoveredBy === run));
  return { amount: list.reduce((s, a) => s + a.amount, 0), refs: list.map((a) => a.ref) };
}

const toNumber = (v: string) => Number(v.replace(/,/g, ""));

/**
 * Record a salary advance. The amount is capped at what the next payslip can recover (net pay less open
 * advances) and the form says that limit before anything is typed. Paying accounts follow the method.
 * Recording confirms first and states the ledger posting and the payslip it comes off.
 */
export function AdvanceForm({
  employees,
  advances,
  accounts,
  period,
  today,
  nextRef,
  onRecord,
}: {
  employees: AdvanceEmployee[];
  advances: SalaryAdvance[];
  accounts: PayingAccount[];
  period: string;
  /** ISO date; advances can't be dated in the future. */
  today: string;
  nextRef: string;
  onRecord: (a: SalaryAdvance) => Promise<void> | void;
}) {
  const blank = { employeeId: "", amount: "", method: "M-Pesa" as AdvanceMethod, paidFrom: "", reference: "", date: today };
  const [f, setF] = React.useState(blank);
  const [tried, setTried] = React.useState(false);
  const [confirming, setConfirming] = React.useState(false);

  const emp = employees.find((e) => e.id === f.employeeId);
  const limit = emp ? advanceAvailable(emp, advances) : null;
  const methodAccounts = accounts.filter((a) => a.method === f.method);
  const account = accounts.find((a) => a.code === f.paidFrom);
  const amount = toNumber(f.amount);

  const errors: Partial<Record<keyof typeof blank, string>> = {
    employeeId: f.employeeId ? undefined : "Choose the employee.",
    amount: !f.amount.trim()
      ? "Enter the amount."
      : !/^\d{1,3}(,?\d{3})*(\.\d{1,2})?$/.test(f.amount.trim()) || !(amount > 0)
        ? "Enter an amount in shillings, e.g. 5,000."
        : limit && amount > limit.available
          ? limit.available === 0
            ? `${emp!.name} has no pay left to recover an advance from this month.`
            : `The most you can advance ${emp!.name} this month is ${kes(limit.available)}; it comes off the ${period} payslip in full.`
          : undefined,
    paidFrom: f.paidFrom ? undefined : "Choose the account it was paid from.",
    reference:
      f.method === "Cash"
        ? undefined
        : !f.reference.trim()
          ? `Enter the ${f.method === "M-Pesa" ? "M-Pesa transaction code" : "bank reference"}.`
          : f.method === "M-Pesa" && !/^[A-Z0-9]{10}$/.test(f.reference.trim())
            ? "An M-Pesa code is 10 letters and numbers, e.g. SJK4L8P2QX."
            : undefined,
    date: !f.date ? "Enter the date it was paid." : f.date > today ? "The date can't be in the future." : undefined,
  };
  const valid = Object.values(errors).every((e) => !e);
  const err = (k: keyof typeof blank) => (tried ? errors[k] : undefined);

  return (
    <div className="max-w-[720px] space-y-4">
      <div className="grid gap-4">
        <Field data-invalid={!!err("employeeId")}>
          <FieldLabel htmlFor="adv-emp">Employee</FieldLabel>
          <NativeSelect id="adv-emp" value={f.employeeId} onChange={(e) => setF({ ...f, employeeId: e.target.value })} aria-invalid={!!err("employeeId")}>
            <NativeSelectOption value="">Choose an employee</NativeSelectOption>
            {employees.map((e) => (
              <NativeSelectOption key={e.id} value={e.id}>
                {e.name}
              </NativeSelectOption>
            ))}
          </NativeSelect>
          {err("employeeId") ? <FieldError>{err("employeeId")}</FieldError> : null}
        </Field>
        <Field data-invalid={!!err("amount")}>
          <FieldLabel htmlFor="adv-amount">Amount (KES)</FieldLabel>
          <Input
            id="adv-amount"
            inputMode="decimal"
            className="tabular-nums"
            value={f.amount}
            onChange={(e) => setF({ ...f, amount: e.target.value })}
            onBlur={() => amount > 0 && setF((x) => ({ ...x, amount: amount.toLocaleString("en-US", { maximumFractionDigits: 2 }) }))}
            aria-invalid={!!err("amount")}
          />
          {err("amount") ? (
            <FieldError>{err("amount")}</FieldError>
          ) : limit ? (
            <FieldDescription>
              Up to {kes(limit.available)}
              {limit.open ? `, after ${kes(limit.open)} already advanced` : ""}. Recovered in full from the {period} payslip.
            </FieldDescription>
          ) : (
            <FieldDescription>Choose the employee to see how much can be advanced.</FieldDescription>
          )}
        </Field>
        <Field>
          <FieldLabel htmlFor="adv-method">Paid by</FieldLabel>
          <NativeSelect id="adv-method" value={f.method} onChange={(e) => setF({ ...f, method: e.target.value as AdvanceMethod, paidFrom: "", reference: "" })}>
            {(["M-Pesa", "Bank transfer", "Cash"] as const).map((m) => (
              <NativeSelectOption key={m} value={m}>
                {m}
              </NativeSelectOption>
            ))}
          </NativeSelect>
        </Field>
        <Field data-invalid={!!err("paidFrom")}>
          <FieldLabel htmlFor="adv-acc">Paid from</FieldLabel>
          <NativeSelect id="adv-acc" value={f.paidFrom} onChange={(e) => setF({ ...f, paidFrom: e.target.value })} aria-invalid={!!err("paidFrom")}>
            <NativeSelectOption value="">Choose an account</NativeSelectOption>
            {methodAccounts.map((a) => (
              <NativeSelectOption key={a.code} value={a.code}>
                {a.code} {a.name}
              </NativeSelectOption>
            ))}
          </NativeSelect>
          {err("paidFrom") ? <FieldError>{err("paidFrom")}</FieldError> : null}
        </Field>
        <Field data-invalid={!!err("reference")}>
          <FieldLabel htmlFor="adv-ref">{f.method === "M-Pesa" ? "M-Pesa code" : f.method === "Bank transfer" ? "Bank reference" : "Voucher number (optional)"}</FieldLabel>
          <Input
            id="adv-ref"
            value={f.reference}
            onChange={(e) => setF({ ...f, reference: f.method === "M-Pesa" ? e.target.value.toUpperCase().replace(/\s/g, "") : e.target.value })}
            aria-invalid={!!err("reference")}
            className="font-mono"
          />
          {err("reference") ? <FieldError>{err("reference")}</FieldError> : null}
        </Field>
        <Field data-invalid={!!err("date")}>
          <FieldLabel htmlFor="adv-date">Date paid</FieldLabel>
          <Input id="adv-date" type="date" max={today} value={f.date} onChange={(e) => setF({ ...f, date: e.target.value })} aria-invalid={!!err("date")} />
          {err("date") ? <FieldError>{err("date")}</FieldError> : null}
        </Field>
      </div>
      <div className="flex justify-end gap-2">
        <Button
          variant="ghost"
          onClick={() => {
            setF(blank);
            setTried(false);
          }}
        >
          Clear
        </Button>
        <Button
          onClick={() => {
            setTried(true);
            if (valid) setConfirming(true);
          }}
        >
          Record advance
        </Button>
      </div>
      {emp && account ? (
        <ConfirmDialog
          open={confirming}
          onOpenChange={setConfirming}
          level="A"
          title={`Record a ${kes(amount)} advance to ${emp.name}?`}
          consequences={[
            `Paid by ${f.method} from ${account.code} ${account.name}${f.reference ? `, ref ${f.reference}` : ""}.`,
            `Posts to the ledger: debit 1300 Staff advances, credit ${account.code} ${account.name}.`,
            `Deducted in full from ${emp.name}'s ${period} payslip.`,
          ]}
          actionLabel="Record advance"
          busyLabel="Recording…"
          onConfirm={async () => {
            await onRecord({
              id: nextRef,
              ref: nextRef,
              date: f.date,
              employeeId: emp.id,
              employeeName: emp.name,
              amount,
              method: f.method,
              paidFrom: `${account.code} ${account.name}`,
              reference: f.reference.trim(),
              period,
            });
            setF(blank);
            setTried(false);
          }}
        />
      ) : null}
    </div>
  );
}

/** Advance records: what was paid, how, and whether a payroll run has recovered it yet. */
export function AdvanceList({ advances, formatDate }: { advances: SalaryAdvance[]; formatDate: (iso: string) => string }) {
  if (!advances.length) return <p className="rounded-lg border border-dashed border-border px-4 py-6 text-center text-sm text-muted-foreground">No advances this period.</p>;
  return (
    <div className="overflow-x-auto rounded-lg border border-[var(--color-border-panel)] bg-card">
      <Table className="text-sm">
        <TableHeader>
          <TableRow>
            <TableHead>Advance</TableHead>
            <TableHead>Date</TableHead>
            <TableHead>Employee</TableHead>
            <TableHead className="text-right">Amount</TableHead>
            <TableHead>Paid</TableHead>
            <TableHead>Recovery</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {advances.map((a) => (
            <TableRow key={a.id}>
              <TableCell className="font-mono text-xs">{a.ref}</TableCell>
              <TableCell>{formatDate(a.date)}</TableCell>
              <TableCell className="font-medium">{a.employeeName}</TableCell>
              <TableCell className="text-right tabular-nums">{kes(a.amount)}</TableCell>
              <TableCell className="text-muted-foreground">
                {a.method} · {a.paidFrom}
                {a.reference ? <span className="block font-mono text-xs">{a.reference}</span> : null}
              </TableCell>
              <TableCell>
                <AsyncBadge state={a.recoveredBy ? "done" : "queued"} label={a.recoveredBy ? `Recovered, ${a.recoveredBy}` : `To recover, ${a.period}`} />
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}
