// #items — Task 4 of docs/superpowers/plans/2026-08-03-inventory.md. The
// Items screen, built on the Chart of Accounts screen's shape (its closest
// analogue): one list, grouped under standard headings, a "Show inactive"
// toggle that is OFF by default, an add modal, and per-row Edit /
// Deactivate-Reactivate actions.
//
// Grouped by KIND — Stocked items / Non-stocked items / Services — because
// that is the distinction that changes what an item DOES to the books, and
// CLAUDE.md §9 (as amended 2026-08-02) says to show standard accounting
// plainly rather than flatten it away. Each section says in one line what its
// kind means, so nobody has to already know.
//
// NOTHING HERE IS CACHED. On hand, average cost and value come from GET
// /api/c/items, which derives all three from inventory_movements at query
// time (0018's inventory_valuation) — the same way every account balance
// derives from journal_lines (CLAUDE.md §3.4). A non-stocked item and a
// service have no quantity at all and render an em dash, never a 0: "0 on
// hand" for a service would read as "we're out of it".
//
// The accounts each item posts to are a first-class column, not a detail-page
// footnote — an item IS a posting rule with a name on it, and hiding that is
// how a chart of accounts quietly drifts from what the books actually do.
//
// Row click opens that item's own page (#item/:id), where its movement
// history lives. Deactivate rather than delete: items have no delete route at
// all, on purpose (see server/src/items.ts's header).

import { useMemo, useState } from "react";
import { useCompany } from "../lib/company";
import { useApiGet } from "../lib/useApiGet";
import { apiPatch, ApiError } from "../lib/api";
import { fmtMoney } from "../lib/money";
import { ErrorBanner } from "../components/ErrorBanner";
import type { NavTarget } from "../lib/useHashView";
import { ItemModal } from "./ItemModal";
import { itemsChanged } from "./useItemsPresence";
import {
  ITEM_KINDS, KIND_HEADINGS, KIND_SECTION_NOTES,
  fmtQuantity, fmtUnitCost, type ApiItem, type ItemKind,
} from "./itemTypes";

interface ItemsResponse {
  items: ApiItem[];
}

export function ItemsScreen({ navigate }: { navigate: (target: NavTarget) => void }) {
  const { selectedCompanyId } = useCompany();
  // The full list including inactive rows is fetched regardless and filtered
  // client-side, so flipping the toggle is instant and a just-deactivated row
  // doesn't vanish-then-reappear oddly (ChartScreen's identical rationale).
  const { data, loading, error, retry } = useApiGet<ItemsResponse>(
    "/api/c/items?includeInactive=1",
    selectedCompanyId,
  );

  const [showInactive, setShowInactive] = useState(false);
  const [modal, setModal] = useState<{ itemId?: string } | null>(null);
  const [busyId, setBusyId] = useState<string | null>(null);
  const [rowError, setRowError] = useState<{ id: string; message: string } | null>(null);

  const items = useMemo(() => data?.items ?? [], [data]);
  const visible = useMemo(
    () => (showInactive ? items : items.filter((i) => i.active)),
    [items, showInactive],
  );
  const inactiveCount = items.filter((i) => !i.active).length;

  async function setActive(item: ApiItem, active: boolean) {
    if (!selectedCompanyId || busyId !== null) return;
    setBusyId(item.id);
    setRowError(null);
    try {
      // PATCH replaces the whole editable set (server/src/items.ts), so an
      // activate/deactivate has to resend the item as it stands.
      await apiPatch(
        `/api/c/items/${item.id}`,
        {
          name: item.name,
          kind: item.kind,
          active,
          ...(item.sku !== null ? { sku: item.sku } : {}),
          ...(item.description !== null ? { description: item.description } : {}),
          ...(item.salesPrice !== null ? { salesPrice: item.salesPrice } : {}),
          ...(item.purchaseCost !== null ? { purchaseCost: item.purchaseCost } : {}),
          ...(item.incomeAccountId !== null ? { incomeAccountId: item.incomeAccountId } : {}),
          ...(item.cogsAccountId !== null ? { cogsAccountId: item.cogsAccountId } : {}),
          ...(item.inventoryAccountId !== null ? { inventoryAccountId: item.inventoryAccountId } : {}),
        },
        selectedCompanyId,
      );
      // Turning the last stocked item off (or back on) changes whether this
      // company has stock at all, which the sidebar's Builds / Stock
      // adjustments entries are gated on (Inventory Task 5).
      itemsChanged(selectedCompanyId);
      retry();
    } catch (err) {
      setRowError({
        id: item.id,
        message: err instanceof ApiError ? err.message : "Couldn't change that item. Try again.",
      });
    } finally {
      setBusyId(null);
    }
  }

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

      {!selectedCompanyId ? (
        <p className="msg-inline">Choose a company from the sidebar to see its items.</p>
      ) : (
        <>
          <p className="sub" style={{ marginBottom: 14 }}>
            Everything this company buys and sells, and where each one posts. Click an item to see
            its history.
          </p>

          <div className="report-toolbar chart-toolbar">
            <label className="register-toggle">
              <input
                type="checkbox"
                checked={showInactive}
                onChange={(ev) => setShowInactive(ev.target.checked)}
              />
              Show inactive{inactiveCount > 0 ? ` (${inactiveCount})` : ""}
            </label>
            <div className="register-toolbar-end">
              <button type="button" className="btn" onClick={() => setModal({})}>
                New item
              </button>
            </div>
          </div>

          {error && <ErrorBanner message={error} onRetry={retry} />}
          {loading && !data && <p className="msg-inline">Loading your items&hellip;</p>}
          {data && items.length === 0 && (
            <div className="empty-state">
              No items yet. Add one for anything you buy or sell — a part you keep on the shelf, a
              finished product, or a service you bill for.
            </div>
          )}
          {data && items.length > 0 && visible.length === 0 && (
            <div className="empty-state">
              Every item here is turned off. Tick &ldquo;Show inactive&rdquo; to see them.
            </div>
          )}

          {ITEM_KINDS.map((kind) => {
            const rows = visible.filter((i) => i.kind === kind);
            if (rows.length === 0) return null;
            return (
              <ItemSection
                key={kind}
                kind={kind}
                rows={rows}
                busyId={busyId}
                rowError={rowError}
                onOpen={(item) => navigate({ view: "item", id: item.id })}
                onEdit={(item) => setModal({ itemId: item.id })}
                onSetActive={(item, active) => void setActive(item, active)}
              />
            );
          })}

          {modal !== null && (
            <ItemModal
              companyId={selectedCompanyId}
              itemId={modal.itemId}
              onClose={() => setModal(null)}
              onSaved={() => {
                setModal(null);
                retry();
              }}
            />
          )}
        </>
      )}
    </div>
  );
}

function ItemSection({
  kind,
  rows,
  busyId,
  rowError,
  onOpen,
  onEdit,
  onSetActive,
}: {
  kind: ItemKind;
  rows: ApiItem[];
  busyId: string | null;
  rowError: { id: string; message: string } | null;
  onOpen: (item: ApiItem) => void;
  onEdit: (item: ApiItem) => void;
  onSetActive: (item: ApiItem, active: boolean) => void;
}) {
  const stocked = kind === "inventory";
  return (
    <section className="chart-type-section">
      <h2>{KIND_HEADINGS[kind]}</h2>
      <p className="sub" style={{ marginTop: -6 }}>{KIND_SECTION_NOTES[kind]}</p>
      <div className="card">
        <table>
          <thead>
            <tr>
              <th>Name</th>
              <th className="num">On hand</th>
              <th className="num">Average cost</th>
              <th className="num">Value</th>
              <th>Posts to</th>
              <th aria-label="Actions" />
            </tr>
          </thead>
          <tbody>
            {rows.map((item) => (
              <ItemRow
                key={item.id}
                item={item}
                stocked={stocked}
                busy={busyId === item.id}
                error={rowError?.id === item.id ? rowError.message : null}
                onOpen={() => onOpen(item)}
                onEdit={() => onEdit(item)}
                onSetActive={onSetActive}
              />
            ))}
          </tbody>
        </table>
      </div>
    </section>
  );
}

function ItemRow({
  item,
  stocked,
  busy,
  error,
  onOpen,
  onEdit,
  onSetActive,
}: {
  item: ApiItem;
  stocked: boolean;
  busy: boolean;
  error: string | null;
  onOpen: () => void;
  onEdit: () => void;
  onSetActive: (item: ApiItem, active: boolean) => void;
}) {
  return (
    <>
      <tr
        className="chart-account-row"
        style={{ cursor: "pointer", opacity: item.active ? 1 : 0.55 }}
        onClick={onOpen}
      >
        <td>
          {item.name}
          {!item.active && <span className="chip chip-muted" style={{ marginLeft: 8 }}>Inactive</span>}
          {item.sku && <div className="register-memo-sub">SKU {item.sku}</div>}
        </td>
        {/* An em dash, never 0: a service has no quantity to be out of. */}
        <td className="num">{stocked && item.qtyOnHand !== null ? fmtQuantity(item.qtyOnHand) : "—"}</td>
        <td className="num">{stocked && item.averageCost !== null ? fmtUnitCost(item.averageCost) : "—"}</td>
        <td className="num">{stocked && item.value !== null ? fmtMoney(item.value) : "—"}</td>
        <td className="item-accounts">
          <ItemAccounts item={item} />
        </td>
        <td
          className="chart-actions"
          style={{ whiteSpace: "nowrap" }}
          onClick={(ev) => ev.stopPropagation()}
        >
          <button type="button" className="acct-link" disabled={busy} onClick={onEdit}>
            Edit
          </button>{" "}
          <button
            type="button"
            className="acct-link"
            disabled={busy}
            onClick={() => onSetActive(item, !item.active)}
          >
            {busy ? "Working…" : item.active ? "Deactivate" : "Reactivate"}
          </button>
        </td>
      </tr>
      {error && (
        <tr>
          <td colSpan={6} className="field-error">{error}</td>
        </tr>
      )}
    </>
  );
}

/** The accounts an item posts to, labelled in standard terms so an accountant
 *  can read the posting rule straight off the row (CLAUDE.md §9 as amended). */
export function ItemAccounts({ item }: { item: ApiItem }) {
  const lines: { label: string; name: string | null }[] = [
    { label: "Income", name: item.incomeAccountName },
    { label: "Cost", name: item.cogsAccountName },
    { label: "Inventory", name: item.inventoryAccountName },
  ].filter((l) => l.name !== null);
  if (lines.length === 0) return <span className="sub">Nothing set yet</span>;
  return (
    <>
      {lines.map((l) => (
        <div key={l.label} className="item-account-line">
          <span className="item-account-label">{l.label}</span> {l.name}
        </div>
      ))}
    </>
  );
}
