/* educator-products.jsx — Course Products backend.
   Each product is a course; its window manages every learner-facing asset:
   Presentation/Courseware (Presentation Studio), Exam, Self-Assessment,
   Memo Cards, Digital Book, Trainer Materials and Gamification. */

/* ── MATERIAL TYPES (learner-facing course assets) ── */
const MAT_TYPES = [
{ key: "presentation", label: "Presentation & Courseware", short: "Courseware", icon: "slides", color: "#1281c4", learner: true,
  desc: "The HTML slide deck learners study — upload, convert, gamify." },
{ key: "exam", label: "Practice Exam", short: "Practice", icon: "cert", color: "#7c3aed", learner: true,
  desc: "Full mock exam — questions, pass mark, timing & attempts." },
{ key: "assessment", label: "Self-Assessment", short: "Self-Assess", icon: "checkCircle", color: "#2e7d32", learner: true,
  desc: "Practice questions learners use to check readiness." },
{ key: "memo", label: "Memo Cards", short: "Memo", icon: "layers", color: "#f99d25", learner: true,
  desc: "Flashcards for spaced-repetition revision of key terms." },
{ key: "book", label: "Digital Book", short: "Book", icon: "book", color: "#00838f", learner: true,
  desc: "Reader with chapters and reference material." },
{ key: "trainer", label: "Trainer Materials", short: "Trainer", icon: "graduation", color: "#1d3d7c", learner: false,
  desc: "Facilitator guide, notes and slides — visible to trainers only." },
{ key: "gamification", label: "Gamification", short: "Gamified", icon: "bolt", color: "#e65100", learner: true,
  desc: "XP, badges, quiz slides and the course leaderboard." }];

const MAT_BY_KEY = Object.fromEntries(MAT_TYPES.map((m) => [m.key, m]));

/* ── SEED ── */
function seedMat(over) {
  return {
    presentation: { status: "published", visible: true },
    exam: { status: "published", visible: true, count: 40, passPct: 65, minutes: 60, attempts: 2, randomize: true,
      questions: [
      { q: "Which document defines the project's justification?", options: ["Business Case", "Risk Register", "Work Package", "Lessons Log"], answer: 0, rationale: "The Business Case captures the reasons, costs, benefits and risks — the ongoing justification for the project. The other documents support delivery but do not justify the project.", source: "IPMA-D Workbook · 1 · Introduction, §1.3" },
      { q: "Who owns the project from the business perspective?", options: ["Team Manager", "Executive", "Project Support", "Supplier"], answer: 1, rationale: "The Executive is accountable for the Business Case and represents the business interest throughout. The Team Manager delivers products; Supplier provides resources; Project Support is administrative.", source: "IPMA-D Workbook · 2 · Project Orientation, §2.1" }] },
    assessment: { status: "published", visible: true, count: 10, passPct: 50, minutes: 0, attempts: 0, randomize: true,
      questions: [{ q: "A stakeholder with high power and low interest should be…", options: ["Kept satisfied", "Monitored", "Managed closely", "Ignored"], answer: 0 }] },
    memo: { status: "published", visible: true,
      cards: [
      { term: "Stakeholder", def: "Any individual, group or organisation that can affect or is affected by the project." },
      { term: "Milestone", def: "A significant point or event in the project, often a decision or deliverable." }] },
    book: { status: "published", visible: true, source: "IPMA-D Workbook.pdf",
      chapters: [{ title: "1 · Introduction to Project Management", pages: "1–24" }, { title: "2 · Project Orientation", pages: "25–58" }] },
    trainer: { status: "published", visible: false,
      files: [{ name: "Facilitator guide.pdf", kind: "Guide" }, { name: "Trainer slides.pptx", kind: "Slides" }] },
    gamification: { on: true, xp: true, badges: true, leaderboard: true, quizSlides: true, xpPerModule: 100 },
    ...over };
}
const SEED_PRODUCTS = [
{ id: "ipma-d", name: "IPMA-D® Certification", icon: "graduation", color: "#1281c4", level: "Foundation", lang: "Dutch", body: "IPMA", price: "€1.595", learners: 234, mat: seedMat({ assessment: { status: "published", visible: true, count: 10, passPct: 50, minutes: 0, attempts: 0, randomize: true,
    app: { name: "IPMA ICB4 Competence Self-Assessment", url: "apps/ipma-self-assessment/index.html", builtin: true },
    questions: [{ q: "A stakeholder with high power and low interest should be…", options: ["Kept satisfied", "Monitored", "Managed closely", "Ignored"], answer: 0 }] } }) },
{ id: "bisl", name: "BiSL® Foundation", icon: "layers", color: "#3bc1ce", level: "Foundation", lang: "Dutch", body: "APMG", price: "€995", learners: 189, mat: seedMat({ assessment: { status: "draft", visible: false, count: 10, passPct: 50, attempts: 0, questions: [] } }) },
{ id: "itil", name: "ITIL® 4 Foundation", icon: "globe", color: "#3a93d4", level: "Foundation", lang: "English", body: "PeopleCert", price: "€1.395", learners: 312, mat: seedMat() },
{ id: "prince2", name: "PRINCE2® Foundation", icon: "shield", color: "#7c3aed", level: "Foundation", lang: "English", body: "PeopleCert", price: "€1.495", learners: 156, mat: seedMat({ presentation: { status: "draft", visible: false }, gamification: { on: false, xp: false, badges: false, leaderboard: false, quizSlides: false, xpPerModule: 100 } }) },
{ id: "togaf", name: "TOGAF® Foundation", icon: "cert", color: "#2e7d32", level: "Foundation", lang: "English", body: "The Open Group", price: "€1.695", learners: 98, mat: seedMat({ memo: { status: "none", visible: false, cards: [] }, book: { status: "draft", visible: false, source: "", chapters: [] } }) }];


/* ── PRIMITIVES ── */
function pStatus(p, key) {
  if (key === "gamification") return p.mat.gamification.on ? "published" : "none";
  return p.mat[key] && p.mat[key].status || "none";
}
function StatusPill({ status, small }) {
  const map = { published: ["Published", "#e8f5e9", "#2e7d32"], draft: ["Draft", "#fff3e0", "#b45309"], none: ["Not set", "#f1f3f7", "#7b899f"] };
  const [lbl, bg, c] = map[status] || map.none;
  return <span style={{ display: "inline-flex", alignItems: "center", gap: 5, fontSize: small ? 11 : 12, fontWeight: 700, padding: small ? "2px 8px" : "3px 10px", borderRadius: 999, background: bg, color: c }}>{status === "published" && <span style={{ width: 6, height: 6, borderRadius: "50%", background: c }} />}{lbl}</span>;
}
function PSwitch({ on, onClick, color = "#1281c4" }) {
  return (
    <button onClick={onClick} style={{ width: 42, height: 24, borderRadius: 999, background: on ? color : "var(--line)", position: "relative", border: "none", cursor: "pointer", flexShrink: 0, transition: "background .15s", padding: 0 }}>
      <span style={{ position: "absolute", top: 3, left: on ? 21 : 3, width: 18, height: 18, borderRadius: "50%", background: "#fff", boxShadow: "0 1px 3px rgba(0,0,0,.25)", transition: "left .15s", display: "block" }} />
    </button>);
}
const PF = { 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 PL = { fontSize: 11, fontWeight: 800, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: ".06em", display: "block", marginBottom: 6 };

/* ── QUESTION EDITOR (exam + self-assessment) ── */
function QuestionList({ questions, onChange }) {
  function patch(i, p) {onChange(questions.map((q, qi) => qi === i ? { ...q, ...p } : q));}
  function add() {onChange([...questions, { q: "", options: ["", "", "", ""], answer: 0 }]);}
  function del(i) {onChange(questions.filter((_, qi) => qi !== i));}
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
      {questions.length === 0 && <div style={{ padding: "22px", textAlign: "center", color: "var(--ink-3)", fontSize: 13.5, border: "1.5px dashed var(--line)", borderRadius: 12 }}>No questions yet — add your first below.</div>}
      {questions.map((q, i) =>
      <div key={i} className="card" style={{ padding: 16 }}>
          <div style={{ display: "flex", gap: 10, marginBottom: 10 }}>
            <span style={{ width: 24, height: 24, borderRadius: 7, background: "var(--vh-blue-50)", color: "var(--vh-blue-600)", display: "grid", placeItems: "center", fontFamily: "var(--display)", fontWeight: 800, fontSize: 12, flexShrink: 0 }}>{i + 1}</span>
            <textarea value={q.q} onChange={(e) => patch(i, { q: e.target.value })} placeholder="Question text…" rows={2} style={{ ...PF, resize: "vertical", lineHeight: 1.45 }} />
            <button onClick={() => del(i)} className="btn btn-ghost btn-sm" style={{ color: "#c62828", flexShrink: 0 }}><Icon name="x" size={13} /></button>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, paddingLeft: 34 }}>
            {q.options.map((o, oi) =>
          <div key={oi} style={{ display: "flex", alignItems: "center", gap: 7 }}>
                <button onClick={() => patch(i, { answer: oi })} title="Mark correct" style={{ width: 20, height: 20, borderRadius: "50%", flexShrink: 0, border: "2px solid " + (q.answer === oi ? "#2e7d32" : "var(--line)"), background: q.answer === oi ? "#2e7d32" : "transparent", cursor: "pointer", display: "grid", placeItems: "center" }}>{q.answer === oi && <Icon name="check" size={11} style={{ color: "#fff" }} stroke={3} />}</button>
                <input value={o} onChange={(e) => {const opts = [...q.options];opts[oi] = e.target.value;patch(i, { options: opts });}} placeholder={"Option " + String.fromCharCode(65 + oi)} style={{ ...PF, padding: "7px 10px", fontSize: 13 }} />
              </div>)}
          </div>
          <div style={{ paddingLeft: 34, marginTop: 10 }}>
            <label style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 11, fontWeight: 800, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: ".06em", marginBottom: 6 }}><Icon name="wand" size={12} style={{ color: "#2e7d32" }} />Rationale · shown after answering</label>
            <textarea value={q.rationale || ""} onChange={(e) => patch(i, { rationale: e.target.value })} placeholder="Explain why the correct answer is right (and why the others are not)…" rows={2} style={{ ...PF, resize: "vertical", lineHeight: 1.45, background: "var(--surface-2)" }} />
            <label style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 11, fontWeight: 800, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: ".06em", margin: "10px 0 6px" }}><Icon name="link" size={12} style={{ color: "var(--vh-blue-500)" }} />Linked source</label>
            <input value={q.source || ""} onChange={(e) => patch(i, { source: e.target.value })} placeholder="e.g. Workbook §3.2, p.41 · or Courseware slide 12" style={{ ...PF, fontSize: 13 }} />
          </div>
        </div>)}
      <button onClick={add} className="btn btn-outline btn-sm" style={{ alignSelf: "flex-start" }}><Icon name="plus" size={13} />Add question</button>
    </div>);
}

/* ── AI QUESTION GENERATOR (grounded in courseware / book) ── */
function normaliseQ(p, fallbackSource) {
  let opts = Array.isArray(p.options) ? p.options.map((o) => String(o)) : [];
  while (opts.length < 4) opts.push("");
  opts = opts.slice(0, 4);
  let ans = p.answer;
  if (typeof ans === "string") ans = ans.trim().length === 1 ? ans.toUpperCase().charCodeAt(0) - 65 : opts.indexOf(ans);
  ans = Math.max(0, Math.min(3, parseInt(ans, 10) || 0));
  return { q: String(p.q || p.question || "").trim(), options: opts, answer: ans, rationale: String(p.rationale || p.explanation || "").trim(), source: String(p.source || p.reference || fallbackSource).trim() };
}

function QuestionGenerator({ product, light, onAdd, toast }) {
  const chapters = product && product.mat.book && product.mat.book.chapters || [];
  const bookName = product && product.mat.book && product.mat.book.source || "Digital book";
  const SOURCES = [["courseware", "Courseware deck", "slides"], ...(chapters.length ? [["book", "Digital book", "book"]] : []), ["custom", "Pasted text", "file"]];
  const [src, setSrc] = useState("courseware");
  const [chapter, setChapter] = useState(chapters[0] ? chapters[0].title : "");
  const [count, setCount] = useState(3);
  const [topic, setTopic] = useState("");
  const [material, setMaterial] = useState("");
  const [busy, setBusy] = useState(false);
  const [note, setNote] = useState("");

  function srcLabel() {
    if (src === "book") return bookName + (chapter ? " · " + chapter : "");
    if (src === "custom") return "Pasted source material";
    return product.name + " courseware";
  }

  async function generate() {
    if (busy) return;
    setNote("");
    if (!window.claude || !window.claude.complete) {setNote("AI generation runs in the live preview environment.");return;}
    setBusy(true);
    const base = srcLabel();
    const prompt = [
    "You are an expert exam author for professional certification training.",
    "Course: \"" + product.name + "\" (" + product.level + ", " + product.body + ").",
    "Write " + count + " high-quality " + (light ? "self-assessment practice" : "mock-exam") + " multiple-choice questions.",
    material.trim() ? "Base the questions STRICTLY on this source material:\n\"\"\"\n" + material.trim().slice(0, 2500) + "\n\"\"\"" : "Base the questions on the standard syllabus for this course, drawing on its " + base + ".",
    topic.trim() ? "Focus area: " + topic.trim() + "." : "",
    "Each question must have exactly 4 options, one correct.",
    "For EACH question include a 'source' string citing the specific paragraph, section, page or slide it comes from (e.g. \"" + base + ", §x.y\"). Keep every question linked to its precise source.",
    "Return ONLY a JSON array, no markdown/code fences. Each item: {\"q\": string, \"options\": [4 strings], \"answer\": 0-3 index of correct option, \"rationale\": short explanation of the correct answer, \"source\": citation string}."].
    filter(Boolean).join("\n");
    try {
      let out = await window.claude.complete(prompt);
      const mt = (out || "").match(/\[[\s\S]*\]/);
      if (!mt) throw new Error("no array");
      const arr = JSON.parse(mt[0]);
      const qs = arr.slice(0, count).map((p) => normaliseQ(p, base)).filter((q) => q.q && q.options.some((o) => o));
      if (!qs.length) throw new Error("empty");
      onAdd(qs);
      toast && toast("Generated " + qs.length + " questions linked to " + (src === "book" ? "the book" : src === "custom" ? "your source" : "the courseware"), "bolt");
      setNote("Added " + qs.length + " questions to the bank below — review & edit.");
    } catch (e) {
      setNote("Couldn't generate — " + (e && e.message ? e.message : "try again") + ".");
    }
    setBusy(false);
  }

  return (
    <div className="card" style={{ padding: 18, border: "1.5px solid var(--vh-blue-200)", background: "var(--vh-blue-50)" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 4 }}>
        <div style={{ width: 32, height: 32, borderRadius: 9, background: "var(--vh-blue-500)", display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name="wand" size={17} style={{ color: "#fff" }} /></div>
        <div style={{ flex: 1 }}>
          <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 15, color: "var(--ink)" }}>Generate questions from source</div>
          <div style={{ fontSize: 12, color: "var(--ink-2)" }}>AI writes questions grounded in your material — each linked back to its source.</div>
        </div>
      </div>

      <div style={{ marginTop: 14 }}>
        <label style={PL}>Source</label>
        <div style={{ display: "flex", gap: 7, flexWrap: "wrap" }}>
          {SOURCES.map(([id, lbl, ic]) =>
          <button key={id} onClick={() => setSrc(id)} style={{ display: "flex", alignItems: "center", gap: 7, padding: "8px 13px", borderRadius: 9, fontSize: 13, fontWeight: 700, cursor: "pointer", border: "1.5px solid " + (src === id ? "var(--vh-blue-500)" : "var(--line)"), background: src === id ? "#fff" : "transparent", color: src === id ? "var(--vh-blue-600)" : "var(--ink-2)" }}><Icon name={ic} size={14} />{lbl}</button>)}
        </div>
      </div>

      {src === "book" && chapters.length > 0 &&
      <div style={{ marginTop: 12 }}><label style={PL}>Chapter / section</label>
          <select value={chapter} onChange={(e) => setChapter(e.target.value)} style={{ ...PF, background: "#fff" }}>{chapters.map((c) => <option key={c.title}>{c.title}</option>)}</select>
        </div>}

      {src === "custom" &&
      <div style={{ marginTop: 12 }}><label style={PL}>Paste source paragraph(s)</label>
          <textarea value={material} onChange={(e) => setMaterial(e.target.value)} rows={4} placeholder="Paste the courseware text or book passage to base questions on…" style={{ ...PF, resize: "vertical", background: "#fff", lineHeight: 1.5 }} />
        </div>}

      <div style={{ marginTop: 12 }}><label style={PL}>Focus / topic (optional)</label>
        <input value={topic} onChange={(e) => setTopic(e.target.value)} placeholder="e.g. risk management, stakeholder analysis…" style={{ ...PF, background: "#fff" }} />
      </div>

      <div style={{ marginTop: 14, display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
        <span style={{ fontSize: 12.5, fontWeight: 700, color: "var(--ink-2)" }}>How many?</span>
        <div style={{ display: "flex", gap: 6 }}>
          {[1, 3, 5, 10].map((n) =>
          <button key={n} onClick={() => setCount(n)} style={{ width: 40, padding: "8px 0", borderRadius: 8, fontSize: 13, fontWeight: 800, fontFamily: "var(--display)", cursor: "pointer", border: "1.5px solid " + (count === n ? "var(--vh-blue-500)" : "var(--line)"), background: count === n ? "#fff" : "transparent", color: count === n ? "var(--vh-blue-600)" : "var(--ink-2)" }}>{n}</button>)}
        </div>
        <button onClick={generate} disabled={busy} style={{ marginLeft: "auto", display: "inline-flex", alignItems: "center", gap: 8, padding: "11px 20px", borderRadius: 10, border: "none", background: busy ? "var(--vh-blue-200)" : "var(--vh-blue-500)", color: "#fff", fontFamily: "var(--display)", fontWeight: 800, fontSize: 14, cursor: busy ? "default" : "pointer", boxShadow: busy ? "none" : "0 6px 16px rgba(18,129,196,.28)" }}>
          {busy ? <>⏳ Generating {count}…</> : <><Icon name="wand" size={15} />Generate {count} question{count > 1 ? "s" : ""}</>}
        </button>
      </div>
      {note && <div style={{ marginTop: 12, fontSize: 12.5, fontWeight: 600, color: note.startsWith("Added") ? "#2e7d32" : "#b45309" }}>{note}</div>}
    </div>);
}

function ExamEditor({ mat, setMat, light, product, toast }) {
  const m = mat;
  function addQuestions(qs) {setMat({ questions: [...m.questions, ...qs] });}
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 18 }}>
      <div className="card" style={{ padding: 18 }}>
        <div className="eyebrow" style={{ marginBottom: 14 }}>{light ? "Practice settings" : "Exam settings"}</div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
          <div><label style={PL}>Questions per {light ? "round" : "exam"}</label><input type="number" min="1" value={m.count} onChange={(e) => setMat({ count: Math.max(1, +e.target.value || 1) })} style={PF} /></div>
          <div><label style={PL}>Pass mark (%)</label><input type="number" value={m.passPct} onChange={(e) => setMat({ passPct: +e.target.value })} style={PF} /></div>
          {!light && <div><label style={PL}>Time limit (min)</label><input type="number" value={m.minutes} onChange={(e) => setMat({ minutes: +e.target.value })} style={PF} /></div>}
          {!light && <div><label style={PL}>Attempts allowed</label><input type="number" value={m.attempts} onChange={(e) => setMat({ attempts: +e.target.value })} style={PF} /></div>}
          <label style={{ display: "flex", alignItems: "center", gap: 10, alignSelf: "end", paddingBottom: 6, cursor: "pointer", fontSize: 13.5, fontWeight: 600 }}>
            <input type="checkbox" checked={!!m.randomize} onChange={(e) => setMat({ randomize: e.target.checked })} style={{ width: 17, height: 17, accentColor: "var(--vh-blue-500)" }} />Shuffle question order
          </label>
        </div>
        {m.count > m.questions.length &&
        <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 12, padding: "9px 12px", borderRadius: 9, background: "#fff3e0", color: "#b45309", fontSize: 12.5, fontWeight: 600 }}>
          <Icon name="lock" size={13} />Asking for {m.count} questions but the bank only has {m.questions.length}. Add {m.count - m.questions.length} more below.
        </div>}
      </div>

      <QuestionGenerator product={product} light={light} onAdd={addQuestions} toast={toast} />

      <div>
        <div className="between" style={{ marginBottom: 12 }}>
          <div className="eyebrow">Question bank · {m.questions.length}</div>
          <span style={{ fontSize: 12, fontWeight: 600, color: "var(--ink-3)" }}>Each {light ? "round" : "exam"} draws {Math.min(m.count, m.questions.length)} of {m.questions.length}{m.randomize ? " at random" : ""}</span>
        </div>
        <QuestionList questions={m.questions} onChange={(qs) => setMat({ questions: qs })} />
      </div>
    </div>);
}

function MemoEditor({ mat, setMat }) {
  const cards = mat.cards;
  function patch(i, p) {setMat({ cards: cards.map((c, ci) => ci === i ? { ...c, ...p } : c) });}
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
      <div className="eyebrow" style={{ marginBottom: 2 }}>Flashcards · {cards.length}</div>
      {cards.length === 0 && <div style={{ padding: "22px", textAlign: "center", color: "var(--ink-3)", fontSize: 13.5, border: "1.5px dashed var(--line)", borderRadius: 12 }}>No cards yet.</div>}
      {cards.map((c, i) =>
      <div key={i} className="card" style={{ padding: 14, display: "grid", gridTemplateColumns: "1fr 1.4fr auto", gap: 10, alignItems: "center" }}>
          <input value={c.term} onChange={(e) => patch(i, { term: e.target.value })} placeholder="Term" style={{ ...PF, fontWeight: 700 }} />
          <input value={c.def} onChange={(e) => patch(i, { def: e.target.value })} placeholder="Definition" style={PF} />
          <button onClick={() => setMat({ cards: cards.filter((_, ci) => ci !== i) })} className="btn btn-ghost btn-sm" style={{ color: "#c62828" }}><Icon name="x" size={13} /></button>
        </div>)}
      <button onClick={() => setMat({ cards: [...cards, { term: "", def: "" }] })} className="btn btn-outline btn-sm" style={{ alignSelf: "flex-start" }}><Icon name="plus" size={13} />Add card</button>
    </div>);
}

/* ── DIGITAL BOOK — real upload (PDF/EPUB) + chapter editor ── */

/* Pull readable paragraphs from a page range of a pdf.js document. */
async function vhExtractParas(pdf, from, to, capChars) {
  const paras = [];
  let chars = 0;
  for (let p = Math.max(1, from); p <= Math.min(to, pdf.numPages) && chars < capChars; p++) {
    try {
      const page = await pdf.getPage(p);
      const tc = await page.getTextContent();
      const text = tc.items.map((i) => i.str).join(" ").replace(/\s+/g, " ").trim();
      if (!text) continue;
      const chunks = text.match(/[^.!?]+[.!?]+(\s|$)/g) || [text];
      let buf = "";
      chunks.forEach((c) => {buf += c;if (buf.length > 420) {paras.push(buf.trim());chars += buf.length;buf = "";}});
      if (buf.trim()) {paras.push(buf.trim());chars += buf.length;}
    } catch (e) {}
  }
  return paras;
}

/* Reader-style preview — shows the uploaded book exactly as learners see it in the Reader. */
function BookPreviewModal({ book, product, live, onClose }) {
  const chs = book.chapters || [];
  const hasText = !!(chs.length && chs[0].paras);
  const [ci, setCi] = useState(0);
  const [speaking, setSpeaking] = useState(false);
  const cur = chs[ci] || {};
  const stopSpeak = () => {try {window.speechSynthesis && window.speechSynthesis.cancel();} catch (e) {}};
  useEffect(() => stopSpeak, []);
  function pick(i) {setCi(i);stopSpeak();setSpeaking(false);}
  function toggleSpeak() {
    if (!window.speechSynthesis) return;
    if (speaking) {stopSpeak();setSpeaking(false);return;}
    const text = (cur.paras || []).join(" ").slice(0, 1400);
    if (!text) return;
    try {
      stopSpeak();
      const u = new SpeechSynthesisUtterance(text);
      u.onend = () => setSpeaking(false);
      u.onerror = () => setSpeaking(false);
      window.speechSynthesis.speak(u);
      setSpeaking(true);
    } catch (e) {}
  }
  return (
    <div onClick={(e) => {if (e.target === e.currentTarget) onClose();}} style={{ position: "fixed", inset: 0, background: "rgba(13,26,52,.55)", backdropFilter: "blur(3px)", zIndex: 260, display: "grid", placeItems: "center", padding: 24 }}>
      <div className="card" style={{ width: "100%", maxWidth: 940, height: "min(82vh, 720px)", display: "flex", flexDirection: "column", overflow: "hidden", padding: 0 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 14, padding: "14px 20px", borderBottom: "1px solid var(--line)", flexShrink: 0 }}>
          {book.cover ?
          <img src={book.cover} alt="" style={{ width: 38, borderRadius: 4, border: "1px solid var(--line)", flexShrink: 0 }} /> :
          <div style={{ width: 38, height: 52, borderRadius: 4, background: "rgba(0,131,143,.12)", display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name="book" size={18} style={{ color: "#00838f" }} /></div>}
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 16, color: "var(--ink)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{book.title}</div>
            <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 2 }}>Learner preview — the book as it appears in the Reader{product ? " for " + product.name : ""}{book.pages ? " · " + book.pages + " pages" : ""}</div>
          </div>
          {hasText && <button className="btn btn-outline btn-sm" onClick={toggleSpeak} style={{ flexShrink: 0, color: speaking ? "#c62828" : undefined }}><Icon name={speaking ? "pause" : "volume"} size={14} />{speaking ? "Stop" : "Read aloud"}</button>}
          <button className="btn btn-ghost btn-sm" onClick={onClose} style={{ flexShrink: 0 }}><Icon name="x" size={14} />Close</button>
        </div>
        <div style={{ flex: 1, display: "flex", minHeight: 0 }}>
          <div className="scroll" style={{ width: 240, flexShrink: 0, borderRight: "1px solid var(--line)", overflow: "auto", padding: 12, background: "var(--surface-2)" }}>
            <div className="eyebrow" style={{ padding: "4px 8px 8px" }}>Chapters · {chs.length}</div>
            {chs.map((c, i) =>
            <button key={i} onClick={() => pick(i)} style={{ display: "block", width: "100%", textAlign: "left", padding: "9px 10px", borderRadius: 8, border: "none", cursor: "pointer", background: i === ci ? "#fff" : "transparent", boxShadow: i === ci ? "var(--shadow-sm)" : "none", marginBottom: 2 }}>
                <div style={{ fontSize: 12.5, fontWeight: i === ci ? 800 : 600, color: i === ci ? "var(--ink)" : "var(--ink-2)", lineHeight: 1.35 }}>{c.title || "Untitled"}</div>
                {c.pages && !c.paras && <div style={{ fontSize: 11, color: "var(--ink-3)", marginTop: 2 }}>pp. {c.pages}</div>}
              </button>)}
          </div>
          <div className="scroll" style={{ flex: 1, overflow: "auto", padding: "26px 34px" }}>
            {hasText ?
            <React.Fragment>
              <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 20, color: "var(--ink)", marginBottom: 16 }}>{cur.title}</div>
              {(cur.paras || []).map((p, i) => <p key={i} style={{ fontSize: 14.5, lineHeight: 1.75, color: "var(--ink-2)", marginBottom: 14, maxWidth: 640 }}>{p}</p>)}
            </React.Fragment> :
            <div style={{ border: "1.5px dashed var(--line)", borderRadius: 12, padding: "30px 24px", textAlign: "center", maxWidth: 460, margin: "40px auto" }}>
              <Icon name="book" size={26} style={{ color: "var(--ink-3)" }} />
              <div style={{ fontWeight: 700, fontSize: 14, color: "var(--ink)", margin: "10px 0 6px" }}>No text preview yet</div>
              <p style={{ fontSize: 13, color: "var(--ink-3)", lineHeight: 1.55 }}>This entry is metadata only. Upload the book as a PDF and its full text is extracted per chapter and published to the learner Reader — then you can preview it here.</p>
            </div>}
          </div>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "11px 20px", borderTop: "1px solid var(--line)", background: live ? "#e8f5e9" : "var(--surface-2)", fontSize: 12.5, fontWeight: 600, color: live ? "#2e7d32" : "var(--ink-3)", flexShrink: 0 }}>
          <Icon name={live ? "checkCircle" : "lock"} size={14} />
          {live ? <span>Connected to the learner environment — learners on <b>{product ? product.name : "this course"}</b> open this book under <b>Library → Reader</b>, with highlights, notes and read-aloud.</span> : <span>Not yet in the learner Reader — upload a PDF to publish it to {product ? product.name : "this course"}.</span>}
        </div>
      </div>
    </div>);
}

function BookEditor({ mat, setMat, toast, product }) {
  const fileRef = React.useRef(null);
  const [busy, setBusy] = useState(false);
  const [drag, setDrag] = useState(false);
  const [preview, setPreview] = useState(null);
  const [pvBusy, setPvBusy] = useState(false);
  const chapters = mat.chapters || [];

  async function handleFile(file) {
    if (!file || busy) return;
    setBusy(true);
    try {
      if (/\.pdf$/i.test(file.name) && window.pdfjsLib) {
        const buf = await file.arrayBuffer();
        const pdf = await pdfjsLib.getDocument({ data: buf, useSystemFonts: true, standardFontDataUrl: "https://unpkg.com/pdfjs-dist@3.11.174/standard_fonts/" }).promise;
        const vhTimeout = (p, ms) => Promise.race([p, new Promise((_, rej) => setTimeout(() => rej(new Error("timeout")), ms))]);
        /* cover thumbnail from page 1 */
        let cover = null;
        try {
          const page = await vhTimeout(pdf.getPage(1), 4000);
          const vp = page.getViewport({ scale: 220 / page.getViewport({ scale: 1 }).width });
          const cv = document.createElement("canvas");cv.width = Math.round(vp.width);cv.height = Math.round(vp.height);
          await vhTimeout(page.render({ canvasContext: cv.getContext("2d"), viewport: vp }).promise, 4000);
          cover = cv.toDataURL("image/jpeg", 0.8);
        } catch (e) {}
        /* chapters from the PDF outline (bookmarks) */
        let chs = [];
        try {
          const outline = await pdf.getOutline();
          if (outline && outline.length) {
            const tops = outline.slice(0, 40);
            const marks = [];
            for (let i = 0; i < tops.length; i++) {
              let pageNo = null;
              try {
                let dest = tops[i].dest;
                if (typeof dest === "string") dest = await pdf.getDestination(dest);
                if (dest && dest[0]) pageNo = (await pdf.getPageIndex(dest[0])) + 1;
              } catch (e) {}
              marks.push({ title: String(tops[i].title || "Untitled").trim(), p: pageNo });
            }
            chs = marks.map((c, i) => {
              const next = marks.slice(i + 1).find((n) => n.p);
              const end = next && next.p ? Math.max(c.p || 1, next.p - 1) : pdf.numPages;
              return { title: c.title, pages: c.p ? c.p + "–" + end : "" };
            });
          }
        } catch (e) {}
        if (!chs.length) chs = [{ title: "Full book", pages: "1–" + pdf.numPages }];

        /* ── Extract the actual text per chapter and publish to the learner Reader ── */
        let readerChapters = [];
        let totalWords = 0;
        for (let i = 0; i < Math.min(chs.length, 12); i++) {
          const m2 = String(chs[i].pages || "").match(/(\d+)\s*[–—-]\s*(\d+)/);
          const single = String(chs[i].pages || "").match(/^(\d+)$/);
          const from = m2 ? +m2[1] : single ? +single[1] : i === 0 ? 1 : 0;
          const to = m2 ? +m2[2] : single ? +single[1] : i === 0 ? pdf.numPages : -1;
          if (from <= 0 || to < from) continue;
          const paras = await vhExtractParas(pdf, from, Math.min(to, from + 11), 14000);
          if (paras.length) {
            totalWords += paras.join(" ").split(/\s+/).length;
            readerChapters.push({ n: readerChapters.length + 1, title: chs[i].title, paras: paras });
          }
        }
        if (!readerChapters.length) readerChapters = [{ n: 1, title: chs[0].title || "Full book", paras: ["No selectable text was found in this PDF — it may contain scanned images. The book is still listed; replace it with a text-based PDF to enable reading and read-aloud."] }];
        const bookId = mat.readerBookId || "ubook_" + (product ? product.id : "x") + "_" + Date.now();
        const niceName = file.name.replace(/\.(pdf|epub)$/i, "");
        let published = false;
        if (window.BookStore) {
          try {
            await window.BookStore.save({
              id: bookId, uploaded: true, created: Date.now(),
              title: niceName, author: "Uploaded · " + (product ? product.name : "Educator"),
              track: product ? product.name : "Uploads", color: product && product.color || "#00838f",
              pages: pdf.numPages, cover: cover, read: 0, done: false,
              mins: Math.max(2, Math.round(totalWords / 180)),
              chapters: readerChapters
            });
            published = true;
          } catch (e) {}
        }
        setMat({ source: file.name, pages: pdf.numPages, cover: cover, chapters: chs, readerBookId: published ? bookId : mat.readerBookId, status: mat.status === "none" ? "draft" : mat.status });
        toast && toast("Book uploaded — " + pdf.numPages + " pages" + (published ? " · now readable in the learner Reader" : ""), "check");
      } else {
        setMat({ source: file.name, pages: null, cover: null, chapters: chapters.length ? chapters : [{ title: "Full book", pages: "" }], status: mat.status === "none" ? "draft" : mat.status });
        toast && toast("Book uploaded — " + file.name, "check");
      }
    } catch (e) {
      toast && toast("Couldn't read that file — is it a valid PDF or EPUB?", "x");
    }
    setBusy(false);
  }

  function chPatch(i, p) {setMat({ chapters: chapters.map((x, xi) => xi === i ? { ...x, ...p } : x) });}

  /* Open the book as the learner Reader stores it (full extracted text); falls back to metadata. */
  async function openPreview() {
    if (pvBusy) return;
    if (mat.readerBookId && window.BookStore) {
      setPvBusy(true);
      try {
        const b = await window.BookStore.get(mat.readerBookId);
        setPvBusy(false);
        if (b) {setPreview(b);return;}
      } catch (e) {setPvBusy(false);}
    }
    setPreview({ title: (mat.source || "Book").replace(/\.(pdf|epub)$/i, ""), pages: mat.pages, cover: mat.cover, chapters: chapters });
  }

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      {preview && <BookPreviewModal book={preview} product={product} live={!!mat.readerBookId} onClose={() => setPreview(null)} />}
      <input ref={fileRef} type="file" accept=".pdf,.epub" style={{ display: "none" }} onChange={(e) => {handleFile(e.target.files[0]);e.target.value = "";}} />

      {mat.source &&
      <div className="card" style={{ padding: 16, display: "flex", gap: 14, alignItems: "center" }}>
        {mat.cover ?
        <img src={mat.cover} alt="Book cover" style={{ width: 56, borderRadius: 6, border: "1px solid var(--line)", boxShadow: "0 4px 12px rgba(0,0,0,.12)", flexShrink: 0 }} /> :
        <div style={{ width: 56, height: 76, borderRadius: 6, background: "rgba(0,131,143,.12)", display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name="book" size={24} style={{ color: "#00838f" }} /></div>}
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontWeight: 700, fontSize: 14.5, color: "var(--ink)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{mat.source}</div>
          <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 3 }}>{[mat.pages ? mat.pages + (mat.pages === 1 ? " page" : " pages") : null, chapters.length ? chapters.length + (chapters.length === 1 ? " chapter" : " chapters") : null].filter(Boolean).join(" · ") || "Uploaded"}</div>
          <div style={{ display: "inline-flex", alignItems: "center", gap: 5, marginTop: 6, fontSize: 11.5, fontWeight: 700, color: mat.readerBookId ? "#2e7d32" : "var(--ink-3)", background: mat.readerBookId ? "#e8f5e9" : "var(--surface-2)", borderRadius: 999, padding: "2px 9px" }}>{mat.readerBookId ? <React.Fragment><Icon name="check" size={11} />Live in the learner Reader{product ? " · " + product.name : ""}</React.Fragment> : "Metadata only — upload a PDF to publish to the Reader"}</div>
        </div>
        <button className="btn btn-outline btn-sm" style={{ flexShrink: 0 }} disabled={pvBusy} onClick={openPreview}><Icon name="book" size={13} />{pvBusy ? "Opening…" : "Preview"}</button>
        <button className="btn btn-ghost btn-sm" style={{ color: "#c62828", flexShrink: 0 }} onClick={() => {if (mat.readerBookId && window.BookStore) {try {window.BookStore.remove(mat.readerBookId);} catch (e) {}}setMat({ source: "", pages: null, cover: null, readerBookId: null });}}><Icon name="x" size={13} />Remove</button>
      </div>}

      <div
        onDragOver={(e) => {e.preventDefault();setDrag(true);}}
        onDragLeave={() => setDrag(false)}
        onDrop={(e) => {e.preventDefault();setDrag(false);handleFile(e.dataTransfer.files && e.dataTransfer.files[0]);}}
        onClick={() => !busy && fileRef.current && fileRef.current.click()}
        style={mat.source ?
        { border: "2px dashed " + (drag ? "#00838f" : "var(--line)"), background: drag ? "rgba(0,131,143,.06)" : "var(--surface)", borderRadius: 12, padding: "13px 16px", display: "flex", alignItems: "center", gap: 12, cursor: busy ? "default" : "pointer", transition: "all .15s" } :
        { border: "2px dashed " + (drag ? "#00838f" : "var(--line)"), background: drag ? "rgba(0,131,143,.06)" : "var(--surface)", borderRadius: 14, padding: "34px 20px", textAlign: "center", cursor: busy ? "default" : "pointer", transition: "all .15s" }}>
        {mat.source ?
        <React.Fragment>
          <div style={{ width: 36, height: 36, borderRadius: 10, background: "rgba(0,131,143,.12)", display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name="download" size={16} style={{ color: "#00838f", transform: "rotate(180deg)" }} /></div>
          <div style={{ flex: 1 }}>
            <div style={{ fontWeight: 700, fontSize: 13.5, color: "var(--ink)" }}>{busy ? "Reading your book…" : "Upload a different book"}</div>
            <div style={{ fontSize: 12, color: "var(--ink-3)" }}>Drop a <b>PDF</b> or <b>EPUB</b> here — it replaces the current file.</div>
          </div>
          <button className="btn btn-outline btn-sm" disabled={busy} onClick={(e) => {e.stopPropagation();fileRef.current && fileRef.current.click();}}><Icon name="file" size={13} />{busy ? "Reading…" : "Choose file"}</button>
        </React.Fragment> :
        <React.Fragment>
          <div style={{ width: 52, height: 52, borderRadius: 13, background: "rgba(0,131,143,.12)", display: "grid", placeItems: "center", margin: "0 auto 14px" }}><Icon name="book" size={24} style={{ color: "#00838f" }} /></div>
          <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 17, color: "var(--ink)", marginBottom: 6 }}>{busy ? "Reading your book…" : "Drop your book here"}</div>
          <p style={{ fontSize: 13, color: "var(--ink-3)", lineHeight: 1.55, maxWidth: 420, margin: "0 auto 14px" }}>Upload a <b>PDF</b> or <b>EPUB</b>. We'll read the page count and detect chapters from the book's table of contents automatically.</p>
          <button className="btn btn-pri btn-sm" disabled={busy} onClick={(e) => {e.stopPropagation();fileRef.current && fileRef.current.click();}}><Icon name="file" size={14} />{busy ? "Reading…" : "Choose file"}</button>
        </React.Fragment>}
      </div>

      <div className="eyebrow" style={{ marginTop: 4 }}>Chapters · {chapters.length}</div>
      {chapters.length === 0 && <div style={{ padding: "18px", textAlign: "center", color: "var(--ink-3)", fontSize: 13, border: "1.5px dashed var(--line)", borderRadius: 12 }}>No chapters yet — upload a book or add them manually.</div>}
      {chapters.map((x, i) =>
      <div key={i} className="card" style={{ padding: 14, display: "grid", gridTemplateColumns: "1fr 130px auto", gap: 10, alignItems: "center" }}>
          <input value={x.title} onChange={(e) => chPatch(i, { title: e.target.value })} placeholder="Title" style={{ ...PF, fontWeight: 600 }} />
          <input value={x.pages} onChange={(e) => chPatch(i, { pages: e.target.value })} placeholder="Pages" style={PF} />
          <button onClick={() => setMat({ chapters: chapters.filter((_, xi) => xi !== i) })} className="btn btn-ghost btn-sm" style={{ color: "#c62828" }}><Icon name="x" size={13} /></button>
        </div>)}
      <button onClick={() => setMat({ chapters: [...chapters, { title: "", pages: "" }] })} className="btn btn-outline btn-sm" style={{ alignSelf: "flex-start" }}><Icon name="plus" size={13} />Add chapter</button>
    </div>);
}

function ResourceEditor({ items, onChange, kinds, addLabel, titleKey, metaKey, metaPlaceholder, sourceLabel, source, onSource }) {
  function patch(i, p) {onChange(items.map((x, xi) => xi === i ? { ...x, ...p } : x));}
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
      {sourceLabel &&
      <div className="card" style={{ padding: 16 }}>
        <label style={PL}>{sourceLabel}</label>
        <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
          <Icon name="file" size={15} style={{ color: "var(--vh-blue-500)" }} />
          <input value={source} onChange={(e) => onSource(e.target.value)} placeholder="No file — click upload" style={PF} />
          <label className="btn btn-outline btn-sm" style={{ cursor: "pointer" }}><Icon name="download" size={13} style={{ transform: "rotate(180deg)" }} />Upload
            <input type="file" style={{ display: "none" }} onChange={(e) => { const f = e.target.files && e.target.files[0]; if (f) onSource(f.name); }} />
          </label>
        </div>
      </div>}
      <div className="eyebrow">{addLabel.replace("Add ", "")}s · {items.length}</div>
      {items.map((x, i) =>
      <div key={i} className="card" style={{ padding: 14, display: "grid", gridTemplateColumns: kinds ? "auto 1fr 130px auto" : "1fr 130px auto", gap: 10, alignItems: "center" }}>
          {kinds && <Icon name="file" size={16} style={{ color: "var(--ink-3)" }} />}
          <input value={x[titleKey]} onChange={(e) => patch(i, { [titleKey]: e.target.value })} placeholder="Title" style={{ ...PF, fontWeight: 600 }} />
          {kinds ?
        <select value={x[metaKey]} onChange={(e) => patch(i, { [metaKey]: e.target.value })} style={{ ...PF, background: "#fff" }}>{kinds.map((k) => <option key={k}>{k}</option>)}</select> :
        <input value={x[metaKey]} onChange={(e) => patch(i, { [metaKey]: e.target.value })} placeholder={metaPlaceholder} style={PF} />}
          <button onClick={() => onChange(items.filter((_, xi) => xi !== i))} className="btn btn-ghost btn-sm" style={{ color: "#c62828" }}><Icon name="x" size={13} /></button>
        </div>)}
      <button onClick={() => onChange([...items, kinds ? { [titleKey]: "", [metaKey]: kinds[0] } : { [titleKey]: "", [metaKey]: "" }])} className="btn btn-outline btn-sm" style={{ alignSelf: "flex-start" }}><Icon name="plus" size={13} />{addLabel}</button>
    </div>);
}

function GamificationEditor({ mat, setMat }) {
  const rows = [["xp", "XP & points", "Award points for completing modules and quizzes"], ["badges", "Achievement badges", "Unlock badges at milestones"], ["leaderboard", "Course leaderboard", "Rank learners within this cohort"], ["quizSlides", "Quiz slides", "Interactive checks embedded in the courseware"]];
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
      <div className="card" style={{ padding: 16, display: "flex", alignItems: "center", gap: 13, background: mat.on ? "rgba(249,157,37,.07)" : "var(--surface)" }}>
        <div style={{ width: 40, height: 40, borderRadius: 11, background: mat.on ? "#f99d25" : "var(--surface-3)", display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name="bolt" size={20} style={{ color: mat.on ? "#fff" : "var(--ink-3)" }} /></div>
        <div style={{ flex: 1 }}><div style={{ fontSize: 15, fontWeight: 700 }}>Gamification {mat.on ? "enabled" : "disabled"}</div><div className="dim" style={{ fontSize: 12.5 }}>Master switch for this course</div></div>
        <PSwitch on={mat.on} onClick={() => setMat({ on: !mat.on })} color="#e65100" />
      </div>
      {mat.on && rows.map(([k, label, desc]) =>
      <div key={k} style={{ display: "flex", alignItems: "center", gap: 14, padding: "11px 4px", borderBottom: "1px solid var(--line-2)" }}>
          <div style={{ flex: 1 }}><div style={{ fontWeight: 600, fontSize: 14 }}>{label}</div><div className="dim" style={{ fontSize: 12 }}>{desc}</div></div>
          <PSwitch on={!!mat[k]} onClick={() => setMat({ [k]: !mat[k] })} color="#e65100" />
        </div>)}
      {mat.on && <div style={{ display: "flex", alignItems: "center", gap: 12 }}><label style={{ ...PL, marginBottom: 0 }}>XP per module</label><input type="number" value={mat.xpPerModule} onChange={(e) => setMat({ xpPerModule: +e.target.value })} style={{ ...PF, width: 110 }} /></div>}
    </div>);
}

/* ── MATERIAL DRAWER ── */
function MaterialDrawer({ product, typeKey, setMat, onClose, toast }) {
  const t = MAT_BY_KEY[typeKey];
  const AppPanel = window.HostedAppPanel;
  const m = product.mat[typeKey];
  const isGame = typeKey === "gamification";
  const wide = typeKey === "presentation";
  const set = (patch) => setMat(typeKey, patch);
  let body;
  if (typeKey === "presentation") body = <PresentationStudio embedded course={product.name} courseColor={product.color} toast={toast} />;else
  if (typeKey === "exam") body = <ExamEditor mat={m} setMat={set} product={product} toast={toast} />;else
  if (typeKey === "assessment") body = <ExamEditor mat={m} setMat={set} product={product} toast={toast} light />;else
  if (typeKey === "memo") body = <MemoEditor mat={m} setMat={set} />;else
  if (typeKey === "book") body = <BookEditor mat={m} setMat={set} toast={toast} product={product} />;else
  if (typeKey === "trainer") body = <ResourceEditor items={m.files} onChange={(v) => set({ files: v })} kinds={["Guide", "Slides", "Notes", "Handout", "Answer key"]} addLabel="Add file" titleKey="name" metaKey="kind" />;else
  if (isGame) body = <GamificationEditor mat={m} setMat={set} />;

  return ReactDOM.createPortal(
    <div style={{ position: "fixed", inset: 0, zIndex: 600, background: "rgba(13,26,52,.45)", backdropFilter: "blur(3px)", display: "flex", justifyContent: wide ? "center" : "flex-end", alignItems: "center", padding: wide ? "2.5vh 0" : 0 }} onClick={(e) => {if (e.target === e.currentTarget) onClose();}}>
      <div style={{ width: wide ? "95vw" : "min(760px, 96vw)", height: wide ? "95vh" : "100%", maxWidth: wide ? "1700px" : "none", borderRadius: wide ? 16 : 0, overflow: "hidden", background: "var(--app-bg, #f4f7fb)", display: "flex", flexDirection: "column", boxShadow: "0 24px 70px rgba(0,0,0,.3)" }}>
        {/* header */}
        <div style={{ flex: "none", background: "var(--surface)", borderBottom: "1px solid var(--line)", padding: "16px 22px", display: "flex", alignItems: "center", gap: 13 }}>
          <div style={{ width: 40, height: 40, borderRadius: 11, background: t.color + "1c", display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name={t.icon} size={20} style={{ color: t.color }} /></div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 17, color: "var(--ink)" }}>{t.label}</div>
            <div style={{ fontSize: 12.5, color: "var(--ink-3)" }}>{product.name}</div>
          </div>
          <button onClick={onClose} className={wide ? "btn btn-ghost btn-sm" : ""} style={wide ? { gap: 7 } : { width: 34, height: 34, borderRadius: 9, background: "var(--surface-2)", border: "none", cursor: "pointer", display: "grid", placeItems: "center" }}><Icon name="x" size={16} style={{ color: "var(--ink-3)" }} />{wide ? "Close" : null}</button>
        </div>
        {/* status bar */}
        {!isGame &&
        <div style={{ flex: "none", background: "var(--surface)", borderBottom: "1px solid var(--line)", padding: "11px 22px", display: "flex", alignItems: "center", gap: 16, flexWrap: "wrap" }}>
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <span style={{ fontSize: 12.5, fontWeight: 600, color: "var(--ink-2)" }}>Status</span>
            <div className="segtabs">
              {["none", "draft", "published"].map((s) =>
              <button key={s} className={"segtab" + (m.status === s ? " on" : "")} onClick={() => set({ status: s })} style={{ textTransform: "capitalize", fontSize: 12.5 }}>{s === "none" ? "Not set" : s}</button>)}
            </div>
          </div>
          {t.learner &&
          <label style={{ display: "flex", alignItems: "center", gap: 9, marginLeft: "auto", cursor: "pointer", fontSize: 13, fontWeight: 600, color: "var(--ink-2)" }}>
            <Icon name="eye" size={14} style={{ color: m.visible ? "#2e7d32" : "var(--ink-3)" }} />Visible to learners
            <PSwitch on={!!m.visible} onClick={() => set({ visible: !m.visible })} color="#2e7d32" />
          </label>}
        </div>}
        {/* body */}
        <div className="scroll" style={{ flex: 1, overflowY: "auto", padding: 22 }}>{!isGame && AppPanel && <AppPanel mat={m} set={set} t={t} product={product} toast={toast} />}{body}</div>
      </div>
    </div>, document.body);
}

/* ── MATERIALS TAB ── */
function MaterialsTab({ product, setMat, toast }) {
  const [open, setOpen] = useState(null);
  function summary(key) {
    const m = product.mat[key];
    if (m && m.app) return "Hosted app · " + m.app.name;
    if (key === "presentation") return "HTML deck · Studio";
    if (key === "exam") return m.questions.length + " questions · pass " + m.passPct + "%";
    if (key === "assessment") return m.questions.length + " practice questions";
    if (key === "memo") return m.cards.length + " flashcards";
    if (key === "book") return (m.chapters.length || "No") + " chapters" + (m.pages ? " · " + m.pages + " pp" : "");
    if (key === "trainer") return m.files.length + " files";
    if (key === "gamification") return m.on ? "On · " + m.xpPerModule + " XP/module" : "Off";
    return "";
  }
  return (
    <div>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(300px, 1fr))", gap: 14 }}>
        {MAT_TYPES.map((t) => {
          const st = pStatus(product, t.key);
          return (
            <div key={t.key} className="card" style={{ padding: 18, display: "flex", flexDirection: "column", gap: 12, cursor: "pointer", transition: "box-shadow .15s, transform .15s" }}
            onClick={() => setOpen(t.key)}
            onMouseEnter={(e) => {e.currentTarget.style.boxShadow = "0 8px 24px rgba(29,61,124,.1)";e.currentTarget.style.transform = "translateY(-2px)";}}
            onMouseLeave={(e) => {e.currentTarget.style.boxShadow = "";e.currentTarget.style.transform = "";}}>
              <div style={{ display: "flex", alignItems: "flex-start", gap: 12 }}>
                <div style={{ width: 44, height: 44, borderRadius: 12, background: t.color + "1a", display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name={t.icon} size={21} style={{ color: t.color }} /></div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontWeight: 700, fontSize: 15, color: "var(--ink)", lineHeight: 1.2 }}>{t.label}</div>
                  <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 3 }}>{summary(t.key)}</div>
                </div>
              </div>
              <div style={{ fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.5, flex: 1 }}>{t.desc}</div>
              <div className="between">
                <StatusPill status={st} small />
                <span style={{ display: "inline-flex", alignItems: "center", gap: 5, fontSize: 12.5, fontWeight: 700, color: t.color }}>Configure<Icon name="arrowR" size={13} /></span>
              </div>
              {!t.learner && <div style={{ fontSize: 11, color: "var(--ink-3)", display: "flex", alignItems: "center", gap: 5 }}><Icon name="lock" size={11} />Trainer-only</div>}
            </div>);
        })}
      </div>
      {open && <MaterialDrawer product={product} typeKey={open} setMat={setMat} toast={toast} onClose={() => setOpen(null)} />}
    </div>);
}

/* ── COURSE PAGE (SEO) TAB ── */
function CoursePageTab({ product, form, setForm }) {
  const set = (k, v) => setForm((f) => ({ ...f, [k]: v }));
  const SECTIONS = [
  { key: "overview", label: "About This Course", hint: "Purpose and unique value.", rows: 4, req: true },
  { key: "audience", label: "Target Audience", hint: "Who should attend.", rows: 3, req: true },
  { key: "objectives", label: "Training Objectives", hint: "Key learning outcomes.", rows: 4, req: true },
  { key: "content", label: "Course Content", hint: "Topics, subtopics, key concepts.", rows: 6, req: true },
  { key: "prerequisites", label: "Prerequisites", hint: "Required prior knowledge.", rows: 2, req: true },
  { key: "followUp", label: "Recommended Follow-Up", hint: "Further learning.", rows: 3, req: false }];
  const reqKeys = ["title", "overview", "audience", "objectives", "content", "prerequisites", "metaDesc", "keywords"];
  const filled = reqKeys.filter((k) => (form[k] || "").length > 10).length;
  const score = Math.round(filled / reqKeys.length * 100);
  const sc = score >= 70 ? "#2e7d32" : score >= 40 ? "#b45309" : "#c62828";
  return (
    <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="eyebrow" style={{ marginBottom: 16 }}>Course basics</div>
          <label style={PL}>SEO title</label><input value={form.title || ""} onChange={(e) => set("title", e.target.value)} style={{ ...PF, marginBottom: 14 }} />
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
            <div><label style={PL}>URL slug</label><input value={form.slug || ""} onChange={(e) => set("slug", e.target.value)} style={PF} /></div>
            <div><label style={PL}>Keywords</label><input value={form.keywords || ""} onChange={(e) => set("keywords", e.target.value)} style={PF} /></div>
          </div>
        </div>
        <div className="card" style={{ padding: 22 }}>
          <div className="eyebrow" style={{ marginBottom: 18 }}>Course description</div>
          {SECTIONS.map((s) =>
          <div key={s.key} style={{ marginBottom: 16 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }}>
                <span style={{ fontSize: 13, fontWeight: 700 }}>{s.label}</span>
                <span style={{ fontSize: 10.5, padding: "2px 8px", borderRadius: 999, background: s.req ? "var(--vh-blue-50)" : "var(--chip)", color: s.req ? "var(--vh-blue-600)" : "var(--ink-3)", fontWeight: 700 }}>{s.req ? "Required" : "Optional"}</span>
                <span style={{ fontSize: 11, color: "var(--ink-3)", marginLeft: "auto", fontStyle: "italic" }}>{s.hint}</span>
              </div>
              <textarea rows={s.rows} value={form[s.key] || ""} onChange={(e) => set(s.key, e.target.value)} placeholder="Start typing…" style={{ ...PF, resize: "vertical", lineHeight: 1.5 }} />
            </div>)}
        </div>
        <div className="card" style={{ padding: 22 }}>
          <div className="eyebrow" style={{ marginBottom: 14 }}>Meta &amp; search preview</div>
          <textarea rows={3} value={form.metaDesc || ""} onChange={(e) => set("metaDesc", e.target.value)} placeholder="Meta description (150–160 chars)" style={{ ...PF, resize: "vertical", marginBottom: 14 }} />
          <div style={{ padding: "16px 20px", borderRadius: 12, background: "var(--surface-2)", border: "1px solid var(--line)" }}>
            <div style={{ fontSize: 12, color: "var(--ink-3)", marginBottom: 4 }}>https://vanharen.net/training/{form.slug || "url-slug"}</div>
            <div style={{ fontSize: 18, color: "#1a0dab", fontWeight: 600, marginBottom: 4 }}>{form.title || product.name}</div>
            <div style={{ fontSize: 13.5, color: "var(--ink-2)", lineHeight: 1.5 }}>{form.metaDesc || "Professional certification training. Expert-led, online and on-site."}</div>
          </div>
        </div>
      </div>
      <div className="card" style={{ padding: 20, position: "sticky", top: 0 }}>
        <div className="eyebrow" style={{ marginBottom: 14 }}>SEO score</div>
        <div style={{ display: "flex", alignItems: "center", gap: 14, marginBottom: 16 }}>
          <Ring value={score} size={68} stroke={7} color={sc}><span style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 17, color: sc }}>{score}</span></Ring>
          <div><div style={{ fontWeight: 700, fontSize: 15, color: sc }}>{score >= 70 ? "Great" : "Fill all fields"}</div><div className="dim" style={{ fontSize: 12 }}>{filled}/{reqKeys.length} complete</div></div>
        </div>
        {reqKeys.map((k) => {const ok = (form[k] || "").length > 10;return (
            <div key={k} style={{ display: "flex", alignItems: "center", gap: 10, padding: "6px 0", borderBottom: "1px solid var(--line-2)" }}>
            <Icon name={ok ? "check" : "lock"} size={13} style={{ color: ok ? "#2e7d32" : "var(--ink-3)" }} />
            <span style={{ fontSize: 12.5, flex: 1, textTransform: "capitalize" }}>{k}</span>
            <span style={{ fontSize: 12, color: ok ? "#2e7d32" : "var(--ink-3)", fontWeight: 700 }}>{ok ? "✓" : "—"}</span>
          </div>);})}
      </div>
    </div>);
}

/* ── SETTINGS TAB ── */
function ProductSettingsTab({ product, update }) {
  const rows = [["Course name", "name", "text"], ["Level", "level", ["Foundation", "Practitioner", "Advanced", "Expert"]], ["Language", "lang", ["Dutch", "English", "German", "French"]], ["Certification body", "body", "text"], ["List price", "price", "text"]];
  return (
    <div style={{ maxWidth: 620, display: "flex", flexDirection: "column", gap: 16 }}>
      <div className="card" style={{ padding: 22 }}>
        <div className="eyebrow" style={{ marginBottom: 16 }}>Course details</div>
        {rows.map(([label, key, type]) =>
        <div key={key} style={{ marginBottom: 14 }}>
            <label style={PL}>{label}</label>
            {Array.isArray(type) ?
          <select value={product[key]} onChange={(e) => update({ [key]: e.target.value })} style={{ ...PF, background: "#fff" }}>{type.map((o) => <option key={o}>{o}</option>)}</select> :
          <input value={product[key]} onChange={(e) => update({ [key]: e.target.value })} style={PF} />}
          </div>)}
      </div>
      <div className="card" style={{ padding: 22, border: "1px solid #f3d4d4" }}>
        <div className="eyebrow" style={{ marginBottom: 8, color: "#c62828" }}>Danger zone</div>
        <p style={{ fontSize: 13, color: "var(--ink-2)", marginBottom: 14 }}>Archiving hides this course and all its materials from learners.</p>
        <button className="btn btn-outline btn-sm" style={{ color: "#c62828", borderColor: "#f3d4d4" }}><Icon name="x" size={13} />Archive course</button>
      </div>
    </div>);
}

/* ── PRODUCT WORKSPACE ── */
function ProductWorkspace({ product, update, setMat, onBack, toast }) {
  const [tab, setTab] = useState("materials");
  const [form, setForm] = useState(() => ({ title: product.name + " — Van Haren Training", slug: product.id, keywords: product.name.split("®")[0].trim() + ", certification, training", overview: "", audience: "", objectives: "", content: "", prerequisites: "", followUp: "", metaDesc: "" }));
  const published = MAT_TYPES.filter((t) => pStatus(product, t.key) === "published").length;
  const TABS = [["materials", "Learning materials", "layers"], ["page", "Course page (SEO)", "globe"], ["settings", "Settings", "gear"]];
  return (
    <div>
      {/* header */}
      <div className="card" style={{ padding: "18px 22px", marginBottom: 18 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap" }}>
          <button className="btn btn-ghost btn-sm" onClick={onBack}><Icon name="chevL" size={14} />Products</button>
          <div style={{ width: 46, height: 46, borderRadius: 12, background: product.color + "22", display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name={product.icon} size={23} style={{ color: product.color }} /></div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <h1 style={{ fontSize: 22, fontWeight: 800, lineHeight: 1.1 }}>{product.name}</h1>
            <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 3 }}>{product.level} · {product.lang} · {product.body} · {product.learners} learners</div>
          </div>
          <div style={{ textAlign: "right" }}>
            <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 18, color: "var(--ink)" }}>{published}/{MAT_TYPES.length}</div>
            <div style={{ fontSize: 11.5, color: "var(--ink-3)", fontWeight: 600 }}>assets published</div>
          </div>
          <button className="btn btn-pri"><Icon name="share" size={15} />Publish course</button>
        </div>
      </div>
      {/* tabs */}
      <div className="segtabs" style={{ marginBottom: 18 }}>
        {TABS.map(([id, lbl, ic]) =>
        <button key={id} className={"segtab" + (tab === id ? " on" : "")} onClick={() => setTab(id)} style={{ display: "flex", alignItems: "center", gap: 7 }}><Icon name={ic} size={14} />{lbl}</button>)}
      </div>
      {tab === "materials" && <MaterialsTab product={product} setMat={setMat} toast={toast} />}
      {tab === "page" && <CoursePageTab product={product} form={form} setForm={setForm} />}
      {tab === "settings" && <ProductSettingsTab product={product} update={update} />}
    </div>);
}

/* ── NEW COURSE MODAL ── */
function NewCourseModal({ onClose, onCreate }) {
  const ICONS = [["graduation", "#1281c4"], ["layers", "#3bc1ce"], ["shield", "#7c3aed"], ["cert", "#2e7d32"], ["globe", "#e65100"], ["book", "#00838f"]];
  const [name, setName] = useState("");
  const [icon, setIcon] = useState(0);
  const [level, setLevel] = useState("Foundation");
  const [lang, setLang] = useState("Dutch");
  const [body, setBody] = useState("");
  const [price, setPrice] = useState("");
  const [seed, setSeed] = useState({ presentation: true, exam: true, assessment: true, memo: true, book: true, trainer: true, gamification: true });
  const id = (name || "new-course").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");

  function create() {
    const mat = seedMat();
    Object.keys(seed).forEach((k) => {if (!seed[k]) {if (k === "gamification") mat[k] = { ...mat[k], on: false };else mat[k] = { ...mat[k], status: "none", visible: false, questions: [], cards: [], chapters: [], files: [] };}});
    onCreate({ id: id + "-" + Date.now().toString(36), name: name.trim() || "Untitled Course", icon: ICONS[icon][0], color: ICONS[icon][1], level, lang, body: body.trim() || "—", price: price.trim() || "€—", learners: 0, mat });
    onClose();
  }
  return (
    <div style={{ position: "fixed", inset: 0, background: "rgba(13,26,52,.5)", backdropFilter: "blur(3px)", zIndex: 200, display: "grid", placeItems: "center", padding: 24 }} onClick={(e) => {if (e.target === e.currentTarget) onClose();}}>
      <div className="card scroll" style={{ width: "100%", maxWidth: 560, maxHeight: "90vh", overflow: "auto", padding: 28 }}>
        <div className="between" style={{ marginBottom: 20 }}>
          <h2 style={{ fontSize: 20, fontWeight: 800 }}>New course product</h2>
          <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: "flex", flexDirection: "column", gap: 14 }}>
          <div><label style={PL}>Course name</label><input value={name} onChange={(e) => setName(e.target.value)} style={PF} placeholder="e.g. PRINCE2® Practitioner" /></div>
          <div>
            <label style={PL}>Icon &amp; colour</label>
            <div style={{ display: "flex", gap: 9 }}>
              {ICONS.map(([ic, c], i) =>
              <button key={ic} onClick={() => setIcon(i)} style={{ width: 44, height: 44, borderRadius: 11, background: c + (icon === i ? "" : "22"), border: icon === i ? "2px solid " + c : "2px solid var(--line)", cursor: "pointer", display: "grid", placeItems: "center" }}><Icon name={ic} size={20} style={{ color: icon === i ? "#fff" : c }} /></button>)}
            </div>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
            <div><label style={PL}>Level</label><select value={level} onChange={(e) => setLevel(e.target.value)} style={{ ...PF, background: "#fff" }}><option>Foundation</option><option>Practitioner</option><option>Advanced</option><option>Expert</option></select></div>
            <div><label style={PL}>Language</label><select value={lang} onChange={(e) => setLang(e.target.value)} style={{ ...PF, background: "#fff" }}><option>Dutch</option><option>English</option><option>German</option><option>French</option></select></div>
            <div><label style={PL}>Certification body</label><input value={body} onChange={(e) => setBody(e.target.value)} style={PF} placeholder="e.g. PeopleCert" /></div>
            <div><label style={PL}>List price</label><input value={price} onChange={(e) => setPrice(e.target.value)} style={PF} placeholder="€1.395" /></div>
          </div>
          <div style={{ border: "1px solid var(--line)", borderRadius: 14, overflow: "hidden" }}>
            <div style={{ padding: "12px 15px", background: "var(--surface-2)", fontSize: 13.5, fontWeight: 700 }}>Set up learner materials</div>
            {MAT_TYPES.map((t) =>
            <label key={t.key} style={{ display: "flex", alignItems: "center", gap: 11, padding: "10px 15px", borderTop: "1px solid var(--line)", cursor: "pointer" }}>
                <div style={{ width: 30, height: 30, borderRadius: 8, background: t.color + "1a", display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name={t.icon} size={15} style={{ color: t.color }} /></div>
                <div style={{ flex: 1 }}><div style={{ fontSize: 13.5, fontWeight: 600 }}>{t.label}</div></div>
                <PSwitch on={seed[t.key]} onClick={() => setSeed((s) => ({ ...s, [t.key]: !s[t.key] }))} color={t.color} />
              </label>)}
          </div>
          <div style={{ display: "flex", gap: 10, marginTop: 4 }}>
            <button className="btn btn-pri" style={{ flex: 1, padding: "12px", borderRadius: 11 }} onClick={create}><Icon name="plus" size={16} />Create course</button>
            <button className="btn btn-ghost" style={{ flex: 1, padding: "12px", borderRadius: 11 }} onClick={onClose}>Cancel</button>
          </div>
        </div>
      </div>
    </div>);
}

/* ── PRODUCTS SCREEN (list + state) ── */
function ProductsScreen({ toast }) {
  const [products, setProducts] = useState(SEED_PRODUCTS);
  const [selId, setSelId] = useState(null);
  const [showNew, setShowNew] = useState(false);
  const selected = products.find((p) => p.id === selId);

  function update(patch) {setProducts((ps) => ps.map((p) => p.id === selId ? { ...p, ...patch } : p));}
  function setMat(key, patch) {setProducts((ps) => ps.map((p) => p.id === selId ? { ...p, mat: { ...p.mat, [key]: { ...p.mat[key], ...patch } } } : p));}
  function createCourse(c) {setProducts((ps) => [c, ...ps]);setSelId(c.id);}

  if (selected) return <ProductWorkspace product={selected} update={update} setMat={setMat} onBack={() => setSelId(null)} toast={toast} />;

  const cols = ["presentation", "exam", "assessment", "memo", "book", "gamification"];
  return (
    <div>
      <div className="between" style={{ marginBottom: 24 }}>
        <div className="page-head" style={{ marginBottom: 0 }}><h1>Products</h1><p>Each course and all its learner materials — courseware, exams, memo cards, assessment & more.</p></div>
        <button className="btn btn-acc" onClick={() => setShowNew(true)}><Icon name="plus" size={14} />New course</button>
      </div>
      <div className="card" style={{ overflowX: "auto" }}>
        <table className="tbl" style={{ minWidth: 920 }}>
          <thead><tr><th>Course</th>{cols.map((c) => <th key={c}>{MAT_BY_KEY[c].short}</th>)}<th>Learners</th><th></th></tr></thead>
          <tbody>
            {products.map((p) =>
            <tr key={p.id} style={{ cursor: "pointer" }} onClick={() => setSelId(p.id)}>
                <td>
                  <div style={{ display: "flex", alignItems: "center", gap: 11 }}>
                    <div style={{ width: 38, height: 38, borderRadius: 10, background: p.color + "22", display: "grid", placeItems: "center", flexShrink: 0 }}><Icon name={p.icon} size={18} style={{ color: p.color }} /></div>
                    <div><div style={{ fontWeight: 700 }}>{p.name}</div><div style={{ fontSize: 11.5, color: "var(--ink-3)" }}>{p.level} · {p.lang}</div></div>
                  </div>
                </td>
                {cols.map((c) => <td key={c}><StatusPill status={pStatus(p, c)} small /></td>)}
                <td><span className="kpi" style={{ fontSize: 15 }}>{p.learners || "—"}</span></td>
                <td><button className="btn btn-outline btn-sm" onClick={(e) => {e.stopPropagation();setSelId(p.id);}}>Open<Icon name="arrowR" size={12} /></button></td>
              </tr>)}
          </tbody>
        </table>
      </div>
      {showNew && <NewCourseModal onClose={() => setShowNew(false)} onCreate={createCourse} />}
    </div>);
}

Object.assign(window, { ProductsScreen, MAT_TYPES });