/**
 * render-worker.js — Web Worker for off-main-thread PDF rendering.
 *
 * Has its own PDF.js instance. Renders pages via OffscreenCanvas and transfers
 * ImageBitmap objects back to the main thread with zero-copy transfer.
 *
 * Message protocol:
 *
 * Main → Worker:
 *   { type: "init",    pdfData: ArrayBuffer }  (transferred, not copied)
 *   { type: "render",  pageNum, scale, gen }
 *   { type: "cancel",  gen }          — renderGen check handles this implicitly
 *   { type: "cleanup" }               — pdfDoc.cleanup() (free caches)
 *   { type: "destroy" }               — pdfDoc.destroy(); self.close()
 *
 * Worker → Main:
 *   { type: "ready",    numPages }
 *   { type: "rendered", pageNum, gen, bitmap }   (bitmap as transferable)
 *   { type: "error",    message }
 */

importScripts("brittle://app/pdfjs/build/pdf.min.js");

const pdfjsLib = globalThis.pdfjsLib;
// Do NOT set workerSrc to a brittle:// URL here.  When PDF.js tries to spawn its
// own sub-worker with new Worker("brittle://…") and that fails, it falls back to
// a "fake worker" that injects a <script> tag — which throws because `document`
// does not exist inside a Web Worker.
//
// Instead, we fetch pdf.worker.min.js in handleInit() and hand PDF.js a blob URL
// it can actually use with new Worker(blobUrl).  Blob URLs created inside a worker
// are same-origin and can be used for nested workers.

let pdfDoc = null;

self.onmessage = async function (ev) {
  const msg = ev.data;
  switch (msg.type) {
    case "init":    await handleInit(msg);   break;
    case "render":  await handleRender(msg); break;
    case "cleanup":
      if (pdfDoc) await pdfDoc.cleanup();
      break;
    case "destroy":
      if (pdfDoc) { await pdfDoc.destroy(); pdfDoc = null; }
      self.close();
      break;
  }
};

async function handleInit({ pdfData }) {
  try {
    // Fetch pdf.worker.min.js and create a blob URL so PDF.js can spawn its
    // own sub-worker without relying on brittle:// for new Worker().
    const resp = await fetch("brittle://app/pdfjs/build/pdf.worker.min.js");
    const text = await resp.text();
    const blob = new Blob([text], { type: "application/javascript" });
    pdfjsLib.GlobalWorkerOptions.workerSrc = URL.createObjectURL(blob);

    pdfDoc = await pdfjsLib.getDocument({ data: new Uint8Array(pdfData) }).promise;
    self.postMessage({ type: "ready", numPages: pdfDoc.numPages });
  } catch (e) {
    self.postMessage({ type: "error", message: e.message });
  }
}

async function handleRender({ pageNum, scale, gen }) {
  if (!pdfDoc) return;
  let page = null;
  try {
    page = await pdfDoc.getPage(pageNum);
    const vp     = page.getViewport({ scale });
    const width  = Math.round(vp.width);
    const height = Math.round(vp.height);
    const canvas = new OffscreenCanvas(width, height);
    const ctx    = canvas.getContext("2d");
    await page.render({ canvasContext: ctx, viewport: vp }).promise;
    const bitmap = canvas.transferToImageBitmap();
    self.postMessage({ type: "rendered", pageNum, gen, bitmap }, [bitmap]);
  } catch (e) {
    if (e?.name !== "RenderingCancelledException") {
      console.warn("[render-worker] render error page", pageNum, e);
    }
  } finally {
    if (page) page.cleanup();
  }
}
