// Mission Control — Command OS's standalone fleet-operations screen.
//
// Deliberately NOT rendered inside <AdminShell> (see admin-shell.jsx) — the
// customer explicitly confirmed this is "a new full page screen... a
// complete separate mission control area" with its own top bar (asset
// picker / search / notifications / user) and its own left sidebar
// (Live Map, Fleet Health, Power, Connectivity, Cameras, Alerts,
// Incidents — the full COMMAND OS sub-tree). Reached from the admin
// sidebar's "Mission Control" nav item (see MC_ENTRY_NAV_ID in
// admin-shell.jsx) via onNavigate("mission-control").
//
// v1 scope (confirmed with the customer — "focus on schema + Mission
// Control UI", full COMMAND OS tree "we can revisit" later): only Fleet
// Health (asset index + per-asset detail matching the mock-up) is fully
// built. Every other nav item renders a "Coming soon" placeholder using
// the exact same pattern UM_ComingSoon already established, so the wider
// nav tree is visibly present (not forgotten) without pretending it's
// built.
//
// Data model per asset (GET /api/admin/mission-control/assets/:id):
//   unit       — asset_units row (+ product/company names)
//   telemetry  — unit_telemetry demo-data fallback (lib/telemetry.ts),
//                ALWAYS present, used for fields with no vendor
//                equivalent yet (Mains Power connected?, Controls
//                toggles, Live Power Draw, Internet Router fallback)
//   victron    — { installation, devices[] } or null if not linked yet.
//                devices[].device_role: shunt | mppt_1 | mppt_2 | mains_charger
//                devices[].cached_fields_json: JSON string of VRM code -> value
//   ajax       — { hub, devices[] } or null if not linked yet.
//   teltonika  — a single teltonika_devices row, or null if not linked yet.
//
// Real vendor data always wins over demo telemetry when present — see
// MC_pick() below. Cards for data that has NO demo-telemetry equivalent
// (Energy Storage, System Status) show an honest "not linked yet" empty
// state instead of inventing numbers, per the customer's own framing:
// "we would need to work on every single data string... that connects
// to either victron, ajax or teltonika."

const MC_NAV_ITEMS = [
  { id: "fleet-health", label: "Fleet Health" },
  { id: "live-map", label: "Live Map" },
  { id: "power", label: "Power" },
  { id: "connectivity", label: "Connectivity" },
  { id: "cameras", label: "Cameras" },
  { id: "alerts", label: "Alerts" },
  { id: "incidents", label: "Incidents" },
];

function MissionControlPage({ onNavigate }) {
  const [state, setState] = useState({ status: "loading", admin: null, fleet: [] });
  const [section, setSection] = useState("fleet-health");
  const [selectedAssetId, setSelectedAssetId] = useState(null);
  const [search, setSearch] = useState("");

  const loadFleet = () => {
    Promise.all([
      fetch("/api/admin/me", { credentials: "same-origin" }).then((r) => (r.ok ? r.json() : Promise.reject())),
      fetch("/api/admin/mission-control/fleet", { credentials: "same-origin" }).then((r) => (r.ok ? r.json() : Promise.reject())),
    ])
      .then(([me, fleetRes]) => {
        setState({ status: "ready", admin: me.admin, fleet: fleetRes.fleet || [] });
      })
      .catch(() => onNavigate("admin-login"));
  };

  useEffect(loadFleet, []); // eslint-disable-line react-hooks/exhaustive-deps

  const handleSelectSection = (id) => {
    setSection(id);
    setSelectedAssetId(null);
  };

  const handleSelectAsset = (id) => {
    setSection("fleet-health");
    setSelectedAssetId(id);
  };

  let body;
  if (state.status === "loading") {
    body = <MC_EmptyNote>Loading fleet&hellip;</MC_EmptyNote>;
  } else if (section === "fleet-health" && selectedAssetId != null) {
    body = (
      <MC_AssetDetail
        assetId={selectedAssetId}
        onBack={() => setSelectedAssetId(null)}
      />
    );
  } else if (section === "fleet-health") {
    body = (
      <MC_FleetHealthIndex
        fleet={state.fleet}
        search={search}
        onSelectAsset={handleSelectAsset}
      />
    );
  } else {
    body = <MC_ComingSoon item={MC_NAV_ITEMS.find((n) => n.id === section)} />;
  }

  return (
    <MC_Shell
      admin={state.admin}
      section={section}
      onSelectSection={handleSelectSection}
      onNavigate={onNavigate}
      fleet={state.fleet}
      onSelectAsset={handleSelectAsset}
      search={search}
      onSearchChange={setSearch}
    >
      {body}
    </MC_Shell>
  );
}

// ─────────────────────────────── Shell ───────────────────────────────

function MC_Shell({ admin, section, onSelectSection, onNavigate, fleet, onSelectAsset, search, onSearchChange, children }) {
  const handleLogout = async () => {
    try { await fetch("/api/admin/logout", { method: "POST", credentials: "same-origin" }); }
    finally { onNavigate("home"); }
  };

  const filteredFleet = search
    ? fleet.filter((a) => (a.serial_number || "").toLowerCase().includes(search.toLowerCase()))
    : fleet;

  return (
    <section style={{ background: "#0B0C0E", color: "#fff", minHeight: "calc(100vh - 88px)", display: "flex", flexDirection: "column" }}>
      {/* Top bar */}
      <header
        data-testid="mission-control-topbar"
        style={{
          display: "flex", alignItems: "center", gap: 20,
          padding: "14px 28px", borderBottom: "1px solid rgba(255,255,255,0.1)",
          background: "#111318", flexWrap: "wrap",
        }}
      >
        <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
          <div style={{
            fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 15,
            letterSpacing: "0.08em", color: "#fff", textTransform: "uppercase",
          }}>Solo Command</div>
          <div style={{
            fontFamily: "var(--font-body)", fontSize: 10, letterSpacing: "0.28em",
            textTransform: "uppercase", color: "rgba(255,255,255,0.6)", fontWeight: 500,
          }}>Mission Control</div>
        </div>

        {/* Asset picker */}
        <select
          value=""
          onChange={(e) => { if (e.target.value) onSelectAsset(Number(e.target.value)); }}
          data-testid="mission-control-asset-picker"
          style={{
            background: "rgba(255,255,255,0.06)", border: "1px solid rgba(255,255,255,0.18)",
            color: "#fff", padding: "9px 12px", fontFamily: "var(--font-body)", fontSize: 12.5,
            minWidth: 200,
          }}
        >
          <option value="">Jump to asset&hellip;</option>
          {fleet.map((a) => (
            <option key={a.id} value={a.id}>{a.serial_number} — {a.product_name}</option>
          ))}
        </select>

        {/* Search */}
        <input
          type="text" value={search} onChange={(e) => onSearchChange(e.target.value)}
          placeholder="Search serial number&hellip;"
          data-testid="mission-control-search"
          style={{
            flex: "1 1 220px", minWidth: 180, background: "rgba(255,255,255,0.06)",
            border: "1px solid rgba(255,255,255,0.18)", color: "#fff",
            padding: "9px 12px", fontFamily: "var(--font-body)", fontSize: 12.5, outline: "none",
          }}
        />

        <div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 16 }}>
          <button
            type="button" title="Notifications" data-testid="mission-control-notifications"
            style={{
              background: "none", border: "1px solid rgba(255,255,255,0.2)", borderRadius: "50%",
              width: 34, height: 34, color: "rgba(255,255,255,0.8)", cursor: "pointer", fontSize: 14,
            }}
          >&#128276;</button>
          {admin && (
            <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
              <div style={{
                width: 30, height: 30, borderRadius: "50%", background: "#fff",
                display: "flex", alignItems: "center", justifyContent: "center",
                fontFamily: "var(--font-body)", fontSize: 12, fontWeight: 600, color: "#000",
              }}>{(admin.name || admin.email || "?").slice(0, 1).toUpperCase()}</div>
              <div style={{ fontFamily: "var(--font-body)", fontSize: 11.5, color: "rgba(255,255,255,0.6)" }}>{admin.email}</div>
            </div>
          )}
          <button
            type="button" onClick={() => onNavigate("admin-assets")} data-testid="mission-control-back-to-admin"
            style={{
              background: "none", border: "1px solid rgba(255,255,255,0.25)", color: "rgba(255,255,255,0.75)",
              cursor: "pointer", padding: "8px 14px", fontFamily: "var(--font-body)", fontSize: 10.5,
              fontWeight: 500, letterSpacing: "0.1em", textTransform: "uppercase",
            }}
          >&larr; Admin</button>
          <button
            type="button" onClick={handleLogout} data-testid="mission-control-logout"
            style={{
              background: "none", border: "1px solid rgba(255,255,255,0.25)", color: "rgba(255,255,255,0.75)",
              cursor: "pointer", padding: "8px 14px", fontFamily: "var(--font-body)", fontSize: 10.5,
              fontWeight: 500, letterSpacing: "0.1em", textTransform: "uppercase",
            }}
          >Sign out</button>
        </div>
      </header>

      <div style={{ display: "flex", flex: 1 }}>
        {/* Sidebar */}
        <aside
          data-testid="mission-control-sidebar"
          style={{
            width: 220, flex: "0 0 220px", borderRight: "1px solid rgba(255,255,255,0.1)",
            display: "flex", flexDirection: "column", padding: "22px 0",
          }}
        >
          <nav style={{ display: "flex", flexDirection: "column", gap: 2 }}>
            {MC_NAV_ITEMS.map((item) => {
              const active = section === item.id;
              return (
                <button
                  key={item.id} type="button" onClick={() => onSelectSection(item.id)}
                  data-testid={`mission-control-nav-${item.id}`}
                  style={{
                    background: active ? "rgba(255,255,255,0.06)" : "none",
                    border: "none",
                    borderLeft: `2px solid ${active ? "#fff" : "transparent"}`,
                    color: active ? "#fff" : "rgba(255,255,255,0.55)",
                    textAlign: "left", cursor: "pointer", padding: "12px 22px",
                    fontFamily: "var(--font-body)", fontSize: 12.5,
                    fontWeight: active ? 600 : 500, letterSpacing: "0.06em",
                  }}
                >{item.label}</button>
              );
            })}
          </nav>

          {/* Wider COMMAND OS tree — visible but deliberately inert in v1
              (customer: "let's make sure we don't forget the wide scope
              but we can revisit"). Keeping these listed, disabled, and
              clearly marked rather than omitted so the roadmap stays
              visible in the product itself, not just in docs. */}
          <div style={{ marginTop: 28, padding: "0 22px" }}>
            <div style={{
              fontFamily: "var(--font-body)", fontSize: 9.5, letterSpacing: "0.2em",
              textTransform: "uppercase", color: "rgba(255,255,255,0.3)", marginBottom: 10,
            }}>Command OS (roadmap)</div>
            {["Fleet", "Assets", "Manufacturing", "Deployments", "Maintenance", "Analytics", "Administration"].map((label) => (
              <div key={label} style={{
                fontFamily: "var(--font-body)", fontSize: 11.5, color: "rgba(255,255,255,0.25)",
                padding: "6px 0",
              }}>{label}</div>
            ))}
          </div>
        </aside>

        <div style={{ flex: 1, minWidth: 0, padding: "28px 32px 80px", overflowX: "auto" }}>
          {children}
        </div>
      </div>
    </section>
  );
}

// ───────────────────────────── Sync Health ─────────────────────────────
//
// Makes the previously-invisible "silent sync failure" bug visible
// (customer: "Silent sync failure on link... Want me to add a visible
// retry/error state?" -> "yes please"). Before this, linking a vendor
// device fired a fire-and-forget sync call (afterLink in
// admin-assets-page.jsx's LinkDevicesModal) with zero UI feedback either
// way -- exactly how unit 547's Victron installation sat silently
// unsynced for 4 real days. See migrations/0049_vendor_sync_health.sql's
// header comment for the full story.
//
// Reads the three GET .../unhealthy endpoints (added alongside this
// banner) and shows anything currently 'error' or 'never_synced'. A
// background health-check cron (routes/cron-sync-health.ts, run by an
// external scheduler every few minutes -- Cloudflare Cron Triggers
// aren't supported on this deploy path) retries the same things
// automatically, but this banner + its "Retry now" button gives an
// admin an immediate, visible way to see and fix a broken link without
// waiting for the next cron tick.
function MC_SyncHealthBanner({ onSelectAsset }) {
  const [state, setState] = useState({ status: "loading", items: [] });
  const [retrying, setRetrying] = useState(null);

  const load = () => {
    Promise.all([
      fetch("/api/admin/victron-installations/unhealthy", { credentials: "same-origin" }).then((r) => (r.ok ? r.json() : { installations: [] })),
      fetch("/api/admin/ajax-hubs/unhealthy", { credentials: "same-origin" }).then((r) => (r.ok ? r.json() : { hubs: [] })),
      fetch("/api/admin/teltonika/unhealthy", { credentials: "same-origin" }).then((r) => (r.ok ? r.json() : { companies: [] })),
    ])
      .then(([v, a, t]) => {
        const items = [
          ...(v.installations || []).map((row) => ({
            vendor: "Victron", key: `victron-${row.id}`, id: row.id, label: row.serial_number || row.installation_name,
            assetUnitId: row.asset_unit_id, status: row.sync_status, error: row.last_sync_error,
            retryUrl: `/api/admin/victron-installations/${row.id}/sync-devices`,
          })),
          ...(a.hubs || []).map((row) => ({
            vendor: "Ajax", key: `ajax-${row.id}`, id: row.id, label: row.serial_number || row.name,
            assetUnitId: row.asset_unit_id, status: row.sync_status, error: row.last_sync_error,
            retryUrl: `/api/admin/ajax-hubs/${row.id}/sync-devices`,
          })),
          ...(t.companies || []).map((row) => ({
            vendor: "Teltonika", key: `teltonika-${row.company_id}`, id: row.company_id, label: `${row.company_name} (${row.unhealthy_device_count} device${row.unhealthy_device_count === 1 ? "" : "s"})`,
            assetUnitId: null, status: "error", error: row.last_sync_error,
            retryUrl: `/api/admin/companies/${row.company_id}/teltonika-sync`,
          })),
        ];
        setState({ status: "ready", items });
      })
      .catch(() => setState({ status: "ready", items: [] }));
  };

  useEffect(load, []); // eslint-disable-line react-hooks/exhaustive-deps

  if (state.status === "loading" || state.items.length === 0) return null;

  const handleRetry = (item) => {
    setRetrying(item.key);
    fetch(item.retryUrl, { method: "POST", credentials: "same-origin" })
      .then(() => load())
      .finally(() => setRetrying(null));
  };

  return (
    <div
      data-testid="mission-control-sync-health-banner"
      style={{
        background: "rgba(255,80,80,0.08)", border: "1px solid rgba(255,80,80,0.35)",
        borderRadius: 4, padding: "14px 18px", marginBottom: 20,
        display: "flex", flexDirection: "column", gap: 10,
      }}
    >
      <div style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 13, textTransform: "uppercase", color: "#ff8080" }}>
        <i className="fas fa-triangle-exclamation" style={{ marginRight: 8 }} />
        Sync Health &mdash; {state.items.length} link{state.items.length === 1 ? "" : "s"} need attention
      </div>
      {state.items.map((item) => (
        <div key={item.key} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, fontFamily: "var(--font-body)", fontSize: 12.5 }}>
          <div style={{ color: "rgba(255,255,255,0.85)" }}>
            <span style={{ fontWeight: 600 }}>{item.vendor}</span>
            {" · "}
            {item.assetUnitId ? (
              <button
                type="button" onClick={() => onSelectAsset(item.assetUnitId)}
                style={{ background: "none", border: "none", color: "#fff", textDecoration: "underline", cursor: "pointer", padding: 0, font: "inherit" }}
              >{item.label}</button>
            ) : item.label}
            {item.status === "never_synced" ? (
              <span style={{ color: "rgba(255,255,255,0.5)" }}> &mdash; never synced</span>
            ) : item.error ? (
              <span style={{ color: "rgba(255,255,255,0.5)" }}> &mdash; {item.error}</span>
            ) : null}
          </div>
          <button
            type="button" onClick={() => handleRetry(item)} disabled={retrying === item.key}
            data-testid={`mission-control-sync-health-retry-${item.key}`}
            style={{
              background: "#fff", color: "#000", border: "none", borderRadius: 3, padding: "5px 12px",
              fontFamily: "var(--font-body)", fontSize: 11, fontWeight: 600, textTransform: "uppercase",
              cursor: retrying === item.key ? "default" : "pointer", opacity: retrying === item.key ? 0.6 : 1, whiteSpace: "nowrap",
            }}
          >{retrying === item.key ? "Retrying…" : "Retry now"}</button>
        </div>
      ))}
    </div>
  );
}

// ───────────────────────────── Fleet Health ─────────────────────────────

function MC_FleetHealthIndex({ fleet, search, onSelectAsset }) {
  const filtered = search
    ? fleet.filter((a) => (a.serial_number || "").toLowerCase().includes(search.toLowerCase()))
    : fleet;

  if (fleet.length === 0) {
    return <MC_EmptyNote>No assets found yet.</MC_EmptyNote>;
  }

  return (
    <div>
      <div style={{
        fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 22,
        textTransform: "uppercase", marginBottom: 20, color: "#fff",
      }}>Fleet Health</div>

      <MC_SyncHealthBanner onSelectAsset={onSelectAsset} />

      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))", gap: 16 }}>
        {filtered.map((a) => (
          <button
            key={a.id} type="button" onClick={() => onSelectAsset(a.id)}
            data-testid={`mission-control-fleet-card-${a.id}`}
            style={{
              textAlign: "left", cursor: "pointer", background: "#15171C",
              border: "1px solid rgba(255,255,255,0.1)", padding: "18px 18px 16px",
              color: "#fff", display: "flex", flexDirection: "column", gap: 10,
            }}
          >
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
              <div>
                <div style={{ fontFamily: "monospace", fontSize: 14, fontWeight: 600 }}>{a.serial_number}</div>
                <div style={{ fontFamily: "var(--font-body)", fontSize: 11.5, color: "rgba(255,255,255,0.5)", marginTop: 2 }}>{a.product_name}</div>
              </div>
              <MC_StatusPill status={a.status} />
            </div>
            <div style={{ fontFamily: "var(--font-body)", fontSize: 11.5, color: "rgba(255,255,255,0.45)" }}>
              {a.company_name || "Unassigned"}{a.region ? ` · ${a.region}` : ""}
            </div>
            <div style={{ display: "flex", gap: 8, marginTop: 4 }}>
              <MC_LinkChip label="Victron" linked={!!a.victron_linked} />
              <MC_LinkChip label="Ajax" linked={!!a.ajax_linked} />
              <MC_LinkChip label="Teltonika" linked={!!a.teltonika_linked} />
            </div>
          </button>
        ))}
      </div>
    </div>
  );
}

function MC_LinkChip({ label, linked }) {
  return (
    <span style={{
      fontFamily: "var(--font-body)", fontSize: 9.5, fontWeight: 600, letterSpacing: "0.04em",
      textTransform: "uppercase", padding: "3px 8px", borderRadius: 3,
      background: linked ? "#fff" : "rgba(255,255,255,0.06)",
      color: linked ? "#000" : "rgba(255,255,255,0.35)",
    }}>{label}</span>
  );
}

function MC_StatusPill({ status }) {
  const tones = {
    assigned: { bg: "#fff", fg: "#000" },
    deployed: { bg: "#fff", fg: "#000" },
    in_stock: { bg: "rgba(255,255,255,0.08)", fg: "rgba(255,255,255,0.6)" },
    maintenance: { bg: "rgba(255,255,255,0.4)", fg: "#000" },
  };
  const c = tones[status] || tones.in_stock;
  return (
    <span style={{
      background: c.bg, color: c.fg, fontFamily: "var(--font-body)", fontSize: 9.5,
      fontWeight: 600, letterSpacing: "0.04em", textTransform: "uppercase",
      padding: "4px 9px", borderRadius: 3, whiteSpace: "nowrap",
    }}>{(status || "unknown").replace(/_/g, " ")}</span>
  );
}

// ───────────────────────────── Asset Detail ─────────────────────────────

function MC_AssetDetail({ assetId, onBack }) {
  const [state, setState] = useState({ status: "loading", data: null, error: "" });

  const load = () => {
    fetch(`/api/admin/mission-control/assets/${assetId}`, { credentials: "same-origin" })
      .then(async (r) => {
        const data = await r.json().catch(() => ({}));
        if (!r.ok) throw new Error(data.error || "Couldn't load this asset.");
        return data;
      })
      .then((data) => setState({ status: "ready", data, error: "" }))
      .catch((err) => setState({ status: "error", data: null, error: err.message }));
  };

  useEffect(load, [assetId]); // eslint-disable-line react-hooks/exhaustive-deps

  if (state.status === "loading") return <MC_EmptyNote>Loading asset&hellip;</MC_EmptyNote>;
  if (state.status === "error") return <MC_EmptyNote>{state.error}</MC_EmptyNote>;

  const { unit, telemetry, victron, ajax, teltonika } = state.data;

  // Victron sub-device lookups.
  const vDevices = (victron && victron.devices) || [];
  const parseVFields = (row) => {
    let fields = {};
    try { fields = JSON.parse(row.cached_fields_json || "{}"); } catch { /* ignore */ }
    return fields;
  };
  const findV = (role) => {
    const row = vDevices.find((d) => d.device_role === role);
    return row ? { row, fields: parseVFields(row) } : null;
  };
  const groupV = (role) => vDevices.filter((d) => d.device_role === role);

  // Mains charger presence/status -- see unit-management-page.jsx's
  // UM_mainsChargerInfo for the full root-cause background (VRM's own
  // diagnostics API entirely OMITS the "Charger" device from its
  // response whenever it's unplugged, rather than reporting stale
  // values, so "a cached row exists" is NOT "AC mains is plugged in
  // right now" -- see migrations/0050_victron_device_presence.sql's
  // is_present column this reads). Mirrored here (rather than shared)
  // since this file duplicates its own independent Victron lookups
  // throughout, same as every other helper on this page.
  const mainsRow = vDevices.find((d) => d.device_role === "mains_charger");
  const mainsInfo = !mainsRow
    ? { status: "never_linked" }
    : !mainsRow.is_present
      ? { status: "unplugged", lastSeenIso: mainsRow.last_synced_at || null }
      : { status: "connected", fields: parseVFields(mainsRow) };
  const mainsTimeAgo = (isoString) => {
    if (!isoString) return "\u2014";
    const then = new Date(/Z$|[+-]\d\d:?\d\d$/.test(isoString) ? isoString : `${isoString}Z`).getTime();
    if (Number.isNaN(then)) return "\u2014";
    const seconds = Math.max(0, Math.round((Date.now() - then) / 1000));
    if (seconds < 60) return `${seconds} sec ago`;
    const minutes = Math.round(seconds / 60);
    if (minutes < 60) return `${minutes} min ago`;
    const hours = Math.round(minutes / 60);
    if (hours < 24) return `${hours} hr ago`;
    const days = Math.round(hours / 24);
    return `${days} day${days === 1 ? "" : "s"} ago`;
  };

  // Pulls the leading numeric part out of a VRM formatted value
  // ("13.57 V" -> 13.57, "-0.77 A" -> -0.77). Returns null if the
  // field is missing or unparseable.
  const parseVrmNumber = (raw) => {
    if (raw == null) return null;
    const m = String(raw).match(/-?[\d.]+/);
    return m ? parseFloat(m[0]) : null;
  };

  // Sums a numeric VRM field (e.g. "ScW": "5 W") across every device row
  // sharing one dashboard role. Up to 3 distinct physical Solar
  // Chargers get their own role (mppt_1/mppt_2/mppt_3, per
  // lib/victron.ts's deviceRoleFor) -- this still sums in case a 4th+
  // charger ever lands on the same role, rather than silently hiding
  // it behind whichever one happened to sync last.
  const sumVField = (rows, code) => {
    let total = 0, any = false, suffixUnit = "";
    for (const row of rows) {
      const raw = parseVFields(row)[code];
      const n = parseVrmNumber(raw);
      if (n == null) continue;
      total += n;
      any = true;
      const suffix = String(raw).slice(String(raw).match(/-?[\d.]+/)[0].length).trim();
      if (suffix) suffixUnit = suffix;
    }
    return any ? `${Math.round(total * 100) / 100}${suffixUnit ? ` ${suffixUnit}` : ""}` : null;
  };
  const combineV = (role) => {
    const rows = groupV(role);
    return rows.length ? { fields: { ScW: sumVField(rows, "ScW") } } : null;
  };

  const shunt = findV("shunt");
  const mppt1 = combineV("mppt_1");
  const mppt2 = combineV("mppt_2");
  const mppt3 = combineV("mppt_3");

  // Victron's own MPPT charge-state -> LED colour convention
  // (SmartSolar/BlueSolar manual §7.1: Bulk = blue LED, Absorption =
  // yellow LED, Float = green LED). "ScS" is the VRM diagnostics code
  // carrying this as plain text (observed live: "ScS":"Float").
  const mpptStateTone = (state) => {
    const s = String(state || "").trim().toLowerCase();
    if (s === "bulk") return { label: "Bulk", tone: "blue" };
    if (s === "absorption") return { label: "Absorption", tone: "amber" };
    if (s === "float") return { label: "Float", tone: "green" };
    if (!s || s === "off" || s === "not charging") return { label: "Standby", tone: "grey" };
    return { label: state, tone: "grey" };
  };
  const mpptStateFor = (role) => {
    const rows = groupV(role);
    for (const row of rows) {
      const s = parseVFields(row).ScS;
      if (s) return s;
    }
    return null;
  };
  // Card labels: "Mppt 3 (RS1 Solar Stand)" is the customer's own exact
  // naming for unit 547's 3rd physical charger -- overriding the raw
  // VRM custom_name ("RDTa Solar Stand") since the customer explicitly
  // asked for this label.
  const mpptCards = [
    { role: "mppt_1", combined: mppt1, label: "Mppt 1" },
    { role: "mppt_2", combined: mppt2, label: "Mppt 2" },
    { role: "mppt_3", combined: mppt3, label: "Mppt 3 (RS1 Solar Stand)" },
  ].filter((m) => m.combined);

  // Live Power Draw: the shunt's own Voltage x Current is the real
  // system power reading (customer: "power draw comes from shunt") --
  // used whenever Victron is linked, falling back to unit_telemetry's
  // demo value only for units with no shunt synced yet. Shown as a
  // magnitude (Math.abs) since "power draw" reads as consumption
  // regardless of whether the shunt's raw current sign means the
  // battery is net charging or discharging at this instant.
  const shuntPowerW = shunt
    ? (() => {
        const v = parseVrmNumber(shunt.fields.V);
        const i = parseVrmNumber(shunt.fields.I);
        return v == null || i == null ? null : Math.abs(v * i);
      })()
    : null;

  const ajaxHub = ajax && ajax.hub;

  const fmt = (n, dp) => (n === null || n === undefined ? "\u2014" : Number(n).toFixed(dp));
  const fmtUptime = (seconds) => {
    if (seconds === null || seconds === undefined) return "\u2014";
    const h = Math.floor(seconds / 3600);
    const d = Math.floor(h / 24);
    return d > 0 ? `${d}d ${h % 24}h` : `${h}h`;
  };

  const backAction = (
    <button
      type="button" onClick={onBack} data-testid="mission-control-asset-back"
      style={{
        background: "none", border: "1px solid rgba(255,255,255,0.3)", color: "rgba(255,255,255,0.8)",
        cursor: "pointer", padding: "9px 16px", fontFamily: "var(--font-body)", fontSize: 11,
        fontWeight: 500, letterSpacing: "0.12em", textTransform: "uppercase",
      }}
    >&larr; Fleet Health</button>
  );

  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 20, flexWrap: "wrap", gap: 12 }}>
        <div>
          <div style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 20, textTransform: "uppercase" }}>{unit.serial_number}</div>
          <div style={{ fontFamily: "var(--font-body)", fontSize: 12.5, color: "rgba(255,255,255,0.5)", marginTop: 2 }}>
            {unit.product_name}{unit.company_name ? ` · ${unit.company_name}` : ""}
          </div>
        </div>
        {backAction}
      </div>

      <div style={{
        background: "#fff", border: "1px solid rgba(0,0,0,0.15)", padding: "32px",
        display: "grid", gridTemplateColumns: "280px 1fr 280px", gap: 28,
      }}>
        {/* Left column */}
        <div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
          <MC_Card
            title="Energy Storage"
            badge={shunt ? { label: "Live", tone: "green" } : { label: "Not linked", tone: "grey" }}
            testId="mission-control-card-energy-storage"
          >
            {shunt ? (
              <>
                <MC_BigValue>{shunt.fields.SOC || "\u2014"}</MC_BigValue>
                <MC_Row label="Voltage" value={shunt.fields.V || "\u2014"} />
                <MC_Row label="Current" value={shunt.fields.I || "\u2014"} />
                <MC_Row label="Consumed Energy" value={shunt.fields.CE || "\u2014"} last />
              </>
            ) : (
              <MC_NotLinked hint="Connect this asset's Victron GlobalLink installation to see live shunt data." />
            )}
          </MC_Card>

          {/* "Row exists" is NOT "plugged in right now" -- see mainsInfo's
              construction above / UM_mainsChargerInfo's header comment in
              unit-management-page.jsx for the full VRM-omits-the-device-
              when-unplugged root cause. "Unplugged" (red) is a genuine,
              distinct confirmed-absent state, not the same grey
              "Disconnected" shown for a never-linked demo unit. */}
          <MC_Card
            title="Mains Power"
            testId="mission-control-card-mains-power"
            badge={
              mainsInfo.status === "connected"
                ? { label: mainsInfo.fields.cSt && mainsInfo.fields.cSt !== "off" ? "Charging" : "Connected", tone: mainsInfo.fields.cSt && mainsInfo.fields.cSt !== "off" ? "blue" : "green" }
                : mainsInfo.status === "unplugged"
                  ? { label: "Unplugged", tone: "red" }
                  : undefined
            }
          >
            {mainsInfo.status === "connected" ? (
              <>
                <MC_BigValue small>{mainsInfo.fields.cSt || "\u2014"}</MC_BigValue>
                <MC_Row label="Output Voltage" value={mainsInfo.fields.c0V || "\u2014"} />
                <MC_Row label="Output Current" value={mainsInfo.fields.c0I || "\u2014"} last />
              </>
            ) : mainsInfo.status === "unplugged" ? (
              <>
                <MC_BigValue small>Not plugged in</MC_BigValue>
                <MC_Row label="Last Seen Plugged In" value={mainsTimeAgo(mainsInfo.lastSeenIso)} last />
              </>
            ) : (
              <MC_BigValue small>{telemetry.mains_connected ? "Connected" : "Disconnected"}</MC_BigValue>
            )}
          </MC_Card>

          {mpptCards.length > 0 ? (
            // One separate card PER physical Solar Charger, each with
            // its own status badge reflecting Victron's Bulk/
            // Absorption/Float LED convention (see mpptStateTone above)
            // -- mirrors unit-management-page.jsx's UM_GeneralTab PV
            // Charger cards exactly.
            mpptCards.map((m) => {
              const tone = mpptStateTone(mpptStateFor(m.role));
              return (
                <MC_Card
                  key={m.role} title={m.label} badge={tone}
                  testId={`mission-control-card-pv-charger-${m.role}`}
                >
                  <MC_BigValue>{m.combined.fields.ScW || "\u2014"}</MC_BigValue>
                  <MC_Row label="Charge State" value={tone.label} last />
                </MC_Card>
              );
            })
          ) : (
            <MC_Card title="PV Charger" testId="mission-control-card-pv-charger">
              <MC_BigValue>{fmt(telemetry.pv_power_w, 0)} W</MC_BigValue>
              <MC_Row label="Panel Voltage" value={`${fmt(telemetry.pv_panel_voltage_v, 2)} V`} />
              <MC_Row label="Output Current" value={`${fmt(telemetry.pv_output_current_a, 2)} A`} last />
            </MC_Card>
          )}

          <MC_Card title="Controls" testId="mission-control-card-controls">
            {!!unit.has_strobe && <MC_Row label="Strobe" value={telemetry.strobe_on ? "On" : "Off"} />}
            {!!unit.has_alarm && <MC_Row label="Armed" value={telemetry.armed_on ? "Armed" : "Disarmed"} />}
            {!!unit.has_cameras && <MC_Row label="Cameras" value={telemetry.cameras_on ? "On" : "Off"} last />}
            {!unit.has_strobe && !unit.has_alarm && !unit.has_cameras && (
              <div style={{ fontFamily: "var(--font-body)", fontSize: 12, color: "rgba(0,0,0,0.4)" }}>No controls on this product.</div>
            )}
          </MC_Card>
        </div>

        {/* Center — product viewer */}
        <div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center" }}>
          <MC_StatusPill status={unit.status} />
          <div style={{ marginTop: 14 }}>
            <TowerSchematic width={220} height={340} stroke="#000" />
          </div>
          <div style={{ fontFamily: "var(--font-body)", fontSize: 13, color: "rgba(0,0,0,0.55)", marginTop: 14, textAlign: "center" }}>{unit.product_name}</div>
          <div style={{ fontFamily: "monospace", fontSize: 12, color: "#000", marginTop: 4, textAlign: "center" }}>{unit.serial_number}</div>
        </div>

        {/* Right column */}
        <div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
          <MC_Card
            title="Live Power Draw"
            badge={shunt ? { label: "Live", tone: "green" } : undefined}
            testId="mission-control-card-power-draw"
          >
            {shunt ? (
              <>
                <MC_BigValue>{shuntPowerW != null ? `${fmt(shuntPowerW, 2)} W` : "\u2014"}</MC_BigValue>
                <MC_Row label="Voltage" value={shunt.fields.V || "\u2014"} />
                <MC_Row label="Current" value={shunt.fields.I || "\u2014"} last />
              </>
            ) : (
              <>
                <MC_BigValue>{fmt(telemetry.power_w, 2)} W</MC_BigValue>
                <MC_Row label="Current" value={`${fmt(telemetry.power_current_a, 4)} A`} last />
              </>
            )}
          </MC_Card>

          <MC_Card
            title="Internet Router"
            badge={teltonika ? { label: teltonika.connection_state || "Live", tone: "green" } : undefined}
            testId="mission-control-card-internet-router"
          >
            {teltonika ? (
              <>
                <MC_Row label="Operator" value={teltonika.operator || "\u2014"} />
                <MC_Row label="Signal" value={teltonika.signal != null ? `${teltonika.signal}%` : "\u2014"} />
                <MC_Row label="WAN IP" value={teltonika.wan_ip || "\u2014"} />
                <MC_Row label="Firmware" value={teltonika.firmware || "\u2014"} />
                <MC_Row label="Temperature" value={teltonika.temperature != null ? `${teltonika.temperature} \u00b0C` : "\u2014"} />
                <MC_Row label="MAC" value={teltonika.mac || "\u2014"} last />
              </>
            ) : (
              <>
                <MC_Row label="Operator" value={telemetry.router_operator || "\u2014"} />
                <MC_Row label="Signal" value={telemetry.router_signal_percent != null ? `${fmt(telemetry.router_signal_percent, 0)}%` : "\u2014"} />
                <MC_Row label="WWAN IP" value={telemetry.router_wwan_ip || "\u2014"} />
                <MC_Row label="Firmware" value={telemetry.router_firmware || "\u2014"} last />
              </>
            )}
          </MC_Card>

          <MC_Card
            title="System Status"
            badge={ajaxHub ? { label: ajaxHub.online ? "Online" : "Offline", tone: ajaxHub.online ? "green" : "grey" } : undefined}
            testId="mission-control-card-system-status"
          >
            {ajaxHub ? (
              <>
                <MC_Row label="State" value={ajaxHub.state || "\u2014"} />
                <MC_Row label="Battery" value={ajaxHub.battery_level != null ? `${ajaxHub.battery_level}%` : "\u2014"} />
                <MC_Row label="GSM Signal" value={ajaxHub.gsm_signal_level != null ? `${ajaxHub.gsm_signal_level}%` : "\u2014"} />
                <MC_Row label="Firmware" value={ajaxHub.firmware_version || "\u2014"} last />
              </>
            ) : (
              <MC_NotLinked hint="Link this asset's Ajax hub to see live security-system status." />
            )}
          </MC_Card>
        </div>
      </div>

      {/* Bottom strip */}
      <div style={{
        marginTop: 20, background: "#15171C", border: "1px solid rgba(255,255,255,0.1)",
        padding: "18px 28px", display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))", gap: 20,
      }}>
        <MC_StripStat label="Connectivity" value={teltonika ? (teltonika.connection_state || "\u2014") : "\u2014"} />
        {/* GPS: real fix (teltonika.latitude) wins when this router has
            a GPS antenna fitted and a live fix. Most fielded RUT241s
            don't -- verified live against a real customer router that
            RMS itself only ever reports via cell_tower_latitude/
            longitude (a coarse, cell-tower-triangulated estimate --
            see migrations/0053_teltonika_cell_tower_location.sql).
            Falls back to that estimate, clearly labelled "Approx." so
            it's never mistaken for a precise GPS reading. */}
        <MC_StripStat
          label={teltonika && teltonika.latitude != null ? "GPS" : "GPS (Approx.)"}
          value={
            teltonika && teltonika.latitude != null
              ? `${fmt(teltonika.latitude, 4)}, ${fmt(teltonika.longitude, 4)}`
              : teltonika && teltonika.cell_tower_latitude != null
              ? `${fmt(teltonika.cell_tower_latitude, 4)}, ${fmt(teltonika.cell_tower_longitude, 4)}`
              : "\u2014"
          }
        />
        <MC_StripStat label="Uptime" value={teltonika ? fmtUptime(teltonika.router_uptime) : "\u2014"} />
        <MC_StripStat label="Last Event" value="\u2014" />
        <MC_StripStat label="Alarms" value={ajaxHub ? (ajaxHub.state || "\u2014") : "\u2014"} />
      </div>

      {/* Ajax Devices -- every device Ajax reports as connected to this
          asset's hub (siren, motion, relay, keypad, etc.), synced from
          the live Ajax API into ajax_devices (see routes/admin-ajax.ts's
          /ajax-hubs/:id/sync-devices). Ajax has no separate "serial
          number" field -- its own 8-char hex device id (ajax_device_id)
          IS the serial printed on the physical unit, so that's what's
          shown in the Serial column. Asset-scoped by construction: this
          whole screen is already keyed to one asset_unit_id, and the
          backend only ever returns devices belonging to THIS asset's
          own linked hub (ajax.devices, from the /assets/:id read above)
          -- never any other asset's hub, even under the same Ajax
          Company API account. */}
      <MC_AjaxDevicesCard ajax={ajax} />
    </div>
  );
}

function MC_AjaxDevicesCard({ ajax }) {
  const ajaxHub = ajax && ajax.hub;
  const devices = (ajax && ajax.devices) || [];

  return (
    <div style={{ marginTop: 20, background: "#fff", border: "1px solid rgba(0,0,0,0.15)", padding: "18px 18px 6px" }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
        <div style={{ fontFamily: "var(--font-body)", fontSize: 11.5, fontWeight: 700, letterSpacing: "0.04em", textTransform: "uppercase", color: "#000" }}>
          Ajax Devices
        </div>
        {ajaxHub && (
          <span style={{ fontFamily: "var(--font-body)", fontSize: 11, color: "rgba(0,0,0,0.4)" }}>
            {devices.length} device{devices.length === 1 ? "" : "s"} on this hub
          </span>
        )}
      </div>

      {!ajaxHub ? (
        <MC_NotLinked hint="Link this asset's Ajax hub to see its connected devices." />
      ) : devices.length === 0 ? (
        <div style={{ fontFamily: "var(--font-body)", fontSize: 12, color: "rgba(0,0,0,0.4)", paddingBottom: 14 }}>
          No devices synced yet for this hub — use Sync devices from the Assets page's Link devices modal.
        </div>
      ) : (
        <div style={{ overflowX: "auto", paddingBottom: 4 }}>
          <table style={{ width: "100%", borderCollapse: "collapse", fontFamily: "var(--font-body)", fontSize: 12.5 }}>
            <thead>
              <tr style={{ textAlign: "left", color: "rgba(0,0,0,0.4)", fontSize: 10.5, letterSpacing: "0.06em", textTransform: "uppercase" }}>
                <th style={{ padding: "6px 10px 6px 0", fontWeight: 600 }}>Name</th>
                <th style={{ padding: "6px 10px", fontWeight: 600 }}>Type</th>
                <th style={{ padding: "6px 10px", fontWeight: 600 }}>Room</th>
                <th style={{ padding: "6px 10px", fontWeight: 600 }}>Serial</th>
                <th style={{ padding: "6px 10px", fontWeight: 600 }}>Battery</th>
                <th style={{ padding: "6px 10px", fontWeight: 600 }}>Signal</th>
                <th style={{ padding: "6px 0 6px 10px", fontWeight: 600 }}>Connected</th>
              </tr>
            </thead>
            <tbody>
              {devices.map((d, idx) => (
                <tr key={d.id} style={{ borderTop: "1px solid rgba(0,0,0,0.08)" }}>
                  <td style={{ padding: "9px 10px 9px 0", fontWeight: 600, color: "#000" }}>{d.name || "\u2014"}</td>
                  <td style={{ padding: "9px 10px", color: "rgba(0,0,0,0.6)" }}>{formatAjaxDeviceType(d.device_type)}</td>
                  <td style={{ padding: "9px 10px", color: "rgba(0,0,0,0.6)" }}>{d.room_name || "\u2014"}</td>
                  <td style={{ padding: "9px 10px", fontFamily: "monospace", color: "rgba(0,0,0,0.6)" }}>{d.ajax_device_id}</td>
                  <td style={{ padding: "9px 10px", color: "rgba(0,0,0,0.6)" }}>{d.battery_level != null ? `${d.battery_level}%` : "\u2014"}</td>
                  <td style={{ padding: "9px 10px", color: "rgba(0,0,0,0.6)" }}>{d.signal_level || "\u2014"}</td>
                  <td style={{ padding: "9px 0 9px 10px" }}>
                    <MC_ConnectedPill online={!!d.online} />
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}

// Ajax's raw deviceType strings are PascalCase vendor identifiers
// (DoorProtectPlus, StreetSirenDoubleDeck, MotionCamOutdoorPhod, ...) --
// this just inserts spaces before capitals so the table reads as
// "Door Protect Plus" instead of a run-on identifier, no lookup table
// needed since new Ajax product types shouldn't silently show as blank.
function formatAjaxDeviceType(deviceType) {
  if (!deviceType) return "\u2014";
  return deviceType.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2");
}

function MC_ConnectedPill({ online }) {
  return (
    <span style={{
      fontFamily: "var(--font-body)", fontSize: 9.5, fontWeight: 600, letterSpacing: "0.04em",
      textTransform: "uppercase", padding: "3px 8px", borderRadius: 3,
      background: online ? "#000" : "rgba(0,0,0,0.06)",
      color: online ? "#fff" : "rgba(0,0,0,0.4)",
    }}>{online ? "Connected" : "Offline"}</span>
  );
}

function MC_StripStat({ label, value }) {
  return (
    <div>
      <div style={{ fontFamily: "var(--font-body)", fontSize: 9.5, letterSpacing: "0.18em", textTransform: "uppercase", color: "rgba(255,255,255,0.4)", marginBottom: 6 }}>{label}</div>
      <div style={{ fontFamily: "var(--font-body)", fontSize: 14, color: "#fff", fontWeight: 500 }}>{value}</div>
    </div>
  );
}

function MC_NotLinked({ hint }) {
  return (
    <div style={{ fontFamily: "var(--font-body)", fontSize: 12, color: "rgba(0,0,0,0.4)", lineHeight: 1.5 }}>{hint}</div>
  );
}

// ─────────────────────────── Shared primitives ───────────────────────────

function MC_Card({ title, badge, children, testId }) {
  return (
    <div data-testid={testId} style={{ background: "#fff", border: "1px solid rgba(0,0,0,0.15)", padding: "18px 18px 14px" }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
        <div style={{ fontFamily: "var(--font-body)", fontSize: 11.5, fontWeight: 700, letterSpacing: "0.04em", textTransform: "uppercase", color: "#000" }}>{title}</div>
        {badge && <MC_Badge label={badge.label} tone={badge.tone} />}
      </div>
      {children}
    </div>
  );
}

function MC_Badge({ label, tone }) {
  const tones = {
    grey: { bg: "rgba(0,0,0,0.06)", fg: "rgba(0,0,0,0.45)" },
    blue: { bg: "#000", fg: "#fff" },
    green: { bg: "#000", fg: "#fff" },
    // MPPT "Absorption" charge state (Victron LED convention: yellow) --
    // see UM_mpptStateTone in unit-management-page.jsx for the full
    // Bulk/Absorption/Float -> tone mapping this mirrors.
    amber: { bg: "#B4740B", fg: "#fff" },
    // Mains Charger "Unplugged" (confirmed absent from the latest
    // Victron sync -- see mainsInfo's construction in MC_AssetDetail).
    red: { bg: "#B0302C", fg: "#fff" },
  };
  const c = tones[tone] || tones.grey;
  return (
    <span style={{ background: c.bg, color: c.fg, fontFamily: "var(--font-body)", fontSize: 10, fontWeight: 600, letterSpacing: "0.05em", textTransform: "uppercase", padding: "4px 10px", borderRadius: 3 }}>{label}</span>
  );
}

function MC_BigValue({ children, small }) {
  return (
    <div style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: small ? 18 : 26, color: "#000", marginBottom: 10 }}>{children}</div>
  );
}

function MC_Row({ label, value, last }) {
  return (
    <div style={{ display: "flex", justifyContent: "space-between", gap: 12, padding: "6px 0", borderBottom: last ? "none" : "1px solid rgba(0,0,0,0.1)", fontFamily: "var(--font-body)", fontSize: 12.5 }}>
      <span style={{ color: "rgba(0,0,0,0.45)" }}>{label}</span>
      <span style={{ color: "#000", fontWeight: 500 }}>{value}</span>
    </div>
  );
}

function MC_ComingSoon({ item }) {
  return (
    <div data-testid="mission-control-coming-soon" style={{
      background: "rgba(255,255,255,0.03)", border: "1px solid rgba(255,255,255,0.12)",
      padding: "60px 32px", textAlign: "center",
    }}>
      <div style={{ fontFamily: "var(--font-body)", fontSize: 9, letterSpacing: "0.2em", textTransform: "uppercase", color: "rgba(255,255,255,0.4)", marginBottom: 12, fontWeight: 500 }}>Coming soon</div>
      <div style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 20, textTransform: "uppercase", color: "rgba(255,255,255,0.85)", marginBottom: 8 }}>{item ? item.label : ""}</div>
      <div style={{ fontFamily: "var(--font-body)", fontSize: 13.5, color: "rgba(255,255,255,0.5)" }}>This part of Mission Control hasn't been built yet — it's on the roadmap.</div>
    </div>
  );
}

function MC_EmptyNote({ children }) {
  return (
    <div style={{ fontFamily: "var(--font-body)", fontSize: 14, color: "rgba(255,255,255,0.5)", padding: "40px 0" }}>{children}</div>
  );
}

Object.assign(window, { MissionControlPage });
