/* marketeer-app.jsx — the Marketeer portal shell + screens.
   Loaded by "Marketeer Dashboard.html" AFTER the full educator component bundle, so
   it reuses those globals (Icon, Ring, HexMark, useTheme, CoursePageTab). The educator
   App auto-mount is guarded off for non-educator portals (educator-screens3.jsx), so
   MktApp is what mounts here.

   LIVE DATA — the Analytics / Leads / Campaigns / Events screens run on REAL platform
   data via /api/marketing (org-wide cohorts + members, events + registrations, the
   published catalog, real campaign records). These are the same records the learner
   environment produces (cohort members = enrolled learners; event registrations =
   sign-ups), so the marketeer is connected to the same learners. No demo seeds live
   here; the course catalog follows what the educator publishes, which itself honours
   the educator's demo switch (seed courses drop out when demo content is off). */

function mktSeoScore(page) {
  const p = page || {};
  const keys = ["title", "overview", "audience", "objectives", "content", "prerequisites", "metaDesc", "keywords"];
  const filled = keys.filter((k) => (p[k] || "").length > 10).length;
  return Math.round((filled / keys.length) * 100);
}
function mktFmtDate(ts) {
  if (!ts) return "—";
  try { return new Date(ts * 1000).toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" }); } catch (e) { return "—"; }
}
function mktFmtDateTime(ts) {
  if (!ts) return "—";
  try { return new Date(ts * 1000).toLocaleString("en-GB", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }); } catch (e) { return "—"; }
}

/* ── Publishing helpers (adapted from the LinkedIn publishing engine blueprint) ── */
async function mktMarketingPost(action, payload) {
  try {
    const r = await fetch("/api/marketing", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action, ...payload }) });
    return r.ok;
  } catch (e) { return false; }
}
async function mktMarketingPostJson(action, payload) {
  try {
    const r = await fetch("/api/marketing", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action, ...payload }) });
    const d = await r.json().catch(() => ({}));
    return { ok: r.ok, ...d };
  } catch (e) { return { ok: false, error: "network" }; }
}
function mktSlug(s) { return String(s || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60); }
/* Unix seconds → value for <input type="datetime-local"> in LOCAL time.
   (toISOString would render UTC and shift the shown time by the timezone offset.) */
function mktLocalDT(ts) {
  if (!ts) return "";
  const d = new Date(ts * 1000), p = (n) => String(n).padStart(2, "0");
  return d.getFullYear() + "-" + p(d.getMonth() + 1) + "-" + p(d.getDate()) + "T" + p(d.getHours()) + ":" + p(d.getMinutes());
}
/* Monday (local) of the ISO week `offset` weeks from now; 0 = this week. */
function mktMonday(offset) {
  const now = new Date();
  const dow = (now.getDay() + 6) % 7; // 0 = Monday
  return new Date(now.getFullYear(), now.getMonth(), now.getDate() - dow + offset * 7);
}
function mktIsoWeek(d) {
  const t = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
  const day = t.getUTCDay() || 7;
  t.setUTCDate(t.getUTCDate() + 4 - day);
  const yearStart = new Date(Date.UTC(t.getUTCFullYear(), 0, 1));
  const wk = Math.ceil((((t - yearStart) / 86400000) + 1) / 7);
  return t.getUTCFullYear() + "-W" + String(wk).padStart(2, "0");
}
const MKT_WEEKDAYS = ["monday", "tuesday", "wednesday", "thursday", "friday"];
const MKT_WD_SHORT = { monday: "Mon", tuesday: "Tue", wednesday: "Wed", thursday: "Thu", friday: "Fri" };
/* The six rotating post strategies from the engine blueprint (§5). */
const MKT_STRATEGIES = [
  { id: "expert_insight", label: "Expert insight", desc: "A standalone practical insight, no call to action.", hint: "0–2 hashtags" },
  { id: "practical_checklist", label: "Practical checklist", desc: "3–5 numbered, actionable steps.", hint: "2–3 hashtags" },
  { id: "misconception", label: "Misconception", desc: "\"Most companies think X…\" — a contrarian correction.", hint: "1–3 hashtags" },
  { id: "case_observation", label: "Case observation", desc: "An anonymised client anecdote: pattern + lesson.", hint: "0–2 hashtags" },
  { id: "blog_derived", label: "From a blog post", desc: "References one of your website posts.", hint: "0–3 hashtags" },
  { id: "discussion", label: "Discussion", desc: "Ends in one specific, sincere question.", hint: "1–2 hashtags" },
];
const MKT_PLATFORMS = [
  { id: "linkedin", label: "LinkedIn", color: "#0a66c2", icon: "users" },
  { id: "x", label: "X", color: "#111827", icon: "share" },
  { id: "facebook", label: "Facebook", color: "#1877f2", icon: "globe" },
  { id: "instagram", label: "Instagram", color: "#c13584", icon: "star" },
];
function mktPlatform(id) { return MKT_PLATFORMS.find((p) => p.id === id) || MKT_PLATFORMS[0]; }
function mktExtractJson(s) {
  if (!s) return null;
  const m = String(s).match(/\{[\s\S]*\}/);
  if (!m) return null;
  try { return JSON.parse(m[0]); } catch (e) { return null; }
}
function mktCanAI() { return !!(window.claude && window.claude.complete); }
const MKT_DEFAULT_VOICE = "Clear, expert and practical B2B tone for a professional training & certification audience (project management, IT service management, enterprise architecture). Plain language, no hype, no AI clichés, no em dashes. Short paragraphs. Never invent statistics or dates.";

/* ── NAV ── */
const MKT_NAV = [
  { section: null, items: [{ id: "dashboard", label: "Dashboard", icon: "grid" }] },
  { section: "Marketing", items: [
    { id: "catalog", label: "Course Catalog", icon: "layers" },
    { id: "campaigns", label: "Campaigns", icon: "mail" },
    { id: "events", label: "Events & Webinars", icon: "calendar" },
    { id: "leads", label: "Leads & Contacts", icon: "users" },
  ]},
  { section: "Publishing", items: [
    { id: "website", label: "Website posting", icon: "edit" },
    { id: "social", label: "Social posting agenda", icon: "share" },
  ]},
  { section: "Insights", items: [
    { id: "analytics", label: "Analytics", icon: "bars" },
    { id: "seo", label: "SEO & Keywords", icon: "search" },
  ]},
  { section: "Brand & assets", items: [
    { id: "library", label: "Shared Library", icon: "file" },
    { id: "styling", label: "Style Tailor", icon: "palette" },
  ]},
  { section: null, items: [{ id: "settings", label: "Settings", icon: "gear" }] },
];

function MktSidebar({ route, go }) {
  const { theme, branded } = (typeof useTheme !== "undefined" ? useTheme() : { theme: null, branded: false });
  return (
    <nav className="rail">
      <div style={{ padding: "20px 16px 10px", display: "flex", alignItems: "center", gap: 11 }}>
        <HexMark size={34} />
        <div className="rail-wordmark">
          {branded
            ? <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 16, color: "var(--ink)", lineHeight: 1, maxWidth: 150, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={theme.name}>{theme.name}</div>
            : <img src="assets/vanharen-logo.png" alt="Van Haren" style={{ height: 20, width: "auto", display: "block" }} />}
          <div className="dim" style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: ".18em", textTransform: "uppercase", marginTop: 3 }}>Marketeer</div>
        </div>
      </div>
      <div style={{ flex: 1, overflowY: "auto", paddingBottom: 8 }} className="scroll">
        {MKT_NAV.map((g, gi) => (
          <div key={gi}>
            {gi > 0 && <div style={{ height: 1, background: "var(--rail-border)", margin: "5px 16px" }} />}
            {g.section && (
              <div style={{ padding: "6px 16px 2px" }}>
                <span className="lbl" style={{ fontSize: 10, fontWeight: 800, textTransform: "uppercase", letterSpacing: ".16em", color: "var(--rail-ink-dim)" }}>{g.section}</span>
              </div>
            )}
            <div style={{ padding: "1px 13px 0", display: "flex", flexDirection: "column", gap: 1 }}>
              {g.items.map((n) => (
                <button key={n.id} className={"nav-item" + (route === n.id ? " active" : "")} data-route={n.id} onClick={() => go(n.id)} style={{ width: "100%" }}>
                  <span className="nav-ico"><Icon name={n.icon} size={21} /></span>
                  <span className="lbl">{n.label}</span>
                </button>
              ))}
            </div>
          </div>
        ))}
      </div>
      <div style={{ padding: "0 13px 16px" }}>
        <div style={{ height: 1, background: "var(--rail-border)", marginBottom: 12 }} />
        <button className="nav-item" onClick={async () => { try { await fetch("/api/auth/logout", { method: "POST" }); } catch (e) {} window.location.href = "Login.html"; }}
          onMouseEnter={(e) => e.currentTarget.style.color = "#c62828"} onMouseLeave={(e) => e.currentTarget.style.color = ""}>
          <span className="nav-ico"><Icon name="chevL" size={21} /></span>
          <span className="lbl" style={{ fontSize: 13.5 }}>Log out</span>
        </button>
      </div>
    </nav>
  );
}

const MKT_TITLES = {
  dashboard: "Marketing Dashboard", catalog: "Course Catalog", campaigns: "Campaigns",
  events: "Events & Webinars", leads: "Leads & Contacts", analytics: "Analytics", seo: "SEO & Keywords",
  website: "Website posting", social: "Social posting agenda",
  library: "Shared Library", styling: "Style Tailor", settings: "Settings",
};

function MktTopbar({ route, onRefresh }) {
  const u = window.AUTH_USER || {};
  const initials = (u.name || "Marketeer").split(/\s+/).filter(Boolean).map((w) => w[0]).join("").slice(0, 2).toUpperCase();
  return (
    <header className="topbar">
      <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 17, color: "var(--ink)" }}>{MKT_TITLES[route] || "Marketeer"}</div>
      <div style={{ flex: 1 }} />
      <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
        {onRefresh && <button className="btn btn-ghost btn-sm" onClick={onRefresh} title="Reload live data"><Icon name="refresh" size={14} />Live data</button>}
        <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12, fontWeight: 700, color: "#2e7d32", background: "#e8f5e9", border: "1px solid #bfe3c5", borderRadius: 999, padding: "5px 11px" }}>
          <span style={{ width: 7, height: 7, borderRadius: "50%", background: "#2e7d32" }} />Connected to Educator
        </span>
        <div style={{ display: "flex", alignItems: "center", gap: 9 }}>
          <div style={{ width: 34, height: 34, borderRadius: "50%", background: "var(--vh-navy)", color: "#fff", display: "grid", placeItems: "center", fontFamily: "var(--display)", fontWeight: 800, fontSize: 13 }}>{initials}</div>
          <div style={{ lineHeight: 1.15 }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: "var(--ink)" }}>{u.name || "Marketeer"}</div>
            <div className="dim" style={{ fontSize: 11 }}>Marketing</div>
          </div>
        </div>
      </div>
    </header>
  );
}

/* small shared bits */
function MktStat({ icon, color, value, label, sub }) {
  return (
    <div className="card" style={{ padding: 18, display: "flex", flexDirection: "column", gap: 10 }}>
      <div style={{ width: 40, height: 40, borderRadius: 11, background: color + "1a", display: "grid", placeItems: "center" }}><Icon name={icon} size={20} style={{ color }} /></div>
      <div>
        <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 26, color: "var(--ink)", lineHeight: 1 }}>{value}</div>
        <div style={{ fontSize: 13, fontWeight: 700, color: "var(--ink)", marginTop: 6 }}>{label}</div>
        {sub && <div className="dim" style={{ fontSize: 12, marginTop: 2 }}>{sub}</div>}
      </div>
    </div>
  );
}
function MktEmpty({ icon, title, sub }) {
  return (
    <div className="card" style={{ padding: "38px 24px", textAlign: "center" }}>
      <div style={{ width: 52, height: 52, borderRadius: 14, background: "var(--surface-2)", display: "grid", placeItems: "center", margin: "0 auto 14px" }}><Icon name={icon || "search"} size={24} style={{ color: "var(--ink-3)" }} /></div>
      <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 16, color: "var(--ink)", marginBottom: 5 }}>{title}</div>
      {sub && <p className="dim" style={{ fontSize: 13, maxWidth: 420, margin: "0 auto", lineHeight: 1.5 }}>{sub}</p>}
    </div>
  );
}
function MktStatusPill({ status }) {
  const map = { sent: ["Sent", "#e8f5e9", "#2e7d32"], scheduled: ["Scheduled", "#e8f2fb", "#0d6cab"], draft: ["Draft", "rgba(249,157,37,.14)", "#c97010"],
    registered: ["Registered", "#e8f5e9", "#2e7d32"], enrolled: ["Enrolled", "#e8f2fb", "#0d6cab"], waitlist: ["Waitlist", "rgba(249,157,37,.14)", "#c97010"], cancelled: ["Cancelled", "#f1f3f7", "#7b899f"],
    confirmed: ["Confirmed", "#e8f5e9", "#2e7d32"], full: ["Full", "rgba(249,157,37,.14)", "#c97010"] };
  const [lbl, bg, c] = map[status] || [status || "—", "#f1f3f7", "#7b899f"];
  return <span style={{ fontSize: 11.5, fontWeight: 800, padding: "2px 9px", borderRadius: 999, background: bg, color: c, whiteSpace: "nowrap" }}>{lbl}</span>;
}
const MKT_TH = { textAlign: "left", fontSize: 11, fontWeight: 800, textTransform: "uppercase", letterSpacing: ".05em", color: "var(--ink-3)", padding: "0 12px 8px" };
const MKT_TD = { fontSize: 13.5, color: "var(--ink)", padding: "11px 12px", borderTop: "1px solid var(--line-2)" };

/* ── DASHBOARD (live) ── */
function MktDashboard({ go, catalog, data }) {
  const t = (data && data.totals) || { courses: 0, cohorts: 0, learners: 0, events: 0, registrations: 0 };
  const scored = (catalog || []).map((c) => ({ c, s: mktSeoScore(c.page) }));
  const avgSeo = scored.length ? Math.round(scored.reduce((a, x) => a + x.s, 0) / scored.length) : 0;
  const ready = scored.filter((x) => x.s >= 70).length;
  const needsWork = scored.filter((x) => x.s < 70).slice(0, 4);
  const campaigns = (data && data.campaigns) || [];
  return (
    <div className="vh-in" style={{ padding: "26px 30px", maxWidth: 1180, margin: "0 auto" }}>
      <div style={{ marginBottom: 22 }}>
        <div className="eyebrow" style={{ marginBottom: 6 }}>Marketing · live data</div>
        <h1 style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 26, color: "var(--ink)" }}>Promote the Van Haren catalog</h1>
        <p className="dim" style={{ fontSize: 14, marginTop: 6, maxWidth: 660, lineHeight: 1.5 }}>
          Everything here is live: the catalog the educators publish, the real cohorts and enrolled learners, and actual event sign-ups. Write the public course pages, run campaigns to real audiences and track the funnel.
        </p>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 14, marginBottom: 16 }}>
        <MktStat icon="layers" color="#1281c4" value={t.courses} label="Courses in catalog" sub="from the Educator studio" />
        <MktStat icon="users" color="#00838f" value={t.cohorts} label="Cohorts" sub="live groups" />
        <MktStat icon="person" color="#2e7d32" value={t.learners} label="Enrolled learners" sub="real cohort members" />
        <MktStat icon="calendar" color="#3bc1ce" value={t.registrations} label="Event registrations" sub={t.events + " events"} />
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "1.5fr 1fr", gap: 16 }}>
        <div className="card" style={{ padding: 20 }}>
          <div className="between" style={{ marginBottom: 14 }}>
            <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 16 }}>Course pages that need copy</div>
            <button className="btn btn-ghost btn-sm" onClick={() => go("catalog")}>Open catalog<Icon name="arrowR" size={13} /></button>
          </div>
          {(catalog || []).length === 0 ? (
            <div className="dim" style={{ fontSize: 13.5, padding: "18px 0", textAlign: "center" }}>No courses published yet — an educator publishes the catalog from the Educator studio.</div>
          ) : needsWork.length === 0 ? (
            <div className="dim" style={{ fontSize: 13.5, padding: "18px 0", textAlign: "center" }}>Every course page is in good shape ({ready}/{scored.length} ready). 🎉</div>
          ) : needsWork.map(({ c, s }) => (
            <div key={c.id} style={{ display: "flex", alignItems: "center", gap: 12, padding: "11px 0", borderBottom: "1px solid var(--line-2)" }}>
              <div style={{ width: 34, height: 34, borderRadius: 9, background: (c.color || "#1281c4") + "1a", display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name={c.icon || "layers"} size={17} style={{ color: c.color || "#1281c4" }} /></div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 13.5, fontWeight: 700, color: "var(--ink)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{c.name}</div>
                <div className="dim" style={{ fontSize: 12 }}>{c.level} · {c.lang}</div>
              </div>
              <span style={{ fontSize: 12, fontWeight: 800, color: s >= 40 ? "#b45309" : "#c62828" }}>{s}%</span>
              <button className="btn btn-outline btn-sm" onClick={() => go("catalog")}><Icon name="wand" size={13} />Write</button>
            </div>
          ))}
        </div>

        <div className="card" style={{ padding: 20 }}>
          <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 16, marginBottom: 6 }}>At a glance</div>
          <div style={{ display: "flex", flexDirection: "column", gap: 2, marginBottom: 14 }}>
            <div style={{ display: "flex", justifyContent: "space-between", padding: "7px 0", borderBottom: "1px solid var(--line-2)", fontSize: 13.5 }}><span className="dim">Avg SEO completeness</span><b>{avgSeo}%</b></div>
            <div style={{ display: "flex", justifyContent: "space-between", padding: "7px 0", borderBottom: "1px solid var(--line-2)", fontSize: 13.5 }}><span className="dim">Live campaigns</span><b>{campaigns.length}</b></div>
            <div style={{ display: "flex", justifyContent: "space-between", padding: "7px 0", fontSize: 13.5 }}><span className="dim">Upcoming events</span><b>{t.events}</b></div>
          </div>
          {[["website", "edit", "Write a website post", "#1281c4"], ["social", "share", "Plan the social agenda", "#0a66c2"], ["campaigns", "mail", "Launch a campaign", "#f99d25"], ["events", "calendar", "Create an event / webinar", "#3bc1ce"]].map(([r, ic, lbl, col]) => (
            <button key={r} onClick={() => go(r)} style={{ width: "100%", display: "flex", alignItems: "center", gap: 12, padding: "11px 12px", marginBottom: 8, borderRadius: 11, border: "1px solid var(--line)", background: "var(--surface)", cursor: "pointer", textAlign: "left", fontFamily: "var(--body)" }}>
              <div style={{ width: 30, height: 30, borderRadius: 9, background: col + "1a", display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name={ic} size={15} style={{ color: col }} /></div>
              <span style={{ flex: 1, fontSize: 13, fontWeight: 700, color: "var(--ink)" }}>{lbl}</span>
              <Icon name="arrowR" size={14} style={{ color: "var(--ink-3)" }} />
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

/* ── CATALOG (shared, no demo seeds) ── */
function MktCatalog({ catalog, onCopySaved }) {
  const [selId, setSelId] = useState(null);
  const sel = (catalog || []).find((c) => c.id === selId) || null;
  if (sel) return <MktCoursePage course={sel} onBack={() => setSelId(null)} onCopySaved={onCopySaved} />;
  return (
    <div className="vh-in" style={{ padding: "26px 30px", maxWidth: 1180, margin: "0 auto" }}>
      <div style={{ marginBottom: 18 }}>
        <h1 style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 24, color: "var(--ink)" }}>Course Catalog</h1>
        <p className="dim" style={{ fontSize: 13.5, marginTop: 4 }}>The live catalog the educators publish. Open a course to write and optimise its public marketing page.</p>
      </div>
      {(catalog || []).length === 0 ? (
        <MktEmpty icon="layers" title="No courses in the catalog yet" sub="When an educator publishes courses from the Educator studio (with demo content on, or their own real courses), they appear here." />
      ) : (
        <div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 16 }}>
          {(catalog || []).map((c) => {
            const s = mktSeoScore(c.page);
            const sc = s >= 70 ? "#2e7d32" : s >= 40 ? "#b45309" : "#c62828";
            return (
              <button key={c.id} className="card" onClick={() => setSelId(c.id)} style={{ padding: 0, overflow: "hidden", textAlign: "left", cursor: "pointer", border: "1px solid var(--line)" }}>
                <div style={{ height: 6, background: c.color || "#1281c4" }} />
                <div style={{ padding: 18 }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 12 }}>
                    <div style={{ width: 42, height: 42, borderRadius: 11, background: (c.color || "#1281c4") + "1a", display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name={c.icon || "layers"} size={20} style={{ color: c.color || "#1281c4" }} /></div>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: 15, fontWeight: 800, color: "var(--ink)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{c.name}</div>
                      <div className="dim" style={{ fontSize: 12 }}>{c.level} · {c.lang} · {c.body}</div>
                    </div>
                  </div>
                  <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                    <div style={{ flex: 1, height: 6, borderRadius: 999, background: "var(--surface-3)", overflow: "hidden" }}><div style={{ height: "100%", width: s + "%", background: sc, borderRadius: 999 }} /></div>
                    <span style={{ fontSize: 12, fontWeight: 800, color: sc }}>{s}%</span>
                  </div>
                  <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: 12 }}>
                    <span style={{ fontSize: 13, fontWeight: 700, color: "var(--ink)" }}>{c.price || "—"}</span>
                    <span style={{ display: "inline-flex", alignItems: "center", gap: 5, fontSize: 12.5, fontWeight: 700, color: "var(--vh-blue-600)" }}><Icon name="wand" size={13} />Edit page</span>
                  </div>
                </div>
              </button>
            );
          })}
        </div>
      )}
    </div>
  );
}

function MktCoursePage({ course, onBack, onCopySaved }) {
  const [form, setForm] = useState(() => course.page || { title: "", slug: course.id, keywords: "", overview: "", audience: "", objectives: "", content: "", prerequisites: "", followUp: "", metaDesc: "" });
  const [saved, setSaved] = useState(false);
  useEffect(() => { setSaved(false); }, [form]);
  async function save() {
    let ok = false;
    try { ok = window.VHCatalog ? await window.VHCatalog.saveMarketing(course.id, form) : false; } catch (e) {}
    if (ok) { setSaved(true); if (onCopySaved) onCopySaved(course.id, form); if (window.eduToast) window.eduToast("Marketing copy saved — visible to the educator too", "check"); }
    else if (window.eduToast) window.eduToast("Couldn't save — try again", "x");
  }
  return (
    <div className="vh-in" style={{ padding: "22px 30px", maxWidth: 1180, margin: "0 auto" }}>
      <button className="btn btn-ghost btn-sm" onClick={onBack} style={{ marginBottom: 14 }}><Icon name="chevL" size={14} />Back to catalog</button>
      <div style={{ display: "flex", alignItems: "center", gap: 14, marginBottom: 18 }}>
        <div style={{ width: 48, height: 48, borderRadius: 12, background: (course.color || "#1281c4") + "1a", display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name={course.icon || "layers"} size={23} style={{ color: course.color || "#1281c4" }} /></div>
        <div>
          <h1 style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 22, color: "var(--ink)", lineHeight: 1.1 }}>{course.name}</h1>
          <div className="dim" style={{ fontSize: 13 }}>{course.level} · {course.lang} · {course.body} · Public marketing page</div>
        </div>
      </div>
      {typeof CoursePageTab !== "undefined"
        ? <CoursePageTab product={course} form={form} setForm={setForm} onSave={save} saved={saved} />
        : <div className="card" style={{ padding: 30 }}>Editor unavailable.</div>}
    </div>
  );
}

/* ── ANALYTICS (live) ── */
function MktFunnelRow({ label, value, max, color }) {
  const pct = max > 0 ? Math.round((value / max) * 100) : 0;
  return (
    <div style={{ marginBottom: 12 }}>
      <div style={{ display: "flex", justifyContent: "space-between", fontSize: 13, marginBottom: 5 }}><span style={{ fontWeight: 700, color: "var(--ink)" }}>{label}</span><span style={{ fontWeight: 800, color }}>{value}</span></div>
      <div style={{ height: 10, borderRadius: 999, background: "var(--surface-3)", overflow: "hidden" }}><div style={{ height: "100%", width: Math.max(pct, value > 0 ? 4 : 0) + "%", background: color, borderRadius: 999 }} /></div>
    </div>
  );
}
function MktAnalytics({ data, catalog }) {
  if (!data) return <div className="vh-in" style={{ padding: 30 }}><div className="card" style={{ padding: 40, textAlign: "center", color: "var(--ink-3)" }}>Loading live data…</div></div>;
  const t = data.totals || {};
  const scored = (catalog || []).map((c) => mktSeoScore(c.page));
  const avgSeo = scored.length ? Math.round(scored.reduce((a, x) => a + x, 0) / scored.length) : 0;
  const cohorts = data.cohorts || [];
  const events = data.events || [];
  const funnelMax = Math.max(t.learners || 0, t.registrations || 0, t.cohorts || 0, 1);
  return (
    <div className="vh-in" style={{ padding: "26px 30px", maxWidth: 1180, margin: "0 auto" }}>
      <h1 style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 24, marginBottom: 4 }}>Analytics</h1>
      <p className="dim" style={{ fontSize: 13.5, marginBottom: 18 }}>Real numbers from the platform — cohorts, enrolled learners and event sign-ups, live.</p>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 14, marginBottom: 18 }}>
        <MktStat icon="layers" color="#1281c4" value={t.courses || 0} label="Courses" />
        <MktStat icon="users" color="#00838f" value={t.cohorts || 0} label="Cohorts" />
        <MktStat icon="person" color="#2e7d32" value={t.learners || 0} label="Enrolled learners" />
        <MktStat icon="wand" color="#7c3aed" value={avgSeo + "%"} label="Avg SEO completeness" />
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginBottom: 16 }}>
        <div className="card" style={{ padding: 20 }}>
          <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 15, marginBottom: 14 }}>Acquisition funnel</div>
          <MktFunnelRow label="Courses in catalog" value={t.courses || 0} max={funnelMax} color="#1281c4" />
          <MktFunnelRow label="Cohorts running" value={t.cohorts || 0} max={funnelMax} color="#00838f" />
          <MktFunnelRow label="Enrolled learners" value={t.learners || 0} max={funnelMax} color="#2e7d32" />
          <MktFunnelRow label="Event registrations" value={t.registrations || 0} max={funnelMax} color="#3bc1ce" />
        </div>
        <div className="card" style={{ padding: 20 }}>
          <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 15, marginBottom: 14 }}>Cohorts</div>
          {cohorts.length === 0 ? <div className="dim" style={{ fontSize: 13, padding: "14px 0", textAlign: "center" }}>No cohorts yet.</div> : (
            <div style={{ maxHeight: 240, overflowY: "auto" }} className="scroll">
              <table style={{ width: "100%", borderCollapse: "collapse" }}>
                <thead><tr><th style={MKT_TH}>Cohort</th><th style={MKT_TH}>Course</th><th style={{ ...MKT_TH, textAlign: "right" }}>Members</th></tr></thead>
                <tbody>{cohorts.map((c) => (
                  <tr key={c.id}><td style={MKT_TD}>{c.name}</td><td style={{ ...MKT_TD, color: "var(--ink-2)" }}>{c.course || "—"}</td><td style={{ ...MKT_TD, textAlign: "right", fontWeight: 800 }}>{c.members}</td></tr>
                ))}</tbody>
              </table>
            </div>
          )}
        </div>
      </div>
      <div className="card" style={{ padding: 20 }}>
        <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 15, marginBottom: 14 }}>Events & registrations</div>
        {events.length === 0 ? <div className="dim" style={{ fontSize: 13, padding: "14px 0", textAlign: "center" }}>No events yet.</div> : (
          <table style={{ width: "100%", borderCollapse: "collapse" }}>
            <thead><tr><th style={MKT_TH}>Event</th><th style={MKT_TH}>Date</th><th style={MKT_TH}>Status</th><th style={{ ...MKT_TH, textAlign: "right" }}>Registrations</th></tr></thead>
            <tbody>{events.map((e) => (
              <tr key={e.id}><td style={MKT_TD}>{e.title}</td><td style={{ ...MKT_TD, color: "var(--ink-2)" }}>{mktFmtDate(e.start_at)}</td><td style={MKT_TD}><MktStatusPill status={e.status} /></td><td style={{ ...MKT_TD, textAlign: "right", fontWeight: 800 }}>{e.registrations}</td></tr>
            ))}</tbody>
          </table>
        )}
      </div>
    </div>
  );
}

/* ── LEADS (live) ── */
function MktLeads({ data }) {
  const [q, setQ] = useState("");
  const leads = (data && data.leads) || [];
  const query = q.trim().toLowerCase();
  const shown = query ? leads.filter((l) => (l.name + " " + l.email + " " + l.detail).toLowerCase().includes(query)) : leads;
  function exportCsv() {
    const rows = [["Name", "Email", "Source", "Detail", "Status", "Date"]].concat(shown.map((l) => [l.name, l.email, l.source, l.detail, l.status, mktFmtDate(l.ts)]));
    const csv = rows.map((r) => r.map((v) => '"' + String(v == null ? "" : v).replace(/"/g, '""') + '"').join(",")).join("\r\n");
    const url = URL.createObjectURL(new Blob([csv], { type: "text/csv" }));
    const a = document.createElement("a"); a.href = url; a.download = "leads.csv"; a.click(); URL.revokeObjectURL(url);
  }
  return (
    <div className="vh-in" style={{ padding: "26px 30px", maxWidth: 1180, margin: "0 auto" }}>
      <div className="between" style={{ marginBottom: 16, gap: 12, flexWrap: "wrap" }}>
        <div>
          <h1 style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 24 }}>Leads & Contacts</h1>
          <p className="dim" style={{ fontSize: 13.5, marginTop: 4 }}>Real people from the platform — event registrations and enrolled cohort learners.</p>
        </div>
        <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
          <label className="search" style={{ minWidth: 220 }}><Icon name="search" size={16} /><input placeholder="Search name, email, source…" value={q} onChange={(e) => setQ(e.target.value)} /></label>
          <button className="btn btn-outline btn-sm" onClick={exportCsv} disabled={!shown.length}><Icon name="download" size={13} />Export CSV</button>
        </div>
      </div>
      {leads.length === 0 ? (
        <MktEmpty icon="users" title="No leads yet" sub="Leads appear here when learners join a cohort or register for an event. Publish an event registration page or share a cohort voucher to start collecting them." />
      ) : (
        <div className="card" style={{ padding: "6px 8px 10px" }}>
          <div className="dim" style={{ fontSize: 12, padding: "8px 12px" }}>{shown.length} of {leads.length} contacts</div>
          <table style={{ width: "100%", borderCollapse: "collapse" }}>
            <thead><tr><th style={MKT_TH}>Name</th><th style={MKT_TH}>Email</th><th style={MKT_TH}>Source</th><th style={MKT_TH}>Detail</th><th style={MKT_TH}>Status</th><th style={MKT_TH}>Date</th></tr></thead>
            <tbody>{shown.map((l, i) => (
              <tr key={i}>
                <td style={{ ...MKT_TD, fontWeight: 700 }}>{l.name}</td>
                <td style={{ ...MKT_TD, color: "var(--ink-2)" }}>{l.email || "—"}</td>
                <td style={MKT_TD}><span style={{ fontSize: 11.5, fontWeight: 800, padding: "2px 9px", borderRadius: 999, background: l.source === "event" ? "#e8f2fb" : "#e8f5e9", color: l.source === "event" ? "#0d6cab" : "#2e7d32" }}>{l.source === "event" ? "Event" : "Enrolment"}</span></td>
                <td style={{ ...MKT_TD, color: "var(--ink-2)" }}>{l.detail}</td>
                <td style={MKT_TD}><MktStatusPill status={l.status} /></td>
                <td style={{ ...MKT_TD, color: "var(--ink-2)" }}>{mktFmtDate(l.ts)}</td>
              </tr>
            ))}</tbody>
          </table>
        </div>
      )}
    </div>
  );
}

/* ── CAMPAIGNS (live, target real cohorts) ── */
function MktCampaigns({ data, reload }) {
  const [creating, setCreating] = useState(false);
  const [busy, setBusy] = useState(false);
  const [sendBusy, setSendBusy] = useState(null);   // campaign id being sent
  const [f, setF] = useState({ name: "", subject: "", audience: "all", body: "" });
  const campaigns = (data && data.campaigns) || [];
  const cohorts = (data && data.cohorts) || [];
  const totals = (data && data.totals) || {};
  const emailReady = !!(data && data.connections && data.connections.email && data.connections.email.configured);

  async function sendTest(c) {
    setSendBusy(c.id);
    const res = await mktMarketingPostJson("sendCampaign", { id: c.id, test: true });
    setSendBusy(null);
    if (window.eduToast) window.eduToast(res.ok ? "Test sent to your own address ✓" : "Test failed — " + (res.detail || res.error || ""), res.ok ? "check" : "x");
  }
  async function sendNow(c, a) {
    if (!window.confirm('Send "' + c.name + '" to ' + a.count + " " + (a.count === 1 ? "recipient" : "recipients") + " (" + a.label + ") now?")) return;
    setSendBusy(c.id);
    const res = await mktMarketingPostJson("sendCampaign", { id: c.id });
    setSendBusy(null);
    if (res.ok) { if (window.eduToast) window.eduToast("Campaign sent — " + res.sent + " delivered" + (res.failed ? ", " + res.failed + " failed" : ""), "check"); if (reload) reload(); }
    else if (window.eduToast) window.eduToast(res.error === "email_not_configured" ? "Email server not connected — Settings → Connections" : res.message || ("Send failed — " + (res.detail || res.error || "")), "x");
  }

  function audienceLabel(aud) {
    if (aud === "all" || !aud) return { label: "All enrolled learners", count: totals.learners || 0 };
    const id = aud.indexOf("cohort:") === 0 ? aud.slice(7) : aud;
    const c = cohorts.find((x) => x.id === id);
    return { label: c ? ("Cohort · " + c.name) : "Cohort", count: c ? c.members : 0 };
  }
  async function saveCampaign(rec) {
    setBusy(true);
    let ok = false;
    try {
      const r = await fetch("/api/marketing", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action: "saveCampaign", campaign: rec }) });
      ok = r.ok;
    } catch (e) {}
    setBusy(false);
    if (ok) { if (window.eduToast) window.eduToast("Campaign saved", "check"); if (reload) reload(); }
    else if (window.eduToast) window.eduToast("Couldn't save campaign", "x");
    return ok;
  }
  async function create() {
    if (!f.name.trim() || busy) return;
    const ok = await saveCampaign({ name: f.name.trim(), subject: f.subject.trim(), audience: f.audience, body: f.body.trim(), status: "draft" });
    if (ok) { setCreating(false); setF({ name: "", subject: "", audience: "all", body: "" }); }
  }
  async function del(c) {
    try { await fetch("/api/marketing", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action: "deleteCampaign", id: c.id }) }); } catch (e) {}
    if (window.eduToast) window.eduToast("Campaign removed", "check"); if (reload) reload();
  }
  const inp = { width: "100%", padding: "9px 12px", border: "1.5px solid var(--line)", borderRadius: 9, fontSize: 14, color: "var(--ink)", background: "var(--surface)", fontFamily: "var(--body)", outline: "none" };

  return (
    <div className="vh-in" style={{ padding: "26px 30px", maxWidth: 1180, margin: "0 auto" }}>
      <div className="between" style={{ marginBottom: 16, gap: 12, flexWrap: "wrap" }}>
        <div>
          <h1 style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 24 }}>Campaigns</h1>
          <p className="dim" style={{ fontSize: 13.5, marginTop: 4 }}>Email campaigns to real audiences — recipient counts come from live cohort membership.</p>
        </div>
        <button className="btn btn-acc" onClick={() => setCreating((v) => !v)}><Icon name={creating ? "x" : "plus"} size={15} />{creating ? "Cancel" : "New campaign"}</button>
      </div>

      {creating && (
        <div className="card" style={{ padding: 20, marginBottom: 16 }}>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 12 }}>
            <div><label style={{ fontSize: 11, fontWeight: 800, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: ".06em", display: "block", marginBottom: 6 }}>Campaign name</label><input style={inp} value={f.name} onChange={(e) => setF({ ...f, name: e.target.value })} placeholder="e.g. IPMA-D spring intake" /></div>
            <div><label style={{ fontSize: 11, fontWeight: 800, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: ".06em", display: "block", marginBottom: 6 }}>Audience</label>
              <select style={inp} value={f.audience} onChange={(e) => setF({ ...f, audience: e.target.value })}>
                <option value="all">All enrolled learners ({totals.learners || 0})</option>
                {cohorts.map((c) => <option key={c.id} value={"cohort:" + c.id}>{c.name} ({c.members})</option>)}
              </select>
            </div>
          </div>
          <label style={{ fontSize: 11, fontWeight: 800, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: ".06em", display: "block", marginBottom: 6 }}>Subject line</label>
          <input style={{ ...inp, marginBottom: 12 }} value={f.subject} onChange={(e) => setF({ ...f, subject: e.target.value })} placeholder="Subject learners will see" />
          <label style={{ fontSize: 11, fontWeight: 800, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: ".06em", display: "block", marginBottom: 6 }}>Message</label>
          <textarea style={{ ...inp, minHeight: 90, resize: "vertical", lineHeight: 1.5, marginBottom: 12 }} value={f.body} onChange={(e) => setF({ ...f, body: e.target.value })} placeholder="Write the campaign message…" />
          <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
            <button className="btn btn-acc" disabled={busy || !f.name.trim()} onClick={create}><Icon name="check" size={15} />{busy ? "Saving…" : "Save campaign"}</button>
            <span className="dim" style={{ fontSize: 12.5 }}>Reaches <b>{audienceLabel(f.audience).count}</b> {audienceLabel(f.audience).count === 1 ? "learner" : "learners"}</span>
          </div>
        </div>
      )}

      {campaigns.length === 0 && !creating ? (
        <MktEmpty icon="mail" title="No campaigns yet" sub="Create a campaign and target all enrolled learners or a specific cohort. Recipient counts stay in sync with live membership." />
      ) : campaigns.length > 0 && (
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {campaigns.map((c) => {
            const a = audienceLabel(c.audience);
            return (
              <div key={c.id} className="card" style={{ padding: 16 }}>
                <div style={{ display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
                  <div style={{ width: 40, height: 40, borderRadius: 10, background: "rgba(249,157,37,.14)", display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name="mail" size={19} style={{ color: "#c97010" }} /></div>
                  <div style={{ flex: 1, minWidth: 180 }}>
                    <div style={{ fontWeight: 700, fontSize: 14.5, color: "var(--ink)" }}>{c.name}</div>
                    <div className="dim" style={{ fontSize: 12.5 }}>{c.subject || "No subject"} · {a.label} · <b>{a.count}</b> recipients</div>
                    {c.status === "sent" && c.sent_at && <div style={{ fontSize: 12, color: "#2e7d32", fontWeight: 700, marginTop: 2 }}>Sent {mktFmtDateTime(c.sent_at)} · {c.sent_count || 0} delivered{c.fail_count ? " · " + c.fail_count + " failed" : ""}</div>}
                  </div>
                  <MktStatusPill status={c.status} />
                  {c.status !== "sent" && (
                    emailReady ? (
                      <>
                        <button className="btn btn-outline btn-sm" disabled={sendBusy === c.id} onClick={() => sendTest(c)} title="Send this campaign only to your own address to check it"><Icon name="mail" size={13} />Test to me</button>
                        <button className="btn btn-acc btn-sm" disabled={sendBusy === c.id} onClick={() => sendNow(c, a)}><Icon name="share" size={13} />{sendBusy === c.id ? "Sending…" : "Send now"}</button>
                      </>
                    ) : (
                      <button className="btn btn-outline btn-sm" onClick={() => { if (window.eduToast) window.eduToast("Connect the email server first — Settings → Connections", "mail"); }} title="Email server not connected"><Icon name="lock" size={13} />Send</button>
                    )
                  )}
                  <button className="btn btn-outline btn-sm" onClick={() => del(c)} style={{ color: "#c62828" }}><Icon name="x" size={13} /></button>
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

/* ── EVENTS (live, org-wide read + create) ── */
function MktEvents({ data, reload }) {
  const [creating, setCreating] = useState(false);
  const [busy, setBusy] = useState(false);
  const [f, setF] = useState({ title: "", date: "", mode: "virtual", capacity: 100, publish: true });
  const events = (data && data.events) || [];
  async function create() {
    if (!f.title.trim() || busy) return;
    setBusy(true);
    const start = f.date ? Math.floor(new Date(f.date).getTime() / 1000) : 0;
    let ok = false;
    try {
      const r = await fetch("/api/events", { method: "POST", headers: { "content-type": "application/json" },
        body: JSON.stringify({ event: { title: f.title.trim(), mode: f.mode, start_at: start, capacity: Number(f.capacity) || 0, status: "confirmed", regPublished: !!f.publish, regSlug: f.title.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) } }) });
      ok = r.ok;
    } catch (e) {}
    setBusy(false);
    if (ok) { setCreating(false); setF({ title: "", date: "", mode: "virtual", capacity: 100, publish: true }); if (window.eduToast) window.eduToast("Event created — visible across the platform", "check"); if (reload) reload(); }
    else if (window.eduToast) window.eduToast("Couldn't create event", "x");
  }
  const inp = { width: "100%", padding: "9px 12px", border: "1.5px solid var(--line)", borderRadius: 9, fontSize: 14, color: "var(--ink)", background: "var(--surface)", fontFamily: "var(--body)", outline: "none" };
  return (
    <div className="vh-in" style={{ padding: "26px 30px", maxWidth: 1180, margin: "0 auto" }}>
      <div className="between" style={{ marginBottom: 16, gap: 12, flexWrap: "wrap" }}>
        <div>
          <h1 style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 24 }}>Events & Webinars</h1>
          <p className="dim" style={{ fontSize: 13.5, marginTop: 4 }}>Every event on the platform with its live registration count. Learners register through the hosted sign-up pages.</p>
        </div>
        <button className="btn btn-acc" onClick={() => setCreating((v) => !v)}><Icon name={creating ? "x" : "plus"} size={15} />{creating ? "Cancel" : "New event"}</button>
      </div>

      {creating && (
        <div className="card" style={{ padding: 20, marginBottom: 16 }}>
          <div style={{ display: "grid", gridTemplateColumns: "2fr 1fr", gap: 12, marginBottom: 12 }}>
            <div><label style={{ fontSize: 11, fontWeight: 800, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: ".06em", display: "block", marginBottom: 6 }}>Event title</label><input style={inp} value={f.title} onChange={(e) => setF({ ...f, title: e.target.value })} placeholder="e.g. PRINCE2 webinar — agile in practice" /></div>
            <div><label style={{ fontSize: 11, fontWeight: 800, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: ".06em", display: "block", marginBottom: 6 }}>Date & time</label><input type="datetime-local" style={inp} value={f.date} onChange={(e) => setF({ ...f, date: e.target.value })} /></div>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr auto", gap: 12, alignItems: "end" }}>
            <div><label style={{ fontSize: 11, fontWeight: 800, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: ".06em", display: "block", marginBottom: 6 }}>Mode</label>
              <select style={inp} value={f.mode} onChange={(e) => setF({ ...f, mode: e.target.value })}><option value="virtual">Virtual</option><option value="physical">In person</option><option value="hybrid">Hybrid</option></select>
            </div>
            <div><label style={{ fontSize: 11, fontWeight: 800, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: ".06em", display: "block", marginBottom: 6 }}>Capacity</label><input type="number" style={inp} value={f.capacity} onChange={(e) => setF({ ...f, capacity: e.target.value })} /></div>
            <label style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 13, fontWeight: 600, paddingBottom: 9 }}><input type="checkbox" checked={f.publish} onChange={(e) => setF({ ...f, publish: e.target.checked })} />Open registration</label>
          </div>
          <div style={{ marginTop: 14 }}><button className="btn btn-acc" disabled={busy || !f.title.trim()} onClick={create}><Icon name="check" size={15} />{busy ? "Creating…" : "Create event"}</button></div>
        </div>
      )}

      {events.length === 0 && !creating ? (
        <MktEmpty icon="calendar" title="No events yet" sub="Create an event or webinar. Once registration is open, sign-ups show up here and in Leads." />
      ) : events.length > 0 && (
        <div className="card" style={{ padding: "6px 8px 10px" }}>
          <table style={{ width: "100%", borderCollapse: "collapse" }}>
            <thead><tr><th style={MKT_TH}>Event</th><th style={MKT_TH}>Date</th><th style={MKT_TH}>Mode</th><th style={MKT_TH}>Status</th><th style={MKT_TH}>Registration</th><th style={{ ...MKT_TH, textAlign: "right" }}>Sign-ups</th></tr></thead>
            <tbody>{events.map((e) => (
              <tr key={e.id}>
                <td style={{ ...MKT_TD, fontWeight: 700 }}>{e.title}</td>
                <td style={{ ...MKT_TD, color: "var(--ink-2)" }}>{mktFmtDateTime(e.start_at)}</td>
                <td style={{ ...MKT_TD, color: "var(--ink-2)", textTransform: "capitalize" }}>{e.mode || "—"}</td>
                <td style={MKT_TD}><MktStatusPill status={e.status} /></td>
                <td style={MKT_TD}>{e.reg_published ? <span style={{ fontSize: 12, fontWeight: 700, color: "#2e7d32" }}>● Open</span> : <span className="dim" style={{ fontSize: 12 }}>Closed</span>}</td>
                <td style={{ ...MKT_TD, textAlign: "right", fontWeight: 800 }}>{e.registrations}</td>
              </tr>
            ))}</tbody>
          </table>
        </div>
      )}
    </div>
  );
}

/* ── WEBSITE POSTING (blog feed) ── */
const MKT_INP = { width: "100%", padding: "9px 12px", border: "1.5px solid var(--line)", borderRadius: 9, fontSize: 14, color: "var(--ink)", background: "var(--surface)", fontFamily: "var(--body)", outline: "none" };
const MKT_LBL = { fontSize: 11, fontWeight: 800, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: ".06em", display: "block", marginBottom: 6 };

function MktWebsite({ data, catalog, reload }) {
  const posts = (data && data.blogPosts) || [];
  const voice = (data && data.voice) || "";
  const [editing, setEditing] = useState(null);
  if (editing) return <MktBlogEditor post={editing} voice={voice} catalog={catalog} onClose={() => setEditing(null)} reload={reload} />;

  async function del(p) { await mktMarketingPost("deleteBlogPost", { id: p.id }); if (window.eduToast) window.eduToast("Post removed", "check"); if (reload) reload(); }
  async function shareToSocial(p) {
    // Next upcoming Tuesday 08:30 (the engine's new-content day); never a slot in the past.
    const mon = mktMonday(0);
    let d = new Date(mon.getFullYear(), mon.getMonth(), mon.getDate() + 1, 8, 30);
    if (d.getTime() <= Date.now()) d = new Date(d.getFullYear(), d.getMonth(), d.getDate() + 7, 8, 30);
    const ok = await mktMarketingPost("saveSocialPost", { post: { platform: "linkedin", strategy: "blog_derived", blog_id: p.id, text: "", status: "draft", scheduled_at: Math.floor(d.getTime() / 1000), week: mktIsoWeek(d), slot: "tuesday" } });
    if (window.eduToast) window.eduToast(ok ? "Added to the social agenda as a draft — open Social posting to write it" : "Couldn't add", ok ? "check" : "x");
    if (reload) reload();
  }
  return (
    <div className="vh-in" style={{ padding: "26px 30px", maxWidth: 1180, margin: "0 auto" }}>
      <div className="between" style={{ marginBottom: 16, gap: 12, flexWrap: "wrap" }}>
        <div>
          <h1 style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 24 }}>Website posting</h1>
          <p className="dim" style={{ fontSize: 13.5, marginTop: 4 }}>Write blog posts, draft them with AI in your brand voice, and schedule when they go live — published posts appear on the public blog automatically.</p>
        </div>
        <div style={{ display: "flex", gap: 8 }}>
          <a className="btn btn-outline" href="blog.html" target="_blank" rel="noopener" style={{ textDecoration: "none" }}><Icon name="globe" size={15} />View live blog</a>
          <button className="btn btn-acc" onClick={() => setEditing({ title: "", slug: "", excerpt: "", body: "", cover: "", course: null, status: "draft", publish_at: 0 })}><Icon name="plus" size={15} />New post</button>
        </div>
      </div>
      {posts.length === 0 ? (
        <MktEmpty icon="edit" title="No posts yet" sub="Write your first blog post — give it a title and let AI draft the article in your brand voice, then schedule it to the website feed." />
      ) : (
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {posts.map((p) => (
            <div key={p.id} className="card" style={{ padding: 16, display: "flex", gap: 14, alignItems: "center", flexWrap: "wrap" }}>
              <div style={{ width: 52, height: 52, borderRadius: 10, background: p.cover ? "#000" : "var(--vh-blue-50)", flexShrink: 0, overflow: "hidden", display: "grid", placeItems: "center" }}>
                {p.cover ? <img src={p.cover} alt="" style={{ width: "100%", height: "100%", objectFit: "cover" }} /> : <Icon name="edit" size={22} style={{ color: "var(--vh-blue-500)" }} />}
              </div>
              <div style={{ flex: 1, minWidth: 200 }}>
                <div style={{ fontWeight: 700, fontSize: 15, color: "var(--ink)" }}>{p.title}</div>
                <div className="dim" style={{ fontSize: 12.5, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", maxWidth: 520 }}>{p.excerpt || "No excerpt yet"}</div>
                <div className="dim" style={{ fontSize: 12, marginTop: 3 }}>{p.status === "scheduled" ? "Scheduled for " + mktFmtDate(p.publish_at) : p.status === "published" ? "Published " + mktFmtDate(p.publish_at) : "Draft"}{p.course ? " · " + p.course : ""}</div>
              </div>
              {p.webhook && <span title={p.webhook.ok ? "Pushed to the external website webhook" : "Webhook push failed" + (p.webhook.error ? " — " + p.webhook.error : "")} style={{ fontSize: 10.5, fontWeight: 800, padding: "2px 8px", borderRadius: 999, background: p.webhook.ok ? "#e8f5e9" : "rgba(198,40,40,.1)", color: p.webhook.ok ? "#2e7d32" : "#c62828" }}>{p.webhook.ok ? "↗ webhook" : "webhook ✗"}</span>}
              <MktStatusPill status={p.status} />
              {(p.status === "published" || (p.status === "scheduled" && p.publish_at && p.publish_at * 1000 <= Date.now())) &&
                <a className="btn btn-outline btn-sm" href={"blog.html#" + encodeURIComponent(p.id)} target="_blank" rel="noopener" style={{ textDecoration: "none" }} title="Open on the live blog"><Icon name="globe" size={13} />Live</a>}
              <button className="btn btn-outline btn-sm" onClick={() => setEditing(p)}><Icon name="edit" size={13} />Edit</button>
              <button className="btn btn-outline btn-sm" onClick={() => shareToSocial(p)} title="Create a social post from this blog post"><Icon name="share" size={13} />Social</button>
              <button className="btn btn-outline btn-sm" onClick={() => del(p)} style={{ color: "#c62828" }}><Icon name="x" size={13} /></button>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function MktBlogEditor({ post, voice, catalog, onClose, reload }) {
  const [f, setF] = useState({ ...post });
  const [busy, setBusy] = useState(false);
  const set = (k, v) => setF((s) => ({ ...s, [k]: v }));
  const courseName = f.course ? ((catalog || []).find((c) => c.id === f.course) || {}).name || f.course : "";
  async function aiWrite() {
    if (!f.title.trim()) { if (window.eduToast) window.eduToast("Add a title first", "x"); return; }
    if (!mktCanAI()) { if (window.eduToast) window.eduToast("AI isn't available here", "x"); return; }
    setBusy(true);
    const prompt = [
      "You are a B2B content writer for Van Haren Publishing, a professional training and certification publisher.",
      "Brand voice: " + (voice || MKT_DEFAULT_VOICE),
      'Write a blog post titled "' + f.title.trim() + '".',
      courseName ? ("It relates to the certification/course: " + courseName + ".") : "",
      "Return ONLY a JSON object (no markdown, no code fences): {\"excerpt\":\"a 1-2 sentence summary\",\"body\":\"the full article in markdown, 400-700 words, short paragraphs and 2-4 subheadings\"}.",
      "Do not invent statistics, dates, client names or quotes.",
    ].filter(Boolean).join("\n");
    try {
      const out = await window.claude.complete(prompt);
      const j = mktExtractJson(out);
      if (j) { setF((s) => ({ ...s, excerpt: j.excerpt || s.excerpt, body: j.body || s.body, slug: s.slug || mktSlug(f.title) })); if (window.eduToast) window.eduToast("Draft written — review and edit", "check"); }
      else if (window.eduToast) window.eduToast("Couldn't parse the AI output — try again", "x");
    } catch (e) { if (window.eduToast) window.eduToast("AI request failed", "x"); }
    setBusy(false);
  }
  async function save(status, publishAt) {
    const rec = { ...f, slug: f.slug || mktSlug(f.title), status, publish_at: publishAt != null ? publishAt : f.publish_at };
    const ok = await mktMarketingPost("saveBlogPost", { post: rec });
    if (ok) { if (window.eduToast) window.eduToast(status === "published" ? "Published to the website feed" : status === "scheduled" ? "Scheduled" : "Draft saved", "check"); if (reload) reload(); onClose(); }
    else if (window.eduToast) window.eduToast("Couldn't save", "x");
  }
  const schedVal = mktLocalDT(f.publish_at);
  return (
    <div className="vh-in" style={{ padding: "22px 30px", maxWidth: 1180, margin: "0 auto" }}>
      <button className="btn btn-ghost btn-sm" onClick={onClose} style={{ marginBottom: 14 }}><Icon name="chevL" size={14} />Back to posts</button>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 320px", gap: 20, alignItems: "start" }}>
        <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
          <div className="card" style={{ padding: 22 }}>
            <div className="between" style={{ marginBottom: 14 }}>
              <div className="eyebrow">Post</div>
              <button className="btn btn-acc btn-sm" disabled={busy} onClick={aiWrite}><Icon name="wand" size={14} />{busy ? "Writing…" : "Write with AI"}</button>
            </div>
            <label style={MKT_LBL}>Title</label>
            <input style={{ ...MKT_INP, marginBottom: 14 }} value={f.title} onChange={(e) => set("title", e.target.value)} placeholder="e.g. Why PRINCE2 still matters in agile teams" />
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14, marginBottom: 14 }}>
              <div><label style={MKT_LBL}>URL slug</label><input style={MKT_INP} value={f.slug} onChange={(e) => set("slug", e.target.value)} placeholder={mktSlug(f.title) || "url-slug"} /></div>
              <div><label style={MKT_LBL}>Related course (optional)</label>
                <select style={MKT_INP} value={f.course || ""} onChange={(e) => set("course", e.target.value || null)}>
                  <option value="">— none —</option>
                  {(catalog || []).map((c) => <option key={c.id} value={c.id}>{c.name}</option>)}
                </select>
              </div>
            </div>
            <label style={MKT_LBL}>Excerpt</label>
            <textarea style={{ ...MKT_INP, minHeight: 56, resize: "vertical", lineHeight: 1.5, marginBottom: 14 }} value={f.excerpt} onChange={(e) => set("excerpt", e.target.value)} placeholder="Short summary shown in the feed" />
            <label style={MKT_LBL}>Body (markdown)</label>
            <textarea style={{ ...MKT_INP, minHeight: 300, resize: "vertical", lineHeight: 1.6, fontFamily: "ui-monospace, Menlo, monospace", fontSize: 13 }} value={f.body} onChange={(e) => set("body", e.target.value)} placeholder="Write the article, or use “Write with AI” above…" />
          </div>
          <div className="card" style={{ padding: 22 }}>
            <label style={MKT_LBL}>Cover image URL (optional)</label>
            <input style={MKT_INP} value={f.cover} onChange={(e) => set("cover", e.target.value)} placeholder="https://…" />
          </div>
        </div>
        <div className="card" style={{ padding: 20, position: "sticky", top: 0 }}>
          <div className="eyebrow" style={{ marginBottom: 12 }}>Publish</div>
          <div style={{ marginBottom: 12 }}><MktStatusPill status={f.status} /></div>
          <label style={MKT_LBL}>Schedule date & time</label>
          <input type="datetime-local" style={{ ...MKT_INP, marginBottom: 14 }} value={schedVal} onChange={(e) => set("publish_at", e.target.value ? Math.floor(new Date(e.target.value).getTime() / 1000) : 0)} />
          <button className="btn btn-outline" style={{ width: "100%", justifyContent: "center", marginBottom: 8 }} onClick={() => save("draft")}><Icon name="file" size={14} />Save draft</button>
          <button className="btn btn-outline" style={{ width: "100%", justifyContent: "center", marginBottom: 8 }} disabled={!f.publish_at} onClick={() => save("scheduled")}><Icon name="clock" size={14} />Schedule</button>
          <button className="btn btn-acc" style={{ width: "100%", justifyContent: "center" }} onClick={() => save("published", Math.floor(Date.now() / 1000))}><Icon name="share" size={14} />Publish now</button>
          <p className="dim" style={{ fontSize: 11.5, lineHeight: 1.5, marginTop: 12 }}>Publishing puts the post on the live public blog; scheduled posts appear there automatically when their time arrives. If an external website webhook is connected (Settings → Connections), the post is pushed there too.</p>
        </div>
      </div>
    </div>
  );
}

/* ── SOCIAL POSTING AGENDA (weekly board) ── */
function MktSocial({ data, reload }) {
  const social = (data && data.socialPosts) || [];
  const blogs = (data && data.blogPosts) || [];
  const voice = (data && data.voice) || "";
  const [editing, setEditing] = useState(null); // {post?, week, slot, scheduled_at}
  const WEEKS = 6;
  const byKey = {};
  social.forEach((p) => { if (p.week && p.slot) byKey[p.week + "|" + p.slot] = p; });

  function openSlot(weekOffset, slot, slotIdx) {
    const mon = mktMonday(weekOffset);
    const d = new Date(mon.getFullYear(), mon.getMonth(), mon.getDate() + slotIdx, 8, 30);
    const week = mktIsoWeek(d);
    const existing = byKey[week + "|" + slot];
    setEditing(existing || { platform: "linkedin", strategy: "expert_insight", blog_id: null, text: "", hashtags: [], status: "draft", scheduled_at: Math.floor(d.getTime() / 1000), week, slot });
  }
  const upcoming = social.slice().filter((p) => p.scheduled_at).sort((a, b) => a.scheduled_at - b.scheduled_at);

  return (
    <div className="vh-in" style={{ padding: "26px 30px", maxWidth: 1180, margin: "0 auto" }}>
      <div className="between" style={{ marginBottom: 8, gap: 12, flexWrap: "wrap" }}>
        <div>
          <h1 style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 24 }}>Social posting agenda</h1>
          <p className="dim" style={{ fontSize: 13.5, marginTop: 4 }}>Plan social posts across the week. Pick a slot, choose a strategy, and let AI draft it in your brand voice — one post per weekday is the sweet spot.</p>
        </div>
      </div>

      <div style={{ overflowX: "auto" }} className="scroll">
        <div style={{ minWidth: 720 }}>
          <div style={{ display: "grid", gridTemplateColumns: "88px repeat(5,1fr)", gap: 8, marginBottom: 8 }}>
            <div />
            {MKT_WEEKDAYS.map((wd) => <div key={wd} style={{ fontSize: 11.5, fontWeight: 800, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: ".05em", padding: "0 4px" }}>{MKT_WD_SHORT[wd]}</div>)}
          </div>
          {Array.from({ length: WEEKS }).map((_, wOff) => {
            const mon = mktMonday(wOff);
            const label = wOff === 0 ? "This week" : wOff === 1 ? "Next week" : mon.toLocaleDateString("en-GB", { day: "numeric", month: "short" });
            return (
              <div key={wOff} style={{ display: "grid", gridTemplateColumns: "88px repeat(5,1fr)", gap: 8, marginBottom: 8 }}>
                <div style={{ fontSize: 12, fontWeight: 700, color: "var(--ink-2)", display: "flex", alignItems: "center" }}>{label}</div>
                {MKT_WEEKDAYS.map((wd, wi) => {
                  const d = new Date(mon.getFullYear(), mon.getMonth(), mon.getDate() + wi, 8, 30);
                  const key = mktIsoWeek(d) + "|" + wd;
                  const p = byKey[key];
                  if (!p) return (
                    <button key={wd} onClick={() => openSlot(wOff, wd, wi)} style={{ minHeight: 74, borderRadius: 10, border: "1.5px dashed var(--line)", background: "var(--surface)", cursor: "pointer", color: "var(--ink-3)", display: "grid", placeItems: "center", fontSize: 12, fontWeight: 600, fontFamily: "var(--body)" }}
                      onMouseEnter={(e) => { e.currentTarget.style.borderColor = "var(--vh-blue-500)"; e.currentTarget.style.color = "var(--vh-blue-500)"; }}
                      onMouseLeave={(e) => { e.currentTarget.style.borderColor = "var(--line)"; e.currentTarget.style.color = "var(--ink-3)"; }}><Icon name="plus" size={15} /></button>
                  );
                  const plat = mktPlatform(p.platform);
                  return (
                    <button key={wd} onClick={() => openSlot(wOff, wd, wi)} style={{ minHeight: 74, borderRadius: 10, border: "1.5px solid var(--line)", background: "var(--surface)", cursor: "pointer", textAlign: "left", padding: "8px 9px", display: "flex", flexDirection: "column", gap: 4, fontFamily: "var(--body)", overflow: "hidden" }}>
                      <div style={{ display: "flex", alignItems: "center", gap: 5 }}>
                        <span style={{ width: 16, height: 16, borderRadius: 4, background: plat.color, display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name={plat.icon} size={9} style={{ color: "#fff" }} /></span>
                        <span style={{ fontSize: 10.5, fontWeight: 800, color: p.status === "published" ? "#2e7d32" : p.status === "scheduled" ? "#0d6cab" : "#c97010" }}>{p.status}</span>
                      </div>
                      <div style={{ fontSize: 11.5, color: "var(--ink)", lineHeight: 1.35, display: "-webkit-box", WebkitLineClamp: 3, WebkitBoxOrient: "vertical", overflow: "hidden" }}>{p.text ? p.text.slice(0, 90) : "(empty — click to write)"}</div>
                    </button>
                  );
                })}
              </div>
            );
          })}
        </div>
      </div>

      <div style={{ marginTop: 22 }}>
        <div className="eyebrow" style={{ marginBottom: 10 }}>Upcoming posts</div>
        {upcoming.length === 0 ? (
          <MktEmpty icon="share" title="Nothing scheduled yet" sub="Click a day in the board above to plan a post." />
        ) : (
          <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
            {upcoming.map((p) => {
              const plat = mktPlatform(p.platform);
              const strat = MKT_STRATEGIES.find((s) => s.id === p.strategy);
              return (
                <div key={p.id} className="card" style={{ padding: 14, display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" }}>
                  <span style={{ width: 30, height: 30, borderRadius: 8, background: plat.color, display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name={plat.icon} size={15} style={{ color: "#fff" }} /></span>
                  <div style={{ flex: 1, minWidth: 200 }}>
                    <div style={{ fontSize: 13.5, color: "var(--ink)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", maxWidth: 560, fontWeight: 600 }}>{p.text ? p.text.slice(0, 120) : "(empty draft)"}</div>
                    <div className="dim" style={{ fontSize: 12 }}>{plat.label} · {strat ? strat.label : p.strategy} · {mktFmtDateTime(p.scheduled_at)}</div>
                  </div>
                  <MktStatusPill status={p.status} />
                  <button className="btn btn-outline btn-sm" onClick={() => setEditing(p)}><Icon name="edit" size={13} />Edit</button>
                </div>
              );
            })}
          </div>
        )}
      </div>

      {editing && <MktSocialEditor post={editing} voice={voice} blogs={blogs} liConnected={!!(data && data.connections && data.connections.linkedin && data.connections.linkedin.connected)} onClose={() => setEditing(null)} reload={reload} />}
    </div>
  );
}

function MktSocialEditor({ post, voice, blogs, liConnected, onClose, reload }) {
  const [f, setF] = useState({ ...post, hashtags: post.hashtags || [] });
  const [busy, setBusy] = useState(false);
  const [liBusy, setLiBusy] = useState(false);
  const set = (k, v) => setF((s) => ({ ...s, [k]: v }));
  const strat = MKT_STRATEGIES.find((s) => s.id === f.strategy) || MKT_STRATEGIES[0];
  async function aiGen() {
    if (!mktCanAI()) { if (window.eduToast) window.eduToast("AI isn't available here", "x"); return; }
    setBusy(true);
    const blog = f.blog_id ? (blogs || []).find((b) => b.id === f.blog_id) : null;
    const plat = mktPlatform(f.platform).label;
    const prompt = [
      "You are a social media ghostwriter for Van Haren Publishing (professional training & certification).",
      "Brand voice: " + (voice || MKT_DEFAULT_VOICE),
      "Write ONE " + plat + " post using this strategy — " + strat.label + ": " + strat.desc,
      blog ? ('Base it on this blog post. Title: "' + blog.title + '". Summary: ' + (blog.excerpt || "")) : "",
      "Rules: between 50 and 3000 characters; short paragraphs; NO URLs in the body; no engagement bait; do not invent statistics or dates.",
      "Return ONLY a JSON object (no markdown, no code fences): {\"text\":\"the post text\",\"hashtags\":[\"#Tag\"]} with 0-3 niche hashtags.",
    ].filter(Boolean).join("\n");
    try {
      const out = await window.claude.complete(prompt);
      const j = mktExtractJson(out);
      if (j) {
        let text = String(j.text || "").replace(/https?:\/\/\S+/g, "").slice(0, 3000).trim();
        let tags = Array.isArray(j.hashtags) ? j.hashtags.map((h) => String(h).trim()).filter(Boolean).map((h) => h[0] === "#" ? h : "#" + h).slice(0, 5) : [];
        setF((s) => ({ ...s, text, hashtags: tags }));
        if (window.eduToast) window.eduToast("Draft generated — review and edit", "check");
      } else if (window.eduToast) window.eduToast("Couldn't parse the AI output — try again", "x");
    } catch (e) { if (window.eduToast) window.eduToast("AI request failed", "x"); }
    setBusy(false);
  }
  async function save(status) {
    const ok = await mktMarketingPost("saveSocialPost", { post: { ...f, status } });
    if (ok) { if (window.eduToast) window.eduToast(status === "published" ? "Marked as posted" : status === "scheduled" ? "Scheduled" : "Draft saved", "check"); if (reload) reload(); onClose(); }
    else if (window.eduToast) window.eduToast("Couldn't save", "x");
  }
  async function del() { if (f.id) await mktMarketingPost("deleteSocialPost", { id: f.id }); if (window.eduToast) window.eduToast("Post removed", "check"); if (reload) reload(); onClose(); }
  /* Really publish on LinkedIn via the OAuth connection: save first (to get an id), then post. */
  async function postToLinkedIn() {
    if (!f.text.trim() || liBusy) return;
    setLiBusy(true);
    const saved = await mktMarketingPostJson("saveSocialPost", { post: { ...f } });
    const id = (saved.post && saved.post.id) || f.id;
    if (!saved.ok || !id) { setLiBusy(false); if (window.eduToast) window.eduToast("Couldn't save the post first", "x"); return; }
    const res = await mktMarketingPostJson("postLinkedIn", { id });
    setLiBusy(false);
    if (res.ok) { if (window.eduToast) window.eduToast("Posted to LinkedIn ✓" + (res.url ? " — view it on your feed" : ""), "check"); if (reload) reload(); onClose(); }
    else if (window.eduToast) window.eduToast(res.message || ("LinkedIn post failed — " + (res.error || "")), "x");
  }
  const tagStr = (f.hashtags || []).join(" ");
  return ReactDOM.createPortal(
    <div style={{ position: "fixed", inset: 0, background: "rgba(13,26,52,.5)", backdropFilter: "blur(3px)", zIndex: 1000, display: "grid", placeItems: "center", padding: 24 }} onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="card scroll" style={{ width: "100%", maxWidth: 620, maxHeight: "92vh", overflow: "auto", padding: 24 }}>
        <div className="between" style={{ marginBottom: 14 }}>
          <div>
            <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 18 }}>Social post</div>
            <div className="dim" style={{ fontSize: 12.5 }}>{MKT_WD_SHORT[f.slot] || ""} · {mktFmtDateTime(f.scheduled_at)}</div>
          </div>
          <button onClick={onClose} style={{ width: 32, height: 32, borderRadius: 9, background: "var(--surface-2)", border: "none", cursor: "pointer", display: "grid", placeItems: "center" }}><Icon name="x" size={16} style={{ color: "var(--ink-3)" }} /></button>
        </div>

        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 12 }}>
          <div><label style={MKT_LBL}>Platform</label>
            <select style={MKT_INP} value={f.platform} onChange={(e) => set("platform", e.target.value)}>{MKT_PLATFORMS.map((p) => <option key={p.id} value={p.id}>{p.label}</option>)}</select>
          </div>
          <div><label style={MKT_LBL}>Strategy</label>
            <select style={MKT_INP} value={f.strategy} onChange={(e) => set("strategy", e.target.value)}>{MKT_STRATEGIES.map((s) => <option key={s.id} value={s.id}>{s.label}</option>)}</select>
          </div>
        </div>
        <p className="dim" style={{ fontSize: 12, marginBottom: 12, lineHeight: 1.5 }}>{strat.desc} · <span style={{ fontStyle: "italic" }}>{strat.hint}</span></p>

        {f.strategy === "blog_derived" && (
          <div style={{ marginBottom: 12 }}>
            <label style={MKT_LBL}>From blog post</label>
            <select style={MKT_INP} value={f.blog_id || ""} onChange={(e) => set("blog_id", e.target.value || null)}>
              <option value="">— pick a website post —</option>
              {(blogs || []).map((b) => <option key={b.id} value={b.id}>{b.title}</option>)}
            </select>
          </div>
        )}

        <div className="between" style={{ marginBottom: 6 }}>
          <label style={MKT_LBL}>Post text</label>
          <button className="btn btn-acc btn-sm" disabled={busy} onClick={aiGen}><Icon name="wand" size={13} />{busy ? "Writing…" : "Generate"}</button>
        </div>
        <textarea style={{ ...MKT_INP, minHeight: 150, resize: "vertical", lineHeight: 1.55, marginBottom: 6 }} value={f.text} onChange={(e) => set("text", e.target.value)} placeholder="Write the post, or pick a strategy and hit Generate…" />
        <div className="dim" style={{ fontSize: 11.5, textAlign: "right", marginBottom: 12 }}>{(f.text || "").length}/3000</div>

        <label style={MKT_LBL}>Hashtags</label>
        <input style={{ ...MKT_INP, marginBottom: 14 }} value={tagStr} onChange={(e) => set("hashtags", e.target.value.split(/[\s,]+/).filter(Boolean).map((h) => h[0] === "#" ? h : "#" + h).slice(0, 5))} placeholder="#ProjectManagement #IPMA" />

        <label style={MKT_LBL}>Scheduled date & time</label>
        <input type="datetime-local" style={{ ...MKT_INP, marginBottom: 18 }} value={mktLocalDT(f.scheduled_at)}
          onChange={(e) => { const ts = e.target.value ? Math.floor(new Date(e.target.value).getTime() / 1000) : 0; const d = ts ? new Date(ts * 1000) : null; set("scheduled_at", ts); if (d) { setF((s) => ({ ...s, scheduled_at: ts, week: mktIsoWeek(d), slot: MKT_WEEKDAYS[(d.getDay() + 6) % 7] || s.slot })); } }} />

        <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
          <button className="btn btn-outline" onClick={() => save("draft")}><Icon name="file" size={14} />Save draft</button>
          <button className="btn btn-outline" onClick={() => save("scheduled")}><Icon name="clock" size={14} />Schedule</button>
          {f.platform === "linkedin" && liConnected
            ? <button className="btn btn-acc" disabled={liBusy || !f.text.trim()} onClick={postToLinkedIn} style={{ background: "#0a66c2", borderColor: "#0a66c2" }}><Icon name="share" size={14} />{liBusy ? "Posting…" : "Post to LinkedIn now"}</button>
            : <button className="btn btn-acc" onClick={() => save("published")} title={f.platform === "linkedin" ? "Connect LinkedIn in Settings to post natively; this just records it as posted" : "Records the post as published"}><Icon name="check" size={14} />Mark posted</button>}
          <div style={{ flex: 1 }} />
          {f.id && <button className="btn btn-ghost" onClick={del} style={{ color: "#c62828" }}><Icon name="x" size={14} />Delete</button>}
        </div>
        {f.platform === "linkedin" && !liConnected && <p className="dim" style={{ fontSize: 11.5, marginTop: 10, lineHeight: 1.5 }}>Tip: connect a LinkedIn account under Settings → Connections to publish natively from here.</p>}
      </div>
    </div>, document.body);
}

/* ── SETTINGS + CONNECTIONS ── */
function MktConnBadge({ ok, warnLabel, okLabel }) {
  return ok
    ? <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12, fontWeight: 800, color: "#2e7d32", background: "#e8f5e9", border: "1px solid #bfe3c5", borderRadius: 999, padding: "4px 11px" }}><span style={{ width: 7, height: 7, borderRadius: "50%", background: "#2e7d32" }} />{okLabel || "Connected"}</span>
    : <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12, fontWeight: 800, color: "#c97010", background: "rgba(249,157,37,.14)", border: "1px solid rgba(249,157,37,.4)", borderRadius: 999, padding: "4px 11px" }}><span style={{ width: 7, height: 7, borderRadius: "50%", background: "#f99d25" }} />{warnLabel || "Not connected"}</span>;
}
function MktSteps({ steps }) {
  return (
    <ol style={{ margin: "10px 0 0 18px, ", padding: 0, marginLeft: 18, display: "flex", flexDirection: "column", gap: 6 }}>
      {steps.map((s, i) => <li key={i} style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.5 }}>{s}</li>)}
    </ol>
  );
}
function MktCode({ children }) {
  return <code style={{ background: "var(--surface-2)", border: "1px solid var(--line)", borderRadius: 6, padding: "1px 7px", fontSize: 12, fontFamily: "ui-monospace, Menlo, monospace", wordBreak: "break-all" }}>{children}</code>;
}

function MktConnections({ data, reload }) {
  const conn = (data && data.connections) || {};
  const email = conn.email || {};
  const li = conn.linkedin || {};
  const web = conn.website || {};
  const [testBusy, setTestBusy] = useState(false);
  const [whUrl, setWhUrl] = useState(web.webhookUrl || "");
  const [whSecret, setWhSecret] = useState("");
  useEffect(() => { setWhUrl(web.webhookUrl || ""); }, [web.webhookUrl]);

  async function sendTest() {
    setTestBusy(true);
    try {
      const r = await fetch("/api/marketing", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action: "testEmail" }) });
      const d = await r.json().catch(() => ({}));
      if (r.ok) { if (window.eduToast) window.eduToast("Test email sent to " + (d.to || "you") + " ✓", "check"); }
      else if (window.eduToast) window.eduToast(d.error === "email_not_configured" ? "Email server not connected yet — follow the steps below" : "Send failed — " + (d.detail || d.error), "x");
    } catch (e) { if (window.eduToast) window.eduToast("Send failed", "x"); }
    setTestBusy(false);
  }
  async function saveWebhook() {
    const ok = await mktMarketingPost("saveConnections", { website_webhook_url: whUrl.trim(), website_webhook_secret: whSecret.trim() });
    if (window.eduToast) window.eduToast(ok ? (whUrl.trim() ? "Webhook saved — published posts will be pushed to it" : "Webhook removed") : "Couldn't save", ok ? "check" : "x");
    setWhSecret("");
    if (ok && reload) reload();
  }
  async function disconnectLi() {
    try { await fetch("/api/linkedin/disconnect", { method: "POST" }); } catch (e) {}
    if (window.eduToast) window.eduToast("LinkedIn disconnected", "check");
    if (reload) reload();
  }

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
      {/* EMAIL SERVER */}
      <div className="card" style={{ padding: 22 }}>
        <div className="between" style={{ marginBottom: 6 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <div style={{ width: 36, height: 36, borderRadius: 10, background: "rgba(249,157,37,.14)", display: "grid", placeItems: "center" }}><Icon name="mail" size={18} style={{ color: "#c97010" }} /></div>
            <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 16 }}>Email server</div>
          </div>
          <MktConnBadge ok={email.configured} okLabel={"Connected · " + (email.from || "")} warnLabel="Not connected" />
        </div>
        <p className="dim" style={{ fontSize: 13, lineHeight: 1.55, marginBottom: 10 }}>Powers real campaign sends and test emails (via Resend). {email.configured ? "Campaigns can send for real." : "Until connected, campaigns can be drafted and scheduled but not sent."}</p>
        {!email.configured && (
          <MktSteps steps={[
            <span key="1">Create a free account at <b>resend.com</b> and verify your sending domain (e.g. vanharen.net).</span>,
            <span key="2">Create an API key in Resend.</span>,
            <span key="3">Add two secrets to the Cloudflare Pages project <b>learnalot-lms</b> (Dashboard → Settings → Environment variables, or <MktCode>npx wrangler pages secret put RESEND_API_KEY</MktCode> and <MktCode>npx wrangler pages secret put EMAIL_FROM</MktCode> — e.g. <MktCode>Van Haren Learning &lt;news@vanharen.net&gt;</MktCode>).</span>,
            <span key="4">Redeploy (any deploy picks the secrets up), then press "Send test email" here.</span>,
          ]} />
        )}
        <div style={{ marginTop: 12 }}>
          <button className="btn btn-outline btn-sm" disabled={testBusy} onClick={sendTest}><Icon name="mail" size={13} />{testBusy ? "Sending…" : "Send test email to me"}</button>
        </div>
      </div>

      {/* WEBSITE */}
      <div className="card" style={{ padding: 22 }}>
        <div className="between" style={{ marginBottom: 6 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <div style={{ width: 36, height: 36, borderRadius: 10, background: "var(--vh-blue-50)", display: "grid", placeItems: "center" }}><Icon name="globe" size={18} style={{ color: "var(--vh-blue-500)" }} /></div>
            <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 16 }}>Website</div>
          </div>
          <MktConnBadge ok={true} okLabel="Hosted feed live" />
        </div>
        <p className="dim" style={{ fontSize: 13, lineHeight: 1.55, marginBottom: 10 }}>
          Published (and scheduled-then-due) website posts appear automatically on the hosted public blog: <a href={web.feedUrl || "blog.html"} target="_blank" rel="noopener" style={{ color: "var(--vh-blue-600)", fontWeight: 700 }}>{web.feedUrl || "blog.html"}</a>. Share or link that page anywhere.
        </p>
        <div style={{ borderTop: "1px solid var(--line-2)", paddingTop: 12, marginTop: 4 }}>
          <div style={{ fontSize: 13, fontWeight: 700, marginBottom: 4 }}>Push to an external website (optional)</div>
          <p className="dim" style={{ fontSize: 12.5, lineHeight: 1.5, marginBottom: 10 }}>Also POST every published post as JSON to your own CMS endpoint (WordPress plugin, Zapier/Make webhook, custom API). The secret is sent as the <MktCode>x-learnalot-secret</MktCode> header.</p>
          <div style={{ display: "grid", gridTemplateColumns: "2fr 1fr auto", gap: 8 }}>
            <input style={MKT_INP} value={whUrl} onChange={(e) => setWhUrl(e.target.value)} placeholder="https://your-site.com/webhooks/learnalot" />
            <input style={MKT_INP} type="password" value={whSecret} onChange={(e) => setWhSecret(e.target.value)} placeholder={web.hasWebhookSecret ? "Secret set — type to replace" : "Shared secret"} />
            <button className="btn btn-outline btn-sm" onClick={saveWebhook}><Icon name="check" size={13} />Save</button>
          </div>
        </div>
      </div>

      {/* LINKEDIN */}
      <div className="card" style={{ padding: 22 }}>
        <div className="between" style={{ marginBottom: 6 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <div style={{ width: 36, height: 36, borderRadius: 10, background: "rgba(10,102,194,.12)", display: "grid", placeItems: "center" }}><Icon name="users" size={18} style={{ color: "#0a66c2" }} /></div>
            <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 16 }}>LinkedIn account</div>
          </div>
          <MktConnBadge ok={li.connected} okLabel={"Connected · " + (li.person || "")} warnLabel={li.appConfigured ? "App ready — not connected" : "App not configured"} />
        </div>
        <p className="dim" style={{ fontSize: 13, lineHeight: 1.55, marginBottom: 10 }}>Lets the Social posting agenda publish posts natively to LinkedIn with one click.</p>
        {!li.appConfigured && (
          <MktSteps steps={[
            <span key="1">Create an app at <b>linkedin.com/developers</b> and request the free <b>"Share on LinkedIn"</b> product (grants the posting permission).</span>,
            <span key="2">Under Auth, register this redirect URL: <MktCode>{li.callbackUrl || "…/api/linkedin/callback"}</MktCode></span>,
            <span key="3">Add the app's credentials as Pages secrets: <MktCode>npx wrangler pages secret put LINKEDIN_CLIENT_ID</MktCode> and <MktCode>npx wrangler pages secret put LINKEDIN_CLIENT_SECRET</MktCode>, then redeploy.</span>,
            <span key="4">Come back here and press "Connect LinkedIn account".</span>,
          ]} />
        )}
        <div style={{ marginTop: 12, display: "flex", gap: 8 }}>
          {li.connected
            ? <button className="btn btn-outline btn-sm" onClick={disconnectLi} style={{ color: "#c62828" }}><Icon name="x" size={13} />Disconnect</button>
            : <a className="btn btn-acc btn-sm" href="/api/linkedin/connect" style={{ textDecoration: "none", opacity: li.appConfigured ? 1 : .55, pointerEvents: li.appConfigured ? "auto" : "none" }}><Icon name="link" size={13} />Connect LinkedIn account</a>}
          {li.connected && li.expires_at > 0 && <span className="dim" style={{ fontSize: 12, alignSelf: "center" }}>Token valid until {mktFmtDate(li.expires_at)}</span>}
        </div>
      </div>
    </div>
  );
}

function MktSettings({ data, reload }) {
  const u = window.AUTH_USER || {};
  const [voice, setVoice] = useState((data && data.voice) || "");
  const [savingV, setSavingV] = useState(false);
  useEffect(() => { setVoice((data && data.voice) || ""); }, [data && data.voice]);
  async function saveVoice() {
    setSavingV(true);
    const ok = await mktMarketingPost("saveVoice", { voice });
    setSavingV(false);
    if (window.eduToast) window.eduToast(ok ? "Brand voice saved" : "Couldn't save", ok ? "check" : "x");
    if (ok && reload) reload();
  }
  return (
    <div className="vh-in" style={{ padding: "26px 30px", maxWidth: 780, margin: "0 auto" }}>
      <h1 style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 24, marginBottom: 6 }}>Settings</h1>
      <p className="dim" style={{ fontSize: 13.5, marginBottom: 18 }}>Connections make the marketing tools operate for real: email campaigns, the public website feed and native LinkedIn posting.</p>

      <div className="eyebrow" style={{ marginBottom: 12 }}>Connections</div>
      <MktConnections data={data} reload={reload} />

      <div className="eyebrow" style={{ margin: "22px 0 12px" }}>Content</div>
      <div className="card" style={{ padding: 22, marginBottom: 16 }}>
        <div className="eyebrow" style={{ marginBottom: 8 }}>Brand voice</div>
        <p className="dim" style={{ fontSize: 13, lineHeight: 1.55, marginBottom: 12 }}>The style rules the AI follows when it drafts website posts and social posts. Edit it to match your tone, audience and taboo phrases — no code changes needed.</p>
        <textarea style={{ ...MKT_INP, minHeight: 120, resize: "vertical", lineHeight: 1.5, marginBottom: 12 }} value={voice} onChange={(e) => setVoice(e.target.value)} placeholder={MKT_DEFAULT_VOICE} />
        <button className="btn btn-acc" disabled={savingV} onClick={saveVoice}><Icon name="check" size={15} />{savingV ? "Saving…" : "Save brand voice"}</button>
      </div>

      <div className="eyebrow" style={{ margin: "6px 0 12px" }}>Account & environment</div>
      <div className="card" style={{ padding: 22, marginBottom: 16 }}>
        <div className="eyebrow" style={{ marginBottom: 12 }}>Account</div>
        <div style={{ display: "grid", gridTemplateColumns: "140px 1fr", rowGap: 10, fontSize: 14 }}>
          <div className="dim">Name</div><div style={{ fontWeight: 600 }}>{u.name || "—"}</div>
          <div className="dim">Email</div><div style={{ fontWeight: 600 }}>{u.email || "—"}</div>
          <div className="dim">Role</div><div style={{ fontWeight: 600, textTransform: "capitalize" }}>{u.role || "marketeer"}</div>
        </div>
      </div>
      <div className="card" style={{ padding: 22 }}>
        <div className="eyebrow" style={{ marginBottom: 8 }}>Connected environment · live data</div>
        <p className="dim" style={{ fontSize: 13.5, lineHeight: 1.55 }}>
          This marketeer workspace runs on the same live platform data as the learner and educator environments: the course catalog the educators publish, real cohorts and their enrolled learners, and actual event registrations. Campaigns send to real enrolled learners once the email server is connected; website posts go live on the hosted blog feed; social posts publish natively once LinkedIn is connected.
        </p>
      </div>
    </div>
  );
}

/* ── APP ── */
function MktApp() {
  const [route, setRoute] = useState("dashboard");
  const [toastMsg, setToastMsg] = useState(null);
  const [catalog, setCatalog] = useState([]);
  const [mkt, setMkt] = useState(null);
  const toast = (msg, icon) => setToastMsg({ msg, icon, key: Date.now() });
  useEffect(() => { window.eduToast = toast; }, []);
  useEffect(() => { if (!toastMsg) return; const id = setTimeout(() => setToastMsg(null), 2800); return () => clearTimeout(id); }, [toastMsg]);

  async function loadCatalog() {
    let shared = [];
    try { shared = window.VHCatalog ? await window.VHCatalog.get() : []; } catch (e) {}
    setCatalog(Array.isArray(shared) ? shared : []);
  }
  async function loadMkt() {
    try { const r = await fetch("/api/marketing", { headers: { accept: "application/json" } }); setMkt(r.ok ? await r.json() : null); }
    catch (e) { setMkt(null); }
  }
  function reloadAll() { loadCatalog(); loadMkt(); }
  useEffect(() => { reloadAll(); }, []);
  function onCopySaved(id, page) { setCatalog((cs) => cs.map((c) => c.id === id ? { ...c, page } : c)); loadMkt(); }

  const go = (r) => { setRoute(r); document.querySelector(".canvas")?.scrollTo(0, 0); };
  const Provider = (typeof LangContext !== "undefined") ? LangContext.Provider : React.Fragment;
  const provProps = (typeof LangContext !== "undefined") ? { value: { lang: "en", setLang: () => {} } } : {};

  return (
    <Provider {...provProps}>
      <div className="app">
        <MktSidebar route={route} go={go} />
        <div className="main">
          <MktTopbar route={route} onRefresh={reloadAll} />
          <div className="canvas scroll">
            {route === "dashboard" && <MktDashboard go={go} catalog={catalog} data={mkt} />}
            {route === "catalog" && <MktCatalog catalog={catalog} onCopySaved={onCopySaved} />}
            {route === "campaigns" && <MktCampaigns data={mkt} reload={loadMkt} />}
            {route === "events" && <MktEvents data={mkt} reload={loadMkt} />}
            {route === "leads" && <MktLeads data={mkt} />}
            {route === "website" && <MktWebsite data={mkt} catalog={catalog} reload={loadMkt} />}
            {route === "social" && <MktSocial data={mkt} reload={loadMkt} />}
            {route === "analytics" && <MktAnalytics data={mkt} catalog={catalog} />}
            {route === "seo" && window.MktSeo && <window.MktSeo />}
            {route === "library" && window.SharedLibrary && <window.SharedLibrary />}
            {route === "styling" && window.StyleTailor && <window.StyleTailor />}
            {route === "settings" && <MktSettings data={mkt} reload={loadMkt} />}
          </div>
        </div>
        {toastMsg && (
          <div key={toastMsg.key} style={{ position: "fixed", bottom: 28, left: "50%", transform: "translateX(-50%)", zIndex: 400, display: "flex", alignItems: "center", gap: 10, background: "var(--vh-navy)", color: "#fff", padding: "13px 22px", borderRadius: 12, boxShadow: "0 12px 40px rgba(13,26,52,.35)", fontSize: 14, fontWeight: 600 }}>
            <Icon name={toastMsg.icon || "check"} size={17} style={{ color: "var(--vh-orange)" }} />{toastMsg.msg}
          </div>
        )}
      </div>
    </Provider>
  );
}

function __mktMount() {
  if (window.PORTAL_ROLE !== "marketeer") return;
  if (window.__mktMounted) return; window.__mktMounted = true;
  ReactDOM.createRoot(document.getElementById("root")).render(<MktApp />);
}
if (window.AUTH_USER) __mktMount();
else { window.addEventListener("vh-auth", __mktMount, { once: true }); setTimeout(__mktMount, 4000); }
