window.pageBoot = async (me) => {
  const state = { accounts: [] };

  const fmtDT = window.fmtDT || (s => s || "—");

  function statusBadge(a){
    if (a.cooldown_until && new Date(a.cooldown_until) > new Date())
      return `<span class="badge b-amber">cooldown</span>`;
    if (a.authorized) return `<span class="badge b-green">authorized</span>`;
    const m = {expired:"b-amber", revoked:"b-red", pending:"b-gray", error:"b-red"};
    return `<span class="badge ${m[a.status]||"b-gray"}">${esc(a.status)}</span>`;
  }

  function render(){
    const q = $("#accSearch").value.trim().toLowerCase();
    const rows = state.accounts.filter(a => !q || a.email.toLowerCase().includes(q));
    $("#accCount").textContent = String(state.accounts.length);
    $("#accEmpty").style.display = state.accounts.length ? "none" : "";
    $("#accTbl").style.display = state.accounts.length ? "" : "none";
    $("#accBody").innerHTML = rows.map(a => `
      <tr>
        <td>
          <div><b>${esc(a.email)}</b></div>
          <div class="mut" style="font-size:.8rem">
            refresh token: ${a.has_refresh_token ? "stored" : "none"}
            ${a.token_expires_at ? " · expires " + esc(fmtDT(a.token_expires_at)) : ""}
          </div>
        </td>
        <td>${statusBadge(a)}</td>
        <td><span class="badge b-blue">${esc((a.sending_method||"api").toUpperCase())}</span></td>
        <td class="mono">${a.daily_limit} / ${a.campaign_limit}</td>
        <td>${a.sends_today ?? 0} sent</td>
        <td class="mut" style="font-size:.82rem">${a.last_success_at ? esc(fmtDT(a.last_success_at)) : "—"}</td>
        <td>${a.error_count ? `<span class="badge b-red">${a.error_count}</span>
             <div class="mut" style="font-size:.78rem;max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${esc(a.last_error||"")}">${esc(a.last_error||"")}</div>` : "0"}</td>
        <td>
          <div class="btns" style="gap:6px">
            <button class="btn xs" data-test="${a.id}">Test</button>
            <button class="btn xs secondary" data-edit="${a.id}">Limits</button>
            <button class="btn xs secondary" data-reauth="${a.id}">Reauthorize</button>
            <button class="btn xs danger-o" data-disc="${a.id}">Disconnect</button>
          </div>
        </td>
      </tr>`).join("");
  }

  async function load(){
    const d = await GET("/gmail/accounts");
    state.accounts = d.accounts || [];
    render();
  }

  $("#accSearch").addEventListener("input", render);

  // ---- Connect flow (real Google OAuth start) ----
  $("#btnConnect").addEventListener("click", async () => {
    const btn = $("#btnConnect"); btn.disabled = true; btn.textContent = "Starting…";
    try {
      const d = await POST("/gmail/accounts/connect");
      const url = d.authorization_url || d.url || d.auth_url;
      if (!url) throw new Error("No authorization URL returned");
      openModal("Google Authorization", el("div", {},
        el("p", {style:"margin:0 0 10px"}, "A new tab will open to Google's consent screen. Grant access to the requested Gmail scopes, then return here — the account will appear automatically after the redirect completes."),
        el("p", {class:"mut", style:"font-size:.82rem;margin-bottom:12px"}, "Redirect URI: " + esc(url.split("redirect_uri=")[1] ? decodeURIComponent(url.split("redirect_uri=")[1].split("&")[0]) : "")),
        el("div", {class:"btns"},
          el("button", {class:"btn", onclick:() => { window.open(url, "_blank"); }}, "Open Google Consent"),
          el("button", {class:"btn secondary", onclick:async () => { await load(); toast("Account list refreshed"); }}, "I'm back — Refresh list"))));
      window.open(url, "_blank");
    } catch (e) {
      toast("Connect failed: " + e.message, "err");
    } finally { btn.disabled = false; btn.textContent = "Connect Gmail Account"; }
  });

  // ---- Import OAuth client config ----
  $("#btnImportCfg").addEventListener("click", () => {
    $("#cfgCard").style.display = $("#cfgCard").style.display === "none" ? "" : "none";
  });
  $("#cfgForm").addEventListener("submit", async (e) => {
    e.preventDefault();
    const f = $("#cfgFile").files[0];
    if (!f) return;
    const fd = new FormData(); fd.append("file", f);
    try {
      const d = await api("/gmail/accounts/import-config", { method: "POST", body: fd });
      $("#cfgRedirect").textContent = d.redirect_uri;
      toast(`Configuration imported (project: ${d.project_id}). You can now connect accounts.`, "ok");
      $("#cfgForm").reset();
    } catch (err) { toast("Import failed: " + err.message, "err"); }
  });

  // ---- Clear all ----
  $("#btnClearAll").addEventListener("click", () => {
    confirmedAction("Clear ALL Gmail accounts? This revokes tokens at Google and deletes all stored credentials for every connected account. Audit logs are preserved.", async () => {
      const d = await POST("/gmail/accounts/clear-all");
      toast(`Cleared ${d.removed} account(s); revoked at Google: ${d.revoked_at_google}`, "ok");
      await load();
    });
  });

  // ---- Row actions ----
  $("#accBody").addEventListener("click", async (e) => {
    const b = e.target.closest("button"); if (!b) return;
    const id = b.dataset.test || b.dataset.edit || b.dataset.reauth || b.dataset.disc;
    if (b.dataset.test) {
      b.disabled = true; b.textContent = "Testing…";
      try {
        const d = await POST(`/gmail/accounts/${id}/test`);
        if (d.ok) toast("Connection verified: " + (d.detail || "Gmail profile OK"), "ok");
        else toast("Test failed: " + (d.detail || d.error || "unknown"), "err");
        await load();
      } finally { b.disabled = false; b.textContent = "Test"; }
    }
    if (b.dataset.reauth) {
      try {
        const d = await POST(`/gmail/accounts/${id}/reauthorize`);
        if (d.authorization_url) {
          openModal("Reauthorize Account", el("div", {},
            el("p", {style:"margin:0 0 12px"}, "Open Google's consent screen to re-grant access. The token will be refreshed on completion."),
            el("div", {class:"btns"},
              el("button", {class:"btn", onclick:()=>window.open(d.authorization_url,"_blank")}, "Open Google Consent"),
              el("button", {class:"btn secondary", onclick:async()=>{await load(); toast("Refreshed");}}, "Refresh list"))));
          window.open(d.authorization_url, "_blank");
        }
      } catch (err) { toast("Reauthorize failed: " + err.message, "err"); }
    }
    if (b.dataset.edit) {
      const a = state.accounts.find(x => x.id == id);
      const dm = el("select", {class:"inp"}, ...[["api","Gmail API (recommended)"],["smtp","SMTP XOAUTH2"]].map(([v,l]) =>
        el("option", {value:v, selected:(a.sending_method||"api")===v}, l)));
      const dl = el("input", {class:"inp", type:"number", value:a.daily_limit, min:1, max:2000});
      const cl = el("input", {class:"inp", type:"number", value:a.campaign_limit, min:1, max:2000});
      openModal("Account Limits — " + a.email, el("div", {class:"form"},
        el("label", {}, "Sending method"), dm,
        el("label", {}, "Daily send limit (conservative recommended)"), dl,
        el("label", {}, "Per-campaign limit"), cl), {
        buttons: [{label:"Save", kind:"btn", onClick: async (close) => {
          const payload = { sending_method: dm.value, daily_limit: +dl.value, campaign_limit: +cl.value };
          await api(`/gmail/accounts/${id}`, { method: "PATCH", body: payload });
          toast("Limits updated", "ok"); close(); await load();
        }}]
      });
    }
    if (b.dataset.disc) {
      confirmedAction(`Disconnect this account? The stored OAuth token will be revoked at Google and deleted locally.`, async () => {
        const d = await api(`/gmail/accounts/${id}`, { method: "DELETE" });
        toast(`Disconnected (revoked at Google: ${d.revoked_at_google})`, "ok");
        await load();
      });
    }
  });

  await load();
};
=====RECIPIENTS.JS=====
window.pageBoot = async (me) => {
  const state = { lists: [], cur: null, page: 1, total: 0, pageSize: 50, data: [] };
  const fmtDT = window.fmtDT || (s => s || "—");

  async function loadLists(){
    const d = await GET("/recipients/lists");
    state.lists = d.lists || [];
    $("#listEmpty").style.display = state.lists.length ? "none" : "";
    $("#listTbl").style.display = state.lists.length ? "" : "none";
    $("#listBody").innerHTML = state.lists.map(l => `
      <tr>
        <td><b>${esc(l.name)}</b></td>
        <td class="mut">${esc(l.description || "")}</td>
        <td><span class="badge b-gray">${esc(l.source || "—")}</span></td>
        <td>${l.row_count}</td>
        <td class="mut">${esc(fmtDT(l.created_at))}</td>
        <td>
          <div class="btns" style="gap:6px">
            <button class="btn xs" data-view="${l.id}">View</button>
            <button class="btn xs secondary" data-import="${l.id}">Import</button>
            <button class="btn xs warn" data-clear="${l.id}">Clear</button>
            <button class="btn xs danger-o" data-del="${l.id}">Delete</button>
          </div>
        </td>
      </tr>`).join("");
  }

  async function loadRecs(){
    const list = state.cur; if (!list) return;
    const d = await GET(`/recipients/list?list_id=${list.id}&q=${encodeURIComponent($("#recSearch").value)}&page=${state.page}&limit=${state.pageSize}`);
    state.total = d.total; state.data = d.recipients || [];
    $("#recsTitle").textContent = `${list.name} — Recipients (${d.total})`;
    $("#recBody").innerHTML = state.data.map(r => `
      <tr>
        <td>${esc(r.email)}</td>
        <td>${esc(r.first_name || "")}</td>
        <td>${esc(r.last_name || "")}</td>
        <td>${esc(r.company_name || "")}</td>
        <td class="mut" style="font-size:.78rem">${esc(Object.keys(r.custom_fields||{}).join(", ") || "—")}</td>
        <td>${r.is_valid ? '<span class="badge b-green">valid</span>' : `<span class="badge b-red" title="${esc(r.validation_error||"")}">invalid</span>`}</td>
        <td><button class="btn xs danger-o" data-delrec="${r.id}">Del</button></td>
      </tr>`).join("") || `<tr><td colspan="7" class="mut" style="text-align:center;padding:18px">No recipients</td></tr>`;
    const pages = Math.max(1, Math.ceil(state.total / state.pageSize));
    $("#pager").innerHTML = [
      `<button class="btn xs secondary" data-pg="${state.page-1}" ${state.page<=1?"disabled":""}>← Prev</button>`,
      `<span class="mut" style="align-self:center;font-size:.85rem">Page ${state.page} / ${pages} · ${state.total} rows</span>`,
      `<button class="btn xs secondary" data-pg="${state.page+1}" ${state.page>=pages?"disabled":""}>Next →</button>`
    ].join("");
  }

  function renderSummary(s){
    return el("div", {class:"vsum"},
      el("div", {class:"box all"}, el("div",{class:"n"}, String(s.total)), "parsed"),
      el("div", {class:"box ok"}, el("div",{class:"n"}, String(s.valid)), "valid"),
      el("div", {class:"box bad"}, el("div",{class:"n"}, String(s.invalid)), "invalid"),
      el("div", {class:"box dup"}, el("div",{class:"n"}, String(s.duplicates)), "duplicates"),
      el("div", {class:"box sup"}, el("div",{class:"n"}, String(s.suppressed)), "suppressed"));
  }
  function sampleTable(title, rows, cols){
    if (!rows || !rows.length) return null;
    return el("div", {style:"margin-top:10px"},
      el("div", {style:"font-weight:600;margin-bottom:4px"}, title),
      el("div", {class:"tblwrap"},
        el("table", {class:"tbl"},
          el("thead",{},el("tr",{},...cols.map(c=>el("th",{},c)))),
          el("tbody",{},...rows.map(r=>el("tr",{},...cols.map(c=>el("td",{style:"font-size:.82rem"},esc(String(r[c]!==undefined&&r[c]!==null?r[c]:""))))))))));
  }

  function importModal(listId){
    const file = el("input", {type:"file", accept:".csv,.txt,text/csv,text/plain", required:"required"});
    const res = el("div");
    let parsed = null;
    const form = el("form", {class:"form", onsubmit: async (e) => {
      e.preventDefault();
      if (!file.files[0]) return;
      res.innerHTML = ""; res.appendChild(el("div",{class:"mut"},"Validating…"));
      const fd = new FormData(); fd.append("file", file.files[0]);
      try {
        const d = await api("/recipients/import-preview", { method:"POST", body: fd });
        parsed = d;
        res.innerHTML = "";
        res.appendChild(el("div",{style:"font-weight:600;margin-top:14px"}, `Validation report — ${esc(d.filename)}`));
        res.appendChild(renderSummary(d.summary));
        if (d.headers && d.headers.length)
          res.appendChild(el("div",{class:"mut",style:"font-size:.8rem"}, "Headers: " + esc(d.headers.join(", "))));
        const inv = sampleTable("Invalid (sample)", d.invalid_sample, ["email","error"]);
        const dup = sampleTable("Duplicates (sample)", d.duplicates_sample, ["email","error"]);
        const sup = sampleTable("Suppressed (sample)", d.suppressed_sample, ["email","error"]);
        [inv,dup,sup].forEach(n => n && res.appendChild(n));
        if (d.parse_errors && d.parse_errors.length)
          res.appendChild(el("div",{class:"notice warn",style:"margin-top:10px;font-size:.82rem"}, "Parse errors: " + esc(d.parse_errors.slice(0,5).join(" | "))));
      } catch (err) {
        res.innerHTML = "";
        res.appendChild(el("div",{class:"notice err"}, "Preview failed: " + esc(err.message)));
      }
    }},
      el("label",{}, "CSV or TXT file (JSON is rejected)"), file,
      el("div",{class:"btns"}, el("button",{class:"btn secondary",type:"submit"}, "Validate (preview only)")));
    openModal("Import Recipients", el("div", {},
      el("p",{class:"mut",style:"margin-top:0;font-size:.85rem"},
        "CSV must contain an <b>email</b> column (headers: email, first_name, last_name, company_name, company_phone + custom columns). TXT format: <span class='mono'>email, first, last</span> per line."),
      form, res), {
      wide: true,
      buttons: [{ label:"Commit import", kind:"btn", onClick: async (close) => {
          if (!file.files[0]) { toast("Choose a file first", "err"); return; }
          if (parsed && (parsed.summary.invalid > 0 || parsed.summary.duplicates > 0))
            toast("Note: only valid, non-duplicate rows will be imported", "warn");
          const fd = new FormData(); fd.append("file", file.files[0]);
          fd.append("list_id", listId);
          try {
            const d = await api("/recipients/import", { method:"POST", body: fd });
            toast(`Imported ${d.added} recipients (${d.duplicates} dup, ${d.invalid} invalid, ${d.suppressed} suppressed)`, "ok");
            close(); await loadLists(); if (state.cur) await loadRecs();
          } catch (err) { toast("Import failed: " + err.message, "err"); }
        }}]
    });
  }

  function manualModal(listId){
    const em = el("input", {class:"inp", type:"email", required:"required", placeholder:"name@example.com"});
    const fn = el("input", {class:"inp", placeholder:"First name"});
    const ln = el("input", {class:"inp", placeholder:"Last name"});
    const cn = el("input", {class:"inp", placeholder:"Company name"});
    const cf = el("input", {class:"inp", placeholder:'Custom fields JSON, e.g. {"Account":"ACC-991"}'});
    openModal("Add Recipient Manually", el("div", {class:"form"},
      el("label",{}, "Email"), em,
      el("label",{}, "First name"), fn,
      el("label",{}, "Last name"), ln,
      el("label",{}, "Company name"), cn,
      el("label",{}, "Custom fields (optional)"), cf), {
      buttons: [{ label:"Add", kind:"btn", onClick: async (close) => {
        let custom = {};
        try { if (cf.value.trim()) custom = JSON.parse(cf.value); } catch { toast("Custom fields must be valid JSON","err"); return; }
        const d = await POST("/recipients/manual", { list_id: listId, recipient: {
          email: em.value, first_name: fn.value, last_name: ln.value,
          company_name: cn.value, custom_fields: custom }});
        if (d.ok) { toast("Recipient added", "ok"); close(); await loadLists(); if (state.cur) await loadRecs(); }
        else {
          const s = d.summary || {};
          const why = (d.duplicates && d.duplicates[0] && d.duplicates[0].reason) ||
                      (d.invalid && d.invalid[0] && d.invalid[0].reason) ||
                      (d.suppressed && d.suppressed[0] && d.suppressed[0].reason) || "rejected";
          toast(`Rejected: ${why}`, "err");
        }
      }}]
    });
  }

  $("#btnNewList").addEventListener("click", () => {
    const nm = el("input", {class:"inp", required:"required", placeholder:"e.g. Q1 invoices"});
    const ds = el("input", {class:"inp", placeholder:"Description (optional)"});
    openModal("New Recipient List", el("div",{class:"form"}, el("label",{},"List name"), nm, el("label",{},"Description"), ds), {
      buttons: [{ label:"Create", kind:"btn", onClick: async (close) => {
        await POST("/recipients/lists", { name: nm.value, description: ds.value });
        toast("List created", "ok"); close(); await loadLists();
      }}]
    });
  });
  $("#btnImport").addEventListener("click", () => {
    if (!state.lists.length) { toast("Create a list first", "warn"); return; }
    const sel = el("select", {class:"inp"}, ...state.lists.map(l => el("option",{value:l.id}, `${l.name} (${l.row_count})`)));
    openModal("Import into which list?", el("div",{class:"form"}, el("label",{},"Target list"), sel), {
      buttons: [{ label:"Continue", kind:"btn", onClick:(close)=>{ const id=+sel.value; close(); importModal(id); }}]
    });
  });
  $("#btnManual").addEventListener("click", () => {
    if (!state.lists.length) { toast("Create a list first", "warn"); return; }
    const sel = el("select", {class:"inp"}, ...state.lists.map(l => el("option",{value:l.id}, `${l.name} (${l.row_count})`)));
    openModal("Add to which list?", el("div",{class:"form"}, el("label",{},"Target list"), sel), {
      buttons: [{ label:"Continue", kind:"btn", onClick:(close)=>{ const id=+sel.value; close(); manualModal(id); }}]
    });
  });

  $("#listBody").addEventListener("click", (e) => {
    const b = e.target.closest("button"); if (!b) return;
    const id = +(b.dataset.view || b.dataset.import || b.dataset.clear || b.dataset.del);
    if (b.dataset.view) { state.cur = state.lists.find(l=>l.id===id); state.page=1; $("#recsCard").style.display=""; loadRecs(); }
    if (b.dataset.import) importModal(id);
    if (b.dataset.clear) confirmedAction("Remove ALL recipients from this list? The list itself is kept.", async () => {
      await POST(`/recipients/lists/${id}/clear`); toast("List cleared", "ok"); await loadLists(); if (state.cur) await loadRecs();
    });
    if (b.dataset.del) confirmedAction("Delete this list and ALL its recipients?", async () => {
      await api(`/recipients/lists/${id}`, { method:"DELETE" }); toast("List deleted", "ok");
      if (state.cur && state.cur.id === id) { state.cur=null; $("#recsCard").style.display="none"; }
      await loadLists();
    });
  });
  $("#recBody").addEventListener("click", async (e) => {
    const b = e.target.closest("button"); if (!b || !b.dataset.delrec) return;
    await api(`/recipients/${b.dataset.delrec}`, { method:"DELETE" });
    toast("Recipient deleted", "ok"); await loadRecs(); await loadLists();
  });
  $("#pager").addEventListener("click", (e) => {
    const b = e.target.closest("button"); if (!b || b.disabled) return;
    state.page = +b.dataset.pg; loadRecs();
  });
  $("#recSearch").addEventListener("input", debounce(() => { state.page=1; loadRecs(); }, 400));

  await loadLists();
};
