/* educator-slide-editor.jsx — Slide-editor A2a: element aanklikken, tekst ter
   plekke bewerken, opslaan/undo. De runtime draait ín het slide-iframe
   (slide-editor-runtime.js); dit is de schil eromheen. Spec §3–§4.
   De z-index (1300) ligt bewust boven de Study Builder (1200) en de presenter
   (1260): de editor wordt vanuit die overlays geopend en zou er anders
   onzichtbaar achter landen. Bewaakt door test/overlay-stacking.test.js. */
/* Knoppen op de donkere editorbalk. De dashboardklassen (btn-ghost) zijn voor
   een lichte ondergrond gemaakt en vallen hier weg — vandaar eigen stijlen met
   voldoende contrast, ook in uitgeschakelde toestand. */
function EdBtn({ tone = "sec", disabled, onClick, title, children, style }) {
  const [hover, setHover] = React.useState(false);
  const tint = {
    pri:  { bg: "#2274bb", bgHover: "#2f86d2", border: "#3d92d6", ink: "#ffffff" },
    sec:  { bg: "#1c2f4d", bgHover: "#25406a", border: "#37547f", ink: "#e8f1fb" },
    quiet:{ bg: "transparent", bgHover: "#1c2f4d", border: "#34496a", ink: "#cfe0f2" },
    /* Amber = er staat werk klaar dat nog niet bewaard is. */
    warn: { bg: "#d97e0d", bgHover: "#eb8f1c", border: "#f0a03a", ink: "#1a1000" },
  }[tone];
  const uit = { bg: "transparent", bgHover: "transparent", border: "#243654", ink: "#5c7699" };
  const c = disabled ? uit : tint;
  return (
    <button type="button" onClick={disabled ? undefined : onClick} disabled={disabled} title={title}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{ display: "inline-flex", alignItems: "center", gap: 7, padding: "9px 15px",
               borderRadius: 9, border: "1px solid " + c.border,
               background: hover && !disabled ? c.bgHover : c.bg, color: c.ink,
               fontFamily: "var(--body)", fontWeight: 700, fontSize: 13.5, lineHeight: 1,
               cursor: disabled ? "default" : "pointer", whiteSpace: "nowrap",
               transition: "background .14s, border-color .14s", ...style }}>{children}</button>);
}

/* Laadvenster met wisselende meldingen: zonder terugkoppeling lijkt een knop
   die even nadenkt gewoon kapot. */
function EdBusy({ titel, stappen }) {
  const [i, setI] = React.useState(0);
  React.useEffect(() => {
    const id = setInterval(() => setI((n) => (n + 1) % stappen.length), 1400);
    return () => clearInterval(id);
  }, [stappen.length]);
  return (
    <div style={{ position: "absolute", inset: 0, zIndex: 20, background: "rgba(6,13,24,.78)", display: "grid", placeItems: "center" }}>
      <div style={{ background: "#0d1929", border: "1px solid #24344f", borderRadius: 14, padding: "26px 30px", textAlign: "center", minWidth: 280, boxShadow: "0 24px 70px rgba(0,0,0,.5)" }}>
        <div style={{ width: 34, height: 34, margin: "0 auto 14px", borderRadius: "50%",
                      border: "3px solid #24344f", borderTopColor: "#58a8dd", animation: "edspin .8s linear infinite" }} />
        <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 15, color: "#e8f1fb", marginBottom: 6 }}>{titel}</div>
        <div style={{ fontSize: 12.5, color: "#9db2cc", minHeight: 18 }}>{stappen[i]}</div>
      </div>
    </div>);
}

function SlideEditor({ deckTitle, slides, startIndex, onSave, onClose, toast, seedEdits, seedGame, onMigrated }) {
  const R = window.SlideEditorRuntime;
  const [runtimeSrc, setRuntimeSrc] = React.useState(window.__edRuntimeSrc || null);
  const [idx, setIdx] = React.useState(startIndex || 0);
  /* Bewerkingen per slide; pas bij Save gaan ze allemaal naar het deck. */
  /* seedEdits: al klaargezette bewerkingen (bijv. een uit de Studio gemigreerde
     vraag). Meteen als onbewaard werk zichtbaar, geen wachten op de iframe. */
  const [edits, setEdits] = React.useState(() => ({ ...(seedEdits || {}) }));
  const [selInfo, setSelInfo] = React.useState(null);
  const [dirty, setDirty] = React.useState(false);
  const frameRef = React.useRef(null);
  const editsRef = React.useRef(edits);
  editsRef.current = edits;
  /* Wat er bij de laatste Opslaan naar het deck ging: alles wat daarvan
     afwijkt is nog onbewaard werk. Zonder dit blijft de knop amber staan. */
  const bewaardRef = React.useRef({});


  const base = React.useMemo(
    () => R.normalizeSlideDoc(edits[idx] || slides[idx] || ""), [idx, slides]);
  const [hist, setHist] = React.useState(() => ({ stack: [base], at: 0 }));
  /* Van slide wisselen: de huidige versie vasthouden, dan verse geschiedenis. */
  React.useEffect(() => { setHist({ stack: [base], at: 0 }); setSelInfo(null); }, [idx]);
  const cur = hist.stack[hist.at];

  function gotoSlide(n) {
    if (n === idx || n < 0 || n >= slides.length || !slides[n]) return;
    setEdits((prev) => (hist.at > 0 ? { ...prev, [idx]: cur } : prev));
    setIdx(n);
  }

  /* De runtimebron één keer ophalen; daarna uit de paginacache. */
  React.useEffect(() => {
    if (runtimeSrc) return;
    fetch("js/slide-editor-runtime.js").then((r) => { if (!r.ok) throw new Error("fetch " + r.status); return r.text(); })
      .then((t) => { window.__edRuntimeSrc = t; setRuntimeSrc(t); })
      .catch(() => { toast && toast("Editor kon de runtime niet laden — probeer opnieuw", "x"); onClose(); });
  }, []);

  React.useEffect(() => {
    function onMsg(e) {
      if (!frameRef.current || e.source !== frameRef.current.contentWindow) return;
      const d = e.data || {};
      if (d.ed === "select") setSelInfo(d.id ? { id: d.id, tag: d.tag, text: d.text, styles: d.styles || {} } : null);
      if (d.ed === "mutated" && typeof d.html === "string") {
        setHist((h) => {
          const stack = [...h.stack.slice(0, h.at + 1), d.html];
          while (stack.length > 50) stack.shift();
          return { stack, at: stack.length - 1 };
        });
        setDirty(true);
      }
    }
    window.addEventListener("message", onMsg);
    return () => window.removeEventListener("message", onMsg);
  }, []);

  /* Eigenschap naar de runtime sturen; die commit een undo-snapshot. */
  function sendApply(prop, value) {
    if (!selInfo || !frameRef.current) return;
    frameRef.current.contentWindow.postMessage({ ed: "apply", id: selInfo.id, prop, value }, "*");
  }
  function px(v) { const n = parseFloat(v); return isFinite(n) ? Math.round(n) : ""; }
  function hex(v) { /* rgb(a) → #hex voor <input type=color> */
    const m = /rgba?\((\d+),\s*(\d+),\s*(\d+)/.exec(String(v || ""));
    if (!m) return /^#/.test(String(v)) ? v : "#000000";
    return "#" + [m[1], m[2], m[3]].map((x) => (+x).toString(16).padStart(2, "0")).join("");
  }

  /* Een game als echte slide-HTML invoegen; hij neemt het palet van de slide over. */
  /* Bij migratie staat de vraag al in de slide; het paneel toont hem ook, zodat
     bijstellen niet betekent dat je alles opnieuw intypt. */
  const [game, setGame] = React.useState(() => {
    const g = seedGame;
    const opts = (g && g.options ? g.options.slice(0, 4) : []).concat(["", "", "", ""]).slice(0, 4);
    return g ? { type: g.type || "quiz", q: g.q || "", options: opts,
                 answer: g.answer || 0, xp: g.xp || 50, labels: g.labels || "abc",
                 result: g.result || "next", qr: false }
             : { type: "quiz", q: "", options: ["", "", "", ""], answer: 0, xp: 50, labels: "abc",
                 result: "next", qr: false };
  });
  function insertGame() {
    const opties = game.options.filter((o) => o.trim());
    if (!game.q.trim() || opties.length < 2) { toast && toast("Vul een vraag en minstens twee opties in", "x"); return; }
    /* Rechtstreeks in de HTML, niet via een bericht naar de iframe: die route
       hing af van het opstartmoment van de runtime en sloeg soms over. */
    const spec = { type: game.type, q: game.q.trim(), options: opties,
      answer: Math.min(game.answer, opties.length - 1), xp: game.xp, labels: game.labels,
      result: game.result,
      key: "s" + (idx + 1) + "-" + Date.now().toString(36) };
    let nieuw = R.insertGameIntoHtml(cur, spec);
    if (nieuw === cur) { toast && toast("De vraag kon niet worden geplaatst", "x"); return; }
    /* QR naar de sessie erbij: dezelfde lege join-marker als de Live-plak,
       gevuld met de echte QR zodra er gepresenteerd wordt. */
    if (game.qr && R.insertJoinIntoHtml) nieuw = R.insertJoinIntoHtml(nieuw, { layout: "klein" });
    setHist((h) => {
      const stack = [...h.stack.slice(0, h.at + 1), nieuw];
      while (stack.length > 50) stack.shift();
      return { stack, at: stack.length - 1 };
    });
    setDirty(true);
    toast && toast(game.qr ? "Vraag + QR-vak in de slide gezet — sleep ze op hun plek"
                            : "Vraag in de slide gezet — sleep of restyle hem als elk ander blok", "check");
  }

  /* QR-plek voor LearnALot Live invoegen; idempotent — bij een al aanwezige
     join-marker geeft de runtime dezelfde HTML terug (geen dubbele plek). */
  function insertJoin() {
    const R = window.SlideEditorRuntime;
    if (!R || !R.insertJoinIntoHtml) return;
    const volgend = R.insertJoinIntoHtml(cur);
    if (volgend === cur) { toast && toast("Er staat al een QR-plek op deze slide", "info"); return; }
    setHist((h) => {
      const stack = [...h.stack.slice(0, h.at + 1), volgend];
      while (stack.length > 50) stack.shift();
      return { stack, at: stack.length - 1 };
    });
    setDirty(true);
    toast && toast("QR-plek toegevoegd — de echte QR verschijnt tijdens het presenteren", "check");
  }

  /* Deck-branding: logo, achtergrond en voettekst als één laag in elke slide.
     Het merk komt uit de Style Tailor-bibliotheek; plek en tekst stel je hier in. */
  const [brandOpen, setBrandOpen] = React.useState(false);
  const themes = React.useMemo(() => {
    const T = window.VHTheme;
    if (!T) return [];
    const lijst = (T.savedList && T.savedList()) || [];
    const nu = T.current && T.current();
    return nu ? [nu, ...lijst.filter((x) => x.domain !== nu.domain)] : lijst;
  }, []);
  const [brand, setBrand] = React.useState(() =>
    R.brandFromTheme(themes[0]) || { logo: null, logoPos: "tr", logoSize: 8, margin: 3,
      bg: { color: null, image: null }, footer: { text: "", number: true }, skipFirst: true });

  function bestandNaarDataUri(file, klaar) {
    const r = new FileReader();
    r.onload = () => klaar(r.result);
    r.readAsDataURL(file);
  }

  const [bezig, setBezig] = React.useState(null);   // {titel, stappen} tijdens werk
  /* Pas een venster tonen als het laden echt merkbaar duurt; anders flitst het
     even en oogt het rommeliger dan helemaal niets. */
  const [traagLaden, setTraagLaden] = React.useState(false);
  React.useEffect(() => {
    if (runtimeSrc) { setTraagLaden(false); return; }
    const id = setTimeout(() => setTraagLaden(true), 150);
    return () => clearTimeout(id);
  }, [runtimeSrc]);

  function applyBrandAll(merk) {
    setBezig({ titel: merk ? "Branding toepassen" : "Branding verwijderen",
               stappen: ["Slides inlezen…", "Logo en voettekst plaatsen…",
                         "Achtergronden bijwerken…", slides.length + " slides bijwerken…"] });
    /* Even lucht geven zodat het venster zichtbaar wordt vóór het rekenwerk. */
    setTimeout(() => { brandingUitvoeren(merk); setBezig(null); }, 60);
  }

  function brandingUitvoeren(merk) {
    const nieuweEdits = { ...editsRef.current };
    if (hist.at > 0) nieuweEdits[idx] = cur;
    slides.forEach((h, i) => {
      const bron = nieuweEdits[i] || h;
      if (!bron) return;
      nieuweEdits[i] = R.applyBrand(R.normalizeSlideDoc(bron), merk, { index: i, total: slides.length });
    });
    setEdits(nieuweEdits);
    setHist({ stack: [nieuweEdits[idx] || base], at: 0 });
    setDirty(true);
    toast && toast(merk ? "Branding op alle slides gezet — nog opslaan" : "Branding verwijderd", merk ? "check" : "x");
  }

  function step(d) { setHist((h) => ({ ...h, at: Math.max(0, Math.min(h.stack.length - 1, h.at + d)) })); setDirty(true); }
  function save() {
    const alles = { ...editsRef.current };
    if (hist.at > 0) alles[idx] = cur;
    const n = Object.keys(alles).length;
    if (!n) { toast && toast("Nog niets gewijzigd", "refresh"); return; }
    setEdits(alles);
    /* Wachten op de échte serverbevestiging: pas dan versoepelt de knop en
       klopt de melding. Bij een weigering blijft alles amber en onbewaard. */
    Promise.resolve(onSave(alles)).then(() => {
      setDirty(false);
      bewaardRef.current = { ...alles };
      /* De vraag staat nu in het slide-ontwerp; de losse overlay mag weg,
         anders zou de cursist hem twee keer krijgen. */
      if (onMigrated) Object.keys(seedEdits || {}).forEach((i) => onMigrated(+i));
      setHist({ stack: [alles[idx] || cur], at: 0 });   /* bewaard = nieuwe nulstand */
      toast && toast(n === 1 ? "Slide opgeslagen in het deck ✓" : n + " slides opgeslagen in het deck ✓", "check");
    }).catch((e) => {
      toast && toast(String((e && e.message) || "Opslaan mislukt"), "x");
    });
  }
  function close() {
    if (teBewaren === 0) { onClose(); return; }
    if (window.confirm(teBewaren + " slide(s) niet opgeslagen — toch sluiten?")) onClose();
  }

  /* Aantal slides met onopgeslagen werk: de verzamelde bewerkingen plus de
     slide waar je nu in bezig bent. */
  const teBewaren =
    Object.keys(edits).filter((k) => edits[k] !== bewaardRef.current[k]).length +
    (hist.at > 0 && cur !== bewaardRef.current[idx] && edits[idx] !== cur ? 1 : 0);

  const srcDoc = runtimeSrc ? R.withRuntime(cur, runtimeSrc) : null;
  return ReactDOM.createPortal(
    <div style={{ position: "fixed", inset: 0, zIndex: 1300, background: "#0a1526", display: "flex", flexDirection: "column" }}>
      <div style={{ flex: "none", display: "flex", alignItems: "center", gap: 10, padding: "12px 18px", background: "#0d1929", borderBottom: "1px solid #1e2e4a", color: "#fff" }}>
        <Icon name="wand" size={17} style={{ color: "#58a8dd" }} />
        <strong style={{ fontFamily: "var(--display)", fontSize: 15 }}>{deckTitle} · slide {idx + 1}/{slides.length}</strong>
        <span style={{ fontSize: 12.5, color: "#9db2cc" }}>
          {selInfo ? "<" + selInfo.tag + "> " + (selInfo.text || "") : "Klik = selecteren · dubbelklik = tekst · sleep = verplaatsen"}
        </span>
        <div style={{ flex: 1 }} />
        <EdBtn tone="sec" onClick={() => setBrandOpen(true)}
          title="Logo, achtergrond en voettekst voor het hele deck"><Icon name="star" size={14} />Branding</EdBtn>

        {/* Ongedaan maken als één duidelijk paar, ook zichtbaar wanneer uit */}
        <span style={{ display: "inline-flex", marginLeft: 4 }}>
          <EdBtn tone="sec" disabled={hist.at === 0} onClick={() => step(-1)} title="Ongedaan maken"
            style={{ borderRadius: "9px 0 0 9px", borderRightWidth: 0, padding: "9px 13px" }}>↩ Terug</EdBtn>
          <EdBtn tone="sec" disabled={hist.at >= hist.stack.length - 1} onClick={() => step(1)} title="Opnieuw doen"
            style={{ borderRadius: "0 9px 9px 0", padding: "9px 13px" }}>↪ Vooruit</EdBtn>
        </span>

        <EdBtn tone={teBewaren > 0 ? "warn" : "sec"} disabled={teBewaren === 0} onClick={save}
          title={teBewaren > 0 ? "Je wijzigingen in het deck opslaan" : "Alles is opgeslagen"}
          style={{ marginLeft: 4 }}>
          <Icon name="check" size={14} />
          {teBewaren > 0 ? "Wijzigingen opslaan" + (teBewaren > 1 ? " (" + teBewaren + ")" : "") : "Alles opgeslagen"}
        </EdBtn>
        <EdBtn tone="quiet" onClick={close} title="Editor sluiten"><Icon name="x" size={14} />Sluiten</EdBtn>
      </div>
      <div style={{ flex: 1, display: "flex", minHeight: 0 }}>
        {/* Slidestrip: door het deck lopen zonder de editor te verlaten. */}
        <div className="scroll" style={{ width: 132, flex: "none", background: "#0d1929", borderRight: "1px solid #1e2e4a", overflowY: "auto", padding: 10 }}>
          {slides.map((h, i) =>
            <button key={i} onClick={() => gotoSlide(i)} disabled={!h}
              style={{ display: "block", width: "100%", marginBottom: 8, padding: 0, cursor: h ? "pointer" : "default",
                       border: "2px solid " + (i === idx ? "#58a8dd" : "transparent"), borderRadius: 7, overflow: "hidden",
                       background: "#0a1526", position: "relative", opacity: h ? 1 : .35 }}>
              <div style={{ position: "relative", aspectRatio: "1.7778", background: "#0f1b2d" }}>
                {h ? <iframe title={"Slide " + (i + 1)} srcDoc={editsRef.current[i] || h} sandbox="allow-scripts"
                       scrolling="no" loading="lazy" tabIndex={-1}
                       style={{ width: "100%", height: "100%", border: 0, pointerEvents: "none", display: "block" }} /> : null}
                <span style={{ position: "absolute", bottom: 2, left: 3, fontSize: 9.5, fontWeight: 800, color: "#fff",
                               background: "rgba(0,0,0,.62)", borderRadius: 4, padding: "1px 5px" }}>{i + 1}</span>
                {editsRef.current[i] && <span title="Bewerkt" style={{ position: "absolute", top: 3, right: 3, width: 8, height: 8, borderRadius: "50%", background: "#26890c" }} />}
              </div>
            </button>)}
        </div>
        <div style={{ flex: 1, display: "grid", placeItems: "center", padding: 20, minWidth: 0 }}>
          {/* Donker zolang de slide er niet is: een wit vlak vóór de inhoud
              leest als een flits, alsof het scherm even kapotgaat. */}
          <div style={{ width: "min(80vw, calc((100vh - 110px) * 1.7778))", maxWidth: "100%", aspectRatio: "1.7778",
                        background: srcDoc ? "#fff" : "#101d31", borderRadius: 6, overflow: "hidden",
                        boxShadow: "0 24px 70px rgba(0,0,0,.6)", transition: "background .2s" }}>
            {srcDoc ?
              <iframe key={hist.at} ref={frameRef} title="Slide-editor" srcDoc={srcDoc} sandbox="allow-scripts"
                style={{ width: "100%", height: "100%", border: 0, display: "block" }} /> :
              <div style={{ height: "100%", display: "grid", placeItems: "center", gap: 10,
                            color: "#5f7797", fontSize: 13, animation: "edpulse 1.4s ease-in-out infinite" }}>
                Slide klaarzetten…
              </div>}
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: 12, marginTop: 12 }}>
            <EdBtn tone="sec" disabled={idx === 0} onClick={() => gotoSlide(idx - 1)} title="Vorige slide">‹ Vorige</EdBtn>
            <span style={{ color: "#9db2cc", fontSize: 12.5, fontVariantNumeric: "tabular-nums" }}>{idx + 1} / {slides.length}</span>
            <EdBtn tone="sec" disabled={idx >= slides.length - 1} onClick={() => gotoSlide(idx + 1)} title="Volgende slide">Volgende ›</EdBtn>
          </div>
        </div>
        <div className="scroll" style={{ width: 260, flex: "none", background: "#0d1929", borderLeft: "1px solid #1e2e4a", padding: 16, overflowY: "auto", color: "#dfe9f5", fontSize: 13 }}>
          {!selInfo && <div style={{ color: "#7f93b3", fontSize: 12.5, marginBottom: 14 }}>Klik een element aan om het vorm te geven.</div>}
          {selInfo && <div>
          <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: ".08em", textTransform: "uppercase", color: "#7f93b3", marginBottom: 10 }}>{"<" + selInfo.tag + "> "}{selInfo.text}</div>
          {[["Size", "fontSize"], ["Line height", "lineHeight"]].map(([lbl, prop]) =>
            <label key={prop} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }}>{lbl}
              <input type="number" step={prop === "lineHeight" ? 0.05 : 1} defaultValue={prop === "lineHeight" ? ((parseFloat(selInfo.styles.lineHeight) / parseFloat(selInfo.styles.fontSize)) || 1.2).toFixed(2) : px(selInfo.styles[prop])}
                key={selInfo.id + prop + selInfo.styles[prop]}
                onKeyDown={(e) => e.key === "Enter" && e.target.blur()}
                onBlur={(e) => e.target.value !== "" && sendApply(prop, parseFloat(e.target.value))}
                style={{ width: 80, background: "#121e30", color: "#fff", border: "1px solid #24344f", borderRadius: 6, padding: "5px 8px" }} />
            </label>)}
          <label style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }}>Weight
            <select defaultValue={selInfo.styles.fontWeight} key={selInfo.id + "w" + selInfo.styles.fontWeight}
              onChange={(e) => sendApply("fontWeight", +e.target.value)}
              style={{ width: 80, background: "#121e30", color: "#fff", border: "1px solid #24344f", borderRadius: 6, padding: "5px 4px" }}>
              {[300, 400, 500, 600, 700, 800, 900].map((w) => <option key={w} value={w}>{w}</option>)}
            </select>
          </label>
          {[["Colour", "color"], ["Background", "background"]].map(([lbl, prop]) =>
            <label key={prop} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }}>{lbl}
              <input type="color" value={hex(selInfo.styles[prop])} onChange={(e) => sendApply(prop, e.target.value)}
                style={{ width: 42, height: 28, border: "1px solid #24344f", borderRadius: 6, background: "none", padding: 1 }} />
            </label>)}
          <div style={{ display: "flex", gap: 5, marginBottom: 10 }}>
            {["left", "center", "right", "justify"].map((a) =>
              <button key={a} onClick={() => sendApply("textAlign", a)}
                style={{ flex: 1, padding: "6px 0", borderRadius: 6, border: "1px solid #24344f", cursor: "pointer", background: selInfo.styles.textAlign === a ? "#2274bb" : "#121e30", color: "#fff", fontSize: 11, fontWeight: 700 }}>{a[0].toUpperCase()}</button>)}
          </div>
          <div style={{ fontSize: 10.5, fontWeight: 800, letterSpacing: ".07em", textTransform: "uppercase", color: "#7f93b3", margin: "4px 0 6px" }}>Position</div>
          <div style={{ display: "flex", gap: 5, marginBottom: 10 }}>
            {[["toinline", "Inline", false], ["toabs", "Absoluut", true]].map(([msg, lbl, abs]) =>
              <button key={msg} onClick={() => frameRef.current && frameRef.current.contentWindow.postMessage({ ed: msg }, "*")}
                style={{ flex: 1, padding: "6px 0", borderRadius: 6, border: "1px solid #24344f", cursor: "pointer", background: (selInfo.styles.position === "absolute") === abs ? "#2274bb" : "#121e30", color: "#fff", fontSize: 11, fontWeight: 700 }}>{lbl}</button>)}
          </div>
          {[["Width", "width"], ["Height", "height"], ["Padding", "padding"], ["Radius", "borderRadius"]].map(([lbl, prop]) =>
            <label key={prop} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }}>{lbl}
              <span style={{ display: "flex", gap: 5 }}>
                <input type="number" defaultValue={px(selInfo.styles[prop])} key={selInfo.id + prop + selInfo.styles[prop]}
                  onKeyDown={(e) => e.key === "Enter" && e.target.blur()}
                  onBlur={(e) => e.target.value !== "" && sendApply(prop, parseFloat(e.target.value))}
                  style={{ width: 62, background: "#121e30", color: "#fff", border: "1px solid #24344f", borderRadius: 6, padding: "5px 8px" }} />
                {(prop === "width" || prop === "height") &&
                  <button onClick={() => sendApply(prop, "hug")} title="Hug — maat volgt de inhoud"
                    style={{ padding: "0 8px", borderRadius: 6, border: "1px solid #24344f", cursor: "pointer", background: "#121e30", color: "#9fb3cc", fontSize: 11, fontWeight: 700 }}>Hug</button>}
              </span>
            </label>)}
          </div>}

          {/* Game invoegen — wordt echte slide-HTML in de stijl van het deck */}
          <div style={{ borderTop: "1px solid #1e2e4a", marginTop: 14, paddingTop: 14 }}>
            <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: ".08em", textTransform: "uppercase", color: "#7f93b3", marginBottom: 4 }}>⚡ Vraag invoegen</div>
            <p style={{ fontSize: 11.5, color: "#7f93b3", lineHeight: 1.5, margin: "0 0 9px" }}>
              Komt als blok ín de slide, in de kleuren van dit deck — daarna te slepen en te restylen als elk ander element.
            </p>
            <select value={game.type} onChange={(e) => setGame({ ...game, type: e.target.value })}
              style={{ width: "100%", background: "#121e30", color: "#fff", border: "1px solid #24344f", borderRadius: 6, padding: "6px 8px", marginBottom: 7 }}>
              <option value="quiz">Quiz</option><option value="kahoot">Kahoot</option><option value="poll">Poll</option>
            </select>
            <input value={game.q} onChange={(e) => setGame({ ...game, q: e.target.value })} placeholder="De vraag…"
              style={{ width: "100%", background: "#121e30", color: "#fff", border: "1px solid #24344f", borderRadius: 6, padding: "6px 8px", marginBottom: 7 }} />
            <div style={{ display: "flex", alignItems: "center", gap: 6, margin: "2px 0 7px" }}>
              <span style={{ fontSize: 11.5, color: "#7f93b3", flex: "none" }}>Nummering</span>
              {[["abc", "A B C"], ["123", "1 2 3"], ["", "geen"]].map(([v, lbl]) =>
                <button key={v || "geen"} onClick={() => setGame({ ...game, labels: v })}
                  style={{ flex: 1, padding: "5px 0", borderRadius: 6, border: "1px solid #24344f", cursor: "pointer",
                           fontSize: 11, fontWeight: 700, background: game.labels === v ? "#2274bb" : "#121e30", color: "#fff" }}>{lbl}</button>)}
            </div>
            {game.options.map((o, i) =>
              <div key={i} style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 5 }}>
                <input type="radio" name="ged-correct" checked={game.answer === i} onChange={() => setGame({ ...game, answer: i })}
                  title="Markeer als juiste antwoord" style={{ accentColor: "#26890c" }} />
                {game.labels && <span style={{ flex: "none", width: 16, textAlign: "center", fontSize: 11.5, fontWeight: 800, color: "#9db2cc" }}>
                  {game.labels === "123" ? i + 1 : String.fromCharCode(65 + i)}</span>}
                <input value={o} onChange={(e) => { const n = [...game.options]; n[i] = e.target.value; setGame({ ...game, options: n }); }}
                  placeholder={"Optie " + (game.labels === "123" ? i + 1 : String.fromCharCode(65 + i))}
                  style={{ flex: 1, minWidth: 0, background: "#121e30", color: "#fff", border: "1px solid #24344f", borderRadius: 6, padding: "5px 8px" }} />
              </div>)}
            <label style={{ display: "flex", alignItems: "center", justifyContent: "space-between", margin: "7px 0 9px" }}>XP
              <input type="number" value={game.xp} onChange={(e) => setGame({ ...game, xp: +e.target.value || 0 })}
                style={{ width: 70, background: "#121e30", color: "#fff", border: "1px solid #24344f", borderRadius: 6, padding: "5px 8px" }} />
            </label>

            <div style={{ fontSize: 10.5, fontWeight: 800, letterSpacing: ".07em", textTransform: "uppercase", color: "#7f93b3", margin: "2px 0 5px" }}>Uitslag van de opdracht</div>
            <div style={{ display: "flex", gap: 5, marginBottom: 8 }}>
              {[["inline", "Op deze slide (live)"], ["next", "Volgende slide (onthullen)"]].map(([v, lbl]) =>
                <button key={v} onClick={() => setGame({ ...game, result: v })} title={v === "inline"
                    ? "De uitslag verschijnt live ín deze slide terwijl er gestemd wordt"
                    : "De uitslag komt op de slide erna en wordt pas onthuld als je doorklikt"}
                  style={{ flex: 1, padding: "6px 4px", borderRadius: 6, border: "1px solid #24344f", cursor: "pointer",
                           fontSize: 10.5, fontWeight: 700, lineHeight: 1.25,
                           background: game.result === v ? "#2274bb" : "#121e30", color: "#fff" }}>{lbl}</button>)}
            </div>
            <label style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 10, fontSize: 12.5, cursor: "pointer" }}>
              <input type="checkbox" checked={game.qr} onChange={(e) => setGame({ ...game, qr: e.target.checked })} />
              QR-code naar de sessie erbij (zaal stemt mee via telefoon)
            </label>
            <div style={{ display: "flex", gap: 7 }}>
              <EdBtn tone="pri" onClick={insertGame} style={{ flex: 1, justifyContent: "center" }}>
                <Icon name="bolt" size={13} />In de slide zetten
              </EdBtn>
              <EdBtn tone="sec" onClick={insertJoin} style={{ flex: 1, justifyContent: "center" }} title="Een QR-plek voor LearnALot Live toevoegen">
                <Icon name="qr" size={13} />QR-blok invoegen
              </EdBtn>
            </div>
          </div>
        </div>
      </div>

      <style>{"@keyframes edspin{to{transform:rotate(360deg)}}@keyframes edpulse{0%,100%{opacity:.45}50%{opacity:1}}"}</style>
      {!runtimeSrc && traagLaden && <EdBusy titel="Editor klaarzetten"
        stappen={["Bewerkmotor laden…", "Slides inlezen…", "Ontwerpvlak opbouwen…", "Bijna klaar…"]} />}
      {bezig && <EdBusy titel={bezig.titel} stappen={bezig.stappen} />}
      {brandOpen && <div style={{ position: "absolute", inset: 0, background: "rgba(4,10,20,.72)", display: "grid", placeItems: "center", padding: 24, zIndex: 5 }}
        onClick={(e) => e.target === e.currentTarget && setBrandOpen(false)}>
        <div className="scroll" style={{ width: 460, maxWidth: "100%", maxHeight: "88%", overflowY: "auto", background: "#0d1929", border: "1px solid #1e2e4a", borderRadius: 12, padding: 22, color: "#dfe9f5" }}>
          <div style={{ display: "flex", alignItems: "center", gap: 9, marginBottom: 4 }}>
            <Icon name="star" size={16} style={{ color: "#58a8dd" }} />
            <strong style={{ fontFamily: "var(--display)", fontSize: 16 }}>Deck-branding</strong>
          </div>
          <p style={{ fontSize: 12.5, color: "#7f93b3", lineHeight: 1.55, margin: "0 0 16px" }}>
            Eén laag per slide: logo, achtergrond en voettekst. Opnieuw toepassen vervangt de vorige laag,
            dus je kunt blijven bijstellen zonder te stapelen.
          </p>

          {themes.length > 0 &&
            <label style={{ display: "block", marginBottom: 12 }}>
              <span style={{ fontSize: 11, fontWeight: 800, letterSpacing: ".07em", textTransform: "uppercase", color: "#7f93b3" }}>Merk uit Style Tailor</span>
              <select onChange={(e) => { const t = themes[+e.target.value]; if (t) setBrand({ ...R.brandFromTheme(t), logoPos: brand.logoPos, skipFirst: brand.skipFirst }); }}
                style={{ width: "100%", marginTop: 5, background: "#121e30", color: "#fff", border: "1px solid #24344f", borderRadius: 6, padding: "7px 8px" }}>
                {themes.map((t, i) => <option key={i} value={i}>{t.name || t.domain}</option>)}
              </select>
            </label>}

          <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: ".07em", textTransform: "uppercase", color: "#7f93b3", margin: "14px 0 6px" }}>Logo</div>
          <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 9 }}>
            {brand.logo ? <img src={brand.logo} alt="" style={{ height: 34, width: "auto", background: "#fff", borderRadius: 5, padding: 3 }} />
              : <span style={{ fontSize: 12, color: "#7f93b3" }}>Nog geen logo</span>}
            <label style={{ display: "inline-flex", alignItems: "center", gap: 6, padding: "8px 13px", borderRadius: 9,
                              border: "1px solid #37547f", background: "#1c2f4d", color: "#e8f1fb",
                              fontWeight: 700, fontSize: 12.5, cursor: "pointer" }}>
              Uploaden
              <input type="file" accept="image/*" style={{ display: "none" }}
                onChange={(e) => e.target.files[0] && bestandNaarDataUri(e.target.files[0], (u) => setBrand({ ...brand, logo: u }))} />
            </label>
            {brand.logo && <EdBtn tone="quiet" onClick={() => setBrand({ ...brand, logo: null })} style={{ padding: "7px 12px", fontSize: 12 }}>Weg</EdBtn>}
          </div>
          <div style={{ display: "flex", gap: 5, marginBottom: 9 }}>
            {[["tl", "◤"], ["tr", "◥"], ["bl", "◣"], ["br", "◢"]].map(([pos, teken]) =>
              <button key={pos} onClick={() => setBrand({ ...brand, logoPos: pos })}
                style={{ flex: 1, padding: "8px 0", borderRadius: 6, border: "1px solid #24344f", cursor: "pointer", fontSize: 15,
                         background: brand.logoPos === pos ? "#2274bb" : "#121e30", color: "#fff" }}>{teken}</button>)}
          </div>
          <label style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 6 }}>Formaat (% van de breedte)
            <input type="number" min={2} max={30} value={brand.logoSize} onChange={(e) => setBrand({ ...brand, logoSize: +e.target.value || 8 })}
              style={{ width: 70, background: "#121e30", color: "#fff", border: "1px solid #24344f", borderRadius: 6, padding: "5px 8px" }} /></label>
          <label style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 4 }}>Marge (%)
            <input type="number" min={0} max={15} value={brand.margin} onChange={(e) => setBrand({ ...brand, margin: +e.target.value || 0 })}
              style={{ width: 70, background: "#121e30", color: "#fff", border: "1px solid #24344f", borderRadius: 6, padding: "5px 8px" }} /></label>

          <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: ".07em", textTransform: "uppercase", color: "#7f93b3", margin: "16px 0 6px" }}>Achtergrond</div>
          <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 6 }}>
            <input type="color" value={brand.bg.color || "#0d1929"} onChange={(e) => setBrand({ ...brand, bg: { ...brand.bg, color: e.target.value } })}
              style={{ width: 42, height: 28, border: "1px solid #24344f", borderRadius: 6, background: "none", padding: 1 }} />
            <label style={{ display: "inline-flex", alignItems: "center", gap: 6, padding: "8px 13px", borderRadius: 9,
                              border: "1px solid #37547f", background: "#1c2f4d", color: "#e8f1fb",
                              fontWeight: 700, fontSize: 12.5, cursor: "pointer" }}>
              Afbeelding
              <input type="file" accept="image/*" style={{ display: "none" }}
                onChange={(e) => e.target.files[0] && bestandNaarDataUri(e.target.files[0], (u) => setBrand({ ...brand, bg: { ...brand.bg, image: u } }))} />
            </label>
            {(brand.bg.color || brand.bg.image) &&
              <EdBtn tone="quiet" onClick={() => setBrand({ ...brand, bg: { color: null, image: null } })} style={{ padding: "7px 12px", fontSize: 12 }}>Geen</EdBtn>}
          </div>
          <p style={{ fontSize: 11.5, color: "#7f93b3", lineHeight: 1.5, margin: "0 0 4px" }}>
            Een achtergrond zet de eigen vlakken van de slide doorzichtig, zodat hij zichtbaar is. Verwijder je de branding, dan komen die terug.
          </p>

          <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: ".07em", textTransform: "uppercase", color: "#7f93b3", margin: "16px 0 6px" }}>Voettekst</div>
          <input value={brand.footer.text} onChange={(e) => setBrand({ ...brand, footer: { ...brand.footer, text: e.target.value } })}
            placeholder="© Van Haren Publishing · vertrouwelijk"
            style={{ width: "100%", background: "#121e30", color: "#fff", border: "1px solid #24344f", borderRadius: 6, padding: "7px 8px", marginBottom: 8 }} />
          <label style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 6, fontSize: 13 }}>
            <input type="checkbox" checked={brand.footer.number} onChange={(e) => setBrand({ ...brand, footer: { ...brand.footer, number: e.target.checked } })} />
            Slidenummer tonen (3 / {slides.length})
          </label>
          <label style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 16, fontSize: 13 }}>
            <input type="checkbox" checked={brand.skipFirst} onChange={(e) => setBrand({ ...brand, skipFirst: e.target.checked })} />
            Titelslide overslaan
          </label>

          <div style={{ display: "flex", gap: 8 }}>
            <EdBtn tone="pri" onClick={() => { applyBrandAll(brand); setBrandOpen(false); }}
              style={{ flex: 1, justifyContent: "center" }}><Icon name="check" size={13} />Op alle slides</EdBtn>
            <EdBtn tone="sec" onClick={() => { applyBrandAll(null); setBrandOpen(false); }}>Verwijderen</EdBtn>
            <EdBtn tone="quiet" onClick={() => setBrandOpen(false)}>Annuleren</EdBtn>
          </div>
        </div>
      </div>}
    </div>, document.body);
}
window.SlideEditor = SlideEditor;
