/*! Sentrize v3.0.0 · content/fullpage-shot.js · sentrize.com
 * ADDED (not part of the original CRX): full-page device screenshot.
 *
 * The shipped extension can shoot the visible viewport ("device") or the whole
 * browser window ("full"), but not the device's entire scrollable page. This
 * module adds that: it scrolls the same-origin device iframe top to bottom,
 * asks the background worker to captureVisibleTab at each step, crops the
 * on-screen device screen rect out of each capture, and stitches the slices
 * into one tall PNG.
 *
 * Self-contained on purpose: it locates the iframe and screen rect from the
 * Shadow DOM host rather than from classic.js's minified closure, and binds
 * its own Shift+5 shortcut, so it needs no edits inside classic.js.
 *
 * PROTOTYPE — logic is structurally complete but has NOT been pixel-verified in
 * a live browser. Load unpacked, open a long page, click the extension, then
 * press Shift+5 (or call self.MVC.captureFullPage()). Needs a matching
 * "capViewport" handler in background.js (added alongside this file).
 */
((self.MVC = self.MVC || {}),
  (function () {
    const HOST_ID = "__sentrize_ext_host__";
    const MAX_OUT_PX = 30000; // guard against browser canvas height limits

    const raf = () => new Promise((r) => requestAnimationFrame(() => r()));
    const raf2 = async () => {
      await raf();
      await raf();
    };
    const delay = (ms) => new Promise((r) => setTimeout(r, ms));

    function msg(payload) {
      return new Promise((resolve) => {
        try {
          chrome.runtime.sendMessage(payload, (res) => {
            if (chrome.runtime.lastError) return resolve({ ok: false, error: chrome.runtime.lastError.message });
            resolve(res || { ok: false, error: "no response" });
          });
        } catch (e) {
          resolve({ ok: false, error: String((e && e.message) || e) });
        }
      });
    }

    function blobToDataURL(blob) {
      return new Promise((resolve, reject) => {
        const fr = new FileReader();
        fr.onload = () => resolve(fr.result);
        fr.onerror = () => reject(fr.error);
        fr.readAsDataURL(blob);
      });
    }

    // captureVisibleTab is rate-limited; retry a couple of times on failure.
    async function captureViewport() {
      for (let attempt = 0; attempt < 4; attempt++) {
        const res = await msg({ type: "capViewport" });
        if (res && res.ok && res.dataUrl) return res;
        await delay(550 * (attempt + 1));
      }
      return { ok: false, error: "capture rate-limited" };
    }

    function findTargets(opts) {
      const host = document.getElementById(HOST_ID);
      const root = host && host.shadowRoot;
      if (!root) return null;
      const iframe = (opts && opts.iframe) || root.querySelector(".frame");
      const screenEl = (opts && opts.screenEl) || root.querySelector(".screen");
      if (!iframe || !screenEl) return null;
      return { root, iframe, screenEl };
    }

    // Hide position:fixed / sticky elements so headers/footers do not repeat in
    // every slice. Returns a restore() closure. Uses visibility (keeps layout).
    function suppressSticky(doc) {
      const touched = [];
      let nodes = [];
      try {
        nodes = doc.body ? Array.prototype.slice.call(doc.body.querySelectorAll("*")) : [];
      } catch (e) {
        return () => {};
      }
      for (const el of nodes) {
        let pos;
        try {
          pos = doc.defaultView.getComputedStyle(el).position;
        } catch (e) {
          continue;
        }
        if (pos === "fixed" || pos === "sticky") {
          touched.push([el, el.style.visibility]);
          try {
            el.style.visibility = "hidden";
          } catch (e) {}
        }
      }
      return function restore() {
        for (const [el, v] of touched) {
          try {
            el.style.visibility = v;
          } catch (e) {}
        }
      };
    }

    function makeOverlay(root) {
      const o = document.createElement("div");
      o.setAttribute("aria-hidden", "true");
      o.style.cssText = [
        "position:fixed",
        "inset:0",
        "z-index:2147483647",
        "display:flex",
        "align-items:center",
        "justify-content:center",
        "background:rgba(12,14,20,.55)",
        "font:600 15px -apple-system,system-ui,Segoe UI,Roboto,sans-serif",
        "color:#fff",
        "pointer-events:auto",
      ].join(";");
      const label = document.createElement("div");
      label.style.cssText =
        "padding:14px 20px;border-radius:12px;background:rgba(20,24,33,.92);box-shadow:0 8px 30px rgba(0,0,0,.4)";
      label.textContent = "Capturing… 0%";
      o.appendChild(label);
      (root || document.documentElement).appendChild(o);
      if (typeof o.showPopover === "function") {
        try {
          o.setAttribute("popover", "manual");
          o.showPopover();
        } catch (e) {}
      }
      return {
        set(p) {
          label.textContent = "Capturing… " + Math.round(p * 100) + "%";
        },
        done() {
          try {
            o.remove();
          } catch (e) {}
        },
      };
    }

    let busy = false;

    self.MVC.captureFullPage = async function (opts) {
      opts = opts || {};
      if (busy) return { ok: false, error: "busy" };
      const t = findTargets(opts);
      if (!t) return { ok: false, error: "no device frame found" };
      const { root, iframe, screenEl } = t;

      let win, doc, docEl;
      try {
        win = iframe.contentWindow;
        doc = iframe.contentDocument;
        void win.location.href; // throws if the frame navigated cross-origin
        docEl = doc.scrollingElement || doc.documentElement;
        if (!docEl) throw new Error("no document");
      } catch (e) {
        return { ok: false, error: "cross-origin frame — full page unavailable", crossOrigin: true };
      }

      busy = true;
      const overlay = opts.overlay === false ? null : makeOverlay(root);
      const onProgress = opts.onProgress || (overlay ? (p) => overlay.set(p) : function () {});

      const dpr = window.devicePixelRatio || 1;
      const viewW = win.innerWidth;
      const viewH = win.innerHeight;
      const origScroll = win.scrollY || docEl.scrollTop || 0;

      let totalH = Math.max(docEl.scrollHeight, docEl.offsetHeight || 0, viewH);
      // clamp to a safe canvas height (in device px)
      const maxDeviceH = Math.floor(MAX_OUT_PX / dpr);
      let truncated = false;
      if (totalH > maxDeviceH) {
        totalH = maxDeviceH;
        truncated = true;
      }

      const rect = screenEl.getBoundingClientRect();
      let radius = 0;
      try {
        radius = parseFloat(getComputedStyle(screenEl).borderTopLeftRadius) || 0;
      } catch (e) {}

      const outW = Math.max(1, Math.round(viewW * dpr));
      const outH = Math.max(1, Math.round(totalH * dpr));
      const bandH = Math.round(viewH * dpr);

      const canvas = document.createElement("canvas");
      canvas.width = outW;
      canvas.height = outH;
      const ctx = canvas.getContext("2d");
      ctx.fillStyle = "#ffffff";
      ctx.fillRect(0, 0, outW, outH);

      const steps = Math.max(1, Math.ceil(totalH / viewH));
      let restoreSticky = () => {};
      let result;

      try {
        for (let i = 0; i < steps; i++) {
          if (i === 1) restoreSticky = suppressSticky(doc); // keep slice 0 intact
          let y = i * viewH;
          if (y + viewH > totalH) y = Math.max(0, totalH - viewH);
          win.scrollTo(0, y);
          await raf2();
          await delay(opts.settle || 130);

          const shot = await captureViewport();
          if (!shot.ok) {
            result = { ok: false, error: shot.error };
            break;
          }
          const bmp = await createImageBitmap(await (await fetch(shot.dataUrl)).blob());

          // source: the on-screen device screen rect, in physical px
          const sx = Math.max(0, Math.round(rect.left * dpr));
          const sy = Math.max(0, Math.round(rect.top * dpr));
          const sw = Math.max(1, Math.round(rect.width * dpr));
          const sh = Math.max(1, Math.round(rect.height * dpr));
          const destY = Math.round(y * dpr);
          ctx.drawImage(bmp, sx, sy, sw, sh, 0, destY, outW, bandH);
          if (bmp.close) bmp.close();

          onProgress((i + 1) / steps);
        }
        if (!result) {
          if (radius > 0) {
            const r = Math.min(radius * dpr, outW / 2);
            // knock out the top corners so it matches the rounded screen
            ctx.globalCompositeOperation = "destination-in";
            ctx.beginPath();
            ctx.moveTo(0, r);
            ctx.arcTo(0, 0, r, 0, r);
            ctx.lineTo(outW - r, 0);
            ctx.arcTo(outW, 0, outW, r, r);
            ctx.lineTo(outW, outH);
            ctx.lineTo(0, outH);
            ctx.closePath();
            ctx.fill();
            ctx.globalCompositeOperation = "source-over";
          }
          const blob = await new Promise((res) => canvas.toBlob(res, "image/png"));
          const dataUrl = await blobToDataURL(blob);
          result = { ok: true, dataUrl, width: outW, height: outH, truncated };
        }
      } catch (e) {
        result = { ok: false, error: String((e && e.message) || e) };
      } finally {
        restoreSticky();
        try {
          win.scrollTo(0, origScroll);
        } catch (e) {}
        if (overlay) overlay.done();
        busy = false;
      }

      // auto-download on success unless the caller opts out
      if (result.ok && opts.download !== false) {
        try {
          const a = document.createElement("a");
          a.href = result.dataUrl;
          a.download = opts.filename || "mobile-fullpage.png";
          (root || document.documentElement).appendChild(a);
          a.click();
          a.remove();
        } catch (e) {}
      }
      return result;
    };

    // Self-contained trigger: Shift+5, only while the overlay host is present.
    if (!self.MVC._fullShotKeyBound) {
      self.MVC._fullShotKeyBound = true;
      window.addEventListener(
        "keydown",
        (e) => {
          if (e.defaultPrevented || e.repeat) return;
          if (!e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) return;
          if (e.code !== "Digit5" && e.key !== "5" && e.key !== "%") return;
          if (!document.getElementById(HOST_ID)) return;
          e.preventDefault();
          if (typeof self.MVC.captureFullPage === "function") self.MVC.captureFullPage();
        },
        true,
      );
    }
  })());
