window.pageBoot = async (me) => {
  /* ---------------- shared state ---------------- */
  let OPT = null;          // wizard options
  let mode = "list";       // list | monitor
  let editingId = null;    // editing existing campaign
  const ST_BADGE = { draft: "b-gray", queued: "b-blue", running: "b-amber",
                     paused: "b-violet", completed: "b-green",
                     cancelled: "b-gray", failed: "b-red" };
  const C = {};            // wizard state (CampaignIn mirror)
  const DEF = () => ({
    name: "", recipient_list_id: null, subject_set_ids: [], template_ids: [],
    message_type: "html", attach_mode: "none", attach_pdf: false,
    attach_pdf_template_id: null, attachment_id: null, variable_config: {},
    account_ids: [], rotation_strategy: "sequential", rotation_weights: {},
    subject_rotation: "round_robin", template_sequence_mode: "repeat",
    phone_selection: "round_robin", max_per_account: 200,
    send_interval_seconds: 1.0, rate_per_minute: 60, max_retries: 3,
    retry_delay_seconds: 60, unsubscribe_enabled: true, preview_sample_size: 5,
  });
  Object.assign(C, DEF());
  let step = 1;
  const STEPS = [
    "Campaign name", "Recipient list", "Subject lines", "Templates", "Attachments",
    "Variables & amounts", "Gmail accounts", "Rotation & weights", "Limits & pacing",
    "Company phone", "Unsubscribe", "Confirm & start",
  ];
  const STEPS_KEYS = ["name", "recipients", "subjects", "templates", "attachments",
                      "variables", "accounts", "rotation", "limits", "phone",
                      "unsubscribe", "confirm"];

  async function loadOptions() {
    const d = await GET("/campaigns/wizard/options");
    OPT = d;
    return d;
  }

  function stBadge(s) { return `<span class="badge ${ST_BADGE[s] || "b-gray"}">${esc(s || "—")}</span>`; }

  /* ==================== LIST PANE ==================== */
  let lst = { page: 1, status: "", q: "" };
  async function loadList() {
    const box = $("#campTable");
    box.innerHTML = `<div class="skeleton"></div><div class="skeleton" style="width:70%"></div>`;
    try {
      const p = new URLSearchParams({ status: lst.status, search: lst.q,
                                      page: lst.page, page_size: 20 });
      const d = await GET("/campaigns/list?" + p);
      const rows = d.campaigns || [];
      box.innerHTML = rows.length ? `<div class="tbl-wrap"><table class="tbl">
        <thead><tr><th>ID</th><th>Name</th><th>Status</th><th>Recipients</th><th>Sent</th><th>Failed</th><th>Suppressed</th><th>Skipped</th><th>Started</th><th>Actions</th></tr></thead>
        <tbody>${rows.map(c => `<tr>
          <td class="mono-s">${c.id}</td>
          <td><strong>${esc(c.name)}</strong></td>
          <td>${stBadge(c.status)}</td>
          <td class="center">${c.total_recipients ?? 0}</td>
          <td class="center" style="color:var(--green);font-weight:600">${c.sent_count ?? 0}</td>
          <td class="center" style="color:var(--red);font-weight:600">${c.failed_count ?? 0}</td>
          <td class="center">${c.suppressed_count ?? 0}</td>
          <td class="center">${c.skipped_count ?? 0}</td>
          <td class="nowrap small">${fmtDT(c.started_at)}</td>
          <td class="nowrap">
            <button class="btn xs" data-mon="${c.id}">Monitor</button>
            ${["draft", "paused"].includes(c.status) ? `<button class="btn xs secondary" data-edit="${c.id}">Edit</button>` : ""}
            <button class="btn xs secondary" data-prev="${c.id}">Preview</button>
            <button class="btn xs danger-o" data-del="${c.id}">Delete</button>
          </td></tr>`).join("")}</tbody></table></div>`
        : `<div class="empty-state"><div class="e-ico">✦</div><p>No campaigns yet. Use the wizard to create your first campaign.</p></div>`;
      const pages = Math.max(1, Math.ceil(d.total / 20));
      $("#campPager").innerHTML = pages > 1 ?
        `<button class="btn sm secondary" ${lst.page <= 1 ? "disabled" : ""} data-pg="${lst.page - 1}">‹ Prev</button>
         <span class="small muted">Page ${lst.page} of ${pages} · ${d.total} campaigns</span>
         <button class="btn sm secondary" ${lst.page >= pages ? "disabled" : ""} data-pg="${lst.page + 1}">Next ›</button>` : "";
      $$("#campPager [data-pg]").forEach(b => b.onclick = () => { lst.page = +b.dataset.pg; loadList(); });
      $$("#campTable [data-mon]").forEach(b => b.onclick = () => openMonitor(+b.dataset.mon));
      $$("#campTable [data-edit]").forEach(b => b.onclick = () => openWizard(+b.dataset.edit));
      $$("#campTable [data-prev]").forEach(b => b.onclick = () => campaignPreview(+b.dataset.prev));
      $$("#campTable [data-del]").forEach(b => b.onclick = () =>
        confirmedAction(`Delete campaign #${b.dataset.del}? This removes the campaign and its recipient assignments. Logs are kept.`,
          async () => { await POST(`/campaigns/${b.dataset.del}/delete`); loadList(); },
          { title: "Delete campaign", confirmLabel: "Delete" }));
    } catch (e) { box.innerHTML = `<div class="notice err"><span class="n-ico">✕</span><div>${esc(e.message)}</div></div>`; }
  }

  /* ==================== MONITOR PANE ==================== */
  let MON = { id: null, es: null, page: 1, statusF: "", q: "" };
  function openMonitor(cid) {
    MON.id = cid;
    setMode("monitor");
  }
  function setMode(m) {
    mode = m;
    $$("#modeTabs .mode-tab").forEach(t => t.classList.toggle("active", t.dataset.mode === m));
    $("#listPane").classList.toggle("hidden", m !== "list");
    $("#monPane").classList.toggle("hidden", m !== "monitor");
    if (m === "list") { stopStream(); }
    if (m === "monitor" && MON.id) { refreshMonSel(); startMonitor(); }
  }
  async function refreshMonSel() {
    try {
      const d = await GET("/campaigns/list?page_size=100");
      $("#monSel").innerHTML = (d.campaigns || []).map(c =>
        `<option value="${c.id}" ${c.id === MON.id ? "selected" : ""}>#${c.id} — ${esc(c.name)} (${c.status})</option>`).join("");
    } catch {}
    $("#monSel").onchange = () => { MON.id = +$("#monSel").value; startMonitor(); };
  }
  function stopStream() {
    if (MON.es) { MON.es.close(); MON.es = null; }
    $("#liveInd").classList.add("hidden");
  }
  async function startMonitor() {
    if (!MON.id) return;
    stopStream();
    const body = $("#monBody");
    body.innerHTML = `<div class="skeleton"></div><div class="skeleton" style="width:60%"></div>`;
    await drawProgress();     // initial paint via REST
    await drawRecipients();
    drawControls();
    // SSE
    MON.es = new EventSource(`/campaigns/${MON.id}/stream`);
    MON.es.addEventListener("connected", () => $("#liveInd").classList.remove("hidden"));
    MON.es.addEventListener("progress", ev => { try { paintProgress(JSON.parse(ev.data)); } catch {} });
    MON.es.addEventListener("done", ev => {
      try { paintProgress(JSON.parse(ev.data)); } catch {}
      stopStream();
      toast(`Campaign finished — status: ${(JSON.parse(ev.data) || {}).status || "?"}`, "info", 6000);
      drawControls();
    });
    MON.es.onerror = () => { $("#liveInd").classList.add("hidden"); };
  }
  async function drawProgress() {
    try { const p = await GET(`/campaigns/${MON.id}/progress`); paintProgress(p); }
    catch (e) { $("#monBody").innerHTML = `<div class="notice err"><span class="n-ico">✕</span><div>${esc(e.message)}</div></div>`; }
  }
  function paintProgress(p) {
    let box = $("#progBox");
    if (!box) {
      box = el("div", { id: "progBox" });
      $("#monBody").prepend(box);
    }
    const meterSegs = Math.min(p.total || 0, 400);
    const seg = (cls, n) => `<span class="${cls}" style="flex:${Math.max(n, 0)}"></span>`;
    const sum = p.sent + p.perm_failed + p.suppressed + p.skipped;
    box.innerHTML = `
      <div class="card mb-2"><div class="card-bd">
        <div class="flex-between mb-1">
          <div class="flex">
            <strong id="monName">#${p.campaign_id}</strong>
            ${stBadge(p.status)}
            <span class="small muted">${p.started_at ? "started " + fmtDT(p.started_at) : "not started"}</span>
            ${p.finished_at ? `<span class="small muted">· finished ${fmtDT(p.finished_at)}</span>` : ""}
          </div>
          <span class="small muted">${p.progress_pct}% complete</span>
        </div>
        <div class="pbar ${p.status === "completed" ? "ok" : ""}"><div style="width:${Math.min(p.progress_pct, 100)}%"></div></div>
        <div class="meter mt-2" title="sent / permanent fail / suppressed / skipped / in-flight">${meterSegs("sent", p.sent)}${meterSegs("fail", p.perm_failed)}${meterSegs("supp", p.suppressed)}${meterSegs("skip", p.skipped)}${meterSegs("proc", (p.total || 0) - sum)}</div>
        <div class="stat-grid mt-2">
          <div class="cell"><div class="v">${p.sent}</div><div class="l">Accepted & sent</div></div>
          <div class="cell"><div class="v">${p.temp_failed}</div><div class="l">Temp failed</div></div>
          <div class="cype cell"><div class="v">${p.perm_failed}</div><div class="l">Perm failed</div></div>
          <div class="cell"><div class="v">${p.suppressed}</div><div class="l">Suppressed</div></div>
          <div class="cell"><div class="v">${p.skipped}</div><div class="l">Skipped</div></div>
          <div class="cell"><div class="v">${p.pending}</div><div class="l">Pending</div></div>
          <div class="cell"><div class="v">${p.queued}</div><div class="l">Queued</div></div>
          <div class="cell"><div class="v">${p.total}</div><div class="l">Total</div></div>
        </div>
        <div class="small muted mt-1">${p.jobs_total} send jobs · delivery attempts are retried with backoff; temporary failures never masquerade as success.</div>
      </div></div>`;
    drawControls();
  }
  function drawControls() {
    const ctl = $("#monCtl");
    if (!ctl) return;
    fetch(`/campaigns/${MON.id}`).then(r => r.json()).then(c => {
      ctl.innerHTML = "";
      const mk = (label, cls, fn, confirmMsg) => {
        const b = el("button", { class: "btn sm " + cls, onclick: () =>
          confirmMsg ? confirmedAction(confirmMsg, fn, { title: label }) : fn() }, label);
        ctl.append(b);
      };
      if (c.status === "draft") mk("Start", "", async () => { await POST(`/campaigns/${MON.id}/start`); toast("Campaign started", "ok"); startMonitor(); });
      if (c.status === "running") mk("Pause", "secondary", async () => { await POST(`/campaigns/${MON.id}/pause`); toast("Campaign paused", "ok"); startMonitor(); });
      if (c.status === "paused") mk("Resume", "", async () => { await POST(`/campaigns/${MON.id}/resume`); toast("Campaign resumed", "ok"); startMonitor(); });
      if (["running", "paused", "queued", "draft"].includes(c.status))
        mk("Cancel", "danger-o", async () => { await POST(`/campaigns/${MON.id}/cancel`); toast("Campaign cancelled", "warn"); startMonitor(); },
          `Cancel campaign #${MON.id}? In-flight messages already accepted by Gmail cannot be recalled.`);
      const ex = el("button", { class: "btn sm secondary", onclick: () => { window.location = `/campaigns/${MON.id}/export`; } }, "Export CSV");
      const pv = el("button", { class: "btn sm secondary", onclick: () => campaignPreview(MON.id) }, "Preview");
      ctl.append(ex, pv);
    }).catch(() => {});
  }
  async function drawRecipients() {
    let box = $("#recBox");
    if (!box) { box = el("div", { id: "recBox" }); $("#monBody").append(box); }
    await loadRecipients(box);
  }
  async function loadRecipients(box) {
    const p = new URLSearchParams({ status: MON.statusF, search: MON.q,
                                    page: MON.page, page_size: 50 });
    let d;
    try { d = await GET(`/campaigns/${MON.id}/recipients?` + p); }
