"use client";

import * as React from "react";
import { CircleCheckIcon, PlusIcon, Trash2Icon, TriangleAlertIcon } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { NativeSelect, NativeSelectOptGroup, NativeSelectOption } from "@/components/ui/native-select";
import { cn } from "@/lib/utils";

import { TONE_BOX } from "./tones";

export type Account = { code: string; name: string; type: "Asset" | "Liability" | "Equity" | "Income" | "Expense" };
export type JournalLine = { id: string; account: string; memo: string; debit: string; credit: string };

const fmt = new Intl.NumberFormat("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
/** Amounts as typed ("1,250.5") to cents, so totals never drift with floating point. */
const cents = (s: string) => {
  const n = Number(s.replace(/,/g, ""));
  return Number.isFinite(n) ? Math.round(n * 100) : NaN;
};
const money = (c: number) => fmt.format(c / 100);

let seq = 0;
export const newLine = (): JournalLine => ({ id: `l${++seq}`, account: "", memo: "", debit: "", credit: "" });

/** Problems per line, in words, shown on Post (not while typing). */
export function lineProblems(l: JournalLine): string[] {
  const d = l.debit.trim();
  const c = l.credit.trim();
  const p: string[] = [];
  if (!l.account) p.push("Choose an account.");
  if (d && c) p.push("Enter a debit or a credit, not both.");
  if (!d && !c) p.push("Enter a debit or a credit.");
  for (const [v, side] of [
    [d, "Debit"],
    [c, "Credit"],
  ] as const) {
    if (!v) continue;
    const n = cents(v);
    if (Number.isNaN(n)) p.push(`${side} must be an amount, e.g. 1,250.00.`);
    else if (n <= 0) p.push(`${side} must be more than zero.`);
  }
  return p;
}

export function journalTotals(lines: JournalLine[]) {
  const sum = (k: "debit" | "credit") => lines.reduce((s, l) => s + (l[k].trim() && !Number.isNaN(cents(l[k])) ? cents(l[k]) : 0), 0);
  const debit = sum("debit");
  const credit = sum("credit");
  return { debit, credit, difference: debit - credit };
}

/**
 * Journal entry editor (accounting, AC-*): lines of account, memo, debit or credit. Totals and the
 * difference are always visible; Post is allowed only when debits equal credits, there are at least two
 * complete lines and every line is valid. Amounts are kept as typed and summed in cents. Problems are listed
 * per line on Post, not while typing.
 */
export function JournalEditor({
  accounts,
  lines,
  onChange,
  showProblems,
  disabled = false,
}: {
  accounts: Account[];
  lines: JournalLine[];
  onChange: (lines: JournalLine[]) => void;
  /** Set after the first Post attempt. */
  showProblems: boolean;
  disabled?: boolean;
}) {
  const t = journalTotals(lines);
  const balanced = t.difference === 0 && t.debit > 0;
  const groups = (["Asset", "Liability", "Equity", "Income", "Expense"] as const).map((type) => [type, accounts.filter((a) => a.type === type)] as const);
  const update = (id: string, patch: Partial<JournalLine>) => onChange(lines.map((l) => (l.id === id ? { ...l, ...patch } : l)));
  const amountCls = "h-8 text-right tabular-nums";

  return (
    <div className="space-y-3">
      <div className="overflow-x-auto rounded-lg border border-[var(--color-border-panel)] bg-card">
        <table className="w-full min-w-[720px] border-collapse text-sm">
          <caption className="sr-only">Journal lines</caption>
          <thead className="bg-[var(--color-bg-surface-subtle)] text-left text-xs text-muted-foreground">
            <tr>
              <th scope="col" className="w-10 px-2 py-2 text-right font-medium">
                #
              </th>
              <th scope="col" className="w-[34%] px-2 py-2 font-medium">
                Account
              </th>
              <th scope="col" className="px-2 py-2 font-medium">
                Memo
              </th>
              <th scope="col" className="w-36 px-2 py-2 text-right font-medium">
                Debit (KES)
              </th>
              <th scope="col" className="w-36 px-2 py-2 text-right font-medium">
                Credit (KES)
              </th>
              <th scope="col" className="w-10 px-2 py-2">
                <span className="sr-only">Remove</span>
              </th>
            </tr>
          </thead>
          <tbody className="divide-y divide-[var(--color-border-subtle)]">
            {lines.map((l, i) => {
              const problems = showProblems ? lineProblems(l) : [];
              const bad = problems.length > 0;
              const label = `line ${i + 1}`;
              return (
                <React.Fragment key={l.id}>
                  <tr className={cn("align-top", bad && "bg-status-error-bg/40")}>
                    <td className="px-2 py-1.5 text-right text-muted-foreground tabular-nums">{i + 1}</td>
                    <td className="px-2 py-1.5">
                      <NativeSelect
                        size="sm"
                        aria-label={`Account, ${label}`}
                        aria-invalid={showProblems && !l.account}
                        value={l.account}
                        disabled={disabled}
                        onChange={(e) => update(l.id, { account: e.target.value })}
                        className="w-full"
                      >
                        <NativeSelectOption value="">Choose an account</NativeSelectOption>
                        {groups.map(([type, list]) =>
                          list.length ? (
                            <NativeSelectOptGroup key={type} label={type}>
                              {list.map((a) => (
                                <NativeSelectOption key={a.code} value={a.code}>
                                  {a.code} {a.name}
                                </NativeSelectOption>
                              ))}
                            </NativeSelectOptGroup>
                          ) : null,
                        )}
                      </NativeSelect>
                    </td>
                    <td className="px-2 py-1.5">
                      <Input className="h-8" aria-label={`Memo, ${label}`} value={l.memo} disabled={disabled} onChange={(e) => update(l.id, { memo: e.target.value })} />
                    </td>
                    <td className="px-2 py-1.5">
                      <Input
                        className={amountCls}
                        inputMode="decimal"
                        aria-label={`Debit, ${label}`}
                        value={l.debit}
                        disabled={disabled}
                        aria-invalid={bad && problems.some((p) => p.includes("Debit") || p.includes("debit"))}
                        // Typing a debit clears the credit: a line is one or the other.
                        onChange={(e) => update(l.id, { debit: e.target.value, credit: e.target.value ? "" : l.credit })}
                        onBlur={(e) => {
                          const n = cents(e.target.value);
                          if (e.target.value.trim() && !Number.isNaN(n)) update(l.id, { debit: money(n) });
                        }}
                      />
                    </td>
                    <td className="px-2 py-1.5">
                      <Input
                        className={amountCls}
                        inputMode="decimal"
                        aria-label={`Credit, ${label}`}
                        value={l.credit}
                        disabled={disabled}
                        aria-invalid={bad && problems.some((p) => p.includes("Credit") || p.includes("credit"))}
                        onChange={(e) => update(l.id, { credit: e.target.value, debit: e.target.value ? "" : l.debit })}
                        onBlur={(e) => {
                          const n = cents(e.target.value);
                          if (e.target.value.trim() && !Number.isNaN(n)) update(l.id, { credit: money(n) });
                        }}
                      />
                    </td>
                    <td className="px-2 py-1.5">
                      <Button
                        variant="ghost"
                        size="icon-sm"
                        aria-label={`Remove ${label}`}
                        disabled={disabled || lines.length <= 2}
                        onClick={() => onChange(lines.filter((x) => x.id !== l.id))}
                      >
                        <Trash2Icon />
                      </Button>
                    </td>
                  </tr>
                  {bad ? (
                    <tr>
                      <td />
                      <td colSpan={5} className="px-2 pb-2 text-xs text-status-error-text">
                        Line {i + 1}: {problems.join(" ")}
                      </td>
                    </tr>
                  ) : null}
                </React.Fragment>
              );
            })}
          </tbody>
          <tfoot className="border-t-2 border-[var(--color-border-panel)] text-sm font-semibold tabular-nums">
            <tr>
              <td colSpan={3} className="px-2 py-2">
                <Button variant="ghost" size="sm" disabled={disabled} onClick={() => onChange([...lines, newLine()])}>
                  <PlusIcon data-icon="inline-start" />
                  Add line
                </Button>
              </td>
              <td className="px-4 py-2 text-right">{money(t.debit)}</td>
              <td className="px-4 py-2 text-right">{money(t.credit)}</td>
              <td />
            </tr>
          </tfoot>
        </table>
      </div>
      <p className={cn("flex items-center gap-2 rounded-md border px-3 py-2 text-sm", balanced ? TONE_BOX.success : t.debit || t.credit ? TONE_BOX.warning : TONE_BOX.neutral)}>
        {balanced ? <CircleCheckIcon className="size-4 shrink-0" aria-hidden /> : <TriangleAlertIcon className="size-4 shrink-0" aria-hidden />}
        {balanced
          ? `Balanced: debits and credits are both KES ${money(t.debit)}.`
          : t.debit || t.credit
            ? `Not balanced: debits are KES ${money(Math.abs(t.difference))} ${t.difference > 0 ? "more" : "less"} than credits.`
            : "Enter the lines. Debits must equal credits before you can post."}
      </p>
    </div>
  );
}
