// dashboard-linkedin-activity-view.js — extracted verbatim from dashboard.js (pure move).

// ── LinkedIn Activity view ────────────────────────────────────────
//
// Live feed of every LinkedIn action recorded in linkedin_action_log on
// the bot side, plus the in-extension session log. The user opens this
// when they want to know *why* an automation seems to have stopped working
// — broken selectors, expired cookies, 400 from a deprecated Voyager
// endpoint, etc. all show up here within seconds of happening.
//
// Mounted at #linkedin/activity. Polls /api/linkedin/log/recent every 5s
// while visible; stops polling on route change.

let _liActivityPollTimer = null;
let _liActivityLastId = 0;
let _liActivityFilter = { status: null, source: null, action: null };
const _LI_ACTIVITY_MAX_ROWS = 200;
let _liActivityRows = [];

function _stopLinkedInActivityPoll() {
  if (_liActivityPollTimer) {
    clearInterval(_liActivityPollTimer);
    _liActivityPollTimer = null;
  }
}

function _renderLinkedInActivityHeader(stats, health) {
  const ext = health?.extension;
  const sessions = health?.sessions || [];
  const activeSessions = sessions.filter((s) => s.status === "active");

  // Status pill: tab-open / heartbeat freshness
  let extStatus = "No heartbeat";
  let extDetail = "Extension hasn't checked in — pair via /pair";
  if (ext) {
    const ageS = Math.round((Date.now() - new Date(ext.last_seen_at).getTime()) / 1000);
    const ageHuman = ageS < 60 ? `${ageS}s` : `${Math.round(ageS / 60)}m`;
    if (ageS > 300) {
      extStatus = "Stale heartbeat";
      extDetail = `Last seen ${ageHuman} ago`;
    } else if (ext.tab_open) {
      extStatus = "Extension live";
      extDetail = `LinkedIn tab open · last seen ${ageHuman} ago`;
    } else {
      extStatus = "No LinkedIn tab";
      extDetail = `Extension running but no linkedin.com tab open (last seen ${ageHuman} ago)`;
    }
  }

  // Summary tiles
  const total = stats?.total ?? 0;
  const ok = stats?.ok ?? 0;
  const fail = stats?.fail ?? 0;
  const skip = stats?.skip ?? 0;
  const failRate = total > 0 ? Math.round((fail / total) * 100) : 0;

  // Top error codes (max 3)
  const topErrors = Object.entries(stats?.byErrorCode || {})
    .sort((a, b) => b[1] - a[1])
    .slice(0, 3);

  return el("div", { class: "li-activity-header" }, [
    el("div", { class: "li-activity-status-row" }, [
      el("div", { class: "li-activity-status-pill" }, [
        el("span", { class: "li-activity-status-text" }, extStatus),
        el("span", { class: "muted", style: "font-size: 12px;" }, " — " + extDetail),
      ]),
      el("div", { class: "li-activity-sessions" }, [
        el(
          "span",
          { class: "muted" },
          `${activeSessions.length} active session${activeSessions.length === 1 ? "" : "s"}`,
        ),
        activeSessions.length > 0
          ? el(
              "span",
              { style: "margin-left: 8px;" },
              activeSessions.map((s) => s.identifier).join(", "),
            )
          : null,
      ]),
    ]),
    el("div", { class: "li-activity-tiles" }, [
      el("div", { class: "li-activity-tile" }, [
        el("div", { class: "li-activity-tile-value" }, String(total)),
        el("div", { class: "li-activity-tile-label" }, "actions / 24h"),
      ]),
      el("div", { class: "li-activity-tile li-activity-tile-ok" }, [
        el("div", { class: "li-activity-tile-value" }, String(ok)),
        el("div", { class: "li-activity-tile-label" }, "ok"),
      ]),
      el("div", { class: "li-activity-tile li-activity-tile-fail" }, [
        el("div", { class: "li-activity-tile-value" }, String(fail)),
        el("div", { class: "li-activity-tile-label" }, `fail (${failRate}%)`),
      ]),
      el("div", { class: "li-activity-tile li-activity-tile-skip" }, [
        el("div", { class: "li-activity-tile-value" }, String(skip)),
        el("div", { class: "li-activity-tile-label" }, "skip"),
      ]),
    ]),
    topErrors.length > 0
      ? el("div", { class: "li-activity-top-errors" }, [
          el("span", { class: "muted" }, "Top errors: "),
          ...topErrors.map(([code, n]) =>
            el("span", { class: "li-activity-error-chip", title: code }, `${code} × ${n}`),
          ),
        ])
      : null,
  ]);
}

const _LI_FILTER_CHIPS = {
  status: [
    { value: null, label: "All" },
    { value: "ok", label: "ok" },
    { value: "fail", label: "fail" },
    { value: "skip", label: "skip" },
  ],
  source: [
    { value: null, label: "All" },
    { value: "voyager", label: "voyager" },
    { value: "extension", label: "extension" },
    { value: "health", label: "health" },
  ],
};

function _renderLinkedInActivityFilters() {
  const wrap = el("div", { class: "li-activity-filters" });
  for (const [field, options] of Object.entries(_LI_FILTER_CHIPS)) {
    wrap.appendChild(el("span", { class: "muted" }, `${field}:`));
    for (const opt of options) {
      const active = _liActivityFilter[field] === opt.value;
      wrap.appendChild(
        el(
          "button",
          {
            type: "button",
            class: `li-activity-chip${active ? " active" : ""}`,
            onclick: () => {
              _liActivityFilter[field] = opt.value;
              _liActivityLastId = 0;
              _liActivityRows = [];
              _refreshLinkedInActivity({ full: true });
            },
          },
          opt.label,
        ),
      );
    }
  }
  return wrap;
}

function _renderLinkedInActivityRow(row) {
  const time = new Date(row.created_at).toLocaleTimeString([], {
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
  });
  const icon =
    row.status === "ok" ? "🟢" : row.status === "fail" ? "🔴" : row.status === "skip" ? "⚪" : "🟡";
  const dur = row.duration_ms != null ? ` ${row.duration_ms}ms` : "";
  const codeChip = row.error_code
    ? el(
        "span",
        { class: "li-activity-error-chip", title: row.error_message || "" },
        row.error_code,
      )
    : null;

  const detail = row.error_message
    ? row.error_message
    : row.result_json
      ? _summarizeRow(row.result_json) || ""
      : "";

  // Pull a post/message link out of result_json (set by content-twitter's
  // addLog → _twRelayLog). Lets the persisted history link straight to the
  // tweet/profile/message, not just the ephemeral run drawer.
  let resultUrl = null;
  try {
    if (row.result_json) {
      const rj =
        typeof row.result_json === "string" ? JSON.parse(row.result_json) : row.result_json;
      resultUrl = rj?.url || null;
    }
  } catch {
    /* not JSON */
  }

  return el(
    "div",
    {
      class: `li-activity-row li-activity-row-${row.status}`,
      onclick: () => _openLinkedInActivityDrawer(row),
    },
    [
      el("span", { class: "li-activity-time" }, time),
      el("span", { class: "li-activity-icon" }, icon),
      el("span", { class: "li-activity-action" }, row.action),
      el("span", { class: "li-activity-status" }, row.status.toUpperCase()),
      el("span", { class: "li-activity-source muted" }, row.source + dur),
      codeChip,
      el("span", { class: "li-activity-detail muted" }, detail),
      // Trailing actions (right-aligned): 📸 replay this run's screenshots +
      // 🔗 the resulting post/message. run_id is present on rows from a driven run.
      row.run_id || resultUrl
        ? el(
            "span",
            { style: "margin-left:auto;display:flex;gap:8px;padding-left:8px;align-items:center;" },
            [
              row.run_id
                ? el(
                    "a",
                    {
                      href: "#",
                      title: "Replay this run (step screenshots)",
                      style: "color:#a78bfa;text-decoration:none;",
                      onclick: (e) => {
                        e.preventDefault();
                        e.stopPropagation();
                        _openRunReplayModal(row.run_id);
                      },
                    },
                    "📸",
                  )
                : null,
              resultUrl
                ? el(
                    "a",
                    {
                      href: resultUrl,
                      target: "_blank",
                      rel: "noopener noreferrer",
                      title: resultUrl,
                      style: "color:#6ea8ff;text-decoration:none;",
                      onclick: (e) => e.stopPropagation(),
                    },
                    "🔗",
                  )
                : null,
            ],
          )
        : null,
    ],
  );
}

function _summarizeRow(jsonStr) {
  try {
    const obj = JSON.parse(jsonStr);
    const bits = [];
    for (const k of [
      "results",
      "conversations",
      "posts",
      "new",
      "newMessages",
      "newContacts",
      "fetched",
      "step",
    ]) {
      if (obj[k] != null) bits.push(`${k}=${obj[k]}`);
    }
    return bits.join(" ");
  } catch {
    return "";
  }
}

function _openLinkedInActivityDrawer(row) {
  const title = `${row.action} — ${row.status.toUpperCase()}`;
  const $drawerTitle = document.getElementById("drawer-title");
  const $drawerBody = document.getElementById("drawer-body");
  if (!$drawerTitle || !$drawerBody) return;
  $drawerTitle.textContent = title;
  $drawerBody.innerHTML = "";

  const fields = [
    ["ID", String(row.id)],
    ["Time", row.created_at],
    ["Source", row.source],
    ["Status", row.status],
    ["Duration", row.duration_ms != null ? `${row.duration_ms}ms` : "—"],
    ["Account", row.account_identifier || "—"],
    ["Error code", row.error_code || "—"],
    ["Error message", row.error_message || "—"],
  ];
  const dl = el("dl", { class: "li-activity-drawer-dl" });
  for (const [k, v] of fields) {
    dl.appendChild(el("dt", {}, k));
    dl.appendChild(el("dd", {}, v));
  }
  $drawerBody.appendChild(dl);

  if (row.params_json) {
    $drawerBody.appendChild(el("h4", {}, "Params"));
    $drawerBody.appendChild(
      el("pre", { class: "li-activity-drawer-pre" }, _prettyJson(row.params_json)),
    );
  }
  if (row.result_json) {
    $drawerBody.appendChild(el("h4", {}, "Result"));
    $drawerBody.appendChild(
      el("pre", { class: "li-activity-drawer-pre" }, _prettyJson(row.result_json)),
    );
  }

  // Replay button — only meaningful for failed extension/Voyager rows.
  if (row.status === "fail" && (row.source === "extension" || row.source === "voyager")) {
    const btn = el(
      "button",
      {
        type: "button",
        class: "btn btn-primary",
        style: "margin-top: 12px;",
        onclick: () => _replayLinkedInActivity(row, btn),
      },
      "↻ Replay this action",
    );
    $drawerBody.appendChild(btn);
  }

  openDrawer();
}

function _prettyJson(maybeJson) {
  try {
    return JSON.stringify(JSON.parse(maybeJson), null, 2);
  } catch {
    return String(maybeJson);
  }
}

async function _replayLinkedInActivity(row, btn) {
  btn.textContent = "Replaying…";
  btn.disabled = true;
  try {
    let params = {};
    try {
      params = JSON.parse(row.params_json || "{}");
    } catch {
      /* leave empty */
    }
    // The "type" key is preserved when extension-side actions log themselves.
    const type = params?.type || (row.action.startsWith("http-") ? `linkedin:${row.action}` : null);
    if (!type) {
      btn.textContent = "Can't replay — no command type";
      return;
    }
    const result = await api("linkedin-command", { type, params: params.params || {} });
    btn.textContent = result?.error ? `Error: ${result.error}` : "Replayed — see feed";
  } catch (err) {
    btn.textContent = `Failed: ${err.message}`;
  }
}

async function _refreshLinkedInActivity({ full = false } = {}) {
  const params = {
    limit: _LI_ACTIVITY_MAX_ROWS,
    status: _liActivityFilter.status || undefined,
    source: _liActivityFilter.source || undefined,
    action: _liActivityFilter.action || undefined,
    stats: 1,
  };
  if (!full && _liActivityLastId > 0) params.since = _liActivityLastId;

  const [logRes, healthRes] = await Promise.all([
    api("li-server-log-recent", { params }),
    full ? api("li-health-summary") : Promise.resolve(null),
  ]);

  const newRows = logRes?.rows || null;
  // Incremental poll that brought nothing new (the steady state, 12x/minute):
  // the row buffer and the rendered feed are already exactly what a rebuild
  // would produce, so skip tearing down and re-creating up to 200 rows.
  if (!full && !newRows?.length) return;

  const hadRenderedRows = _liActivityRows.length > 0;
  if (newRows) {
    if (full) {
      _liActivityRows = newRows;
    } else {
      _liActivityRows = [...newRows, ..._liActivityRows].slice(0, _LI_ACTIVITY_MAX_ROWS);
    }
    if (newRows[0]) _liActivityLastId = Math.max(_liActivityLastId, newRows[0].id);
  }

  // Re-render the feed only — header is heavier, refresh on full only.
  const feedEl = document.getElementById("li-activity-feed");
  if (feedEl && !full && hadRenderedRows) {
    // Same rows in the same order a rebuild would give: prepend just the
    // genuinely-new rows (newest first) and drop the overflow off the tail.
    for (let i = newRows.length - 1; i >= 0; i--) {
      feedEl.insertBefore(_renderLinkedInActivityRow(newRows[i]), feedEl.firstChild);
    }
    while (feedEl.childElementCount > _liActivityRows.length)
      feedEl.removeChild(feedEl.lastElementChild);
  } else if (feedEl) {
    feedEl.innerHTML = "";
    if (_liActivityRows.length === 0) {
      feedEl.appendChild(
        el("div", { class: "empty-state", style: "padding: 32px;" }, [
          el("div", { class: "empty-state-icon" }, icon("inbox")),
          el("div", { class: "empty-state-title" }, "No LinkedIn actions yet"),
          el(
            "div",
            { class: "empty-state-hint" },
            "Run an action from the floating Nexus panel on linkedin.com, or send /lihealth in the bot to probe. Rows will appear here in real time.",
          ),
          el(
            "div",
            {
              style: "margin-top:16px;display:flex;gap:8px;justify-content:center;flex-wrap:wrap;",
            },
            [
              el(
                "button",
                { class: "btn btn-primary btn-tiny", onclick: () => navigate("objectives") },
                "Pick a goal",
              ),
              el(
                "button",
                {
                  class: "btn btn-secondary btn-tiny",
                  onclick: () => navigate("automations/linkedin"),
                },
                "LinkedIn automations",
              ),
            ],
          ),
        ]),
      );
    } else {
      for (const row of _liActivityRows) feedEl.appendChild(_renderLinkedInActivityRow(row));
    }
  }

  // Refresh header tiles on full or when stats came back.
  if (full && logRes?.stats && healthRes) {
    const headerEl = document.getElementById("li-activity-header-mount");
    if (headerEl) {
      headerEl.innerHTML = "";
      headerEl.appendChild(_renderLinkedInActivityHeader(logRes.stats, healthRes));
    }
  }
}

async function renderLinkedInActivity() {
  // Legacy entry — kept so #linkedin/activity deep links still work
  // until the redirect catches them. The standalone page is otherwise
  // identical to the LinkedIn tab inside #action-log.
  const root = el("div", { class: "li-activity-view", style: "padding: 16px;" });
  setContentNode(root);
  await _buildLiActivityBody(root, { routeName: "linkedin/activity" });
}
