// #bill/:id — AP Task 4, mechanical mirror of invoices/InvoiceDetail.tsx.
// Header (reference or "Draft", state chip, dates, vendor), lines table,
// totals, applications (AP Task 5: each now links to its #bill-payment/:id
// detail), entry links (posted/void) to #entry/:id, and the action set: Edit
// + Issue (drafts only), "Pay this bill" (open/partial — AP Task 5, routes
// to #pay-bills with this bill preselected), Void (issued bills, with an
// inline English confirm — the API's own "refused: has payments applied"
// message surfaces verbatim on failure, mirroring the design brief).
// Deliberately no Print action — bills aren't printed.

import { useState } from "react";
import { useCompany } from "../lib/company";
import { useApiGet } from "../lib/useApiGet";
import { apiPost, apiDelete, ApiError } from "../lib/api";
import { fmtDate, todayIso } from "../lib/date";
import { fmtMoney } from "../lib/money";
import { ErrorBanner } from "../components/ErrorBanner";
import { BackLink } from "../components/BackLink";
import type { NavTarget } from "../lib/useHashView";
import { BillForm, type BillFormInitial } from "./BillForm";

interface BillLine {
  id: string;
  description: string | null;
  quantity: string;
  unitPrice: string;
  amount: string;
  categoryAccountId: string;
  categoryAccountName: string;
  // Inventory Task 4 — null on every line that doesn't name an item.
  itemId: string | null;
  itemName: string | null;
  itemKind: string | null;
  poLineId: string | null;
  poId: string | null;
  poNo: string | null;
}

interface BillApplication {
  id: string;
  paymentId: string;
  amount: string;
  paymentDate: string;
  paymentStatus: string;
}

interface BillDetailData {
  billId: string;
  refNo: string | null;
  status: string;
  state: "draft" | "open" | "partial" | "paid" | "void";
  overdue: boolean;
  billDate: string;
  dueDate: string;
  memo: string | null;
  documentOnly: boolean;
  postedEntryId: string | null;
  voidEntryId: string | null;
  vendor: {
    id: string;
    name: string;
    email: string | null;
    phone: string | null;
    address: string | null;
    termsDays: number;
  };
  lines: BillLine[];
  applications: BillApplication[];
  total: string;
  applied: string;
  open: string;
}

const STATE_LABEL: Record<BillDetailData["state"], string> = {
  draft: "Draft",
  open: "Open",
  partial: "Partly paid",
  paid: "Paid",
  void: "Void",
};

const STATE_CHIP_CLASS: Record<BillDetailData["state"], string> = {
  draft: "chip chip-muted",
  open: "chip chip-info",
  partial: "chip chip-warn",
  paid: "chip",
  void: "chip chip-muted",
};

/** "1.0000" -> "1", "0.5000" -> "0.5" — quantity isn't money (CLAUDE.md §2 is
 * about monetary values), so plain string trimming is fine here. */
function fmtQuantity(q: string): string {
  if (!q.includes(".")) return q;
  return q.replace(/0+$/, "").replace(/\.$/, "");
}

export function BillDetail({
  billId,
  navigate,
}: {
  billId: string;
  navigate: (target: NavTarget) => void;
}) {
  const { selectedCompanyId } = useCompany();
  const path = `/api/c/bills/${billId}`;
  const { data, loading, error, retry } = useApiGet<BillDetailData>(path, selectedCompanyId);

  const [editing, setEditing] = useState(false);
  const [issuing, setIssuing] = useState(false);
  const [issueError, setIssueError] = useState<string | null>(null);
  const [voidConfirming, setVoidConfirming] = useState(false);
  const [voiding, setVoiding] = useState(false);
  const [voidError, setVoidError] = useState<string | null>(null);
  const [discardConfirming, setDiscardConfirming] = useState(false);
  const [discarding, setDiscarding] = useState(false);
  const [discardError, setDiscardError] = useState<string | null>(null);

  async function handleIssue() {
    if (!selectedCompanyId) return;
    setIssuing(true);
    setIssueError(null);
    try {
      await apiPost(`/api/c/bills/${billId}/issue`, undefined, selectedCompanyId);
      retry();
    } catch (err) {
      setIssueError(err instanceof ApiError ? err.message : "Couldn't issue that bill. Try again.");
    } finally {
      setIssuing(false);
    }
  }

  async function handleVoid() {
    if (!selectedCompanyId) return;
    setVoiding(true);
    setVoidError(null);
    try {
      await apiPost(`/api/c/bills/${billId}/void`, { date: todayIso() }, selectedCompanyId);
      setVoidConfirming(false);
      retry();
    } catch (err) {
      setVoidError(err instanceof ApiError ? err.message : "Couldn't void that bill. Try again.");
    } finally {
      setVoiding(false);
    }
  }

  /** Throwing away a draft is safe in a way voiding is not: a draft has posted
   *  nothing at all — no entry, no stock movement, no balance anywhere — so
   *  there is nothing to reverse (DELETE /api/c/bills/:id, draft-only both in
   *  the route and at the table). This is also the way out of a purchase
   *  order's "everything left is already on a bill you haven't entered yet". */
  async function handleDiscard() {
    if (!selectedCompanyId) return;
    setDiscarding(true);
    setDiscardError(null);
    try {
      await apiDelete(`/api/c/bills/${billId}`, selectedCompanyId);
      navigate("bills");
    } catch (err) {
      setDiscardError(err instanceof ApiError ? err.message : "Couldn't throw that draft away. Try again.");
      setDiscardConfirming(false);
    } finally {
      setDiscarding(false);
    }
  }

  const initialForEdit: BillFormInitial | null = data
    ? {
        vendorId: data.vendor.id,
        billDate: data.billDate,
        dueDate: data.dueDate,
        refNo: data.refNo ?? "",
        memo: data.memo ?? "",
        lines: data.lines.map((l) => ({
          description: l.description ?? "",
          quantity: fmtQuantity(l.quantity),
          unitPrice: l.unitPrice.replace(/0+$/, "").replace(/\.$/, "") || l.unitPrice,
          categoryAccountId: l.categoryAccountId,
          itemId: l.itemId,
          // Kept so editing a draft receipt doesn't quietly unlink it from the
          // purchase order it was raised against.
          poLineId: l.poLineId,
        })),
      }
    : null;

  // The Item column only exists on a bill that actually has one — a service
  // company's bill looks exactly as it did before inventory existed.
  const hasItemLines = (data?.lines ?? []).some((l) => l.itemId !== null);

  return (
    <div className="wrap">
      <BackLink label="Back to bills" />

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

      {data && !editing && (
        <>
          <header className="top">
            {/* Unlike invoices.invoiceNo (server-assigned only on issue, so
                its absence always means "draft"), a bill's refNo is optional
                and vendor-supplied — it can be blank on an issued bill too.
                The state chip right next to this title is what actually says
                draft vs. issued, so the title falls back to state, not
                refNo, when there's no reference to show. */}
            <h1>{data.refNo ? `Bill #${data.refNo}` : data.state === "draft" ? "Draft bill" : "Bill (no reference)"}</h1>
            <span className={STATE_CHIP_CLASS[data.state]}>{STATE_LABEL[data.state]}</span>
            {data.overdue && <span className="chip chip-bad">Overdue</span>}
          </header>
          <div className="asof">
            {data.vendor.name} · Bill date {fmtDate(data.billDate)} · Due {fmtDate(data.dueDate)}
          </div>

          {data.documentOnly && (
            <p className="badge" style={{ marginTop: 10, display: "inline-flex" }}>
              No accounting impact — already in your books
            </p>
          )}

          <div className="card" style={{ margin: "18px 0" }}>
            <h2>Lines</h2>
            <table>
              <thead>
                <tr>
                  {hasItemLines && <th>Item</th>}
                  <th>Description</th>
                  <th className="num">Qty</th>
                  <th className="num">Unit price</th>
                  <th className="num">Amount</th>
                  <th>Category</th>
                </tr>
              </thead>
              <tbody>
                {data.lines.map((l) => (
                  <tr key={l.id}>
                    {hasItemLines && (
                      <td>
                        {l.itemId ? (
                          <button
                            type="button"
                            className="acct-link"
                            onClick={() => navigate({ view: "item", id: l.itemId! })}
                          >
                            {l.itemName}
                          </button>
                        ) : (
                          "—"
                        )}
                        {l.poNo && <div className="register-memo-sub">PO #{l.poNo}</div>}
                      </td>
                    )}
                    <td>{l.description ?? "—"}</td>
                    <td className="num">{fmtQuantity(l.quantity)}</td>
                    <td className="num">{fmtMoney(l.unitPrice)}</td>
                    <td className="num">{fmtMoney(l.amount)}</td>
                    <td>{l.categoryAccountName}</td>
                  </tr>
                ))}
              </tbody>
            </table>

            <div className="rows" style={{ marginTop: 12 }}>
              <div className="row">
                <span className="n">Total</span>
                <span className="a">{fmtMoney(data.total)}</span>
              </div>
              <div className="row">
                <span className="n">Paid</span>
                <span className="a">{fmtMoney(data.applied)}</span>
              </div>
              <div className="row total">
                <span className="n">Open</span>
                <span className="a">{fmtMoney(data.open)}</span>
              </div>
            </div>
          </div>

          {data.memo && (
            <div className="card" style={{ marginBottom: 18 }}>
              <h2>Memo</h2>
              <p>{data.memo}</p>
            </div>
          )}

          <div className="card" style={{ marginBottom: 18 }}>
            <h2>Payments</h2>
            {data.applications.length === 0 ? (
              <p className="sub">No payments applied yet.</p>
            ) : (
              <ul className="entry-lines">
                {data.applications.map((a) => (
                  <li key={a.id}>
                    <button
                      type="button"
                      className="acct-link"
                      onClick={() => navigate({ view: "bill-payment", id: a.paymentId })}
                    >
                      {fmtDate(a.paymentDate)} — {fmtMoney(a.amount)} applied
                    </button>
                    {a.paymentStatus === "void" && " (payment voided)"}
                  </li>
                ))}
              </ul>
            )}
          </div>

          {(data.postedEntryId || data.voidEntryId) && (
            <div className="card" style={{ marginBottom: 18 }}>
              <h2>In your books</h2>
              <div className="reversal-links">
                {data.postedEntryId && (
                  <button
                    type="button"
                    className="acct-link"
                    onClick={() => navigate({ view: "entry", id: data.postedEntryId! })}
                  >
                    See how this was recorded
                  </button>
                )}
                {data.voidEntryId && (
                  <button
                    type="button"
                    className="acct-link"
                    onClick={() => navigate({ view: "entry", id: data.voidEntryId! })}
                  >
                    See the correcting entry
                  </button>
                )}
              </div>
            </div>
          )}

          <div className="card">
            <h2>Actions</h2>
            {issueError && <ErrorBanner message={issueError} onRetry={() => void handleIssue()} />}
            {voidError && <ErrorBanner message={voidError} onRetry={() => void handleVoid()} />}
            {discardError && <ErrorBanner message={discardError} onRetry={() => void handleDiscard()} />}

            {discardConfirming ? (
              <div className="confirm-panel">
                <p>
                  This throws the draft away for good. Nothing was ever recorded in your books — a
                  draft doesn&rsquo;t touch them — so there is nothing to correct
                  {hasItemLines ? ", and no stock has moved" : ""}.
                  {data.lines.some((l) => l.poNo)
                    ? " Whatever it was receiving goes back to the purchase order as still outstanding."
                    : ""}
                </p>
                <div className="wizard-actions">
                  <button type="button" className="btn" disabled={discarding} onClick={() => void handleDiscard()}>
                    {discarding ? "Throwing away…" : "Yes, throw it away"}
                  </button>
                  <button
                    type="button"
                    className="acct-link"
                    disabled={discarding}
                    onClick={() => setDiscardConfirming(false)}
                  >
                    Cancel
                  </button>
                </div>
              </div>
            ) : !voidConfirming ? (
              <div className="wizard-actions">
                {data.state === "draft" && (
                  <>
                    <button type="button" className="btn" onClick={() => setEditing(true)}>
                      Edit
                    </button>
                    <button type="button" className="btn" disabled={issuing} onClick={() => void handleIssue()}>
                      {issuing ? "Issuing…" : "Issue"}
                    </button>
                    <button type="button" className="acct-link" onClick={() => setDiscardConfirming(true)}>
                      Throw this draft away
                    </button>
                  </>
                )}
                {(data.state === "open" || data.state === "partial") && (
                  <button
                    type="button"
                    className="btn"
                    onClick={() =>
                      navigate({
                        view: "pay-bills",
                        params: { vendorId: data.vendor.id, billId: data.billId },
                      })
                    }
                  >
                    Pay this bill
                  </button>
                )}
                {data.status === "open" && (
                  <button type="button" className="btn" onClick={() => setVoidConfirming(true)}>
                    Void
                  </button>
                )}
              </div>
            ) : (
              <div className="confirm-panel">
                <p>
                  {/* Void is only offered once status is 'open' (never
                      draft) — see below — so refNo being blank here just
                      means this vendor's bill never carried a reference,
                      never "draft" (mirrors the header's same fix). */}
                  This voids the bill{data.refNo ? ` #${data.refNo}` : ""} — it will no longer count toward{" "}
                  {data.vendor.name}&rsquo;s balance. If it already has payments applied, voiding is
                  refused until those are undone.
                </p>
                <div className="wizard-actions">
                  <button type="button" className="btn" disabled={voiding} onClick={() => void handleVoid()}>
                    {voiding ? "Voiding…" : "Yes, void it"}
                  </button>
                  <button
                    type="button"
                    className="acct-link"
                    disabled={voiding}
                    onClick={() => setVoidConfirming(false)}
                  >
                    Cancel
                  </button>
                </div>
              </div>
            )}
            {data.state === "draft" && <p className="invoice-form-drafts-note">Drafts stay until issued or edited.</p>}
          </div>
        </>
      )}

      {data && editing && initialForEdit && (
        <BillForm
          mode="edit"
          billId={billId}
          initial={initialForEdit}
          onCancel={() => setEditing(false)}
          onSaved={() => {
            setEditing(false);
            retry();
          }}
        />
      )}
    </div>
  );
}
