// Record screen — Task 11 of docs/superpowers/plans/2026-07-31-local-app.md.
// Three verb-first entry points ("Money came in" / "Money went out" / "Moved
// between accounts", CLAUDE.md §9 — never "debit"/"credit", never noun-first
// like QuickBooks' sales-receipt/payment/deposit split) each open the same
// shape of form: amount, date, an optional memo/who, and one or two account
// pickers (SearchablePicker). A live preview sentence below the form mirrors
// what POST /api/c/entries (server/src/entries.ts) will actually do — worded
// as a future ("This will record...") since it's a client-side guess built
// from the same account names the picker already has in memory; the
// authoritative wording only exists once the server replies, which is why
// the success panel shows the server's own `summary` verbatim, never this
// preview's text.
//
// Client-side validation (the amount regex, "pick two different accounts"
// for a transfer) exists only to give inline feedback before a round trip —
// the server re-validates everything and remains the authority (CLAUDE.md
// §12 "never widen... to make a query work" / "database-level enforcement,
// every time" extends in spirit to "never trust the client's copy of a
// server rule").
//
// Undo lives on the success panel only, not a global toast queue (task
// brief: "keep simple") — it calls the same POST /api/c/entries/:id/reverse
// EntryDetail.tsx uses, dated today, and then shows ITS summary alongside a
// link to the reversal entry, leaving the original success message visible
// too so both entries stay reachable without navigating away.

import { useMemo, useState } from "react";
import { useCompany } from "./lib/company";
import { useApiGet } from "./lib/useApiGet";
import { apiPost, ApiError } from "./lib/api";
import { todayIso } from "./lib/date";
import { formatEnteredAmount } from "./lib/money";
import { ErrorBanner } from "./components/ErrorBanner";
import { SearchablePicker, type PickerGroup } from "./components/SearchablePicker";
import { NewAccountModal } from "./accounts/NewAccountModal";
import type { ApiAccount } from "./accounts/subtypes";
import {
  accountPickerGroups,
  CATEGORY_TYPES_MONEY_IN,
  CATEGORY_TYPES_MONEY_OUT,
} from "./accounts/pickerGroups";
import type { NavTarget } from "./lib/useHashView";

type Kind = "money-in" | "money-out" | "transfer";

interface EntryResult {
  entryId: string;
  entryNo: string;
  summary: string;
}

interface ReverseResult {
  reversalEntryId: string;
  entryNo: string;
  summary: string;
}

interface UndoState {
  busy: boolean;
  error: string | null;
  result: ReverseResult | null;
}

type Stage =
  | { name: "choose" }
  // AR Task 6: "Money came in" doesn't drop straight into the plain form —
  // it asks once whether this is a customer paying an invoice (routes to
  // the dedicated #receive-payment flow, which knows how to apply money
  // across invoices) or other income (falls through to the existing form
  // below, unchanged). AP Task 5 adds the mirror question for "Money went
  // out" (routes to #pay-bills for "A bill I owe", or the existing form for
  // "Other spending"). "Moved between accounts" skips this stage entirely —
  // see chooseKind().
  | { name: "money-in-choice" }
  | { name: "money-out-choice" }
  | { name: "form"; kind: Kind }
  | { name: "success"; kind: Kind; result: EntryResult; undo: UndoState | null };

// Same shape as server/src/entries.ts's amountSchema — see that file's
// comment for why this is a regex/string check and never Number()/parseFloat.
const AMOUNT_RE = /^(0|[1-9]\d*)(\.\d{1,2})?$/;

function amountError(raw: string): string | null {
  const trimmed = raw.trim();
  if (trimmed === "") return null; // not yet touched / empty: no need to nag
  if (!AMOUNT_RE.test(trimmed)) {
    return "Enter an amount like 120 or 120.50 (no more than 2 decimal places, no leading zeros).";
  }
  if (!/[1-9]/.test(trimmed)) return "Enter an amount greater than zero.";
  return null;
}

// The verb-first prompts ("Where did it land?" / "What was it for?") stay —
// but the pickers under them group by the STANDARD sections (Chart
// Management Task 3, CLAUDE.md §9 as amended 2026-08-02): the money pickers
// keep their asset restriction and show Bank accounts / Accounts receivable
// / ... sections; the category pickers offer every non-asset type either
// way, with the likeliest type's sections first (income for money in,
// expenses for money out — a refund can still land in an expense category).
function moneyGroups(accounts: ApiAccount[]): PickerGroup[] {
  return accountPickerGroups(accounts, ["asset"]);
}

function categoryGroups(accounts: ApiAccount[], kind: "money-in" | "money-out"): PickerGroup[] {
  return accountPickerGroups(
    accounts,
    kind === "money-in" ? CATEGORY_TYPES_MONEY_IN : CATEGORY_TYPES_MONEY_OUT,
  );
}

const VERB_CARDS: { kind: Kind; title: string; hint: string }[] = [
  {
    kind: "money-in",
    title: "Money came in",
    hint: "A customer paid you, a refund landed, interest posted — anything that added money.",
  },
  {
    kind: "money-out",
    title: "Money went out",
    hint: "A bill, a purchase, a fee — anything that spent money.",
  },
  {
    kind: "transfer",
    title: "Moved between accounts",
    hint: "Moved money from one of your own accounts to another.",
  },
];

export function Record({ navigate }: { navigate: (target: NavTarget) => void }) {
  const { selectedCompanyId } = useCompany();
  const {
    data: accounts,
    loading: accountsLoading,
    error: accountsError,
    retry: retryAccounts,
  } = useApiGet<ApiAccount[]>("/api/c/accounts", selectedCompanyId);

  const [stage, setStage] = useState<Stage>({ name: "choose" });

  const [date, setDate] = useState(todayIso);
  const [amount, setAmount] = useState("");
  const [amountTouched, setAmountTouched] = useState(false);
  const [memo, setMemo] = useState("");
  const [who, setWho] = useState("");
  const [intoAccountId, setIntoAccountId] = useState<string | null>(null);
  const [fromAccountId, setFromAccountId] = useState<string | null>(null);
  const [toAccountId, setToAccountId] = useState<string | null>(null);
  const [categoryAccountId, setCategoryAccountId] = useState<string | null>(null);

  const [submitting, setSubmitting] = useState(false);
  const [submitError, setSubmitError] = useState<string | null>(null);

  // "+ New category" (Chart Management Task 2): the category pickers carry
  // an inline affordance opening the shared add-account modal, type preset
  // by context — income for "money came in", expense for "money went out".
  // On success the new account is selected right where the user was.
  const [newCatOpen, setNewCatOpen] = useState(false);

  function resetForm() {
    setDate(todayIso());
    setAmount("");
    setAmountTouched(false);
    setMemo("");
    setWho("");
    setIntoAccountId(null);
    setFromAccountId(null);
    setToAccountId(null);
    setCategoryAccountId(null);
    setSubmitError(null);
  }

  function chooseKind(kind: Kind) {
    resetForm();
    if (kind === "money-in") {
      setStage({ name: "money-in-choice" });
      return;
    }
    if (kind === "money-out") {
      setStage({ name: "money-out-choice" });
      return;
    }
    setStage({ name: "form", kind });
  }

  function backToChoose() {
    resetForm();
    setStage({ name: "choose" });
  }

  const accountNameById = useMemo(() => {
    const map = new Map<string, string>();
    for (const a of accounts ?? []) map.set(a.id, a.name);
    return map;
  }, [accounts]);

  const amtErr = amountTouched ? amountError(amount) : null;
  const amountValid = amount.trim() !== "" && amountError(amount) === null;

  function buildBody(kind: Kind): Record<string, unknown> | null {
    if (!amountValid) return null;
    const trimmedWho = who.trim() || undefined;
    const trimmedMemo = memo.trim() || undefined;
    if (kind === "money-in") {
      if (!intoAccountId || !categoryAccountId) return null;
      return { kind, date, amount, intoAccountId, categoryAccountId, who: trimmedWho, memo: trimmedMemo };
    }
    if (kind === "money-out") {
      if (!fromAccountId || !categoryAccountId) return null;
      return { kind, date, amount, fromAccountId, categoryAccountId, who: trimmedWho, memo: trimmedMemo };
    }
    if (!fromAccountId || !toAccountId || fromAccountId === toAccountId) return null;
    return { kind, date, amount, fromAccountId, toAccountId, memo: trimmedMemo };
  }

  function previewSentence(kind: Kind): string | null {
    if (!amountValid) return null;
    const amt = formatEnteredAmount(amount);
    if (kind === "money-in") {
      if (!intoAccountId || !categoryAccountId) return null;
      const into = accountNameById.get(intoAccountId);
      const category = accountNameById.get(categoryAccountId);
      if (!into || !category) return null;
      return `This will record ${amt} in from ${category} into ${into}.`;
    }
    if (kind === "money-out") {
      if (!fromAccountId || !categoryAccountId) return null;
      const from = accountNameById.get(fromAccountId);
      const category = accountNameById.get(categoryAccountId);
      if (!from || !category) return null;
      return `This will record ${amt} spent on ${category} from ${from}.`;
    }
    if (!fromAccountId || !toAccountId || fromAccountId === toAccountId) return null;
    const from = accountNameById.get(fromAccountId);
    const to = accountNameById.get(toAccountId);
    if (!from || !to) return null;
    return `This will move ${amt} from ${from} to ${to}.`;
  }

  async function handleSubmit(kind: Kind) {
    const body = buildBody(kind);
    if (!body || !selectedCompanyId) return;
    setSubmitting(true);
    setSubmitError(null);
    try {
      const result = await apiPost<EntryResult>("/api/c/entries", body, selectedCompanyId);
      setStage({ name: "success", kind, result, undo: null });
    } catch (err) {
      setSubmitError(err instanceof ApiError ? err.message : "Couldn't record that. Try again.");
    } finally {
      setSubmitting(false);
    }
  }

  async function handleUndo() {
    if (stage.name !== "success" || !selectedCompanyId) return;
    const entryId = stage.result.entryId;
    setStage((s) => (s.name === "success" ? { ...s, undo: { busy: true, error: null, result: null } } : s));
    try {
      const reversal = await apiPost<ReverseResult>(
        `/api/c/entries/${entryId}/reverse`,
        { date: todayIso() },
        selectedCompanyId,
      );
      setStage((s) =>
        s.name === "success" ? { ...s, undo: { busy: false, error: null, result: reversal } } : s,
      );
    } catch (err) {
      const message = err instanceof ApiError ? err.message : "Couldn't undo that. Try again.";
      setStage((s) => (s.name === "success" ? { ...s, undo: { busy: false, error: message, result: null } } : s));
    }
  }

  return (
    <div className="wrap">
      <header className="top">
        <h1>Record something</h1>
      </header>

      {accountsError && <ErrorBanner message={accountsError} onRetry={retryAccounts} />}
      {accountsLoading && !accounts && <p className="msg-inline">Loading your accounts&hellip;</p>}

      {accounts && stage.name === "choose" && (
        <div className="verb-cards">
          {VERB_CARDS.map((c) => (
            <button key={c.kind} type="button" className="verb-card" onClick={() => chooseKind(c.kind)}>
              <h2>{c.title}</h2>
              <p>{c.hint}</p>
            </button>
          ))}
        </div>
      )}

      {stage.name === "money-in-choice" && (
        <div className="card record-form">
          <button type="button" className="acct-link" style={{ marginBottom: 14 }} onClick={backToChoose}>
            &larr; Choose a different kind
          </button>
          <h2>How did the money come in?</h2>
          <div className="verb-cards">
            <button
              type="button"
              className="verb-card"
              onClick={() => navigate({ view: "receive-payment", params: {} })}
            >
              <h2>From a customer invoice</h2>
              <p>Apply this payment to one or more of a customer's open invoices.</p>
            </button>
            <button type="button" className="verb-card" onClick={() => setStage({ name: "form", kind: "money-in" })}>
              <h2>Other income</h2>
              <p>Interest, a refund, or anything not tied to an invoice.</p>
            </button>
          </div>
        </div>
      )}

      {stage.name === "money-out-choice" && (
        <div className="card record-form">
          <button type="button" className="acct-link" style={{ marginBottom: 14 }} onClick={backToChoose}>
            &larr; Choose a different kind
          </button>
          <h2>How did the money go out?</h2>
          <div className="verb-cards">
            <button
              type="button"
              className="verb-card"
              onClick={() => navigate({ view: "pay-bills", params: {} })}
            >
              <h2>A bill I owe</h2>
              <p>Pay one or more of a vendor's open bills.</p>
            </button>
            <button type="button" className="verb-card" onClick={() => setStage({ name: "form", kind: "money-out" })}>
              <h2>Other spending</h2>
              <p>A purchase, a fee, or anything not tied to a bill.</p>
            </button>
          </div>
        </div>
      )}

      {accounts && stage.name === "form" && (
        <div className="card record-form">
          <button type="button" className="acct-link" style={{ marginBottom: 14 }} onClick={backToChoose}>
            &larr; Choose a different kind
          </button>

          {submitError && <ErrorBanner message={submitError} onRetry={() => void handleSubmit(stage.kind)} />}

          <div className="field">
            <label htmlFor="rec-amount">Amount</label>
            <input
              id="rec-amount"
              className="text-input"
              type="text"
              inputMode="decimal"
              placeholder="0.00"
              value={amount}
              onChange={(ev) => setAmount(ev.target.value)}
              onBlur={() => setAmountTouched(true)}
            />
            {amtErr && <p className="field-error">{amtErr}</p>}
          </div>

          <div className="field">
            <label htmlFor="rec-date">Date</label>
            <input
              id="rec-date"
              className="text-input"
              type="date"
              value={date}
              onChange={(ev) => setDate(ev.target.value)}
            />
          </div>

          {stage.kind === "money-in" && (
            <>
              <SearchablePicker
                id="rec-into"
                label="Where did it land?"
                placeholder="Search money accounts…"
                groups={moneyGroups(accounts)}
                value={intoAccountId}
                onChange={setIntoAccountId}
              />
              <SearchablePicker
                id="rec-category"
                label="What was it for?"
                placeholder="Search categories…"
                groups={categoryGroups(accounts, "money-in")}
                value={categoryAccountId}
                onChange={setCategoryAccountId}
                action={{ label: "+ New category", onSelect: () => setNewCatOpen(true) }}
              />
              <div className="field">
                <label htmlFor="rec-who">Who from? (optional)</label>
                <input
                  id="rec-who"
                  className="text-input"
                  type="text"
                  value={who}
                  onChange={(ev) => setWho(ev.target.value)}
                />
              </div>
            </>
          )}

          {stage.kind === "money-out" && (
            <>
              <SearchablePicker
                id="rec-from"
                label="Paid from?"
                placeholder="Search money accounts…"
                groups={moneyGroups(accounts)}
                value={fromAccountId}
                onChange={setFromAccountId}
              />
              <SearchablePicker
                id="rec-category"
                label="What was it for?"
                placeholder="Search categories…"
                groups={categoryGroups(accounts, "money-out")}
                value={categoryAccountId}
                onChange={setCategoryAccountId}
                action={{ label: "+ New category", onSelect: () => setNewCatOpen(true) }}
              />
              <div className="field">
                <label htmlFor="rec-who">Who to? (optional)</label>
                <input
                  id="rec-who"
                  className="text-input"
                  type="text"
                  value={who}
                  onChange={(ev) => setWho(ev.target.value)}
                />
              </div>
            </>
          )}

          {stage.kind === "transfer" && (
            <>
              <SearchablePicker
                id="rec-from"
                label="From"
                placeholder="Search money accounts…"
                groups={moneyGroups(accounts)}
                value={fromAccountId}
                onChange={setFromAccountId}
              />
              <SearchablePicker
                id="rec-to"
                label="To"
                placeholder="Search money accounts…"
                groups={moneyGroups(accounts)}
                value={toAccountId}
                onChange={setToAccountId}
              />
              {fromAccountId && toAccountId && fromAccountId === toAccountId && (
                <p className="field-error">Pick two different accounts to move money between.</p>
              )}
            </>
          )}

          <div className="field">
            <label htmlFor="rec-memo">Memo (optional)</label>
            <input
              id="rec-memo"
              className="text-input"
              type="text"
              value={memo}
              onChange={(ev) => setMemo(ev.target.value)}
            />
          </div>

          {previewSentence(stage.kind) && <p className="record-preview">{previewSentence(stage.kind)}</p>}

          <div className="wizard-actions">
            <button
              type="button"
              className="btn"
              disabled={submitting || !buildBody(stage.kind)}
              onClick={() => void handleSubmit(stage.kind)}
            >
              {submitting ? "Recording…" : "Record it"}
            </button>
          </div>
        </div>
      )}

      {newCatOpen && selectedCompanyId && stage.name === "form" && (
        <NewAccountModal
          companyId={selectedCompanyId}
          title="New category"
          presetType={stage.kind === "money-in" ? "income" : "expense"}
          lockType
          typeNote={
            stage.kind === "money-in"
              ? "Money that came in gets an income category."
              : "Money that went out gets an expense category."
          }
          onClose={() => setNewCatOpen(false)}
          onCreated={(acct) => {
            setNewCatOpen(false);
            setCategoryAccountId(acct.id);
            retryAccounts(); // pull the fresh account into the picker's groups
          }}
        />
      )}

      {stage.name === "success" && (
        <div className="card success-screen">
          <h2>Done</h2>
          <p>{stage.result.summary}</p>
          <div className="wizard-actions">
            <button type="button" className="btn" onClick={backToChoose}>
              Record another
            </button>
            <button
              type="button"
              className="btn"
              onClick={() => navigate({ view: "entry", id: stage.result.entryId })}
            >
              View entry
            </button>
            {!stage.undo?.result && (
              <button type="button" className="btn" disabled={stage.undo?.busy} onClick={() => void handleUndo()}>
                {stage.undo?.busy ? "Undoing…" : "Undo"}
              </button>
            )}
          </div>

          {stage.undo?.error && <ErrorBanner message={stage.undo.error} onRetry={() => void handleUndo()} />}

          {stage.undo?.result && (
            <div style={{ marginTop: 16, paddingTop: 16, borderTop: "1px solid var(--grid)" }}>
              <p>{stage.undo.result.summary}</p>
              <button
                type="button"
                className="acct-link"
                onClick={() => navigate({ view: "entry", id: stage.undo!.result!.reversalEntryId })}
              >
                View the reversal entry
              </button>
            </div>
          )}
        </div>
      )}
    </div>
  );
}
