// ---------------------------------------------------------------------------
// settings / AdminPane.tsx — THE LOOPABLE ADMIN PLANE (wave 19, owner item 13 /
// R3+R4, contract C2).
//
// The one surface in this product that looks ACROSS tenants: who our customers
// are, how many people use each workspace, what they have built in it, whether
// their data sources are alive, and what the automation fleet costs.
//
// SELF-CONTAINED BY CONTRACT (C2). It fetches its own data via
// `platformAdminApi` and takes ONE prop, so mounting it is a rail entry plus a
// line in the pane switch — `SettingsModal.tsx` is another session's file this
// wave and must not have to learn anything about this pane's data.
//
// ⚠ THE PROP IS TYPED STRUCTURALLY, not as `SessionUser` from `../shell/session`.
// The mount site passes the shell's session user (a wider object), which
// satisfies this shape by structural typing — and this pane therefore imports
// nothing from the shell, which is what the wave's cross-fence rule asks for
// while `session.ts` is open on another desk.
//
// ⛔ EVERY NUMBER HERE IS THE SERVER'S, AND EVERY NUMBER DRILLS. Clicking a count
// opens the rows it was computed from — the same collector, projected twice
// ([[no-unverifiable-aggregates]]). A count that could not be READ renders as an
// em dash with the reason, never as a zero: "this customer has no databases" and
// "we could not look" are different facts and the pane refuses to conflate them.
//
// DESIGN: sentence-case micro-labels (R6 — no caps in app chrome), short noun
// headers, tabular figures right-aligned, hairlines not shadows, tokens only,
// no emojis (DESIGN.md §2–§4).
// ---------------------------------------------------------------------------

import { useCallback, useEffect, useState } from "react";
import type {
  AutomationRow,
  ConnectorRow,
  DatabaseRow,
  FleetCost,
  Overview,
  PlatformUser,
  ReleasesPayload,
} from "./platformAdminApi";
import {
  getAutomations,
  getAws,
  getReleases,
  getConnectors,
  getDatabases,
  getOverview,
  getUsers,
} from "./platformAdminApi";

// --- formatting -------------------------------------------------------------

const int = (n: number) => n.toLocaleString();

/** A count that may be unknown. `null` is NOT zero — see the api module's header. */
function Count({ n, why }: { n: number | null | undefined; why?: string }) {
  if (n === null || n === undefined)
    return (
      <span className="padmin-unknown" title={why || "This could not be read just now"}>
        —
      </span>
    );
  return <>{int(n)}</>;
}

/** "3 days ago" for a stamp, "never" for an absent one. Never a fabricated date.
 *
 *  ⛔ ONLY FOR OFFSET-BEARING STAMPS. `Date.parse` reads a stamp with no zone as
 *  BROWSER-LOCAL, so running a container-local timestamp through this reports the
 *  viewer's UTC offset as elapsed time — hours of error in a column people scan,
 *  and a future-dated stamp that renders as "just now" indefinitely. Everything
 *  this pane passes here (`last_login`, `last_active`, `generatedAt`) is written
 *  with an explicit offset; the automation engine's `lastRunAt` is NOT, so it is
 *  rendered verbatim instead. */
function ago(stamp: string): string {
  if (!stamp) return "never";
  const t = Date.parse(stamp.includes("T") ? stamp : stamp.replace(" ", "T"));
  if (Number.isNaN(t)) return stamp;
  const mins = Math.floor((Date.now() - t) / 60000);
  if (mins < 2) return "just now";
  if (mins < 60) return `${mins} minutes ago`;
  const hours = Math.floor(mins / 60);
  if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
  const days = Math.floor(hours / 24);
  if (days < 60) return `${days} day${days === 1 ? "" : "s"} ago`;
  return `${Math.floor(days / 30)} months ago`;
}

type DrillKind = "users" | "databases" | "connectors" | "automations";

const DRILL_NOUN: Record<DrillKind, string> = {
  users: "People",
  databases: "Databases",
  connectors: "Data sources",
  automations: "Automations",
};

/** The loading shape — a skeleton, not a spinner (DESIGN.md §4). */
function Skeleton({ rows = 3 }: { rows?: number }) {
  return (
    <div className="padmin-skel" aria-hidden="true">
      {Array.from({ length: rows }, (_, i) => (
        <div key={i} className="padmin-skel-row" />
      ))}
    </div>
  );
}

// --- the pane ---------------------------------------------------------------

export function AdminPane({ user }: { user: { username: string; name: string; role: string } }) {
  const [ov, setOv] = useState<Overview | null>(null);
  const [err, setErr] = useState("");
  const [fleet, setFleet] = useState<{ rows: AutomationRow[]; cost: FleetCost } | null>(null);

  const [drill, setDrill] = useState<{ kind: DrillKind; tenant: string } | null>(null);
  const [drillRows, setDrillRows] = useState<unknown[] | null>(null);
  const [drillNote, setDrillNote] = useState("");

  const [aws, setAws] = useState<{ text: string; note: string; available: boolean } | null>(null);
  // Wave 20 (R6): the releases panel. Loaded with the overview rather than on demand — it is
  // two small Hub reads and the first question an operator opens this pane to answer.
  const [rel, setRel] = useState<ReleasesPayload | null>(null);
  const [awsBusy, setAwsBusy] = useState(false);

  const load = useCallback(() => {
    setOv(null);
    void getOverview().then((r) => {
      if (r.ok) {
        setOv(r.data);
        setErr("");
      } else setErr(r.message);
    });
    void getAutomations().then((r) => {
      if (r.ok) setFleet({ rows: r.data.automations, cost: r.data.cost });
    });
    void getReleases().then((r) => {
      if (r.ok) setRel(r.data);
    });
  }, []);
  useEffect(load, [load]);

  const openDrill = useCallback((kind: DrillKind, tenant: string) => {
    setDrill({ kind, tenant });
    setDrillRows(null);
    setDrillNote("");
    const fetcher =
      kind === "users"
        ? getUsers
        : kind === "databases"
          ? getDatabases
          : kind === "connectors"
            ? getConnectors
            : getAutomations;
    void fetcher(tenant || undefined).then((r) => {
      if (!r.ok) {
        setDrillRows([]);
        setDrillNote(r.message);
        return;
      }
      const d = r.data as Record<string, unknown>;
      setDrillRows((d[kind] as unknown[]) ?? []);
      // The server's own honest note travels with the rows rather than being
      // re-invented here: it is the one that knows WHY a tenant is missing.
      const errors = (d.errors as Record<string, string>) ?? {};
      const words = Object.entries(errors).map(([t, e]) => `${t}: ${e}`);
      setDrillNote(
        [typeof d.stampsNote === "string" ? d.stampsNote : "", ...words]
          .filter(Boolean)
          .join(" · ")
      );
    });
  }, []);

  const loadAws = useCallback(() => {
    setAwsBusy(true);
    void getAws(7).then((r) => {
      setAwsBusy(false);
      if (r.ok) setAws(r.data.report);
      else setAws({ text: "", note: r.message, available: false });
    });
  }, []);

  return (
    <div className="set-pane">
      <h3 className="set-h">Loopable admin</h3>
      <p className="set-help set-pane-intro">
        Every workspace on the platform, signed in as {user.name}. Counts open the rows they were
        computed from; anything that could not be read shows a dash and says why, rather than a
        zero.
      </p>
      {err ? <p className="set-error">{err}</p> : null}

      {!ov ? (
        <Skeleton rows={4} />
      ) : (
        <>
          <div className="padmin-totals">
            <Total label="Workspaces" value={int(ov.totals.tenants)} />
            <Total label="People" value={int(ov.totals.users)} />
            <Total label="Databases" value={int(ov.totals.databases)} />
            <Total label="Records" value={int(ov.totals.rows)} />
            <Total
              label="Automation cost"
              value={fleet ? `$${fleet.cost.usd.toFixed(2)}` : "—"}
              hint={fleet?.cost.basis}
            />
          </div>
          {ov.totals.unknownTenants > 0 ? (
            <p className="set-help padmin-caveat">
              {ov.totals.unknownTenants} workspace
              {ov.totals.unknownTenants === 1 ? "" : "s"} could not be read, so the totals above
              exclude {ov.totals.unknownTenants === 1 ? "it" : "them"}.
            </p>
          ) : null}

          <div className="padmin-tablewrap">
            <table className="padmin-table">
              <thead>
                <tr>
                  <th>Workspace</th>
                  <th>Storage</th>
                  <th className="padmin-num">People</th>
                  <th className="padmin-num">Databases</th>
                  <th className="padmin-num">Records</th>
                  <th className="padmin-num">Sources</th>
                  <th className="padmin-num">Automations</th>
                </tr>
              </thead>
              <tbody>
                {ov.tenants.map((t) => (
                  <tr key={t.slug}>
                    <td>
                      <span className="padmin-name">{t.name}</span>
                      <span className="padmin-meta">
                        {t.slug}
                        {t.status !== "active" ? ` · ${t.status}` : ""}
                        {t.errors && t.errors.length ? ` · ${t.errors[0]}` : ""}
                      </span>
                    </td>
                    <td>
                      <span className="padmin-meta">
                        {t.storeRepo && t.storeRepo.includes("/")
                          ? "own repository"
                          : t.storePrefix
                            ? "shared repository"
                            : "tenant #0 repository"}
                        {t.keychainLocked ? " · keychain locked" : ""}
                      </span>
                    </td>
                    <DrillCell
                      n={t.users}
                      onOpen={() => openDrill("users", t.slug)}
                      title={`${t.admins} administrator${t.admins === 1 ? "" : "s"}`}
                    />
                    <DrillCell
                      n={t.databases}
                      onOpen={() => openDrill("databases", t.slug)}
                      why={t.errors?.[0]}
                    />
                    <DrillCell
                      n={t.rows}
                      onOpen={() => openDrill("databases", t.slug)}
                      why={t.errors?.[0]}
                    />
                    <DrillCell
                      n={t.connectors}
                      onOpen={() => openDrill("connectors", t.slug)}
                      why={t.errors?.[0]}
                      title={
                        t.connectorsPaused ? `${t.connectorsPaused} paused` : "none paused"
                      }
                    />
                    <DrillCell
                      n={t.automations}
                      onOpen={() => openDrill("automations", t.slug)}
                      why={t.errors?.[0]}
                      title={
                        t.automationsEnabled != null
                          ? `${t.automationsEnabled} scheduled`
                          : undefined
                      }
                    />
                  </tr>
                ))}
              </tbody>
            </table>
          </div>

          <p className="set-help padmin-caveat">
            Read {ago(ov.generatedAt)} in {ov.tookMs} ms.{" "}
            {ov.totals.orphanUsers > 0
              ? `${ov.totals.orphanUsers} account${
                  ov.totals.orphanUsers === 1 ? "" : "s"
                } belong to a workspace this deployment no longer knows. `
              : ""}
            <button type="button" className="padmin-link" onClick={load}>
              Refresh
            </button>
          </p>

          {drill ? (
            <section className="padmin-drill">
              <div className="padmin-drill-head">
                <h4 className="padmin-h4">
                  {DRILL_NOUN[drill.kind]}
                  {drill.tenant ? ` — ${drill.tenant}` : ""}
                </h4>
                <button type="button" className="padmin-link" onClick={() => setDrill(null)}>
                  Close
                </button>
              </div>
              {drillRows === null ? (
                <Skeleton rows={2} />
              ) : drillRows.length === 0 ? (
                <p className="set-help">Nothing here yet.</p>
              ) : (
                <DrillTable kind={drill.kind} rows={drillRows} />
              )}
              {drillNote ? <p className="set-help padmin-caveat">{drillNote}</p> : null}
            </section>
          ) : null}

          {fleet ? (
            <section className="padmin-block">
              {/* Named for what it SHOWS. The fleet's rows live one level down,
                  behind the Automations count — this section is the cost and the
                  reasoning behind it, so calling it "fleet" would promise a
                  table that is deliberately not here. */}
              <h4 className="padmin-h4">Automation cost</h4>
              <p className="set-help">
                {fleet.rows.filter((a) => a.enabled).length} scheduled of {fleet.rows.length}
                {fleet.cost.fleetRunsPerDay
                  ? `, about ${fleet.cost.fleetRunsPerDay} runs a day`
                  : ""}
                {fleet.cost.unknownCadence
                  ? ` (${fleet.cost.unknownCadence} on a custom cadence, not counted)`
                  : ""}
                . {fleet.cost.basis}
              </p>
            </section>
          ) : null}

          {/* ── Wave 20 (owner item 12 / R6): what is running where ──────────────────
              READ-ONLY BY RULING. The operator sees both environments and every cut
              release; moving one is a CLI command, printed below rather than wired to
              a button, so no browser session can rewrite production. */}
          <section className="padmin-block">
            <h4 className="padmin-h4">Releases</h4>
            {!rel ? (
              <Skeleton rows={2} />
            ) : (
              <>
                <table className="padmin-table">
                  <thead>
                    <tr>
                      <th>Environment</th>
                      <th>Version</th>
                      <th>Stage</th>
                      <th>Space</th>
                    </tr>
                  </thead>
                  <tbody>
                    {rel.environments.map((e) => (
                      <tr key={e.env}>
                        <td>{e.env === "live" ? "Live (pinned)" : "Staging (follows the tree)"}</td>
                        {/* ⚠ An unreadable version is a NOTE, never a blank. A blank cell in a
                            column headed "Version" reads as "nothing is deployed", which about a
                            production environment is the worst available way to be wrong. */}
                        <td>{e.version || <span className="set-help">{e.note || "unknown"}</span>}</td>
                        <td>{e.stage || "—"}</td>
                        {/* The Space ID as TEXT, not a link: the server deliberately builds no
                            URL (portability C1 — a host literal in runtime code hard-codes the
                            current host into a process designed to move). */}
                        <td>{e.space}</td>
                      </tr>
                    ))}
                  </tbody>
                </table>
                {rel.releases.length ? (
                  <table className="padmin-table">
                    <thead>
                      <tr>
                        <th>Release</th>
                        <th>Commit</th>
                        <th>Cut</th>
                        <th>What shipped</th>
                      </tr>
                    </thead>
                    <tbody>
                      {rel.releases.map((r) => (
                        <tr key={r.version}>
                          <td>{r.version}</td>
                          <td>{r.sha}</td>
                          <td>{r.date}</td>
                          <td>{r.subject}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                ) : (
                  <p className="set-help">
                    No tagged releases were readable from either Space.
                  </p>
                )}
                <p className="set-help">
                  To move Live to another version, or roll it back, run:{" "}
                  <code>{rel.promote}</code>
                </p>
              </>
            )}
          </section>

          <section className="padmin-block">
            <h4 className="padmin-h4">AWS cron</h4>
            {!aws ? (
              <p className="set-help">
                The external tick that wakes this app on schedule. Reading its usage runs a live
                CloudWatch query and takes a few seconds.{" "}
                <button
                  type="button"
                  className="padmin-link"
                  onClick={loadAws}
                  disabled={awsBusy}
                >
                  {awsBusy ? "Reading…" : "Check usage"}
                </button>
              </p>
            ) : aws.available ? (
              <pre className="padmin-pre">{aws.text}</pre>
            ) : (
              <p className="set-help">{aws.note}</p>
            )}
          </section>
        </>
      )}
    </div>
  );
}

function Total({ label, value, hint }: { label: string; value: string; hint?: string }) {
  // A tooltip nobody can see is a tooltip nobody reads: the label carries a
  // dotted underline exactly when there is something behind it.
  return (
    <div className={"padmin-total" + (hint ? " has-hint" : "")} title={hint || undefined}>
      <span className="padmin-total-label">{label}</span>
      <span className="padmin-total-value">{value}</span>
    </div>
  );
}

/** A numeric cell that opens its own rows. Unknown counts are not clickable —
 *  there is nothing to drill INTO when the read failed, and a button that opens
 *  an empty table would read as "none". */
function DrillCell({
  n,
  onOpen,
  why,
  title,
}: {
  n: number | null | undefined;
  onOpen: () => void;
  why?: string;
  title?: string;
}) {
  if (n === null || n === undefined)
    return (
      <td className="padmin-num">
        <Count n={n} why={why} />
      </td>
    );
  return (
    <td className="padmin-num">
      <button type="button" className="padmin-drill-btn" onClick={onOpen} title={title}>
        {int(n)}
      </button>
    </td>
  );
}

function DrillTable({ kind, rows }: { kind: DrillKind; rows: unknown[] }) {
  if (kind === "users") {
    const rs = rows as PlatformUser[];
    return (
      <div className="padmin-tablewrap">
        <table className="padmin-table">
          <thead>
            <tr>
              <th>Person</th>
              <th>Workspace</th>
              <th>Role</th>
              <th>Last sign-in</th>
              <th>Last active</th>
            </tr>
          </thead>
          <tbody>
            {rs.map((u) => (
              <tr key={`${u.tenant}/${u.username}`}>
                <td>
                  <span className="padmin-name">{u.name}</span>
                  <span className="padmin-meta">
                    {u.username}
                    {u.email ? ` · ${u.email}` : ""}
                    {u.active ? "" : " · deactivated"}
                  </span>
                </td>
                <td>{u.tenant}</td>
                <td>
                  {u.role}
                  {u.platformAdmin ? " · platform" : ""}
                </td>
                <td>{ago(u.lastLogin)}</td>
                <td>{ago(u.lastActive)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
  }
  if (kind === "databases") {
    const rs = rows as DatabaseRow[];
    return (
      <div className="padmin-tablewrap">
        <table className="padmin-table">
          <thead>
            <tr>
              <th>Database</th>
              <th>Workspace</th>
              <th>Built from</th>
              <th className="padmin-num">Fields</th>
              <th className="padmin-num">Records</th>
            </tr>
          </thead>
          <tbody>
            {rs.map((d) => (
              <tr key={`${d.tenant}/${d.key}`}>
                <td>
                  <span className="padmin-name">{d.label}</span>
                  <span className="padmin-meta">
                    {d.key}
                    {d.createdBy ? ` · ${d.createdBy}` : ""}
                  </span>
                </td>
                <td>{d.tenant}</td>
                <td>{d.source}</td>
                <td className="padmin-num">{int(d.fields)}</td>
                <td className="padmin-num">{int(d.rowCount)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
  }
  if (kind === "connectors") {
    const rs = rows as ConnectorRow[];
    return (
      <div className="padmin-tablewrap">
        <table className="padmin-table">
          <thead>
            <tr>
              <th>Source</th>
              <th>Workspace</th>
              <th>Kind</th>
              <th>Status</th>
            </tr>
          </thead>
          <tbody>
            {rs.map((c) => (
              <tr key={`${c.tenant}/${c.key}`}>
                <td>
                  <span className="padmin-name">{c.label}</span>
                  <span className="padmin-meta">
                    {c.source === "env" ? "environment credentials" : "keychain"}
                  </span>
                </td>
                <td>{c.tenant}</td>
                <td>{c.type}</td>
                <td>
                  <span
                    className={
                      "padmin-dot " +
                      (c.paused ? "is-paused" : c.active ? "is-live" : "is-idle")
                    }
                  />
                  {c.paused ? "paused" : c.active ? "serving" : "stored"}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
  }
  const rs = rows as AutomationRow[];
  return (
    <div className="padmin-tablewrap">
      <table className="padmin-table">
        <thead>
          <tr>
            <th>Automation</th>
            <th>Workspace</th>
            <th>Schedule</th>
            <th className="padmin-num">Runs a day</th>
            <th>Last run</th>
          </tr>
        </thead>
        <tbody>
          {rs.map((a) => (
            <tr key={`${a.tenant}/${a.id}`}>
              <td>
                <span className="padmin-name">{a.name}</span>
                <span className="padmin-meta">
                  {a.kind}
                  {a.failedRetained
                    ? ` · ${a.failedRetained} of the last ${a.runsRetained} runs failed`
                    : ""}
                </span>
              </td>
              <td>{a.tenant}</td>
              <td>{a.enabled ? a.cron || "scheduled" : "paused"}</td>
              <td className="padmin-num">
                {a.enabled ? (a.runsPerDay === null ? "custom" : a.runsPerDay) : "—"}
              </td>
              <td>
                <span
                  className={
                    "padmin-dot " +
                    (a.state === "error" ? "is-error" : a.state === "ok" ? "is-live" : "is-idle")
                  }
                />
                {/* Verbatim, not relative: the engine writes this stamp with no
                    zone (`automation_engine._iso()` is naive local-to-container),
                    so "x hours ago" would be wrong by the viewer's offset. */}
                {a.lastRunAt ? a.lastRunAt.replace("T", " ") : "never"}
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

export default AdminPane;
