/* ============================================================
   Shared app core: API client (CSRF), auth guard, layout chrome,
   toasts, modals, confirm dialogs, helpers.
   ============================================================ */
"use strict";

/* ---------- tiny DOM helpers ---------- */
const $ = (sel, root) => (root || document).querySelector(sel);
const $$ = (sel, root) => Array.from((root || document).querySelectorAll(sel));
function el(tag, attrs = {}, ...children) {
  const node = document.createElement(tag);
  for (const [k, v] of Object.entries(attrs)) {
    if (k === "class") node.className = v;
    else if (k === "html") node.innerHTML = v;
    else if (k.startsWith("on") && typeof v === "function") node.addEventListener(k.slice(2), v);
    else if (v !== null && v !== undefined) node.setAttribute(k, v);
  }
  for (const c of children) {
    if (c === null || c === undefined) continue;
    node.append(c instanceof Node ? c : document.createTextNode(String(c)));
  }
  return node;
}
function esc(s) {
  return String(s ?? "").replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
}
function debounce(fn, ms = 300) {
  let t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms); };
}
function fmtBytes(n) {
  if (n == null) return "—";
  if (n < 1024) return n + " B";
  if (n < 1048576) return (n / 1024).toFixed(1) + " KB";
  return (n / 1048576).toFixed(2) + " MB";
}
function fmtDT(iso) {
  if (!iso) return "—";
  const d = new Date(iso);
  return d.toLocaleString([], { year: "numeric", month: "short", day: "2-digit", hour: "2-digit", minute: "2-digit" });
}
function fmtMoney(n) {
  if (n == null) return "—";
  return "$" + Number(n).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function plural(n, w) { return `${n} ${w}${n === 1 ? "" : "s"}`; }

/* ---------- API client ---------- */
const CSRF = {
  get() { return getCookie("mailer_csrf"); },
};
function getCookie(name) {
  return document.cookie.split("; ").find(r => r.startsWith(name + "="))?.split("=").slice(1).join("=") || "";
}

async function api(path, opts = {}) {
  const headers = Object.assign({}, opts.headers || {});
  if (opts.json !== undefined) {
    headers["Content-Type"] = "application/json";
    opts.body = JSON.stringify(opts.json);
    delete opts.json;
  }
  if ((opts.method || "GET").toUpperCase() !== "GET") headers["X-CSRF-Token"] = CSRF.get();
  const res = await fetch(path, Object.assign({}, opts, { headers, credentials: "same-origin" }));
  let data = null;
  try { data = await res.json(); } catch { /* no body */ }
  if (res.status === 401 && !location.pathname.startsWith("/login")) {
    location.href = "/login?next=" + encodeURIComponent(location.pathname);
    throw new Error("Not authenticated");
  }
  if (!res.ok) {
    const detail = data && (data.detail?.message || data.detail || data.error);
    const err = new Error(typeof detail === "string" ? detail : (res.status + " " + res.statusText));
    err.status = res.status;
    err.data = data;
    throw err;
  }
  return data;
}
const GET = p => api(p);
const POST = (p, json) => api(p, { method: "POST", json });

function b64Blob(b64, mime) {
  const bin = atob(b64);
  const arr = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
  return new Blob([arr], { type: mime });
}
function dlBlob(blob, filename) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url; a.download = filename;
  document.body.appendChild(a); a.click();
  setTimeout(() => { URL.revokeObjectURL(url); a.remove(); }, 1500);
}
const PATCH = (p, json) => api(p, { method: "PATCH", json });
const PUT = (p, json) => api(p, { method: "PUT", json });
const DEL = p => api(p, { method: "DELETE" });

/* ---------- Toasts ---------- */
function toast(msg, kind = "info", ms = 4200) {
  let box = $("#toasts");
  if (!box) { box = el("div", { id: "toasts" }); document.body.append(box); }
  const icons = { info: "ℹ", ok: "✓", err: "✕", warn: "⚠" };
  const t = el("div", { class: "toast " + (kind === "info" ? "" : kind) },
    el("span", { class: "n-ico" }, icons[kind] || "ℹ"),
    el("span", {}, msg));
  box.append(t);
  setTimeout(() => { t.style.opacity = "0"; t.style.transition = "opacity .3s"; setTimeout(() => t.remove(), 320); }, ms);
  return t;
}

/* ---------- Modal / confirm ---------- */
function openModal(title, bodyNode, { buttons = [], wide = false, xw = false, onClose } = {}) {
  const back = el("div", { class: "modal-back open" });
  const modal = el("div", { class: "modal" + (wide ? " wide" : "") + (xw ? " xw" : "") });
  const close = () => { back.remove(); document.body.style.overflow = ""; onClose && onClose(); };
  const hd = el("div", { class: "m-hd" }, el("h3", {}, title), el("button", { class: "x-btn", onclick: close }, "×"));
  const ft = el("div", { class: "m-ft" });
  if (!buttons.length) buttons = [{ label: "Close", onClick: close }];
  for (const b of buttons) {
    ft.append(el("button", { class: "btn " + (b.cls || b.kind || "secondary"), onclick: () => b.onClick ? b.onClick(close, modal) : close() }, b.label));
  }
  modal.append(hd, el("div", { class: "m-bd" }, bodyNode), ft);
  back.append(modal);
  back.addEventListener("mousedown", e => { if (e.target === back) close(); });
  document.body.append(back);
  document.body.style.overflow = "hidden";
  return { close, modal };
}

function confirmDialog(message, { title = "Are you sure?", danger = true, confirmLabel = "Confirm", onConfirm } = {}) {
  const p = el("p", { class: "small" }, message);
  return openModal(title, p, {
    buttons: [
      { label: "Cancel", cls: "secondary", onClick: c => c() },
      { label: confirmLabel, cls: danger ? "danger" : "primary", onClick: c => { c(); onConfirm && onConfirm(); } },
    ],
  });
}

/* confirm wrapper for Clear-All style destructive API calls */
async function confirmedAction(message, fn, { title, confirmLabel = "Yes, proceed" } = {}) {
  return new Promise(resolve => {
    confirmDialog(message, { title, confirmLabel, onConfirm: async () => {
      try {
        const r = await fn();
        toast(typeof r === "string" ? r : "Done", "ok");
        resolve(r);
      } catch (e) {
        toast(e.message, "err", 6000);
        resolve(null);
      }
    } });
  });
}

/* ---------- Layout chrome ---------- */
const NAV = [
  { section: "Overview" },
  { id: "index",  ico: "◧", label: "Dashboard", href: "/" },
  { id: "campaigns", ico: "✦", label: "Campaigns", href: "/campaigns" },
  { section: "Sending" },
  { id: "gmail-accounts", ico: "✉", label: "Gmail Accounts", href: "/gmail-accounts" },
  { id: "recipients", ico: "☰", label: "Recipients", href: "/recipients" },
  { id: "send-test", ico: "➤", label: "Send Test", href: "/send-test" },
  { id: "logs", ico: "≣", label: "Logs", href: "/logs" },
  { section: "Content" },
  { id: "subjects", ico: "T", label: "Subject Lines", href: "/subjects" },
  { id: "templates", ico: "❑", label: "HTML Templates", href: "/templates" },
  { id: "saved-templates", ico: "❒", label: "Saved Templates", href: "/saved-templates" },
  { id: "attachments", ico: "◫", label: "Attachments", href: "/attachments" },
  { id: "phones", ico: "☏", label: "Company Phones", href: "/phones" },
  { section: "Compliance" },
  { id: "suppression", ico: "⊘", label: "Suppression List", href: "/suppression" },
  { id: "settings", ico: "⚙", label: "Settings", href: "/settings" },
];

async function loadLayout() {
  let me = null;
  try { me = await GET("/auth/me"); } catch { me = null; }
  if (!me) { if (!location.pathname.startsWith("/login")) location.href = "/login"; return null; }

  const sb = el("aside", { class: "sidebar", id: "sidebar" });
  sb.append(el("div", { class: "brand" },
    el("span", { class: "brand-badge" }, "M"),
    el("span", {}, "Bulk Mailer")));
  const nav = el("nav", {});
  for (const item of NAV) {
    if (item.section) { nav.append(el("div", { class: "nav-section" }, item.section)); continue; }
    const active = location.pathname === item.href || (item.href !== "/" && location.pathname.startsWith(item.href));
    nav.append(el("a", { class: "nav-link" + (active ? " active" : ""), href: item.href },
      el("span", { class: "ico" }, item.ico), item.label));
  }
  sb.append(nav);
  const foot = el("div", { class: "foot" },
    el("div", { class: "user-line" }, "👤 ", me.email || "user"),
    el("button", { class: "btn sm secondary block mt-1", onclick: doLogout }, "Sign out"));
  sb.append(foot);

  const old = $("#sidebar");
  if (old) old.remove();
  document.body.prepend(sb);

  // scrim for mobile
  let scrim = $("#scrim");
  if (!scrim) { scrim = el("div", { id: "scrim", onclick: () => { $("#sidebar").classList.remove("open"); scrim.classList.remove("show"); } }); document.body.append(scrim); }
  const mb = $("#menuBtn");
  if (mb) mb.addEventListener("click", () => { $("#sidebar").classList.toggle("open"); scrim.classList.toggle("show"); });

  // page-specific boot
  if (window.pageBoot) window.pageBoot(me);
  return me;
}

async function doLogout() {
  try { await POST("/auth/logout"); } catch {}
  location.href = "/login";
}

document.addEventListener("DOMContentLoaded", loadLayout);
