// dashboard-missions-composable-bundles-of-p.js — extracted verbatim from dashboard.js (pure move).

// ── Missions: composable bundles of pinned automations ──────────────
//
// A mission is a localStorage-stored ordered list of pin ids (e.g.
// ["custom:42", "platform:li_engage_feed"]) under a user-given name.
// Clicking the mission chip runs all members in sequence (with small
// gaps so non-instant runners — e.g. chrome.tabs.create — don't pile
// up tabs). Replaces the Apple-Shortcuts-style "I run these 3 things
// together" use case without needing any backend changes.
//
// Storage shape: localStorage["automations:missions"] = JSON array of
//   { id: "mission:<uuid>",
//     name: "Q4 hiring push",
//     members: ["custom:42", "platform:x_competitor_siphon", ...],
//     createdAt: ISO string }
//
// Missions appear in the shortcuts row alongside individual pins. They
// have their own pin id format (mission:<uuid>) so the same chip-render
// path handles them — distinguished by the prefix when running.

const MISSIONS_KEY = "automations:missions";
const RUN_MISSION_GAP_MS = 600;

function getMissions() {
  try {
    const raw = localStorage.getItem(MISSIONS_KEY);
    if (!raw) return [];
    const arr = JSON.parse(raw);
    return Array.isArray(arr) ? arr : [];
  } catch {
    return [];
  }
}

function setMissions(list) {
  try {
    localStorage.setItem(MISSIONS_KEY, JSON.stringify(list));
  } catch (err) {
    console.warn("[automations] mission write failed:", err);
  }
}

function findMission(missionId) {
  return getMissions().find((m) => m.id === missionId) || null;
}

// Generate a short-but-unique-enough id without bringing in a uuid lib.
// 8 chars of base36 randomness is ~2.8 trillion combinations — plenty
// for a per-user, per-machine, never-synced collection.
function _newMissionId() {
  const r = Math.random().toString(36).slice(2, 10);
  return `mission:${r}`;
}

// Create a mission from an array of pin ids. Optionally clear the
// individual pins so they don't appear twice in the shortcuts row.
// Returns the new mission's id.
function createMission(name, memberPinIds, { clearPins = true } = {}) {
  const cleanName =
    String(name || "")
      .trim()
      .slice(0, 60) || "Untitled mission";
  const cleanMembers = (memberPinIds || []).map(String).filter(Boolean);
  if (cleanMembers.length === 0) return null;

  const id = _newMissionId();
  const list = getMissions();
  list.push({ id, name: cleanName, members: cleanMembers, createdAt: new Date().toISOString() });
  setMissions(list);

  if (clearPins) {
    const memberSet = new Set(cleanMembers);
    const pins = getPinnedAutomations().filter((p) => !memberSet.has(p.id));
    setPinnedAutomations(pins);
  }

  // Add a top-level pin for the mission so it surfaces in the shortcuts
  // row exactly like other pins. Same render path, different runner.
  togglePinnedAutomation(id, cleanName);
  return id;
}

function deleteMission(missionId) {
  const list = getMissions().filter((m) => m.id !== missionId);
  setMissions(list);
  // Also clean its pin entry from the shortcuts row.
  const pins = getPinnedAutomations().filter((p) => p.id !== missionId);
  setPinnedAutomations(pins);
}

// Run a mission: sequence through members, calling the same per-pin
// runner each one would invoke individually. A small delay between
// steps prevents tab-spam (chrome.tabs.create) and gives toasts time
// to render. Errors don't halt the sequence — each member is best-
// effort; a final summary toast surfaces failures.
async function runMission(missionId, ctx) {
  const m = findMission(missionId);
  if (!m) {
    toast("Mission no longer exists", "err");
    return;
  }
  toast(
    `Running mission "${m.name}" — ${m.members.length} step${m.members.length === 1 ? "" : "s"}…`,
    "info",
    2500,
  );
  logTelemetry("automations", "mission_run_start", { missionId, count: m.members.length });

  let succeeded = 0;
  let failed = 0;
  for (let i = 0; i < m.members.length; i++) {
    const memberPinId = m.members[i];
    // Guard: don't recurse into nested missions. Missions can be created
    // only from individual pins, but a hand-edited localStorage could
    // produce nesting. Skip rather than infinite-loop.
    if (String(memberPinId).startsWith("mission:")) {
      console.warn(`[mission] skipping nested mission member ${memberPinId}`);
      failed++;
      continue;
    }
    const resolved = resolvePinTarget(memberPinId, ctx);
    if (!resolved.target) {
      failed++;
      continue;
    }
    try {
      // Reuse the existing runner. Pass a tiny stub pin entry shape so
      // runPinnedAutomation routes correctly.
      runPinnedAutomation(
        { id: memberPinId, label: resolved.target.name || resolved.target.label || memberPinId },
        ctx,
      );
      succeeded++;
    } catch (err) {
      console.warn(`[mission] member ${memberPinId} failed:`, err);
      failed++;
    }
    // Pacing — skip the gap on the last step.
    if (i < m.members.length - 1) {
      await new Promise((r) => setTimeout(r, RUN_MISSION_GAP_MS));
    }
  }

  if (failed === 0) {
    toast(
      `Mission "${m.name}" complete — ${succeeded} step${succeeded === 1 ? "" : "s"}`,
      "ok",
      3000,
    );
  } else {
    toast(`Mission "${m.name}": ${succeeded} ok, ${failed} skipped/failed`, "err", 4000);
  }
  logTelemetry("automations", "mission_run_done", { missionId, succeeded, failed });
}

// Star button. Stops propagation so clicking pin doesn't open the card's
// drawer. Re-renders the cockpit shortcuts row on toggle so the user sees
// immediate feedback (the card jumping to row 3).
function buildPinButton(pinId, label) {
  const pinned = isPinnedAutomation(pinId);
  return el(
    "button",
    {
      type: "button",
      class: "automation-pin-btn",
      style: [
        "background:transparent",
        "border:none",
        "padding:2px 4px",
        "cursor:pointer",
        "font-size:14px",
        "line-height:1",
        pinned ? "color:#fbbf24" : "color:#52525b",
        "transition:color 120ms,transform 120ms",
      ].join(";"),
      title: pinned ? "Unpin from your shortcuts" : "Pin to your shortcuts",
      onclick: (e) => {
        e.stopPropagation();
        e.preventDefault();
        const nowPinned = togglePinnedAutomation(pinId, label);
        e.currentTarget.style.color = nowPinned ? "#fbbf24" : "#52525b";
        e.currentTarget.textContent = nowPinned ? "★" : "☆";
        e.currentTarget.title = nowPinned ? "Unpin from your shortcuts" : "Pin to your shortcuts";
        // Re-render the shortcuts row in place so the card "jumps" to it.
        const shortcutsMount = document.getElementById("cockpit-shortcuts-mount");
        if (shortcutsMount) {
          const fresh = buildCockpitShortcuts();
          shortcutsMount.replaceWith(
            fresh || el("div", { id: "cockpit-shortcuts-mount", style: "display:none;" }),
          );
        }
        logTelemetry("automations", nowPinned ? "pin" : "unpin", { id: pinId });
      },
      onmouseover: (e) => {
        e.currentTarget.style.transform = "scale(1.15)";
      },
      onmouseout: (e) => {
        e.currentTarget.style.transform = "scale(1)";
      },
    },
    pinned ? "★" : "☆",
  );
}

// Resolve a pinId back into the concrete data needed to render + run it.
// Returns null if the pin references something that no longer exists
// (e.g. archived custom workflow, removed platform action). Callers
// should fall back to displaying the pin's stored label as a graveyard
// entry and offer an "Unpin missing" affordance.
function resolvePinTarget(pinId, ctx) {
  const [type, ...rest] = String(pinId).split(":");
  const id = rest.join(":");
  if (type === "preset") return { type, id, target: ctx.presetById?.[id] || null };
  if (type === "custom") return { type, id, target: ctx.customById?.[id] || null };
  if (type === "platform") return { type, id, target: ctx.platformById?.[id] || null };
  if (type === "operation") return { type, id, target: ctx.operationById?.[id] || null };
  // Phase H — composer compositions are pinnable too
  if (type === "composition") return { type, id, target: ctx.compositionById?.[id] || null };
  if (type === "mission") return { type: "mission", id: pinId, target: findMission(pinId) };
  return { type: null, id: null, target: null };
}

// Run the action behind a pin. Each pin type has its own runner — these
// are the SAME calls the underlying card uses, so behaviour stays
// consistent (toasts, telemetry, error handling).
function runPinnedAutomation(pinEntry, ctx) {
  const resolved = resolvePinTarget(pinEntry.id, ctx);
  if (!resolved.target) {
    toast(`"${pinEntry.label}" is no longer available — unpinning`, "err", 3000);
    togglePinnedAutomation(pinEntry.id, pinEntry.label);
    const shortcutsMount = document.getElementById("cockpit-shortcuts-mount");
    if (shortcutsMount) {
      const fresh = buildCockpitShortcuts();
      shortcutsMount.replaceWith(
        fresh || el("div", { id: "cockpit-shortcuts-mount", style: "display:none;" }),
      );
    }
    return;
  }
  const t = resolved.target;
  if (resolved.type === "custom") {
    runAutomationNow(t);
  } else if (resolved.type === "preset") {
    // Presets aren't directly runnable — open the viewer so user can clone.
    navigate(`automations/view/${t.id}`);
  } else if (resolved.type === "composition") {
    // Phase H — composer compositions: only fire if active. Draft/paused
    // compositions open in canvas (so the user can configure or activate).
    if (t.status === "active") {
      api("composition-run", { id: t.id, body: {} })
        .then(() => toast(`▶ Triggered "${t.name}"`, "ok", 2200))
        .catch((err) => toast(`Run failed: ${err?.message || err}`, "err", 4000));
    } else {
      // Open in canvas so user can activate or edit
      dashboardState.composerDraft = t._raw || t;
      navigate("workflows/canvas");
    }
  } else if (resolved.type === "platform") {
    try {
      chrome.tabs.create({ url: t.launchUrl });
    } catch {
      window.open(t.launchUrl, "_blank");
    }
  } else if (resolved.type === "operation") {
    navigate(`new-workflow/${t.templateKey}`);
  } else if (resolved.type === "mission") {
    // Defensive: chip clicks normally route through runMission directly.
    // This branch handles a mission member that itself references another
    // mission (avoided in createMission but just in case the user hand-
    // edits localStorage). Skip — runMission already filters these out.
    runMission(pinEntry.id, ctx);
  }
}

// Cockpit Row 3 — Your shortcuts. Reads localStorage pins, resolves
// each against the current renderAutomations data context, and renders
// a horizontally-scrolling row of compact action chips. Empty state is
// a soft prompt rather than a wall of help text.
//
// Returns null when the user has 0 pins (we don't render the row at
// all — Hick's Law: don't surface noise). The `id="cockpit-shortcuts-mount"`
// wrapper is always present (even if collapsed) so toggles can swap it
// in place without re-rendering the whole page.

// Compute the inline status pill for a pin chip. Returns null when
// the pin type doesn't have runtime state worth surfacing (presets,
// platform actions, operations — they have no "last run" of their
// own). For customs we use the existing `last_run` summary already
// returned by automations-list, so this is zero-overhead — no extra
// fetches needed.
//
// Returns shape (or null):
//   { severity, icon, text, color, bg, border, tooltip, pill: true }
function chipStatusFor(pin, resolved) {
  if (!resolved?.target) return null;
  const t = resolved.target;

  // Phase H — Composer compositions: surface status pill + last-run time.
  if (resolved.type === "composition") {
    const cStatus = t.status || "draft";
    if (cStatus === "paused") {
      return {
        severity: "stale",
        icon: "pause",
        text: "paused",
        color: "#fde68a",
        bg: "rgba(234,179,8,0.10)",
        border: "rgba(234,179,8,0.35)",
        tooltip: `${pin.label} is paused — click to open in canvas`,
        pill: true,
      };
    }
    if (cStatus === "draft") {
      return {
        severity: "stale",
        icon: "edit",
        text: "draft",
        color: "#a7e8e8",
        bg: "rgba(90,218,218,0.10)",
        border: "rgba(90,218,218,0.35)",
        tooltip: `${pin.label} is a draft — click to open in canvas`,
        pill: true,
      };
    }
    if (cStatus === "archived") {
      return {
        severity: "stale",
        icon: "·",
        text: "archived",
        color: "#888",
        bg: "rgba(255,255,255,0.04)",
        border: "rgba(255,255,255,0.12)",
        tooltip: `${pin.label} is archived`,
        pill: true,
      };
    }
    // Active: surface last_run_at + total_replies if any
    if (!t.last_run_at) {
      return {
        severity: "stale",
        icon: "·",
        text: "never run",
        color: "#9ca3af",
        bg: "rgba(255,255,255,0.04)",
        border: "rgba(255,255,255,0.12)",
        tooltip: `${pin.label} is active but hasn't fired yet — click ▶ to trigger`,
        pill: true,
      };
    }
    const when = _liveFmtSince(t.last_run_at);
    const replies = Number(t.total_replies) || 0;
    const stat = replies > 0 ? `${replies} replies` : "ok";
    return {
      severity: "success",
      icon: "✓",
      text: `${when} · ${stat}`,
      color: "#86efac",
      bg: "rgba(34,197,94,0.08)",
      border: "rgba(34,197,94,0.3)",
      tooltip: `Last run ${when} ago — ${replies} replies, ${t.total_runs || 0} runs total`,
      pill: true,
    };
  }

  // ── Custom workflows: surface last_run status + relative time ──
  if (resolved.type === "custom") {
    const wfStatus = t.status || "draft";
    // Surface "DRAFT / PAUSED" as a soft pill so the user knows the
    // chip won't actually run on click.
    if (wfStatus === "paused") {
      return {
        severity: "stale",
        icon: "pause",
        text: "paused",
        color: "#fde68a",
        bg: "rgba(234,179,8,0.10)",
        border: "rgba(234,179,8,0.35)",
        tooltip: `${pin.label} is paused — click to view, or activate from its detail page`,
        pill: true,
      };
    }
    if (wfStatus === "draft") {
      return {
        severity: "stale",
        icon: "edit",
        text: "draft",
        color: "#c4b5fd",
        bg: "rgba(168,85,247,0.10)",
        border: "rgba(168,85,247,0.35)",
        tooltip: `${pin.label} is a draft — activate it to run`,
        pill: true,
      };
    }
    // Active custom: lean on last_run if present.
    const lr = t.last_run;
    if (!lr) {
      return {
        severity: "stale",
        icon: "·",
        text: "never run",
        color: "#9ca3af",
        bg: "rgba(255,255,255,0.04)",
        border: "rgba(255,255,255,0.12)",
        tooltip: `${pin.label} is active but hasn't fired yet — engine picks it up next cycle`,
        pill: true,
      };
    }
    const when = _liveFmtSince(lr.finished_at || lr.started_at);
    if (lr.status === "success") {
      const kept = Number(lr.items_kept) || 0;
      const proc = Number(lr.items_processed) || 0;
      // If we have items_kept, surface it tersely. Otherwise just "ok".
      const stat = kept > 0 ? `${kept} kept` : proc > 0 ? `${proc} ok` : "ok";
      return {
        severity: "success",
        icon: "✓",
        text: `${when} · ${stat}`,
        color: "#86efac",
        bg: "rgba(34,197,94,0.08)",
        border: "rgba(34,197,94,0.3)",
        tooltip: `Last run ${when} ago — ${proc} processed, ${kept} kept`,
        pill: true,
      };
    }
    if (lr.status === "error") {
      return {
        severity: "error",
        icon: "alert",
        text: `errored ${when}`,
        color: "#fca5a5",
        bg: "rgba(239,68,68,0.08)",
        border: "rgba(239,68,68,0.4)",
        tooltip: `Last run ${when} ago — failed. Click to view details.`,
        pill: true,
      };
    }
    if (lr.status === "running") {
      return {
        severity: "running",
        icon: "play",
        text: "running",
        color: "#86efac",
        bg: "rgba(34,197,94,0.10)",
        border: "rgba(34,197,94,0.4)",
        tooltip: `Currently running — started ${when} ago`,
        pill: true,
      };
    }
    if (lr.status === "skipped") {
      return {
        severity: "stale",
        icon: "·",
        text: `skipped ${when}`,
        color: "#94a3b8",
        bg: "rgba(255,255,255,0.04)",
        border: "rgba(255,255,255,0.12)",
        tooltip: `Last cycle was skipped ${when} ago — schedule override or no work to do`,
        pill: true,
      };
    }
    // Unknown status — just show time.
    return {
      severity: "stale",
      icon: "·",
      text: when,
      color: "#9ca3af",
      bg: "rgba(255,255,255,0.04)",
      border: "rgba(255,255,255,0.12)",
      tooltip: `Last run ${when} ago — status: ${lr.status || "unknown"}`,
      pill: true,
    };
  }

  // ── Presets: aggregated last_run across the user's customs of this
  //    preset (when automations-list returns it). Otherwise no pill —
  //    presets are templates, they don't run themselves.
  if (resolved.type === "preset") {
    const lr = t.last_run;
    if (lr && lr.finished_at) {
      const when = _liveFmtSince(lr.finished_at);
      const ok = lr.status === "success";
      return {
        severity: ok ? "success" : lr.status === "error" ? "error" : "stale",
        icon: ok ? "✓" : lr.status === "error" ? "⚠" : "·",
        text: when,
        color: ok ? "#86efac" : lr.status === "error" ? "#fca5a5" : "#9ca3af",
        bg: ok
          ? "rgba(34,197,94,0.08)"
          : lr.status === "error"
            ? "rgba(239,68,68,0.08)"
            : "rgba(255,255,255,0.04)",
        border: ok
          ? "rgba(34,197,94,0.3)"
          : lr.status === "error"
            ? "rgba(239,68,68,0.4)"
            : "rgba(255,255,255,0.12)",
        tooltip: `A clone of this preset last ran ${when} ago`,
        pill: true,
      };
    }
    return null;
  }

  // ── Platform actions, operations: no native run history. The chip
  //    icon (🌐 / 🛠) already signals type; nothing to add here.
  return null;
}

function buildCockpitShortcuts() {
  const pins = getPinnedAutomations();
  const ctx = window.__automationsRenderCtx || {};

  // Always-present mount node so pin toggles can replace it in-place.
  const mount = el("div", { id: "cockpit-shortcuts-mount" });

  if (pins.length === 0) {
    // Soft empty state — short, single-line, doesn't dominate.
    mount.appendChild(
      el(
        "div",
        {
          style: [
            "display:flex",
            "align-items:center",
            "gap:8px",
            "padding:8px 14px",
            "margin:6px 0 4px",
            "border:1px dashed rgba(251,191,36,0.18)",
            "border-radius:8px",
            "font-size:12px",
            "color:var(--text-2)",
          ].join(";"),
        },
        [
          el("span", { style: "color:#fbbf24;font-size:13px;" }, icon("star")),
          el("span", {}, "Pin your most-used automations to get one-click access."),
        ],
      ),
    );
    return mount;
  }

  const wrap = el("div", {
    style: [
      "display:flex",
      "align-items:center",
      "gap:8px",
      "padding:8px 14px",
      "margin:6px 0 8px",
      "border:1px solid rgba(251,191,36,0.18)",
      "border-radius:10px",
      "background:linear-gradient(135deg,rgba(251,191,36,0.06),rgba(251,191,36,0.02))",
      "overflow-x:auto",
    ].join(";"),
  });
  wrap.appendChild(
    el(
      "span",
      {
        style:
          "font-size:11px;color:var(--text-2);text-transform:uppercase;letter-spacing:0.05em;flex-shrink:0;",
      },
      "Shortcuts",
    ),
  );

  // Count individual (non-mission) pins to decide whether to show the
  // "Group into mission" affordance.
  const individualPins = pins.filter((p) => !p.id.startsWith("mission:"));

  for (const pin of pins) {
    const isMission = pin.id.startsWith("mission:");
    if (isMission) {
      // Mission chip — distinct color (orange-amber), shows step count,
      // runs all members in sequence on click.
      const mission = findMission(pin.id);
      const stepCount = mission ? mission.members.length : 0;
      const chip = el(
        "button",
        {
          type: "button",
          style: [
            "display:inline-flex",
            "align-items:center",
            "gap:6px",
            "padding:6px 12px",
            "background:rgba(251,146,60,0.12)",
            "border:1px solid rgba(251,146,60,0.4)",
            "border-radius:18px",
            "color:#fed7aa",
            "font-size:12px",
            "font-weight:600",
            "cursor:pointer",
            "white-space:nowrap",
            "flex-shrink:0",
            "transition:transform 120ms,border-color 120ms",
          ].join(";"),
          title: mission
            ? `Run mission "${mission.name}" — ${stepCount} step${stepCount === 1 ? "" : "s"} in sequence`
            : "Mission no longer exists — click ✕ to remove",
          onclick: (e) => {
            e.stopPropagation();
            if (mission) runMission(pin.id, ctx);
          },
          onmouseover: (e) => {
            e.currentTarget.style.transform = "translateY(-1px)";
          },
          onmouseout: (e) => {
            e.currentTarget.style.transform = "translateY(0)";
          },
        },
        [
          el("span", { style: "font-size:13px;" }, icon("target")),
          el("span", {}, pin.label || pin.id),
          el(
            "span",
            {
              style:
                "color:#fb923c;font-size:10px;font-family:ui-monospace,monospace;background:rgba(0,0,0,0.2);padding:1px 5px;border-radius:8px;margin-left:2px;",
            },
            `${stepCount}`,
          ),
          el(
            "span",
            {
              style: "color:var(--text-4);font-size:11px;cursor:pointer;padding-left:4px;",
              title: "Delete mission",
              onclick: (e) => {
                e.stopPropagation();
                if (
                  !confirm(`Delete mission "${pin.label}"? Members will return as individual pins.`)
                )
                  return;
                // Restore members as individual pins so user doesn't lose them.
                if (mission) {
                  const restored = getPinnedAutomations();
                  const ctxLocal = window.__automationsRenderCtx || {};
                  for (const memberId of mission.members) {
                    if (!restored.some((p) => p.id === memberId)) {
                      const r = resolvePinTarget(memberId, ctxLocal);
                      const label = r.target?.name || r.target?.label || memberId;
                      restored.push({ id: memberId, label, pinnedAt: new Date().toISOString() });
                    }
                  }
                  setPinnedAutomations(restored);
                }
                deleteMission(pin.id);
                const m = document.getElementById("cockpit-shortcuts-mount");
                if (m) {
                  const fresh = buildCockpitShortcuts();
                  m.replaceWith(
                    fresh || el("div", { id: "cockpit-shortcuts-mount", style: "display:none;" }),
                  );
                }
                logTelemetry("automations", "mission_delete", { id: pin.id });
              },
            },
            "✕",
          ),
        ],
      );
      wrap.appendChild(chip);
      continue;
    }

    // Individual pin chip (preset / custom / platform / operation).
    const resolved = resolvePinTarget(pin.id, ctx);
    const missing = !resolved.target;
    const status = chipStatusFor(pin, resolved);
    // Border accent reflects status — a failed pin shows a red rim, a
    // successful one stays neutral. Recognition over recall: scan the
    // row and instantly know what's healthy.
    const chipBorder = missing
      ? "rgba(75,85,99,0.3)"
      : status?.severity === "error"
        ? "rgba(239,68,68,0.5)"
        : status?.severity === "stale"
          ? "rgba(251,191,36,0.4)"
          : status?.severity === "running"
            ? "rgba(34,197,94,0.5)"
            : "rgba(99,102,241,0.35)";
    const chip = el(
      "button",
      {
        type: "button",
        style: [
          "display:inline-flex",
          "align-items:center",
          "gap:6px",
          "padding:6px 12px",
          "background:" + (missing ? "rgba(75,85,99,0.15)" : "rgba(99,102,241,0.12)"),
          "border:1px solid " + chipBorder,
          "border-radius:18px",
          "color:" + (missing ? "#9ca3af" : "#e5e7eb"),
          "font-size:12px",
          "font-weight:500",
          "cursor:" + (missing ? "default" : "pointer"),
          "white-space:nowrap",
          "flex-shrink:0",
          "transition:transform 120ms,border-color 120ms",
        ].join(";"),
        title: missing
          ? `"${pin.label}" no longer exists — click ✕ to unpin`
          : status?.tooltip || `Run ${pin.label}`,
        onclick: (e) => {
          e.stopPropagation();
          runPinnedAutomation(pin, ctx);
        },
        onmouseover: (e) => {
          if (!missing) e.currentTarget.style.transform = "translateY(-1px)";
        },
        onmouseout: (e) => {
          e.currentTarget.style.transform = "translateY(0)";
        },
      },
      [
        el(
          "span",
          { style: "font-size:11px;color:var(--text-2);" },
          pin.id.startsWith("custom:")
            ? "▶"
            : pin.id.startsWith("platform:")
              ? "🌐"
              : pin.id.startsWith("operation:")
                ? "🛠"
                : "📋",
        ),
        el("span", {}, pin.label || pin.id),
        // Status pill — inline last-run signal. Renders only when the
        // pin has runtime state worth surfacing (custom workflow last_run
        // OR currently-running). Presets, platform actions, and operations
        // don't have direct run state of their own.
        status?.pill
          ? el(
              "span",
              {
                style: [
                  "display:inline-flex",
                  "align-items:center",
                  "gap:3px",
                  "padding:1px 7px",
                  "margin-left:2px",
                  "background:" + status.bg,
                  "border:1px solid " + status.border,
                  "border-radius:9px",
                  "font-size:10px",
                  "font-weight:500",
                  "color:" + status.color,
                  "font-family:ui-monospace,monospace",
                ].join(";"),
              },
              [
                icon.for(status.icon) ? icon(icon.for(status.icon)) : el("span", {}, status.icon),
                el("span", {}, status.text),
              ],
            )
          : null,
        el(
          "span",
          {
            style: "color:var(--text-4);font-size:11px;cursor:pointer;padding-left:4px;",
            title: "Unpin",
            onclick: (e) => {
              e.stopPropagation();
              togglePinnedAutomation(pin.id, pin.label);
              const m = document.getElementById("cockpit-shortcuts-mount");
              if (m) {
                const fresh = buildCockpitShortcuts();
                m.replaceWith(
                  fresh || el("div", { id: "cockpit-shortcuts-mount", style: "display:none;" }),
                );
              }
            },
          },
          "✕",
        ),
      ],
    );
    wrap.appendChild(chip);
  }

  // "Group into mission" button — only when ≥2 individual pins exist.
  // The mission swallows the individual pins (createMission clears them
  // by default) so the row visibly compacts after grouping.
  if (individualPins.length >= 2) {
    wrap.appendChild(
      el(
        "button",
        {
          type: "button",
          style: [
            "display:inline-flex",
            "align-items:center",
            "gap:6px",
            "padding:6px 10px",
            "background:transparent",
            "border:1px dashed rgba(251,146,60,0.4)",
            "border-radius:18px",
            "color:#fed7aa",
            "font-size:11px",
            "font-weight:500",
            "cursor:pointer",
            "white-space:nowrap",
            "flex-shrink:0",
          ].join(";"),
          title: `Group these ${individualPins.length} pins into a single named mission you can run with one click`,
          onclick: (e) => {
            e.stopPropagation();
            const name = prompt(
              `Name this mission (will bundle ${individualPins.length} pins):`,
              "Quick run",
            );
            if (name === null) return; // user cancelled
            const trimmed = String(name).trim();
            if (!trimmed) {
              toast("Mission needs a name", "err");
              return;
            }
            const memberIds = individualPins.map((p) => p.id);
            const id = createMission(trimmed, memberIds, { clearPins: true });
            if (!id) {
              toast("Couldn't create mission", "err");
              return;
            }
            toast(`Mission "${trimmed}" created`, "ok", 2000);
            logTelemetry("automations", "mission_create", { count: memberIds.length });
            const m = document.getElementById("cockpit-shortcuts-mount");
            if (m) {
              const fresh = buildCockpitShortcuts();
              m.replaceWith(
                fresh || el("div", { id: "cockpit-shortcuts-mount", style: "display:none;" }),
              );
            }
          },
        },
        [
          el("span", { style: "font-size:13px;" }, icon("target")),
          el("span", {}, "Group into mission"),
        ],
      ),
    );
  }

  mount.appendChild(wrap);
  return mount;
}

// Cockpit Row 2 — Needs attention. Aggregates 5 categories from data
// already fetched by renderAutomations. Returns null when nothing
// needs flagging (avoid Zeigarnik fatigue from a perpetually-empty
// section). Capped at 5 items total so it can't dominate the page.
//
// Categories (in order of priority — "act on this NOW" first):
//   ✉  Replies waiting — high-signal inbound (DMs, replies, accepts)
//                        new since lastVisitAt OR last 24h on first visit
//   ⚠  Errors          — workflows with run.status=error in last 24h
//   🔴 Broken-verified — coverage.brokenPresets has any
//   💤 Stale wired     — active runners that haven't fired in 24h+
