// Solo Vision AI Cloud — worker management UI.
//
// Entry-phase asset restructuring (migrations/0061_asset_category_vision_ai.sql)
// established Cloud as its own area under Assets; migrations/0062 added
// the actual schema (vision_nodes.kind='cloud_worker', unit_camera_id,
// confidence_threshold/detect_classes_json) and the real
// solo-vision-cloud/ Python worker (YOLO + tracking + zone check + event
// POST) now runs live on Solo's own VPS, one process per camera. This
// file is that feature's admin surface: pick any camera on any asset
// (Cloud "connects to any camera via RTSP/ONVIF", not just assets
// tagged vision_ai_cloud -- see admin-vision-ai.ts's POST /vision-nodes
// header comment), provision a cloud_worker node against it, watch its
// health/telemetry, tune detection settings, and manage its zones/events.
//
// Deliberately NOT a duplicate asset list (same posture as this file's
// previous placeholder) -- admin-assets-page.jsx's own unit list still
// owns listing/adding/assigning Vision AI Cloud *assets*. This page adds
// the *worker* layer on top: a cloud_worker node is keyed to a specific
// camera, not a whole asset, so its own list/detail view is
// camera-centric rather than asset-centric.
//
// Reuses ModalShell/ModalError/SectionLabel/EmptyNote/MiniField/
// ActionButton/adminInputStyle/adminSelectStyle (admin-assets-page.jsx)
// and VisionHealthBadge/VISION_ZONE_TYPES/ZoneEditor/EventDetailModal
// (admin-vision-ai-page.jsx, loaded earlier -- see index.html) rather
// than redefining any of them.

const VISION_DETECT_CLASSES = ["person", "car", "truck", "bus", "motorcycle", "bicycle", "dog", "cat"];

function CloudAiWorkerManagement({ isReadOnly }) {
  const [state, setState] = useState({ status: "loading", nodes: [] });
  const [showAdd, setShowAdd] = useState(false);
  const [managingNode, setManagingNode] = useState(null);

  const load = () => {
    fetch("/api/admin/vision-nodes?kind=cloud_worker", { credentials: "same-origin" })
      .then((r) => (r.ok ? r.json() : Promise.reject()))
      .then((data) => setState({ status: "ready", nodes: data.nodes || [] }))
      .catch(() => setState({ status: "error", nodes: [] }));
  };
  useEffect(load, []); // eslint-disable-line react-hooks/exhaustive-deps

  return (
    <React.Fragment>
      <EmptyNote>
        Server-side AI object-detection analytics (YOLO), processed on Solo's own cloud
        infrastructure and watching a specific camera directly via RTSP/ONVIF — no edge hardware
        required. A Cloud AI worker can be attached to any camera on any asset.
      </EmptyNote>

      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", margin: "20px 0 16px" }}>
        <SectionLabel>Cloud AI workers ({state.nodes.length})</SectionLabel>
        <ActionButton readOnly={isReadOnly} onClick={() => setShowAdd(true)} testId="cloud-ai-add-camera-btn">
          + Add camera to Cloud AI
        </ActionButton>
      </div>

      {state.status === "loading" && (
        <div style={{ fontFamily: "var(--font-body)", fontSize: 13, color: "rgba(0,0,0,0.5)" }}>Loading…</div>
      )}
      {state.status === "error" && <ModalError>Couldn't load Cloud AI workers.</ModalError>}
      {state.status === "ready" && state.nodes.length === 0 && (
        <EmptyNote>No cameras are being watched by Cloud AI yet.</EmptyNote>
      )}
      {state.status === "ready" && state.nodes.length > 0 && (
        <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
          {state.nodes.map((node) => (
            <CloudAiWorkerRow key={node.id} node={node} onManage={() => setManagingNode(node)} />
          ))}
        </div>
      )}

      {showAdd && (
        <AddCameraToCloudAiWizard
          onCancel={() => setShowAdd(false)}
          onProvisioned={() => { setShowAdd(false); load(); }}
        />
      )}
      {managingNode && (
        <CloudAiWorkerDetailModal node={managingNode} isReadOnly={isReadOnly}
          onCancel={() => setManagingNode(null)}
          onChanged={load}
          onRemoved={() => { setManagingNode(null); load(); }}
        />
      )}
    </React.Fragment>
  );
}

function CloudAiWorkerRow({ node, onManage }) {
  return (
    <div data-testid={`cloud-ai-worker-row-${node.id}`} style={{
      background: "rgba(0,0,0,0.03)", border: "1px solid rgba(0,0,0,0.14)",
      padding: "16px 20px", display: "flex", justifyContent: "space-between",
      alignItems: "center", flexWrap: "wrap", gap: 14,
    }} onClick={onManage} role="button">
      <div>
        <div style={{ fontFamily: "var(--font-body)", fontSize: 14.5, fontWeight: 600, color: "#000" }}>
          {node.assetUnitName ? `${node.assetUnitName} — Camera slot ${node.unitCameraSlot}` : node.nodeSerial}
          {node.unitCameraLabel ? ` (${node.unitCameraLabel})` : ""}
          <span style={{ marginLeft: 10 }}><VisionHealthBadge state={node.healthState} /></span>
        </div>
        <div style={{ fontFamily: "var(--font-body)", fontSize: 12.5, color: "rgba(0,0,0,0.55)", marginTop: 4 }}>
          {node.nodeSerial}
        </div>
        <div style={{ fontFamily: "var(--font-body)", fontSize: 12, color: "rgba(0,0,0,0.45)", marginTop: 4 }}>
          Last heartbeat: {node.lastHeartbeatAt || "never"}
          {node.confidenceThreshold != null && ` · Confidence ${Math.round(node.confidenceThreshold * 100)}%`}
          {node.detectClasses && node.detectClasses.length > 0 && ` · Watching: ${node.detectClasses.join(", ")}`}
        </div>
      </div>
      <ActionButton onClick={onManage}>Manage</ActionButton>
    </div>
  );
}

// ---------------------------------------------------------------------
// Add-camera wizard — pick an asset -> pick a configured camera slot on
// it -> optional brand auto-detect (informational only at this step,
// the camera's ONVIF/RTSP config is already saved via admin-assets-page's
// own camera editor; this wizard only ever CONSUMES an existing
// unit_cameras row, it doesn't create/edit one) -> provision the
// cloud_worker node -> one-time device token reveal (exact same pattern
// as ProvisionNodeModal in admin-vision-ai-page.jsx).

function AddCameraToCloudAiWizard({ onCancel, onProvisioned }) {
  const [step, setStep] = useState("pick-asset"); // pick-asset -> pick-camera -> result
  const [serialQuery, setSerialQuery] = useState("");
  const [units, setUnits] = useState([]);
  const [selectedUnit, setSelectedUnit] = useState(null);
  const [cameras, setCameras] = useState([]);
  const [camerasStatus, setCamerasStatus] = useState("idle");
  const [selectedCameraId, setSelectedCameraId] = useState("");
  const [detecting, setDetecting] = useState(false);
  const [detectResult, setDetectResult] = useState(null);
  const [submitting, setSubmitting] = useState(false);
  const [error, setError] = useState("");
  const [result, setResult] = useState(null); // { nodeSerial, deviceToken }

  const searchUnits = (q) => {
    setSerialQuery(q);
    if (!q || q.length < 2) { setUnits([]); return; }
    fetch(`/api/admin/units?serial=${encodeURIComponent(q)}`, { credentials: "same-origin" })
      .then((r) => (r.ok ? r.json() : Promise.reject()))
      .then((data) => setUnits(data.units || []))
      .catch(() => setUnits([]));
  };

  const pickUnit = (unit) => {
    setSelectedUnit(unit);
    setSerialQuery(unit.serial_number);
    setUnits([]);
    setCamerasStatus("loading");
    setSelectedCameraId("");
    setDetectResult(null);
    fetch(`/api/admin/units/${unit.id}/cameras`, { credentials: "same-origin" })
      .then((r) => (r.ok ? r.json() : Promise.reject()))
      .then((data) => {
        setCameras((data.cameras || []).filter((c) => c.configured));
        setCamerasStatus("ready");
        setStep("pick-camera");
      })
      .catch(() => { setCameras([]); setCamerasStatus("error"); });
  };

  const selectedCamera = cameras.find((c) => String(c.id) === selectedCameraId);

  const runDetect = async () => {
    if (!selectedCamera || !selectedCamera.host) return;
    setDetecting(true);
    setDetectResult(null);
    try {
      const res = await fetch("/api/admin/camera-brands/detect", {
        method: "POST", credentials: "same-origin",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          host: selectedCamera.host, port: selectedCamera.port,
          useHttps: !!selectedCamera.use_https, username: selectedCamera.username,
        }),
      });
      const data = await res.json().catch(() => ({}));
      setDetectResult(res.ok ? data : { error: data.error || "Detection failed." });
    } catch {
      setDetectResult({ error: "Couldn't reach the server." });
    } finally {
      setDetecting(false);
    }
  };

  const provision = async () => {
    if (!selectedCameraId) return;
    setSubmitting(true);
    setError("");
    try {
      const res = await fetch("/api/admin/vision-nodes", {
        method: "POST", credentials: "same-origin",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ kind: "cloud_worker", unitCameraId: Number(selectedCameraId) }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) { setError(data.error || "Something went wrong."); return; }
      setResult(data);
      setStep("result");
    } catch {
      setError("Couldn't reach the server.");
    } finally {
      setSubmitting(false);
    }
  };

  if (step === "result" && result) {
    return (
      <ModalShell title="Camera added to Cloud AI" onCancel={onProvisioned}>
        <div style={{
          background: "rgba(190,40,40,0.06)", border: "1px solid rgba(190,40,40,0.3)",
          padding: "12px 14px", marginBottom: 16, fontFamily: "var(--font-body)", fontSize: 12.5,
          color: "#8a1f1f", lineHeight: 1.5,
        }}>
          This device token is shown <strong>once</strong> and can't be retrieved again. Copy it now
          and put it in the VPS worker's environment file for <strong>{result.nodeSerial}</strong>
          (<code>solo-vision-cloud/deploy/add_camera.sh</code>).
        </div>
        <MiniField label="Node serial">
          <input readOnly value={result.nodeSerial} style={{ ...adminInputStyle, fontFamily: "monospace" }} onFocus={(e) => e.target.select()} />
        </MiniField>
        <MiniField label="Device token">
          <div style={{ display: "flex", gap: 8 }}>
            <input readOnly value={result.deviceToken} style={{ ...adminInputStyle, fontFamily: "monospace" }}
              onFocus={(e) => e.target.select()} />
            <ActionButton onClick={() => { navigator.clipboard && navigator.clipboard.writeText(result.deviceToken); }}>
              Copy
            </ActionButton>
          </div>
        </MiniField>
        <div style={{ display: "flex", justifyContent: "flex-end", marginTop: 18 }}>
          <ActionButton onClick={onProvisioned}>Done</ActionButton>
        </div>
      </ModalShell>
    );
  }

  return (
    <ModalShell title="Add camera to Cloud AI" onCancel={onCancel} wide>
      {error && <ModalError>{error}</ModalError>}

      <SectionLabel>1. Pick the asset</SectionLabel>
      <MiniField label="Search asset by serial">
        <input style={adminInputStyle} value={serialQuery} onChange={(e) => searchUnits(e.target.value)}
          placeholder="Type a serial number…" />
      </MiniField>
      {units.length > 0 && (
        <div style={{ border: "1px solid rgba(0,0,0,0.15)", maxHeight: 160, overflowY: "auto", marginBottom: 16 }}>
          {units.map((u) => (
            <div key={u.id} onClick={() => pickUnit(u)}
              style={{ padding: "8px 12px", cursor: "pointer", fontFamily: "var(--font-body)", fontSize: 13, borderBottom: "1px solid rgba(0,0,0,0.06)" }}
              onMouseEnter={(e) => e.currentTarget.style.background = "rgba(0,0,0,0.04)"}
              onMouseLeave={(e) => e.currentTarget.style.background = "none"}>
              {u.serial_number}
            </div>
          ))}
        </div>
      )}

      {selectedUnit && (
        <React.Fragment>
          <SectionLabel>2. Pick the camera slot</SectionLabel>
          {camerasStatus === "loading" && (
            <div style={{ fontFamily: "var(--font-body)", fontSize: 13, color: "rgba(0,0,0,0.5)" }}>Loading cameras…</div>
          )}
          {camerasStatus === "ready" && cameras.length === 0 && (
            <EmptyNote>This asset has no configured cameras yet — set one up on the Tower Assets tab first.</EmptyNote>
          )}
          {camerasStatus === "ready" && cameras.length > 0 && (
            <React.Fragment>
              <MiniField label="Camera">
                <select style={adminSelectStyle} value={selectedCameraId}
                  onChange={(e) => { setSelectedCameraId(e.target.value); setDetectResult(null); }}>
                  <option value="">— Select a camera —</option>
                  {cameras.map((c) => (
                    <option key={c.id} value={c.id}>
                      Slot {c.slot}{c.label ? ` — ${c.label}` : ""}{c.manufacturer ? ` (${c.manufacturer})` : ""}
                    </option>
                  ))}
                </select>
              </MiniField>

              {selectedCamera && (
                <div style={{ marginTop: 4, marginBottom: 20 }}>
                  <div style={{ fontFamily: "var(--font-body)", fontSize: 12, color: "rgba(0,0,0,0.5)", marginBottom: 8 }}>
                    Source: {selectedCamera.source_type || "onvif"}
                    {selectedCamera.host ? ` · ${selectedCamera.host}:${selectedCamera.port}` : ""}
                  </div>
                  {selectedCamera.host && (
                    <ActionButton onClick={runDetect} disabled={detecting}>
                      {detecting ? "Detecting…" : "Auto-detect brand"}
                    </ActionButton>
                  )}
                  {detectResult && detectResult.error && (
                    <div style={{ marginTop: 8, fontFamily: "var(--font-body)", fontSize: 12, color: "rgba(170,30,30,0.85)" }}>
                      {detectResult.error}
                    </div>
                  )}
                  {detectResult && !detectResult.error && (
                    <div style={{ marginTop: 8, fontFamily: "var(--font-body)", fontSize: 12, color: "rgba(0,0,0,0.6)" }}>
                      {detectResult.brand
                        ? `Detected: ${detectResult.manufacturer} — matches brand profile "${detectResult.brand.label}"`
                        : `Detected manufacturer: ${detectResult.manufacturer || "unknown"} (no matching brand profile)`}
                    </div>
                  )}
                </div>
              )}
            </React.Fragment>
          )}
        </React.Fragment>
      )}

      <div style={{ display: "flex", justifyContent: "flex-end", gap: 10, marginTop: 10, borderTop: "1px solid rgba(0,0,0,0.1)", paddingTop: 16 }}>
        <ActionButton danger onClick={onCancel} disabled={submitting}>Cancel</ActionButton>
        <button type="button" onClick={provision} disabled={!selectedCameraId || submitting} style={{
          background: "#000", color: "#fff", border: "1px solid #000",
          padding: "10px 18px", cursor: (!selectedCameraId || submitting) ? "default" : "pointer",
          opacity: (!selectedCameraId || submitting) ? 0.5 : 1,
          fontFamily: "var(--font-body)", fontSize: 11.5, fontWeight: 500,
          letterSpacing: "0.14em", textTransform: "uppercase",
        }}>
          {submitting ? "Adding…" : "Add to Cloud AI"}
        </button>
      </div>
    </ModalShell>
  );
}

// ---------------------------------------------------------------------
// Worker detail — health/telemetry, detection settings, zones (reuses
// ZoneEditor as-is, this node's camera is already known so there's no
// need for VisionZonesTab's own asset-search UI), events (reuses
// EventDetailModal for the individual-event drilldown; the list itself
// is a lighter, node-scoped fetch rather than reusing VisionEventsTab's
// generic all-nodes list), and remove-camera.

function CloudAiWorkerDetailModal({ node, isReadOnly, onCancel, onChanged, onRemoved }) {
  const [detail, setDetail] = useState(null);
  const [confidenceThreshold, setConfidenceThreshold] = useState(node.confidenceThreshold ?? 0.5);
  const [detectClasses, setDetectClasses] = useState(node.detectClasses || ["person", "car"]);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState("");
  const [tab, setTab] = useState("settings"); // settings | zones | events

  const load = () => {
    fetch(`/api/admin/vision-nodes/${node.id}`, { credentials: "same-origin" })
      .then((r) => (r.ok ? r.json() : Promise.reject()))
      .then((data) => {
        setDetail(data.node);
        setConfidenceThreshold(data.node.confidenceThreshold ?? 0.5);
        setDetectClasses(data.node.detectClasses && data.node.detectClasses.length ? data.node.detectClasses : ["person", "car"]);
      })
      .catch(() => setError("Couldn't load worker details."));
  };
  useEffect(load, [node.id]); // eslint-disable-line react-hooks/exhaustive-deps

  const toggleClass = (cls) => {
    setDetectClasses((prev) => prev.includes(cls) ? prev.filter((c) => c !== cls) : [...prev, cls]);
  };

  const saveSettings = async () => {
    if (detectClasses.length === 0) { setError("Pick at least one object class to detect."); return; }
    setBusy(true); setError("");
    try {
      const res = await fetch(`/api/admin/vision-nodes/${node.id}/detection-settings`, {
        method: "PUT", credentials: "same-origin",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ confidenceThreshold: Number(confidenceThreshold), detectClasses }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) { setError(data.error || "Something went wrong."); return; }
      load(); onChanged();
    } finally { setBusy(false); }
  };

  const removeCamera = async () => {
    if (!window.confirm(`Remove ${node.nodeSerial} from Cloud AI? This stops it being watched — the VPS worker process itself must also be stopped/uninstalled separately (deploy/remove_camera.sh).`)) return;
    setBusy(true); setError("");
    try {
      const res = await fetch(`/api/admin/vision-nodes/${node.id}`, { method: "DELETE", credentials: "same-origin" });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) { setError(data.error || "Couldn't remove this worker."); return; }
      onRemoved();
    } finally { setBusy(false); }
  };

  const camera = detail
    ? { id: detail.unitCameraId, slot: detail.unitCameraSlot, label: detail.unitCameraLabel }
    : null;

  return (
    <ModalShell title={`Cloud AI — ${node.nodeSerial}`} onCancel={onCancel} wide>
      {error && <ModalError>{error}</ModalError>}
      {!detail ? (
        <div style={{ fontFamily: "var(--font-body)", fontSize: 13, color: "rgba(0,0,0,0.5)" }}>Loading…</div>
      ) : (
        <React.Fragment>
          <div style={{ marginBottom: 8 }}><VisionHealthBadge state={detail.healthState} /></div>
          <div style={{ fontFamily: "var(--font-body)", fontSize: 12, color: "rgba(0,0,0,0.5)", marginBottom: 20 }}>
            {detail.assetUnitName ? `${detail.assetUnitName} · ` : ""}
            Camera slot {detail.unitCameraSlot}{detail.unitCameraLabel ? ` (${detail.unitCameraLabel})` : ""}
            {" · "}Last heartbeat: {detail.lastHeartbeatAt || "never"}
          </div>

          {(detail.cpuPct != null || detail.inferenceFps != null) && (
            <React.Fragment>
              <SectionLabel>Telemetry</SectionLabel>
              <div style={{ display: "flex", gap: 18, flexWrap: "wrap", marginBottom: 24, fontFamily: "var(--font-body)", fontSize: 12.5, color: "rgba(0,0,0,0.65)" }}>
                {detail.cpuPct != null && <span>CPU {detail.cpuPct}%</span>}
                {detail.memoryPct != null && <span>Memory {detail.memoryPct}%</span>}
                {detail.inferenceFps != null && <span>Inference {detail.inferenceFps} fps</span>}
                {detail.camerasOnline != null && <span>Cameras {detail.camerasOnline}/{detail.camerasTotal}</span>}
                {detail.softwareVersion && <span>Worker {detail.softwareVersion}</span>}
              </div>
            </React.Fragment>
          )}

          <div style={{ display: "flex", gap: 2, marginBottom: 20, borderBottom: "1px solid rgba(0,0,0,0.1)" }}>
            {[{ id: "settings", label: "Detection settings" }, { id: "zones", label: "Zones" }, { id: "events", label: "Events" }].map((t) => (
              <button key={t.id} type="button" onClick={() => setTab(t.id)} data-testid={`cloud-ai-detail-tab-${t.id}`}
                style={{
                  background: "none", border: "none", cursor: "pointer",
                  padding: "8px 14px 12px", fontFamily: "var(--font-body)", fontSize: 12,
                  fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase",
                  color: tab === t.id ? "#000" : "rgba(0,0,0,0.45)",
                  borderBottom: tab === t.id ? "2px solid #000" : "2px solid transparent",
                  marginBottom: -1,
                }}>
                {t.label}
              </button>
            ))}
          </div>

          {tab === "settings" && (
            <React.Fragment>
              <MiniField label={`Confidence threshold (${Math.round(confidenceThreshold * 100)}%)`}>
                <input type="range" min="0" max="1" step="0.05" value={confidenceThreshold}
                  onChange={(e) => setConfidenceThreshold(Number(e.target.value))} disabled={isReadOnly}
                  style={{ width: "100%" }} />
              </MiniField>
              <SectionLabel>Object classes to detect</SectionLabel>
              <div style={{ display: "flex", flexWrap: "wrap", gap: 14, marginBottom: 20 }}>
                {VISION_DETECT_CLASSES.map((cls) => (
                  <label key={cls} style={{ display: "flex", alignItems: "center", gap: 6, fontFamily: "var(--font-body)", fontSize: 13, color: "#000" }}>
                    <input type="checkbox" checked={detectClasses.includes(cls)} onChange={() => toggleClass(cls)} disabled={isReadOnly} />
                    {cls}
                  </label>
                ))}
              </div>
              <div style={{ display: "flex", justifyContent: "space-between", gap: 10 }}>
                <ActionButton danger readOnly={isReadOnly} disabled={busy} onClick={removeCamera}>
                  Remove camera from Cloud AI
                </ActionButton>
                <ActionButton disabled={busy} readOnly={isReadOnly} onClick={saveSettings}>
                  Save detection settings
                </ActionButton>
              </div>
            </React.Fragment>
          )}

          {tab === "zones" && camera && camera.id && (
            <ZoneEditor key={camera.id} unitId={detail.assetUnitId} camera={camera} isReadOnly={isReadOnly} />
          )}

          {tab === "events" && <CloudAiNodeEventsList nodeId={node.id} />}
        </React.Fragment>
      )}
      <div style={{ display: "flex", justifyContent: "flex-end", marginTop: 18, borderTop: "1px solid rgba(0,0,0,0.1)", paddingTop: 18 }}>
        <ActionButton onClick={onCancel}>Close</ActionButton>
      </div>
    </ModalShell>
  );
}

// Lighter, node-scoped events list (GET /vision-events?nodeId=) --
// reuses the shared EventDetailModal for the drilldown, same as
// VisionEventsTab does, but doesn't need that tab's own status filter
// dropdown since a single worker's event volume is small enough to just
// show everything.
function CloudAiNodeEventsList({ nodeId }) {
  const [events, setEvents] = useState([]);
  const [status, setStatus] = useState("loading");
  const [viewingEvent, setViewingEvent] = useState(null);

  const load = () => {
    setStatus("loading");
    fetch(`/api/admin/vision-events?nodeId=${nodeId}`, { credentials: "same-origin" })
      .then((r) => (r.ok ? r.json() : Promise.reject()))
      .then((data) => { setEvents(data.events || []); setStatus("ready"); })
      .catch(() => setStatus("error"));
  };
  useEffect(load, [nodeId]); // eslint-disable-line react-hooks/exhaustive-deps

  return (
    <React.Fragment>
      {status === "loading" && (
        <div style={{ fontFamily: "var(--font-body)", fontSize: 13, color: "rgba(0,0,0,0.5)" }}>Loading…</div>
      )}
      {status === "error" && <ModalError>Couldn't load events.</ModalError>}
      {status === "ready" && events.length === 0 && <EmptyNote>No events recorded for this camera yet.</EmptyNote>}
      {status === "ready" && events.length > 0 && (
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {events.map((ev) => (
            <div key={ev.id} onClick={() => setViewingEvent(ev)} role="button" style={{
              background: "rgba(0,0,0,0.03)", border: "1px solid rgba(0,0,0,0.12)",
              padding: "12px 16px", display: "flex", justifyContent: "space-between",
              alignItems: "center", flexWrap: "wrap", gap: 10, cursor: "pointer",
            }}>
              <div>
                <div style={{ fontFamily: "var(--font-body)", fontSize: 13.5, fontWeight: 600, color: "#000" }}>
                  {ev.event_type.replace(/_/g, " ")}
                  {ev.severity === "high" && <span style={{ marginLeft: 8, color: "rgba(170,30,30,0.9)", fontSize: 10, letterSpacing: "0.08em", textTransform: "uppercase" }}>High</span>}
                </div>
                <div style={{ fontFamily: "var(--font-body)", fontSize: 12, color: "rgba(0,0,0,0.5)", marginTop: 3 }}>
                  {ev.object_class || "—"} · {ev.started_at}
                </div>
              </div>
              <div style={{ fontFamily: "var(--font-body)", fontSize: 11, textTransform: "uppercase", letterSpacing: "0.08em", color: ev.feedback ? "rgba(20,120,50,0.9)" : "rgba(0,0,0,0.4)" }}>
                {ev.feedback ? ev.feedback.replace(/_/g, " ") : "Pending review"}
              </div>
            </div>
          ))}
        </div>
      )}

      {viewingEvent && (
        <EventDetailModal eventId={viewingEvent.id} onCancel={() => setViewingEvent(null)} onChanged={load} />
      )}
    </React.Fragment>
  );
}

Object.assign(window, { CloudAiWorkerManagement });
