// dashboard-proof-of-execution-board-monit.js — extracted verbatim from dashboard.js (pure move).

// ── Proof-of-execution board (Monitor → Proof) ─────────────────────────────
// Renders GET /api/admin/automations/proof: every automation's REAL-WORK
// verdict (not just "did the cron tick"). The product face of the receipt
// spine — green here means proven work, with counts + evidence, not a guess.
const PROOF_VERDICT = {
  worked: { label: "Working", dot: "#34d399", order: 2, hint: "did real work recently" },
  failing: { label: "Failing", dot: "#f87171", order: 0, hint: "ran but the WORK failed" },
  degraded: {
    label: "Blocked",
    dot: "#fbbf24",
    order: 1,
    hint: "precondition missing (not broken)",
  },
  noop: { label: "Idle", dot: "#7c8190", order: 4, hint: "nothing to do (legitimately)" },
  not_adopted: {
    label: "Unproven",
    dot: "#4b5563",
    order: 5,
    hint: "runs, but doesn't yet report real work",
  },
  never_ran: { label: "Never ran", dot: "#374151", order: 3, hint: "no heartbeat since deploy" },
};

function _proofAgeStr(iso) {
  if (!iso) return "never";
  const ms =
    Date.now() -
    new Date(
      String(iso).replace(" ", "T") + (/[zZ]|[+-]\d\d:?\d\d$/.test(iso) ? "" : "Z"),
    ).getTime();
  if (!Number.isFinite(ms)) return "never";
  if (ms < 90_000) return `${Math.round(ms / 1000)}s ago`;
  if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m ago`;
  if (ms < 86_400_000) return `${(ms / 3_600_000).toFixed(1)}h ago`;
  return `${Math.round(ms / 86_400_000)}d ago`;
}

function _proofJobRow(j) {
  const v = PROOF_VERDICT[j.verdict] || PROOF_VERDICT.not_adopted;
  const counts =
    j.attempted != null && j.attempted > 0
      ? `${j.succeeded ?? 0}/${j.attempted} ok${j.failed ? ` · ${j.failed} failed` : ""}`
      : "";
  const left = el(
    "div",
    { style: "display:flex;align-items:center;gap:10px;min-width:0;flex:1;" },
    [
      el("span", {
        style: `flex:0 0 8px;width:8px;height:8px;border-radius:50%;background:${v.dot};`,
      }),
      el("div", { style: "min-width:0;" }, [
        el("div", { style: "display:flex;align-items:center;gap:8px;" }, [
          el("span", { style: "font-weight:600;color:var(--text,#e6e6ee);" }, j.name),
          j.critical
            ? el(
                "span",
                {
                  style:
                    "font-size:10px;color:#fbbf24;border:1px solid #fbbf2455;border-radius:4px;padding:0 4px;",
                },
                "CRITICAL",
              )
            : null,
        ]),
        el(
          "div",
          { style: "font-size:12px;color:var(--text-dim,#9aa0ad);margin-top:2px;" },
          [
            counts ? counts + " · " : "",
            j.reason ? `${j.reason} · ` : "",
            _proofAgeStr(j.lastRunAt),
          ].join(""),
        ),
      ]),
    ],
  );
  const right = el("div", { style: "display:flex;align-items:center;gap:8px;flex:0 0 auto;" }, [
    el("span", { style: `font-size:11px;color:${v.dot};font-weight:600;` }, v.label.toUpperCase()),
    j.triggerable
      ? el(
          "button",
          {
            class: "btn btn-ghost btn-sm",
            title: "Run this automation now and refresh its receipt",
            style: "font-size:11px;padding:3px 8px;",
            onclick: async (e) => {
              const b = e.currentTarget;
              b.disabled = true;
              b.textContent = "running…";
              try {
                await api("admin-trigger", { body: { job: j.name } });
                await new Promise((r) => setTimeout(r, 1200));
                renderAutomationProof(); // refresh the board with the fresh receipt
              } catch (err) {
                b.textContent = "failed";
                b.title = err.message;
                b.disabled = false;
              }
            },
          },
          "▶ Run",
        )
      : null,
  ]);
  const row = el(
    "div",
    {
      style:
        "display:flex;align-items:center;gap:12px;padding:10px 12px;border-bottom:1px solid var(--border-soft,var(--border));",
    },
    [left, right],
  );

  // Evidence (proof refs) — collapsed line when present.
  if (Array.isArray(j.evidence) && j.evidence.length) {
    const ev = j.evidence
      .slice(0, 4)
      .map((e) => `${e.type}:${e.id ?? "?"}${e.path ? `(${e.path})` : ""}`)
      .join("  ");
    const evRow = el(
      "div",
      {
        style:
          "padding:0 12px 8px 30px;font-size:11px;color:var(--text-dim,#7c8190);font-family:ui-monospace,monospace;",
      },
      `${ev}${j.evidence.length > 4 ? ` +${j.evidence.length - 4}` : ""}`,
    );
    return el("div", {}, [row, evRow]);
  }
  return row;
}

async function renderAutomationProof() {
  $("#topbar-title") && ($("#topbar-title").textContent = "Proof of execution");
  const content = $("#content");
  content.innerHTML = "";
  const wrap = el("div", {
    style: "display:flex;flex-direction:column;gap:14px;max-width:920px;margin:0 auto;",
  });
  content.appendChild(wrap);

  wrap.appendChild(
    el(
      "div",
      { style: "display:flex;align-items:baseline;justify-content:space-between;gap:12px;" },
      [
        el("div", {}, [
          el("h2", { style: "margin:0;font-size:18px;" }, "Proof of execution"),
          el(
            "div",
            { style: "font-size:13px;color:var(--text-dim,#9aa0ad);margin-top:2px;" },
            "Did each automation do REAL work — not just “did the cron tick”. Green means proven, with counts + evidence.",
          ),
        ]),
        el(
          "button",
          { class: "btn btn-ghost btn-sm", onclick: () => renderAutomationProof() },
          "↻ Refresh",
        ),
      ],
    ),
  );

  let data;
  try {
    data = await api("automations-proof");
    if (data?.error) throw new Error(data.error);
  } catch (err) {
    wrap.appendChild(
      emptyState(
        "✅",
        "Couldn't load the proof board",
        err.message,
        el(
          "button",
          {
            class: "btn btn-sm",
            style: "margin-top:10px;",
            onclick: () => renderAutomationProof(),
          },
          "Retry",
        ),
      ),
    );
    return;
  }

  // Verdict rollup chips.
  const bv = data.summary?.byVerdict || {};
  const chipRow = el("div", { style: "display:flex;flex-wrap:wrap;gap:8px;" });
  for (const key of ["worked", "failing", "degraded", "noop", "not_adopted", "never_ran"]) {
    const v = PROOF_VERDICT[key];
    const n = bv[key] || 0;
    chipRow.appendChild(
      el(
        "div",
        {
          title: v.hint,
          style: `display:flex;align-items:center;gap:6px;padding:5px 10px;border-radius:999px;border:1px solid var(--border-soft,#2a2a38);background:var(--bg-card,#13131a);${n === 0 ? "opacity:.5;" : ""}`,
        },
        [
          el("span", { style: `width:8px;height:8px;border-radius:50%;background:${v.dot};` }),
          el("span", { style: "font-size:12px;color:var(--text,#e6e6ee);" }, `${v.label} ${n}`),
        ],
      ),
    );
  }
  wrap.appendChild(chipRow);

  // Jobs grouped by verdict (failing first, then blocked, worked, …).
  const jobs = (data.jobs || []).slice().sort((a, b) => {
    const oa = (PROOF_VERDICT[a.verdict] || {}).order ?? 9;
    const ob = (PROOF_VERDICT[b.verdict] || {}).order ?? 9;
    return oa - ob || b.critical - a.critical || String(a.name).localeCompare(b.name);
  });
  const card = el("div", {
    style:
      "background:var(--bg-card,#13131a);border:1px solid var(--border-soft,#2a2a38);border-radius:10px;overflow:hidden;",
  });
  for (const j of jobs) card.appendChild(_proofJobRow(j));
  wrap.appendChild(card);

  // Custom-workflow run outcomes (the user-facing automations).
  if (Array.isArray(data.workflows) && data.workflows.length) {
    wrap.appendChild(
      el(
        "h3",
        { style: "margin:8px 0 0;font-size:14px;color:var(--text-dim,#9aa0ad);" },
        "Your active workflows",
      ),
    );
    const wfCard = el("div", {
      style:
        "background:var(--bg-card,#13131a);border:1px solid var(--border-soft,#2a2a38);border-radius:10px;overflow:hidden;",
    });
    for (const w of data.workflows) {
      const oc = w.outcome || (w.status === "success" ? "worked" : w.status) || "never ran";
      const v = PROOF_VERDICT[oc] || PROOF_VERDICT.not_adopted;
      wfCard.appendChild(
        el(
          "div",
          {
            style:
              "display:flex;align-items:center;gap:10px;padding:9px 12px;border-bottom:1px solid var(--border-soft,var(--border));",
          },
          [
            el("span", { style: `width:8px;height:8px;border-radius:50%;background:${v.dot};` }),
            el(
              "span",
              { style: "flex:1;font-size:13px;color:var(--text,#e6e6ee);" },
              w.preset || w.name || `#${w.id}`,
            ),
            el(
              "span",
              { style: "font-size:12px;color:var(--text-dim,#9aa0ad);" },
              `${oc}${w.items_kept != null ? ` · kept ${w.items_kept}` : ""}`,
            ),
          ],
        ),
      );
    }
    wrap.appendChild(wfCard);
  }

  wrap.appendChild(
    el(
      "div",
      { style: "font-size:11px;color:var(--text-dim,#7c8190);text-align:center;" },
      `${(data.jobs || []).length} automations · generated ${_proofAgeStr(data.generatedAt)} · same data as \`node tools/proof-audit.js\``,
    ),
  );
}

async function renderLiveMap() {
  $("#topbar-title") && ($("#topbar-title").textContent = "Dashboard");
  const content = $("#content");
  content.innerHTML = "";

  // Load persisted prefs once per session (density / range / sound / filter).
  if (!liveMapState._prefsLoaded) {
    liveMapState._prefsLoaded = true;
    await loadLiveMapPrefs();
  }

  // Bootstrap: fetch live-map snapshot AND home (budget / today / contact funnel) in parallel.
  if (!liveMapState.data) {
    try {
      const [fresh, home] = await Promise.all([
        api("live-map"),
        api("dashboard-home").catch(() => ({})),
      ]);
      if (fresh?.error) throw new Error(fresh.error);
      liveMapState.data = fresh;
      liveMapState.homeData = home || {};
      liveMapState.lastFetchAt = new Date().toISOString();
      pushHistory(fresh);
      updateLiveMapNavBadges(liveMapState.homeData);
    } catch (err) {
      content.appendChild(
        el("div", { class: "empty-state" }, [
          el("div", { class: "empty-state-icon" }, icon("signal")),
          el("div", { class: "empty-state-title" }, "Couldn't load dashboard"),
          el("div", { class: "empty-state-hint" }, err.message),
        ]),
      );
      return;
    }
  }

  const data = liveMapState.data || {};
  const home = liveMapState.homeData || {};

  // ── Empty-state onboarding when no accounts connected ──
  const accts = data.accounts || {};
  const totalAccts =
    (accts.x?.length || 0) +
    (accts.linkedin?.length || 0) +
    (accts.telegram?.length || 0) +
    (accts.email?.length || 0);
  if (totalAccts === 0) {
    content.appendChild(renderLiveMapTopbar(data));
    content.appendChild(renderOnboardingEmpty(home));
    return;
  }
  const compact = liveMapState.density === "compact";
  const wrap = el("div", {
    class: compact ? "live-map-compact" : "",
    style: `display:flex;flex-direction:column;gap:${compact ? "8" : "12"}px;`,
  });

  wrap.appendChild(renderLiveMapTopbar(data));

  // 🤖 AI Daily Briefing — fetched lazily, cached server-side for 30 min
  wrap.appendChild(renderAiBriefingCard());
  if (!liveMapState.aiBriefing && !liveMapState.aiBriefingLoading) {
    loadAiBriefing(); // fire-and-forget; renders into the card when ready
  }

  // Insights bar — only renders if there's anything noteworthy
  const insights = computeInsights(data);
  if (insights.length > 0) wrap.appendChild(renderLiveMapInsights(insights));

  wrap.appendChild(renderLiveMapHeroStrip(data));
  wrap.appendChild(renderLiveMapPipelineStrip(data));
  wrap.appendChild(renderLiveMapPlatformLanes(data));

  // 3-column main grid
  const grid = el(
    "div",
    {
      style: `display:grid;grid-template-columns:1.1fr 1.2fr 0.9fr;gap:${compact ? "8" : "12"}px;align-items:start;`,
    },
    [
      renderLiveMapAutomations(data),
      renderLiveMapEventStream(data),
      renderLiveMapQueuesAndCampaigns(data),
    ],
  );
  wrap.appendChild(grid);

  // Platform deep-dive drawer (modal-style, slides over content)
  if (liveMapState.expandedLane) {
    wrap.appendChild(renderPlatformLaneDrawer(data, liveMapState.expandedLane));
  }

  // Cmd+K command palette renders into the persistent #cmdk-overlay
  // element (set up in dashboard.html) — not into the live-map node tree.

  content.appendChild(wrap);

  // Wire the global keyboard shortcut once
  wireCommandPaletteShortcut();

  // Auto-refresh loop — both endpoints in parallel
  if (liveMapState.pollHandle) clearTimeout(liveMapState.pollHandle);
  if (!liveMapState.paused) {
    liveMapState.pollHandle = setTimeout(async () => {
      try {
        const [fresh, home] = await Promise.all([
          api("live-map"),
          api("dashboard-home").catch(() => liveMapState.homeData),
        ]);
        if (!fresh?.error) {
          notifyOnNewHighPriorityEvents(fresh);
          liveMapState.data = fresh;
          liveMapState.homeData = home || liveMapState.homeData;
          liveMapState.lastFetchAt = new Date().toISOString();
          pushHistory(fresh);
          updateLiveMapNavBadges(liveMapState.homeData);
        }
      } catch {
        /* ignore — keep last snapshot */
      }
      // Re-render only if user is still on the dashboard route
      const route = location.hash.replace(/^#/, "");
      if (route === "" || route === "home" || route.startsWith("home")) renderLiveMap();
    }, 5000);
  }
}

// Keep sidebar badges in sync with current state. Same logic that renderHome used.
function updateLiveMapNavBadges(home) {
  const pending = home?.pending || {};
  const sigBadge = $("#nav-badge-signals");
  if (sigBadge) {
    sigBadge.textContent = pending.signals > 0 ? pending.signals : "";
    sigBadge.classList.toggle("hot", pending.signals > 0);
  }
  const crmBadge = $("#nav-badge-crm");
  if (crmBadge) crmBadge.textContent = home?.funnelTotal > 0 ? fmtNum(home.funnelTotal) : "";
}

// Empty state shown when no platform accounts are connected.
// Replaces the entire dashboard with the onboarding checklist from old renderHome.
function renderOnboardingEmpty(home) {
  const channels = home?.channels || [];
  const has = (p) => channels.some((c) => c.platform === p);

  const step = (num, title, desc, done, route) =>
    el(
      "div",
      {
        style: `display:flex;align-items:flex-start;gap:10px;padding:12px;border:1px solid var(--border);border-radius:6px;margin-bottom:8px;cursor:pointer;opacity:${done ? "0.55" : "1"};background:${done ? "var(--bg-2)" : "var(--bg-1)"};`,
        onclick: () => navigate(route),
      },
      [
        el(
          "div",
          {
            style: `width:28px;height:28px;border-radius:50%;background:${done ? "var(--green)" : "var(--accent)"};color:var(--accent-ink);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;flex-shrink:0;`,
          },
          done ? "✓" : num,
        ),
        el("div", { style: "flex:1;" }, [
          el("div", { style: "font-weight:600;font-size:13px;" }, title),
          el("div", { class: "muted small", style: "margin-top:2px;" }, desc),
        ]),
        el("span", { class: "muted" }, "→"),
      ],
    );

  return el("div", { class: "card", style: "padding:24px;max-width:680px;margin:20px auto;" }, [
    el(
      "div",
      { class: "page-title", style: "margin-bottom:6px;" },
      "Welcome — let's connect your first account",
    ),
    el(
      "div",
      { class: "muted small", style: "margin-bottom:18px;" },
      "Once an account is connected, this dashboard will fill with live activity, KPIs, and controls. Each platform unlocks new automation.",
    ),
    step(
      "1",
      "Connect an X / Twitter account",
      "Engage feeds, follow users, run siphon campaigns",
      has("twitter"),
      "accounts",
    ),
    step(
      "2",
      "Connect a LinkedIn account",
      "Connection requests, comments, signal pursuit",
      has("linkedin"),
      "accounts",
    ),
    step(
      "3",
      "Connect a Telegram account",
      "DM campaigns, broadcasts, group scrapes",
      has("telegram"),
      "accounts",
    ),
    step(
      "4",
      "Add an SMTP email account",
      "Email campaigns, sequences, warmup",
      has("email"),
      "accounts",
    ),
    step(
      "5",
      "Add signal keywords",
      "Auto-detect buying signals from matched keywords",
      false,
      "signals",
    ),
    step(
      "6",
      "Import contacts to CRM",
      "Upload CSV or scrape from Telegram groups",
      (home?.funnelTotal || 0) > 0,
      "crm",
    ),
  ]);
}

// Detect new high-priority events arriving between polls and pop a toast for each.
// Debounced: each event ID is only toasted once per session. Plays a sound if enabled.
function notifyOnNewHighPriorityEvents(fresh) {
  const events = fresh?.recentEvents || [];
  const platformIcons = {
    x: "🐦",
    twitter: "🐦",
    linkedin: "🔗",
    telegram: "✈",
    email: "✉",
    deals: "💰",
  };
  // persistNotifLog() writes the whole (already-trimmed) log, so one write
  // after the loop stores exactly what k writes inside the loop would.
  let logChanged = false;
  for (const ev of events) {
    if (!ev.ts) continue;
    if (Date.parse(ev.ts) < Date.now() - 60_000) continue; // only events from last minute
    if (!NOTIFY_KINDS.has(ev.kind)) continue;
    const evId = `${ev.source}-${ev.ts}-${ev.who || ""}`;
    if (liveMapState.notifiedEventIds.has(evId)) continue;
    liveMapState.notifiedEventIds.add(evId);
    const icon = platformIcons[ev.platform] || "•";
    const label =
      ev.kind === "reply_received" || ev.kind === "inbound"
        ? "Reply"
        : ev.kind === "deal_won"
          ? "Deal won"
          : ev.kind === "signal"
            ? "Signal"
            : ev.kind;
    const message = `${icon} ${label}: ${ev.who || "—"}${ev.what ? ` · ${ev.what.slice(0, 60)}` : ""}`;
    toast(message, ev.kind === "deal_won" ? "ok" : "info", 6000);
    if (liveMapState.soundEnabled) playNotificationSound(ev.kind);

    // Push to history log (so user can review missed notifications via the 🔔 bell)
    liveMapState.notificationLog.push({
      evId,
      ts: ev.ts,
      kind: ev.kind,
      platform: ev.platform,
      icon,
      label,
      who: ev.who,
      what: ev.what,
      drillTo: ev.drillTo,
      seenAt: new Date().toISOString(),
    });
    if (liveMapState.notificationLog.length > 50) liveMapState.notificationLog.shift();
    logChanged = true;
  }
  if (logChanged) persistNotifLog();
  if (liveMapState.notifiedEventIds.size > 200) {
    liveMapState.notifiedEventIds = new Set([...liveMapState.notifiedEventIds].slice(-150));
  }
}

// WebAudio chirp — no external asset needed. Three preset patterns.
let _audioCtx = null;
function playNotificationSound(kind) {
  try {
    _audioCtx = _audioCtx || new (window.AudioContext || window.webkitAudioContext)();
    const ctx = _audioCtx;
    if (ctx.state === "suspended") ctx.resume();

    // Pattern per event kind
    const pattern =
      kind === "deal_won"
        ? [880, 1175, 1568] // C-E-G triad ascending — celebratory
        : kind === "signal"
          ? [660, 990] // two short rising tones
          : kind === "test"
            ? [880] // single confirm chirp
            : [660, 880]; // reply / inbound — pleasant two-tone

    const now = ctx.currentTime;
    pattern.forEach((freq, i) => {
      const osc = ctx.createOscillator();
      const gain = ctx.createGain();
      osc.type = "sine";
      osc.frequency.setValueAtTime(freq, now + i * 0.12);
      gain.gain.setValueAtTime(0, now + i * 0.12);
      gain.gain.linearRampToValueAtTime(0.18, now + i * 0.12 + 0.02);
      gain.gain.linearRampToValueAtTime(0, now + i * 0.12 + 0.1);
      osc.connect(gain).connect(ctx.destination);
      osc.start(now + i * 0.12);
      osc.stop(now + i * 0.12 + 0.12);
    });
  } catch {
    /* audio unavailable — silently skip */
  }
}
