// Settings "Sales tax" card — Task 3 of
// docs/superpowers/plans/2026-08-02-sales-tax.md. The per-company toggle
// (PUT /api/c/settings/sales-tax), the payable-account line with a
// change-picker (item B: also the resolution path when enable refuses with
// "More than one active account is named 'Sales Tax Payable'"), and the
// rates table with inline add + edit (/api/c/tax-rates — no delete exists on
// purpose; `active` is the retirement mechanism, and a used rate's percent
// is frozen by the DB, whose 409 English is surfaced verbatim here).
//
// This card's off-state toggle is the ONE tax artifact a disabled company
// ever sees anywhere in the app (spec: "the feature MUST stay invisible
// until enabled") — everything else in this card, and every other tax
// control in the app, is gated on `enabled`.

import { useState } from "react";
import { useCompany } from "../lib/company";
import { useApiGet } from "../lib/useApiGet";
import { apiPost, apiPatch, apiPut, ApiError } from "../lib/api";
import { trimPercent } from "../lib/percent";
import { ErrorBanner } from "../components/ErrorBanner";
import type { ApiAccount } from "../accounts/subtypes";
import { accountSections } from "../accounts/pickerGroups";

interface SalesTaxSettings {
  enabled: boolean;
  accountId: string | null;
  accountName: string | null;
}

interface TaxRate {
  id: string;
  name: string;
  ratePercent: string;
  agencyVendorId: string | null;
  agencyVendorName: string | null;
  active: boolean;
}

interface VendorOption {
  id: string;
  name: string;
  active: boolean;
}

// Loose client-side shape check only — mirrors server/src/salesTax.ts's
// ratePercentSchema regex; the server (and the DB's 0-100 CHECK) stay the
// authority, this just gives an immediate English hint before a round trip.
const PERCENT_RE = /^(0|[1-9]\d{0,2})(\.\d{1,4})?$/;

function percentError(value: string): string | null {
  if (value.trim() === "") return "Enter a rate like 7.25.";
  if (!PERCENT_RE.test(value.trim())) {
    return "Enter a rate like 7.25 (up to 4 decimal places, no leading zeros).";
  }
  return null;
}

/** The agency <select> shared by the add row and the edit row — plain select,
 * not SearchablePicker: a company has a handful of vendors that could be a
 * tax agency, and this matches the card's other inline controls. */
function AgencySelect({
  id,
  value,
  vendors,
  onChange,
}: {
  id: string;
  value: string;
  vendors: VendorOption[];
  onChange: (value: string) => void;
}) {
  return (
    <select id={id} className="select-input" value={value} onChange={(ev) => onChange(ev.target.value)}>
      <option value="">No agency yet</option>
      {vendors.map((v) => (
        <option key={v.id} value={v.id}>
          {v.name}
        </option>
      ))}
    </select>
  );
}

function RateEditRow({
  rate,
  vendors,
  onCancel,
  onSaved,
  companyId,
}: {
  rate: TaxRate;
  vendors: VendorOption[];
  onCancel: () => void;
  onSaved: () => void;
  companyId: string;
}) {
  const [name, setName] = useState(rate.name);
  const [percent, setPercent] = useState(trimPercent(rate.ratePercent));
  const [agencyVendorId, setAgencyVendorId] = useState(rate.agencyVendorId ?? "");
  const [active, setActive] = useState(rate.active);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [touched, setTouched] = useState(false);

  // The rate's current agency may be a since-deactivated vendor, which the
  // active-vendors list no longer contains — include it (marked) so the
  // select shows the truth instead of silently unmatching while the state
  // still holds the id. Keeping it selected is fine: handleSave below only
  // sends agencyVendorId when it actually CHANGED (absent = keep, per the
  // PATCH contract), so an untouched deactivated agency never trips the
  // server's active-vendor check.
  const vendorOptions =
    rate.agencyVendorId !== null && !vendors.some((v) => v.id === rate.agencyVendorId)
      ? [
          { id: rate.agencyVendorId, name: `${rate.agencyVendorName ?? "Former agency"} (deactivated)`, active: false },
          ...vendors,
        ]
      : vendors;

  const validation = touched
    ? name.trim() === ""
      ? "A tax rate needs a name."
      : percentError(percent)
    : null;

  async function handleSave() {
    setTouched(true);
    if (name.trim() === "" || percentError(percent) !== null) return;
    setSaving(true);
    setError(null);
    try {
      const body: Record<string, unknown> = { name: name.trim(), active };
      // Only send the percent when its VALUE actually changed — a rate used
      // on issued invoices has a frozen percent (the DB refuses), but its
      // name/agency/active must stay editable without tripping that guard.
      // Compared in canonical trimmed form so re-typing "7.250" for a stored
      // "7.2500" doesn't count as a change.
      if (trimPercent(percent.trim()) !== trimPercent(rate.ratePercent)) {
        body.ratePercent = percent.trim();
      }
      // Same only-when-changed rule for the agency (absent = keep): an
      // untouched, since-deactivated agency vendor must round-trip rather
      // than be re-validated against the active-vendors check.
      if (agencyVendorId !== (rate.agencyVendorId ?? "")) {
        body.agencyVendorId = agencyVendorId === "" ? null : agencyVendorId;
      }
      await apiPatch(`/api/c/tax-rates/${rate.id}`, body, companyId);
      onSaved();
    } catch (err) {
      // The percent-freeze 409 lands here with the API's own English
      // ("…deactivate it and create a new rate instead") — shown verbatim.
      setError(err instanceof ApiError ? err.message : "Couldn't save that tax rate. Try again.");
    } finally {
      setSaving(false);
    }
  }

  return (
    <>
      <tr>
        <td>
          <input
            className="text-input"
            type="text"
            aria-label="Rate name"
            value={name}
            onChange={(ev) => setName(ev.target.value)}
          />
        </td>
        <td className="num">
          <input
            className="text-input"
            type="text"
            inputMode="decimal"
            aria-label="Percent"
            style={{ width: 90, textAlign: "right" }}
            value={percent}
            onChange={(ev) => setPercent(ev.target.value)}
          />
        </td>
        <td>
          <AgencySelect
            id={`rate-agency-${rate.id}`}
            value={agencyVendorId}
            vendors={vendorOptions}
            onChange={setAgencyVendorId}
          />
        </td>
        <td>
          <label style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
            <input type="checkbox" checked={active} onChange={(ev) => setActive(ev.target.checked)} />
            <span>Active</span>
          </label>
        </td>
        <td style={{ whiteSpace: "nowrap" }}>
          <button type="button" className="btn" disabled={saving} onClick={() => void handleSave()}>
            {saving ? "Saving…" : "Save"}
          </button>{" "}
          <button type="button" className="acct-link" disabled={saving} onClick={onCancel}>
            Cancel
          </button>
        </td>
      </tr>
      {(error || validation) && (
        <tr>
          <td colSpan={5} className="field-error">
            {error ?? validation}
          </td>
        </tr>
      )}
    </>
  );
}

export function SalesTaxCard() {
  const { selectedCompany } = useCompany();
  const companyId = selectedCompany?.id ?? null;

  const { data: settings, loading, error, retry } = useApiGet<SalesTaxSettings>(
    "/api/c/settings/sales-tax", companyId,
  );
  const enabled = settings?.enabled === true;

  const { data: rates, retry: retryRates } = useApiGet<TaxRate[]>(
    enabled ? "/api/c/tax-rates" : null, companyId,
  );
  const { data: vendors } = useApiGet<VendorOption[]>(
    enabled ? "/api/c/vendors" : null, companyId,
  );
  const activeVendors = (vendors ?? []).filter((v) => v.active);

  // The payable-account picker (item B) appears in two situations: enable
  // refused with the ambiguous-name error (several active liability accounts
  // named 'Sales Tax Payable' — the choice resolves it), or the user clicks
  // "Change" next to the account while enabled.
  const [accountPickerOpen, setAccountPickerOpen] = useState(false);
  const needAccounts = enabled || accountPickerOpen;
  const { data: accounts } = useApiGet<ApiAccount[]>(
    needAccounts ? "/api/c/accounts" : null, companyId,
  );
  // Liability-restricted picker under the standard sections (Chart
  // Management Task 3) — sales tax owed is a liability, so the sections are
  // Credit cards / Accounts payable / Other current liabilities / ...; the
  // restriction itself is unchanged.
  const liabilitySections = accountSections(accounts, ["liability"]);
  const [chosenAccountId, setChosenAccountId] = useState("");

  const [toggling, setToggling] = useState(false);
  const [toggleError, setToggleError] = useState<string | null>(null);

  async function putSettings(nextEnabled: boolean, accountId?: string) {
    if (!companyId) return;
    setToggling(true);
    setToggleError(null);
    try {
      await apiPut<SalesTaxSettings>(
        "/api/c/settings/sales-tax",
        accountId ? { enabled: nextEnabled, accountId } : { enabled: nextEnabled },
        companyId,
      );
      setAccountPickerOpen(false);
      setChosenAccountId("");
      retry();
    } catch (err) {
      const msg = err instanceof ApiError ? err.message : "Couldn't change the sales tax settings.";
      setToggleError(msg);
      // The ambiguous-payable-account refusal names its own fix ("set the
      // one to use in Settings") — this IS Settings, so open the picker.
      if (/More than one active account/i.test(msg)) {
        setAccountPickerOpen(true);
      }
    } finally {
      setToggling(false);
    }
  }

  // -------------------------------------------------------------------------
  // inline add-rate row state
  // -------------------------------------------------------------------------
  const [addName, setAddName] = useState("");
  const [addPercent, setAddPercent] = useState("");
  const [addAgencyVendorId, setAddAgencyVendorId] = useState("");
  const [addTouched, setAddTouched] = useState(false);
  const [adding, setAdding] = useState(false);
  const [addError, setAddError] = useState<string | null>(null);
  const [editingRateId, setEditingRateId] = useState<string | null>(null);

  const addValidation = addTouched
    ? addName.trim() === ""
      ? "A tax rate needs a name."
      : percentError(addPercent)
    : null;

  async function handleAddRate() {
    setAddTouched(true);
    if (!companyId || addName.trim() === "" || percentError(addPercent) !== null) return;
    setAdding(true);
    setAddError(null);
    try {
      await apiPost(
        "/api/c/tax-rates",
        {
          name: addName.trim(),
          ratePercent: addPercent.trim(),
          agencyVendorId: addAgencyVendorId === "" ? undefined : addAgencyVendorId,
        },
        companyId,
      );
      setAddName("");
      setAddPercent("");
      setAddAgencyVendorId("");
      setAddTouched(false);
      retryRates();
    } catch (err) {
      setAddError(err instanceof ApiError ? err.message : "Couldn't add that tax rate. Try again.");
    } finally {
      setAdding(false);
    }
  }

  return (
    <div className="card">
      <h2>Sales tax</h2>
      <p className="sub">
        Charge sales tax on invoices, watch what you owe build up, and record payments to the tax
        agency through bills. Off by default — turn it on only if you collect sales tax.
      </p>

      {loading && !settings && <p className="msg-inline">Checking&hellip;</p>}
      {error && !settings && <ErrorBanner message={error} onRetry={retry} />}

      {settings && !enabled && (
        <>
          <p className="sub">
            Sales tax is <strong>off</strong> for this company — no tax fields appear anywhere.
          </p>
          {toggleError && (
            <div style={{ marginTop: 12 }}>
              <ErrorBanner message={toggleError} onRetry={() => void putSettings(true)} />
            </div>
          )}
          {accountPickerOpen && (
            <div className="field" style={{ marginTop: 12 }}>
              <label htmlFor="salestax-account-choice">Which account should track the tax you owe?</label>
              <select
                id="salestax-account-choice"
                className="select-input"
                value={chosenAccountId}
                onChange={(ev) => setChosenAccountId(ev.target.value)}
              >
                <option value="">Pick an account…</option>
                {liabilitySections.map((section) => (
                  <optgroup key={section.label} label={section.label}>
                    {section.accounts.map((a) => (
                      <option key={a.id} value={a.id}>
                        {a.name}
                      </option>
                    ))}
                  </optgroup>
                ))}
              </select>
              <div className="wizard-actions">
                <button
                  type="button"
                  className="btn"
                  disabled={toggling || chosenAccountId === ""}
                  onClick={() => void putSettings(true, chosenAccountId)}
                >
                  {toggling ? "Turning on…" : "Use this account and turn on"}
                </button>
              </div>
            </div>
          )}
          {!accountPickerOpen && (
            <div className="wizard-actions">
              <button type="button" className="btn" disabled={toggling} onClick={() => void putSettings(true)}>
                {toggling ? "Turning on…" : "Turn on sales tax"}
              </button>
            </div>
          )}
        </>
      )}

      {settings && enabled && (
        <>
          <div className="rows">
            <div className="row">
              <span className="n">The tax you collect is tracked in</span>
              <span className="a">
                {settings.accountName ?? "—"}{" "}
                <button
                  type="button"
                  className="acct-link"
                  onClick={() => {
                    setAccountPickerOpen((open) => !open);
                    setChosenAccountId(settings.accountId ?? "");
                  }}
                >
                  Change
                </button>
              </span>
            </div>
          </div>

          {accountPickerOpen && (
            <div className="field" style={{ marginTop: 10 }}>
              <label htmlFor="salestax-account-change">Which account should track the tax you owe?</label>
              <select
                id="salestax-account-change"
                className="select-input"
                value={chosenAccountId}
                onChange={(ev) => setChosenAccountId(ev.target.value)}
              >
                <option value="">Pick an account…</option>
                {liabilitySections.map((section) => (
                  <optgroup key={section.label} label={section.label}>
                    {section.accounts.map((a) => (
                      <option key={a.id} value={a.id}>
                        {a.name}
                      </option>
                    ))}
                  </optgroup>
                ))}
              </select>
              <div className="wizard-actions">
                <button
                  type="button"
                  className="btn"
                  disabled={toggling || chosenAccountId === "" || chosenAccountId === settings.accountId}
                  onClick={() => void putSettings(true, chosenAccountId)}
                >
                  {toggling ? "Saving…" : "Use this account"}
                </button>
                <button type="button" className="acct-link" disabled={toggling} onClick={() => setAccountPickerOpen(false)}>
                  Cancel
                </button>
              </div>
            </div>
          )}

          <table style={{ marginTop: 16 }}>
            <thead>
              <tr>
                <th>Rate</th>
                <th className="num">Percent</th>
                <th>Paid to</th>
                <th>Status</th>
                <th />
              </tr>
            </thead>
            <tbody>
              {(rates ?? []).map((r) =>
                editingRateId === r.id && companyId ? (
                  <RateEditRow
                    key={r.id}
                    rate={r}
                    vendors={activeVendors}
                    companyId={companyId}
                    onCancel={() => setEditingRateId(null)}
                    onSaved={() => {
                      setEditingRateId(null);
                      retryRates();
                    }}
                  />
                ) : (
                  <tr key={r.id} style={{ opacity: r.active ? 1 : 0.6 }}>
                    <td>{r.name}</td>
                    <td className="num">{trimPercent(r.ratePercent)}%</td>
                    <td>{r.agencyVendorName ?? "—"}</td>
                    <td>{r.active ? "Active" : "Inactive"}</td>
                    <td>
                      <button type="button" className="acct-link" onClick={() => setEditingRateId(r.id)}>
                        Edit
                      </button>
                    </td>
                  </tr>
                ),
              )}
              {(rates ?? []).length === 0 && (
                <tr>
                  <td colSpan={5} className="sub">
                    No tax rates yet — add your first one below.
                  </td>
                </tr>
              )}
              {/* inline add row */}
              <tr>
                <td>
                  <input
                    className="text-input"
                    type="text"
                    placeholder="e.g. CA Sales Tax"
                    aria-label="New rate name"
                    value={addName}
                    onChange={(ev) => setAddName(ev.target.value)}
                  />
                </td>
                <td className="num">
                  <input
                    className="text-input"
                    type="text"
                    inputMode="decimal"
                    placeholder="7.25"
                    aria-label="New rate percent"
                    style={{ width: 90, textAlign: "right" }}
                    value={addPercent}
                    onChange={(ev) => setAddPercent(ev.target.value)}
                  />
                </td>
                <td>
                  <AgencySelect
                    id="salestax-add-agency"
                    value={addAgencyVendorId}
                    vendors={activeVendors}
                    onChange={setAddAgencyVendorId}
                  />
                </td>
                <td />
                <td>
                  <button type="button" className="btn" disabled={adding} onClick={() => void handleAddRate()}>
                    {adding ? "Adding…" : "Add rate"}
                  </button>
                </td>
              </tr>
              {(addError || addValidation) && (
                <tr>
                  <td colSpan={5} className="field-error">
                    {addError ?? addValidation}
                  </td>
                </tr>
              )}
            </tbody>
          </table>
          <p className="sub" style={{ marginTop: 10 }}>
            The agency is the vendor you send the tax to (add one on the Vendors page first if it
            isn&rsquo;t listed). A rate that&rsquo;s been used on invoices keeps its percent forever —
            when the rate changes, deactivate it and add a new one.
          </p>

          {toggleError && (
            <div style={{ marginTop: 12 }}>
              <ErrorBanner message={toggleError} onRetry={() => void putSettings(false)} />
            </div>
          )}
          <div className="wizard-actions">
            <button type="button" className="acct-link" disabled={toggling} onClick={() => void putSettings(false)}>
              {toggling ? "Turning off…" : "Turn off sales tax"}
            </button>
          </div>
        </>
      )}
    </div>
  );
}
