/* Beeldvullende preview voor pdf's en krantpagina's, met zoom. Openen kan van overal met:
   window.bbOpenPreview({ src, type: "pdf" | "afbeelding", titel }) */

const BB_ZOOM_STAPPEN = [1, 1.25, 1.5, 2, 2.5, 3, 4, 5];

function bbIsLokaalBestand(href) {
  return !!href && !/^https?:/i.test(href) && /\.(pdf|jpe?g|png|webp)$/i.test(href);
}
function bbPreviewType(href) {
  return /\.pdf$/i.test(href) ? "pdf" : "afbeelding";
}
function bbOpenPreview(detail) {
  window.dispatchEvent(new CustomEvent("bb-preview", { detail }));
}
// Handler voor een link die naar een lokale pdf of afbeelding wijst.
function bbPreviewKlik(href, titel, beeld) {
  return (e) => {
    if (!bbIsLokaalBestand(href)) return;
    e.preventDefault();
    e.stopPropagation();
    bbOpenPreview({ src: href, type: bbPreviewType(href), titel: titel, beeld: beeld });
  };
}
// pdf.js rendert de pdf zelf op canvas, zodat inzoomen scherp blijft.
const BB_PDFJS = "https://unpkg.com/pdfjs-dist@3.11.174/legacy/build/pdf.min.js";
const BB_PDFJS_WORKER = "https://unpkg.com/pdfjs-dist@3.11.174/legacy/build/pdf.worker.min.js";
let bbPdfjsBelofte = null;
function bbLaadPdfjs() {
  if (bbPdfjsBelofte) return bbPdfjsBelofte;
  bbPdfjsBelofte = new Promise((klaar, mis) => {
    const gereed = () => {
      const lib = window.pdfjsLib;
      if (!lib) { mis(new Error("pdfjs ontbreekt")); return; }
      try {
        // Een worker van een ander domein mag niet direct, via een blob wel.
        const blob = new Blob(["importScripts(" + JSON.stringify(BB_PDFJS_WORKER) + ");"], { type: "text/javascript" });
        lib.GlobalWorkerOptions.workerSrc = URL.createObjectURL(blob);
      } catch (e) {
        lib.GlobalWorkerOptions.workerSrc = BB_PDFJS_WORKER;
      }
      klaar(lib);
    };
    if (window.pdfjsLib) { gereed(); return; }
    const s = document.createElement("script");
    s.src = BB_PDFJS;
    s.crossOrigin = "anonymous";
    s.onload = gereed;
    s.onerror = () => mis(new Error("pdfjs kon niet laden"));
    document.head.appendChild(s);
  });
  return bbPdfjsBelofte;
}

function PdfPagina({ doc, nr, zoom, aantal }) {
  const holderRef = useRef(null);
  const canvasRef = useRef(null);
  useEffect(() => {
    let leeft = true;
    let taak = null;
    const id = requestAnimationFrame(() => {
      const holder = holderRef.current, canvas = canvasRef.current;
      if (!holder || !canvas) return;
      doc.getPage(nr).then((page) => {
        if (!leeft) return;
        const basis = page.getViewport({ scale: 1 });
        const vak = (holder.closest(".bb-prev__body") || holder).getBoundingClientRect();
        const breed = vak.width || 800;
        const hoog = aantal > 1 ? Infinity : (vak.height || 1000);
        const fit = Math.min(breed / basis.width, hoog / basis.height) * (zoom || 1);
        const cssB = Math.max(160, basis.width * fit);
        const cssH = cssB * (basis.height / basis.width);
        const dpr = Math.min(Math.max(window.devicePixelRatio || 1, 2), 3);
        const pix = Math.min(cssB * dpr, 6000);
        const vp = page.getViewport({ scale: pix / basis.width });
        canvas.style.width = Math.round(cssB) + "px";
        canvas.style.height = Math.round(cssH) + "px";
        canvas.width = Math.round(vp.width);
        canvas.height = Math.round(vp.height);
        taak = page.render({ canvasContext: canvas.getContext("2d"), viewport: vp });
        taak.promise.catch(() => {});
      }).catch(() => {});
    });
    return () => {
      leeft = false;
      cancelAnimationFrame(id);
      if (taak) { try { taak.cancel(); } catch (e) {} }
    };
  }, [doc, nr, zoom, aantal]);
  return (
    <div className="bb-prev__pdfpagina" ref={holderRef}>
      <canvas ref={canvasRef} role="img" aria-label={"Pagina " + nr}></canvas>
    </div>
  );
}

function PdfWeergave({ src, titel, zoom, beeld, alt }) {
  const [doc, setDoc] = useState(null);
  const [fout, setFout] = useState(false);
  useEffect(() => {
    let leeft = true;
    setDoc(null); setFout(false);
    bbLaadPdfjs()
      .then((lib) => lib.getDocument({ url: src }).promise)
      .then((d) => { if (leeft) setDoc(d); })
      .catch(() => { if (leeft) setFout(true); });
    return () => { leeft = false; };
  }, [src]);

  if (fout) {
    return (
      <object className="bb-prev__pdf" data={src} type="application/pdf" aria-label={titel}>
        {beeld
          ? <img src={beeld} alt={alt || titel} />
          : (
            <div className="bb-prev__terug">
              <p>Je browser kan dit document niet in de pagina tonen.</p>
              <a className="bb-prev__link" href={src} target="_blank" rel="noopener noreferrer">Openen in nieuw tabblad</a>
            </div>
          )}
      </object>
    );
  }
  if (!doc) return <div className="bb-prev__laden" role="status">Document wordt geladen…</div>;
  const paginas = [];
  for (let i = 1; i <= doc.numPages; i++) paginas.push(i);
  return (
    <div className={"bb-prev__pdfdoc" + (doc.numPages > 1 ? " bb-prev__pdfdoc--multi" : "")}>
      {paginas.map((nr) => (
        <PdfPagina key={nr} doc={doc} nr={nr} zoom={zoom} aantal={doc.numPages} />
      ))}
    </div>
  );
}

function bbZoomBeperk(z) {
  return Math.min(5, Math.max(1, Math.round(z * 100) / 100));
}
function bbZoomStap(huidig, richting) {
  if (richting > 0) {
    const op = BB_ZOOM_STAPPEN.find((z) => z > huidig + 0.001);
    return op === undefined ? 5 : op;
  }
  const neer = BB_ZOOM_STAPPEN.slice().reverse().find((z) => z < huidig - 0.001);
  return neer === undefined ? 1 : neer;
}

function PreviewLayer() {
  const [item, setItem] = useState(null);
  const [zoom, setZoom] = useState(1);
  const paneelRef = useRef(null);
  const bodyRef = useRef(null);
  const vorigeFocus = useRef(null);
  const vorigeZoom = useRef(1);
  const zoomRef = useRef(1);

  const zet = (z) => setZoom(bbZoomBeperk(z));
  const stap = (richting) => setZoom((z) => bbZoomBeperk(bbZoomStap(z, richting)));

  useEffect(() => {
    const on = (e) => { setZoom(1); vorigeZoom.current = 1; zoomRef.current = 1; setItem(e.detail || null); };
    window.addEventListener("bb-preview", on);
    return () => window.removeEventListener("bb-preview", on);
  }, []);

  // Openen: scroll vastzetten, focus in de dialog, Escape en Tab afhandelen.
  useEffect(() => {
    if (!item) return;
    vorigeFocus.current = document.activeElement;
    const vorigeOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    const sluitknop = paneelRef.current && paneelRef.current.querySelector(".bb-prev__sluit");
    if (sluitknop) sluitknop.focus();
    const onKey = (e) => {
      if (e.key === "Escape") { e.preventDefault(); setItem(null); return; }
      if (e.key === "+" || e.key === "=") { e.preventDefault(); stap(1); return; }
      if (e.key === "-" || e.key === "_") { e.preventDefault(); stap(-1); return; }
      if (e.key !== "Tab" || !paneelRef.current) return;
      const f = paneelRef.current.querySelectorAll('a[href], button:not([disabled])');
      if (!f.length) return;
      const eerst = f[0], laatst = f[f.length - 1];
      if (!paneelRef.current.contains(document.activeElement)) { e.preventDefault(); eerst.focus(); return; }
      if (e.shiftKey && document.activeElement === eerst) { e.preventDefault(); laatst.focus(); }
      else if (!e.shiftKey && document.activeElement === laatst) { e.preventDefault(); eerst.focus(); }
    };
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("keydown", onKey);
      document.body.style.overflow = vorigeOverflow;
      if (vorigeFocus.current && vorigeFocus.current.focus) vorigeFocus.current.focus();
    };
  }, [item]);

  // Knijpen met twee vingers, plus ctrl of cmd met het muiswiel.
  useEffect(() => {
    const el = bodyRef.current;
    if (!el || !item) return;
    let startAfstand = 0, startZoom = 1;
    const afstand = (t) => Math.hypot(t[0].clientX - t[1].clientX, t[0].clientY - t[1].clientY);
    const onStart = (e) => {
      if (e.touches.length !== 2) return;
      startAfstand = afstand(e.touches);
      startZoom = zoomRef.current;
    };
    const onMove = (e) => {
      if (e.touches.length !== 2 || !startAfstand) return;
      e.preventDefault();
      zet(startZoom * (afstand(e.touches) / startAfstand));
    };
    const onEind = () => { startAfstand = 0; };
    const onWiel = (e) => {
      // Het muiswiel zoomt altijd in en uit, ook zonder ctrl of cmd. Bij documenten
      // van meerdere pagina's blijft schuiven mogelijk zodra er is ingezoomd, door
      // te slepen of met de schuifbalk.
      e.preventDefault();
      zet(zoomRef.current * (e.deltaY < 0 ? 1.12 : 1 / 1.12));
    };
    el.addEventListener("touchstart", onStart, { passive: true });
    el.addEventListener("touchmove", onMove, { passive: false });
    el.addEventListener("touchend", onEind, { passive: true });
    el.addEventListener("wheel", onWiel, { passive: false });
    return () => {
      el.removeEventListener("touchstart", onStart);
      el.removeEventListener("touchmove", onMove);
      el.removeEventListener("touchend", onEind);
      el.removeEventListener("wheel", onWiel);
    };
  }, [item]);

  // Slepen om te schuiven zodra er is ingezoomd.
  useEffect(() => {
    const el = bodyRef.current;
    if (!el || !item) return;
    let sleept = false, x0 = 0, y0 = 0, l0 = 0, t0 = 0;
    const neer = (e) => {
      if (e.pointerType === "touch" || e.button !== 0 || zoomRef.current <= 1) return;
      // Zonder dit begint de browser zijn eigen sleepactie op de afbeelding en
      // stopt het schuiven meteen weer.
      e.preventDefault();
      sleept = true; x0 = e.clientX; y0 = e.clientY; l0 = el.scrollLeft; t0 = el.scrollTop;
      el.classList.add("is-slepend");
      el.setPointerCapture(e.pointerId);
    };
    const beweeg = (e) => {
      if (!sleept) return;
      el.scrollLeft = l0 - (e.clientX - x0);
      el.scrollTop = t0 - (e.clientY - y0);
    };
    const op = () => { sleept = false; el.classList.remove("is-slepend"); };
    const opSleep = (e) => e.preventDefault();
    el.addEventListener("pointerdown", neer);
    el.addEventListener("pointermove", beweeg);
    el.addEventListener("pointerup", op);
    el.addEventListener("pointercancel", op);
    el.addEventListener("dragstart", opSleep);
    return () => {
      el.removeEventListener("pointerdown", neer);
      el.removeEventListener("pointermove", beweeg);
      el.removeEventListener("pointerup", op);
      el.removeEventListener("pointercancel", op);
      el.removeEventListener("dragstart", opSleep);
    };
  }, [item]);

  // Houd het midden van het beeld op zijn plek bij zoomen.
  useEffect(() => {
    zoomRef.current = zoom;
    const el = bodyRef.current;
    if (!el) return;
    const f = zoom / (vorigeZoom.current || 1);
    if (f !== 1) {
      el.scrollLeft = (el.scrollLeft + el.clientWidth / 2) * f - el.clientWidth / 2;
      el.scrollTop = (el.scrollTop + el.clientHeight / 2) * f - el.clientHeight / 2;
    }
    vorigeZoom.current = zoom;
  }, [zoom]);

  if (!item) return null;
  const titel = item.titel || (item.type === "pdf" ? "Documentvoorbeeld" : "Afbeeldingsvoorbeeld");
  const pct = Math.round(zoom * 100);
  const maat = item.type === "pdf" ? undefined : { width: pct + "%", height: pct + "%" };

  return (
    <div className="bb-prev" onMouseDown={(e) => { if (e.target === e.currentTarget) setItem(null); }}>
      <div className="bb-prev__paneel" role="dialog" aria-modal="true" aria-label={titel} ref={paneelRef}>
        <div className="bb-prev__kop">
          <p className="bb-prev__titel">{titel}</p>
          <div className="bb-prev__acts">
            <div className="bb-prev__zoombar">
              <button
                type="button" className="bb-prev__zoomknop" onClick={() => stap(-1)}
                disabled={zoom <= 1} aria-label="Uitzoomen"
              >&minus;</button>
              <button
                type="button" className="bb-prev__zoomwaarde" onClick={() => zet(1)}
                aria-label={"Zoom " + pct + " procent, terug naar volledige weergave"}
              >{pct}%</button>
              <button
                type="button" className="bb-prev__zoomknop" onClick={() => stap(1)}
                disabled={zoom >= 5} aria-label="Inzoomen"
              >+</button>
            </div>
            <a className="bb-prev__link" href={item.src} target="_blank" rel="noopener noreferrer">
              Openen in nieuw tabblad
            </a>
            <button type="button" className="bb-prev__sluit" onClick={() => setItem(null)} aria-label="Voorbeeld sluiten">
              <Icon name="close" size={20} />
            </button>
          </div>
        </div>
        <div className={"bb-prev__body" + (zoom > 1 ? " bb-prev__body--zoom" : "")} ref={bodyRef}>
          <div className="bb-prev__zoomvlak" style={maat}>
            {item.type === "pdf" ? (
              <PdfWeergave src={item.src} titel={titel} zoom={zoom} beeld={item.beeld} alt={item.alt} />
            ) : (
              <img
                src={item.src} alt={item.alt || titel} draggable="false"
                onDoubleClick={() => zet(zoom > 1 ? 1 : 2.5)}
              />
            )}
          </div>
        </div>
        <p className="bb-prev__hulp">Zoom in en uit met het muiswiel, met + en &minus;, of met twee vingers. Sleep met de muis om te schuiven.</p>
      </div>
    </div>
  );
}

Object.assign(window, { PreviewLayer, PdfWeergave, bbOpenPreview, bbPreviewKlik, bbIsLokaalBestand, bbPreviewType });
