(function () {
  "use strict";

  const EPUB_MIME = "application/epub+zip";
  const MAX_ARCHIVE_ENTRIES = 25000;
  const MAX_EXPANDED_BYTES = 1.5 * 1024 * 1024 * 1024;
  const DANGEROUS_SCHEME = /^\s*(?:javascript|vbscript|file|filesystem|ftp):/i;
  const REMOTE_SCHEME = /^\s*https?:/i;
  const SAFE_IMAGE_DATA = /^data:image\/(?:png|gif|jpe?g|webp|avif);base64,/i;

  class EpubValidationError extends Error {
    constructor(code, message, details) {
      super(message);
      this.name = "EpubValidationError";
      this.code = code;
      this.details = details || "";
    }
  }

  function parseXml(text, label) {
    const document = new DOMParser().parseFromString(text, "application/xml");
    const parserError = document.querySelector("parsererror");
    if (parserError) {
      throw new EpubValidationError("invalid-epub", `The ${label} document is not valid XML.`, parserError.textContent);
    }
    return document;
  }

  function firstAttribute(document, selector, attribute) {
    return document.querySelector(selector)?.getAttribute(attribute)?.trim() || "";
  }

  function hasZipSignature(bytes) {
    if (!bytes || bytes.length < 4) return false;
    return (
      bytes[0] === 0x50 &&
      bytes[1] === 0x4b &&
      ((bytes[2] === 0x03 && bytes[3] === 0x04) ||
        (bytes[2] === 0x05 && bytes[3] === 0x06) ||
        (bytes[2] === 0x07 && bytes[3] === 0x08))
    );
  }

  async function validateEpub(blob) {
    if (!(blob instanceof Blob) || blob.size < 30) {
      throw new EpubValidationError("invalid-epub", "This file is not a valid EPUB.");
    }

    const signature = new Uint8Array(await blob.slice(0, 4).arrayBuffer());
    if (!hasZipSignature(signature)) {
      throw new EpubValidationError("invalid-epub", "This file is not a valid EPUB.");
    }

    let archive;
    try {
      archive = await globalThis.JSZip.loadAsync(await blob.arrayBuffer(), {
        checkCRC32: false,
        createFolders: false
      });
    } catch (error) {
      throw new EpubValidationError("corrupted-epub", "This EPUB is corrupted and cannot be opened.", String(error));
    }

    const entries = Object.values(archive.files);
    if (entries.length > MAX_ARCHIVE_ENTRIES) {
      throw new EpubValidationError("unsafe-archive", "This EPUB contains too many files to open safely.");
    }

    let expandedBytes = 0;
    for (const entry of entries) {
      const knownSize = Number(entry?._data?.uncompressedSize || 0);
      expandedBytes += Number.isFinite(knownSize) ? knownSize : 0;
      if (expandedBytes > MAX_EXPANDED_BYTES) {
        throw new EpubValidationError("unsafe-archive", "This EPUB is too large to open safely.");
      }
    }

    const mimetypeEntry = archive.file("mimetype");
    const containerEntry = archive.file("META-INF/container.xml");
    if (!mimetypeEntry || !containerEntry) {
      throw new EpubValidationError("invalid-epub", "This file is not a valid EPUB.");
    }

    const mimetype = (await mimetypeEntry.async("string")).replace(/^\uFEFF/, "").trim();
    if (mimetype !== EPUB_MIME) {
      throw new EpubValidationError("unsupported-format", "Senliz Library supports EPUB files only.");
    }

    const containerText = await containerEntry.async("string");
    const containerDocument = parseXml(containerText, "EPUB container");
    const rootfilePath =
      firstAttribute(containerDocument, "rootfile[full-path]", "full-path") ||
      firstAttribute(containerDocument, "*|rootfile[full-path]", "full-path");
    if (!rootfilePath || !archive.file(rootfilePath)) {
      throw new EpubValidationError("invalid-epub", "This EPUB does not contain a readable package document.");
    }

    const packageText = await archive.file(rootfilePath).async("string");
    const packageDocument = parseXml(packageText, "EPUB package");
    const manifestItems = [...packageDocument.getElementsByTagNameNS("*", "item")];
    const spineItems = [...packageDocument.getElementsByTagNameNS("*", "itemref")];
    const manifestIds = new Set(manifestItems.map((item) => item.getAttribute("id")).filter(Boolean));

    if (!manifestItems.length || !spineItems.length) {
      throw new EpubValidationError("empty-spine", "This EPUB does not contain readable chapters.");
    }

    const missingSpineItems = spineItems.filter((item) => !manifestIds.has(item.getAttribute("idref")));
    if (missingSpineItems.length === spineItems.length) {
      throw new EpubValidationError("empty-spine", "This EPUB does not contain readable chapters.");
    }

    return {
      archive,
      rootfilePath,
      packageDocument,
      entryCount: entries.length,
      expandedBytes
    };
  }

  function scrubCss(value) {
    return String(value || "")
      .replace(/@import\s+(?:url\()?[^;]+;?/gi, "")
      .replace(/url\(\s*(['"]?)\s*https?:[^)]+\1\s*\)/gi, "none")
      .replace(/url\(\s*(['"]?)\s*(?:javascript|vbscript|file):[^)]+\1\s*\)/gi, "none")
      .replace(/expression\s*\([^)]*\)/gi, "")
      .replace(/(?:behavior|-moz-binding)\s*:[^;]+;?/gi, "");
  }

  function isSafeEmbeddedUrl(value) {
    const url = String(value || "").trim();
    if (!url) return true;
    if (url.startsWith("#") || url.startsWith("/") || url.startsWith("./") || url.startsWith("../")) return true;
    if (/^(?:blob|senliz-file):/i.test(url)) return true;
    if (/^data:/i.test(url)) return SAFE_IMAGE_DATA.test(url);
    return !/^[a-z][a-z0-9+.-]*:/i.test(url);
  }

  function sanitizeDocument(document) {
    if (!document?.documentElement) return;

    if (globalThis.DOMPurify?.isSupported) {
      try {
        globalThis.DOMPurify.sanitize(document.documentElement, {
          IN_PLACE: true,
          WHOLE_DOCUMENT: true,
          USE_PROFILES: { html: true, svg: true, svgFilters: false, mathMl: true },
          ADD_TAGS: ["link"],
          ADD_ATTR: ["epub:type", "xmlns:epub", "role", "dir", "lang", "media"],
          FORBID_TAGS: [
            "script",
            "iframe",
            "object",
            "embed",
            "form",
            "input",
            "button",
            "textarea",
            "select",
            "option",
            "base",
            "portal"
          ],
          FORBID_ATTR: ["srcdoc", "formaction", "ping", "nonce"],
          ALLOW_UNKNOWN_PROTOCOLS: false,
          SANITIZE_DOM: true
        });
      } catch (error) {
        console.error("EPUB sanitisation failed.", error);
        document.querySelectorAll("script,iframe,object,embed,form,input,button,textarea,select,option,base,portal").forEach((node) => node.remove());
      }
    }

    document.querySelectorAll("script,iframe,object,embed,form,input,button,textarea,select,option,base,portal").forEach((node) => node.remove());

    for (const element of document.querySelectorAll("*")) {
      for (const attribute of [...element.attributes]) {
        const name = attribute.name.toLowerCase();
        const value = attribute.value || "";
        if (name.startsWith("on") || name === "srcdoc" || name === "formaction" || name === "ping") {
          element.removeAttribute(attribute.name);
          continue;
        }

        if (name === "style") {
          const cleaned = scrubCss(value);
          if (cleaned) element.setAttribute(attribute.name, cleaned);
          else element.removeAttribute(attribute.name);
          continue;
        }

        if (["src", "poster", "xlink:href"].includes(name)) {
          if (DANGEROUS_SCHEME.test(value) || REMOTE_SCHEME.test(value) || !isSafeEmbeddedUrl(value)) {
            element.removeAttribute(attribute.name);
          }
          continue;
        }

        if (name === "href") {
          if (DANGEROUS_SCHEME.test(value)) {
            element.removeAttribute(attribute.name);
          } else if (element.tagName.toLowerCase() === "link" && (REMOTE_SCHEME.test(value) || !isSafeEmbeddedUrl(value))) {
            element.remove();
          }
        }
      }
    }

    document.querySelectorAll("style").forEach((style) => {
      style.textContent = scrubCss(style.textContent);
    });

    const head = document.head || document.documentElement.insertBefore(document.createElement("head"), document.body || null);
    let policy = head.querySelector('meta[http-equiv="Content-Security-Policy"]');
    if (!policy) {
      policy = document.createElement("meta");
      policy.setAttribute("http-equiv", "Content-Security-Policy");
      head.prepend(policy);
    }
    policy.setAttribute(
      "content",
      "default-src 'none'; script-src 'none'; object-src 'none'; frame-src 'none'; child-src 'none'; connect-src 'none'; img-src blob: data:; media-src blob: data:; font-src blob: data:; style-src 'unsafe-inline' blob: data:; base-uri 'none'; form-action 'none'"
    );

    document.documentElement.dataset.senlizSanitized = "true";
  }

  function attachLinkGuard(contents, callbacks) {
    const document = contents?.document;
    if (!document) return;
    document.documentElement.dataset.senlizLinkGuard = "true";

    const handleLink = (event) => {
        const anchor = event.currentTarget;
        const rawHref = anchor.getAttribute("href") || "";
        if (!rawHref || DANGEROUS_SCHEME.test(rawHref)) {
          event.preventDefault();
          event.stopImmediatePropagation();
          return;
        }

        anchor.removeAttribute("target");
        anchor.removeAttribute("download");

        let absolute;
        try {
          absolute = new URL(anchor.href || rawHref, document.baseURI);
        } catch {
          absolute = null;
        }

        const explicitlyRemote = REMOTE_SCHEME.test(rawHref) || /^\s*\/\//.test(rawHref);
        if (explicitlyRemote && absolute && ["http:", "https:"].includes(absolute.protocol)) {
          event.preventDefault();
          event.stopImmediatePropagation();
          callbacks?.onExternal?.(absolute.href);
          return;
        }

        event.preventDefault();
        event.stopImmediatePropagation();
        callbacks?.onInternal?.(rawHref);
    };

    document.querySelectorAll("a[href],area[href]").forEach((anchor) => {
      if (anchor.dataset.senlizLinkGuard === "true") return;
      anchor.dataset.senlizLinkGuard = "true";
      anchor.addEventListener("click", handleLink, true);
    });
  }

  function openExternal(url) {
    let parsed;
    try {
      parsed = new URL(url);
    } catch {
      return false;
    }
    if (!["https:", "http:"].includes(parsed.protocol)) return false;

    if (globalThis.SenlizNative?.openExternal) {
      globalThis.SenlizNative.openExternal(parsed.href);
      return true;
    }

    if (globalThis.webkit?.messageHandlers?.senliz) {
      globalThis.webkit.messageHandlers.senliz.postMessage({ action: "openExternal", url: parsed.href });
      return true;
    }

    globalThis.open(parsed.href, "_blank", "noopener,noreferrer");
    return true;
  }

  globalThis.SenlizSecurity = Object.freeze({
    EPUB_MIME,
    EpubValidationError,
    validateEpub,
    sanitizeDocument,
    attachLinkGuard,
    openExternal,
    scrubCss
  });
})();
