// #purchase-orders — Task 4 of docs/superpowers/plans/2026-08-03-inventory.md.
// The list, shaped like bills/BillsScreen.tsx: filters, a "New purchase order"
// button that swaps the list for the form, and a table whose rows open the
// order's own page.
//
// The Status column shows the DERIVED state (server/src/purchaseOrders.ts's
// `state`), not the stored one: receipt progress is computed by summing what
// has actually been received on issued, non-void bills, because CLAUDE.md §3.4
// says nothing computable is cached. A stored copy would go stale the first
// time some path forgot to update it, and nothing could notice.
//
// Two signals ride alongside it, and both are surfaced rather than smoothed
// over, because each is a real thing the reader would otherwise have to
// reconcile alone:
//
//   - OVER-RECEIVED. More arrived than was ordered. Allowed (it happens, and
//     the bill records what the vendor actually sent) but never silent: the
//     outstanding quantity clamps at zero, so without this chip an
//     over-receipt looks exactly like an exact one.
//   - NOT TRACKED. A description-only line can never be marked received —
//     nothing links a bill line to one — so a mixed order can read "Received"
//     while such a line still shows an outstanding quantity. That is a limit
//     of what can be observed, not a claim that the freight turned up.
//
// Plus "past its date" for an issued order whose expected date has gone by,
// which is the same fact the health panel flags.

import { useEffect, useMemo, useState } from "react";
import { useCompany } from "../lib/company";
import { useApiGet } from "../lib/useApiGet";
import { fmtMoney } from "../lib/money";
import { fmtDate, todayIso } from "../lib/date";
import { ErrorBanner } from "../components/ErrorBanner";
import type { NavTarget } from "../lib/useHashView";
import { PurchaseOrderForm } from "./PurchaseOrderForm";
import { consumePendingPoFilter } from "./vendorFilterHandoff";
import {
  poStateChip, poStateLabel, isPastExpected,
  type PoListResponse, type PoListRow, type PoState,
} from "./poTypes";

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

type Mode = { name: "list" } | { name: "create" };

const STATE_FILTERS: { value: "" | PoState; label: string }[] = [
  { value: "", label: "All" },
  { value: "draft", label: "Draft" },
  { value: "open", label: "Sent" },
  { value: "partial", label: "Partly received" },
  { value: "closed", label: "Received" },
  { value: "cancelled", label: "Cancelled" },
];

export function PurchaseOrdersScreen({ navigate }: { navigate: (target: NavTarget) => void }) {
  const { selectedCompanyId } = useCompany();
  const [mode, setMode] = useState<Mode>({ name: "list" });
  const [stateFilter, setStateFilter] = useState<string>("");
  const [vendorFilter, setVendorFilter] = useState<string>("");
  const today = todayIso();

  // One-time pickup of a vendor filter handed off from elsewhere (a vendor's
  // page), same consumed-exactly-once pattern as #bills'.
  useEffect(() => {
    const pending = consumePendingPoFilter();
    if (pending) setVendorFilter(pending);
  }, []);

  const { data: vendors } = useApiGet<VendorOption[]>("/api/c/vendors", selectedCompanyId);

  const path = useMemo(() => {
    const params = new URLSearchParams();
    if (stateFilter) params.set("state", stateFilter);
    if (vendorFilter) params.set("vendorId", vendorFilter);
    const qs = params.toString();
    return `/api/c/purchase-orders${qs ? `?${qs}` : ""}`;
  }, [stateFilter, vendorFilter]);

  const { data, loading, error, retry } = useApiGet<PoListResponse>(path, selectedCompanyId);

  return (
    <div className="wrap">
      <header className="top">
        <h1>Purchase orders</h1>
        {mode.name === "list" && (
          <button type="button" className="btn" onClick={() => setMode({ name: "create" })}>
            New purchase order
          </button>
        )}
      </header>

      {!selectedCompanyId ? (
        <p className="msg-inline">Choose a company from the sidebar to see its purchase orders.</p>
      ) : mode.name === "create" ? (
        <PurchaseOrderForm
          mode="create"
          onCancel={() => setMode({ name: "list" })}
          onSaved={({ poId }) => {
            setMode({ name: "list" });
            navigate({ view: "purchase-order", id: poId });
          }}
        />
      ) : (
        <>
          <p className="sub" style={{ marginBottom: 14 }}>
            What you&rsquo;ve asked vendors for, and how much of it has arrived. A purchase order
            never touches your books — entering the bill is what records the goods and the money,
            together.
          </p>

          <div className="report-toolbar">
            <label>
              Status
              <select
                className="select-input"
                value={stateFilter}
                onChange={(ev) => setStateFilter(ev.target.value)}
              >
                {STATE_FILTERS.map((s) => (
                  <option key={s.value} value={s.value}>{s.label}</option>
                ))}
              </select>
            </label>
            <label>
              Vendor
              <select
                className="select-input"
                value={vendorFilter}
                onChange={(ev) => setVendorFilter(ev.target.value)}
              >
                <option value="">All vendors</option>
                {(vendors ?? []).map((v) => (
                  <option key={v.id} value={v.id}>{v.name}</option>
                ))}
              </select>
            </label>
          </div>

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

          {data && data.purchaseOrders.length === 0 && (
            <div className="empty-state">
              {stateFilter || vendorFilter
                ? "No purchase orders match those filters."
                : "No purchase orders yet. Raise one when you order stock, then receive it straight onto the bill when it arrives."}
            </div>
          )}

          {data && data.purchaseOrders.length > 0 && (
            <table>
              <thead>
                <tr>
                  <th>Number</th>
                  <th>Vendor</th>
                  <th>Ordered</th>
                  <th>Expected</th>
                  <th>Status</th>
                  <th className="num">Total</th>
                </tr>
              </thead>
              <tbody>
                {data.purchaseOrders.map((po) => (
                  <PoRow
                    key={po.id}
                    po={po}
                    today={today}
                    onOpen={() => navigate({ view: "purchase-order", id: po.id })}
                  />
                ))}
              </tbody>
            </table>
          )}
        </>
      )}
    </div>
  );
}

function PoRow({
  po,
  today,
  onOpen,
}: {
  po: PoListRow;
  today: string;
  onOpen: () => void;
}) {
  const overdue = isPastExpected(po, today);
  return (
    <tr style={{ cursor: "pointer" }} onClick={onOpen}>
      <td>{po.poNo ? `#${po.poNo}` : "—"}</td>
      <td>{po.vendorName}</td>
      <td>{fmtDate(po.orderDate)}</td>
      <td className={overdue ? "invoice-due-overdue" : undefined}>
        {po.expectedDate ? fmtDate(po.expectedDate) : "—"}
      </td>
      <td>
        <span className={poStateChip(po.state)}>{poStateLabel(po.state)}</span>
        {overdue && <span className="chip chip-warn" style={{ marginLeft: 6 }}>Past its date</span>}
        {po.overReceivedLines > 0 && (
          <span className="chip chip-warn" style={{ marginLeft: 6 }}>
            More arrived than ordered
          </span>
        )}
        {po.unreceivableLines > 0 && (
          <div className="register-memo-sub">
            {po.unreceivableLines} line{po.unreceivableLines === 1 ? "" : "s"} can&rsquo;t be tracked
            as received
          </div>
        )}
      </td>
      <td className="num">{fmtMoney(po.total)}</td>
    </tr>
  );
}
