/* mysterion_native_loader.js — v5.5.424
 *
 * Loads the wasm-bindgen ES6 module (`mysterion_native.js`) in an extension
 * context and exposes a singleton `MysterionNativeV2` with typed JS wrappers
 * around the Rust DCT-SS core.
 *
 * Coexists with the existing 18KB bespoke `mysterion.wasm` / `MysterionWASM`
 * loader used for LSB + pHash fast paths. Nothing here modifies that class.
 *
 * Design notes:
 *   * The wasm-bindgen glue is an ES6 module. To load it from a content
 *     script or background page / service worker we use importScripts
 *     with the extension-resolved URL. This requires both the glue JS and
 *     the `_bg.wasm` to be listed in `web_accessible_resources`.
 *   * A single `init()` call is enough per page/worker — subsequent calls
 *     return the cached promise.
 *   * All byte inputs are `Uint8Array`. BigInt / pointer handling stays
 *     inside the glue.
 *   * Self-test runs exactly once during init. A failure leaves the loader
 *     permanently rejected for this context.
 *
 * Exposed surface (on `globalThis.MysterionNativeV2`):
 *   await MysterionNativeV2.init()             -> readiness
 *   MysterionNativeV2.version()                -> string
 *   MysterionNativeV2.buildPayload({...})      -> Uint8Array(80)
 *   MysterionNativeV2.keyframeMap({...})       -> Uint32Array
 *   MysterionNativeV2.embed({...})             -> { yPlane, blocksWritten }
 *   MysterionNativeV2.extract({...})           -> Uint8Array(80)
 *   MysterionNativeV2.lsbEmbed({...})          -> Uint8Array
 *   MysterionNativeV2.lsbExtract({...})        -> Uint8Array(32)
 *
 * v5.5.424 — full v2 multi-layer pipeline (3-layer DCT-SS + LSB v2 facade):
 *   MysterionNativeV2.verifyImage({...})       -> { decoded, payload, orientation, belowMinResolution }
 *   MysterionNativeV2.verifyVideo({...})       -> { decoded, payload, orientation, belowMinResolution }
 *   MysterionNativeV2.embedImage({...})        -> Uint8Array(width*height*3)
 */

(function () {
  'use strict';

  // Resolve the API root that exposes runtime.getURL — works for both MV3
  // (chrome.*) and MV2 Firefox (browser.*).
  const runtimeApi =
    (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.getURL)
      ? chrome
      : (typeof browser !== 'undefined' && browser.runtime && browser.runtime.getURL)
        ? browser
        : null;

  if (!runtimeApi) {
    // Running outside an extension context. Expose a stub that explains.
    globalThis.MysterionNativeV2 = {
      init() {
        return Promise.reject(
          new Error('MysterionNativeV2: no extension runtime.getURL available'),
        );
      },
    };
    return;
  }

  let _modPromise = null; // resolves to the imported ES module namespace
  let _wasmExports = null; // resolved module namespace once ready
  let _selftestOk = false;

  async function _load() {
    const glueUrl = runtimeApi.runtime.getURL('mysterion_native.js');
    const wasmUrl = runtimeApi.runtime.getURL('mysterion_native_bg.wasm');

    // patch17: MV3 service workers forbid dynamic module loading. Load a
    // classic global build via importScripts and read the published
    // module object. (importScripts-only path)
    // (e.g. a real ES-module context), preserving other environments.
    // patch18: importScripts-only (no dynamic-module token anywhere — its mere
    // presence in a classic worker script can be rejected by MV3 at parse
    // time, which stops this file from defining MysterionNativeV2).
    // patch19: the global glue is importScripts'd at TOP LEVEL in
    // background.js (SW-install time); here we just read the already-
    // published module object. No importScripts at call time (MV3 bans it).
    const mod = globalThis.__mysterionNativeMod;
    if (!mod || !mod.default) {
      throw new Error('MysterionNativeV2: global glue not loaded (expected top-level importScripts of mysterion_native_global.js)');
    }
    await mod.default(wasmUrl);

    // Gate everything on the Rust power-on self-test (returns bool).
    const ok = mod.mysterion_selftest();
    if (ok !== true) {
      throw new Error(
        `MysterionNativeV2: self-test failed (returned ${ok})`,
      );
    }
    _selftestOk = true;
    _wasmExports = mod;
    return mod;
  }

  function _ensureReady() {
    if (!_wasmExports || !_selftestOk) {
      throw new Error(
        'MysterionNativeV2: init() must be awaited before use',
      );
    }
    return _wasmExports;
  }

  function _u8(x, name, expectedLen) {
    if (!(x instanceof Uint8Array)) {
      throw new TypeError(`${name} must be Uint8Array`);
    }
    if (typeof expectedLen === 'number' && x.length !== expectedLen) {
      throw new RangeError(
        `${name} must be exactly ${expectedLen} bytes (got ${x.length})`,
      );
    }
    return x;
  }

  const MysterionNativeV2 = {  // patch23: +extractExplicit +payloadReconcile
    /** Kick off module load + self-test. Idempotent; returns a cached promise. */
    init() {
      if (_modPromise) return _modPromise;
      _modPromise = _load().catch((err) => {
        _modPromise = null;
        throw err;
      });
      return _modPromise;
    },

    /** True iff init() has fully resolved and the self-test passed. */
    isReady() {
      return _selftestOk;
    },

    /** Library version string (e.g. "5.5.81"). */
    version() {
      return _ensureReady().mysterion_version();
    },

    /**
     * Build the 80-byte DCT-SS payload.
     * @param {{rawBlake3:Uint8Array,rfc3161Der:Uint8Array,stegId:Uint8Array}} args
     * @returns {Uint8Array} 80-byte payload
     */
    buildPayload({ appVersion, rawBlake3, creatorId, rfc3161Der, stegId }) {  // patch24: +appVersion +creatorId (WASM needs 5 args)
      const mod = _ensureReady();
      _u8(rawBlake3, 'rawBlake3', 32);
      _u8(rfc3161Der, 'rfc3161Der');
      _u8(stegId, 'stegId', 16);
      if (typeof appVersion !== 'string') throw new TypeError('appVersion must be string');
      if (typeof creatorId !== 'string' || creatorId.length !== 36) throw new RangeError('creatorId must be 36-char UUID');
      return mod.mysterion_dct_ss_build_payload(appVersion, rawBlake3, creatorId, rfc3161Der, stegId);
    },

    /**
     * Compute deterministic keyframe indices for the given video.
     * @returns {Uint32Array} sorted ascending
     */
    keyframeMap({
      appVersion,
      rawBlake3,
      creatorId,
      rfc3161Der,
      stegId,
      totalKeyframes,
      densityNumerator = 1,
      densityDenominator = 3,
    }) {
      const mod = _ensureReady();
      _u8(rawBlake3, 'rawBlake3', 32);
      _u8(rfc3161Der, 'rfc3161Der');
      _u8(stegId, 'stegId', 16);
      if (typeof creatorId !== 'string' || creatorId.length !== 36) {
        throw new RangeError('creatorId must be 36-char UUID string');
      }
      if (typeof appVersion !== 'string') {
        throw new TypeError('appVersion must be string');
      }
      return mod.mysterion_keyframe_map(
        appVersion, rawBlake3, creatorId, rfc3161Der, stegId,
        totalKeyframes >>> 0,
        densityNumerator >>> 0,
        densityDenominator >>> 0,
      );
    },

    /**
     * Embed payload into a Y-plane. Returns a new Uint8Array with the
     * mutated plane (the input is left unchanged) plus block count.
     */
    embed({
      appVersion,
      rawBlake3,
      creatorId,
      rfc3161Der,
      stegId,
      yPlane,
      width,
      height,
      payload,
      strength = 6,
      chipRate = 40,
    }) {
      const mod = _ensureReady();
      _u8(rawBlake3, 'rawBlake3', 32);
      _u8(rfc3161Der, 'rfc3161Der');
      _u8(stegId, 'stegId', 16);
      _u8(yPlane, 'yPlane');
      _u8(payload, 'payload', 80);
      if (!(width > 0 && height > 0 && width % 8 === 0 && height % 8 === 0)) {
        throw new RangeError('width/height must be positive multiples of 8');
      }
      if (yPlane.length !== width * height) {
        throw new RangeError('yPlane.length !== width*height');
      }
      if (!(chipRate > 0 && chipRate <= 256)) {
        throw new RangeError('chipRate out of range (1..256)');
      }
      if (typeof creatorId !== 'string' || creatorId.length !== 36) {
        throw new RangeError('creatorId must be 36-char UUID string');
      }
      if (typeof appVersion !== 'string') {
        throw new TypeError('appVersion must be string');
      }

      // Rust signature: fn(...) -> Result<Vec<u8>, JsError>. wasm-bindgen
      // returns the mutated Y-plane as a new Uint8Array (ownership moves
      // into JS). A validation error is thrown as a JS Error.
      const yOut = mod.mysterion_dct_ss_embed(
        appVersion, rawBlake3, creatorId, rfc3161Der, stegId,
        yPlane, width >>> 0, height >>> 0,
        payload, strength | 0, chipRate >>> 0,
      );
      return { yPlane: yOut, blocksWritten: -1 };
    },

    /** Extract the 80-byte payload from a suspect Y-plane. */
    extract({
      appVersion,
      rawBlake3,
      creatorId,
      rfc3161Der,
      stegId,
      yPlane,
      width,
      height,
      chipRate = 40,
    }) {
      const mod = _ensureReady();
      _u8(rawBlake3, 'rawBlake3', 32);
      _u8(rfc3161Der, 'rfc3161Der');
      _u8(stegId, 'stegId', 16);
      _u8(yPlane, 'yPlane');
      if (!(width > 0 && height > 0 && width % 8 === 0 && height % 8 === 0)) {
        throw new RangeError('width/height must be positive multiples of 8');
      }
      if (yPlane.length !== width * height) {
        throw new RangeError('yPlane.length !== width*height');
      }
      if (typeof creatorId !== 'string' || creatorId.length !== 36) {
        throw new RangeError('creatorId must be 36-char UUID string');
      }
      if (typeof appVersion !== 'string') {
        throw new TypeError('appVersion must be string');
      }

      return mod.mysterion_dct_ss_extract(
        appVersion, rawBlake3, creatorId, rfc3161Der, stegId,
        yPlane, width >>> 0, height >>> 0,
        chipRate >>> 0,
      );
    },

    /**
     * patch23: explicit-block-list DCT-SS extract. Mirrors the desktop app:
     * pass the signer's stored dct_block_list (raster (bx,by) u16 pairs) so
     * the extractor does NOT recompute a content-sensitive grid that diverges
     * on re-encoded natural photos. Returns the raw 32-byte payload; feed it
     * to payloadReconcile() for the 1-2 bit error-floor correction.
     * @param {Uint16Array} args.blocks  flat [bx0,by0,bx1,by1,...]
     */
    extractExplicit({
      appVersion,
      rawBlake3,
      creatorId,
      rfc3161Der,
      stegId,
      yPlane,
      width,
      height,
      chipRate = 40,
      blocks,
    }) {
      const mod = _ensureReady();
      _u8(rawBlake3, 'rawBlake3', 32);
      _u8(rfc3161Der, 'rfc3161Der');
      _u8(stegId, 'stegId', 16);
      _u8(yPlane, 'yPlane');
      if (!(blocks instanceof Uint16Array) || blocks.length === 0 || blocks.length % 2 !== 0) {
        throw new RangeError('blocks must be a non-empty Uint16Array of (bx,by) pairs');
      }
      if (!(width > 0 && height > 0 && width % 8 === 0 && height % 8 === 0)) {
        throw new RangeError('width/height must be positive multiples of 8');
      }
      if (yPlane.length !== width * height) {
        throw new RangeError('yPlane.length !== width*height');
      }
      if (typeof creatorId !== 'string' || creatorId.length !== 36) {
        throw new RangeError('creatorId must be 36-char UUID string');
      }
      if (typeof appVersion !== 'string') {
        throw new TypeError('appVersion must be string');
      }
      return mod.mysterion_dct_ss_extract_explicit(
        appVersion, rawBlake3, creatorId, rfc3161Der, stegId,
        yPlane, width >>> 0, height >>> 0,
        chipRate >>> 0, blocks,
      );
    },

    /**
     * patch23: application-layer payload reconciliation (1-2 bit LDPC error
     * floor). Mirrors the app; call on the raw payload from extractExplicit.
     * Throws if no candidate within 2 bits has a valid manifest.
     */
    payloadReconcile({
      extractedPayload,
      appVersion,
      rawBlake3,
      creatorId,
      rfc3161Der,
      stegId,
    }) {
      const mod = _ensureReady();
      _u8(extractedPayload, 'extractedPayload', 32);
      _u8(rawBlake3, 'rawBlake3', 32);
      _u8(rfc3161Der, 'rfc3161Der');
      _u8(stegId, 'stegId', 16);
      if (typeof creatorId !== 'string' || creatorId.length !== 36) {
        throw new RangeError('creatorId must be 36-char UUID string');
      }
      return mod.mysterion_payload_reconcile(
        extractedPayload, appVersion, rawBlake3, creatorId, rfc3161Der, stegId,
      );
    },

    // ── v5.5.90: LSB (seeded, LDPC) embed/extract ──
    // Same payload shape as DCT-SS; use as the "fast, fragile" Phase-1
    // extractor before falling back to DCT-SS.

    /** Embed a 32-byte payload into `y_plane` LSBs at PRNG-selected positions. */
    lsbEmbed({
      appVersion,
      rawBlake3,
      creatorId,
      rfc3161Der,
      stegId,
      yPlane,
      width,
      height,
      payload,
    }) {
      const mod = _ensureReady();
      _u8(rawBlake3, 'rawBlake3', 32);
      _u8(rfc3161Der, 'rfc3161Der');
      _u8(stegId, 'stegId', 16);
      _u8(yPlane, 'yPlane');
      _u8(payload, 'payload', 32);
      if (!(width > 0 && height > 0)) {
        throw new RangeError('width/height must be positive');
      }
      if (yPlane.length !== width * height) {
        throw new RangeError('yPlane.length !== width*height');
      }
      if (typeof creatorId !== 'string' || creatorId.length !== 36) {
        throw new RangeError('creatorId must be 36-char UUID string');
      }
      if (typeof appVersion !== 'string') {
        throw new TypeError('appVersion must be string');
      }
      return mod.mysterion_lsb_embed(
        appVersion, rawBlake3, creatorId, rfc3161Der, stegId,
        yPlane, width >>> 0, height >>> 0, payload,
      );
    },

    /** Extract a 32-byte payload from LSBs of a suspect Y-plane. */
    lsbExtract({
      appVersion,
      rawBlake3,
      creatorId,
      rfc3161Der,
      stegId,
      yPlane,
      width,
      height,
    }) {
      const mod = _ensureReady();
      _u8(rawBlake3, 'rawBlake3', 32);
      _u8(rfc3161Der, 'rfc3161Der');
      _u8(stegId, 'stegId', 16);
      _u8(yPlane, 'yPlane');
      if (!(width > 0 && height > 0)) {
        throw new RangeError('width/height must be positive');
      }
      if (yPlane.length !== width * height) {
        throw new RangeError('yPlane.length !== width*height');
      }
      if (typeof creatorId !== 'string' || creatorId.length !== 36) {
        throw new RangeError('creatorId must be 36-char UUID string');
      }
      if (typeof appVersion !== 'string') {
        throw new TypeError('appVersion must be string');
      }
      return mod.mysterion_lsb_extract(
        appVersion, rawBlake3, creatorId, rfc3161Der, stegId,
        yPlane, width >>> 0, height >>> 0,
      );
    },

    // ── v5.5.424: v2 multi-layer (3-layer DCT-SS + LSB v2 facade) ──
    // Phase 1 verify/embed running the full pipeline inside a single
    // WASM call. RGB888 in, RGB888 out — YCbCr conversion is Rust-side.

    /**
     * Full Phase 1 multi-layer verify on an RGB888 frame.
     *
     * @param {object} args
     * @param {Uint8Array} args.rgb            width*height*3 bytes
     * @param {number}     args.width          must be even
     * @param {number}     args.height         must be even
     * @param {number}     [args.expectedWidth=0]
     * @param {number}     [args.expectedHeight=0]
     * @param {string}     args.appVersion
     * @param {Uint8Array} args.rawBlake3      32 bytes
     * @param {string}     args.creatorId      36-char UUID
     * @param {Uint8Array} args.rfc3161Der
     * @param {Uint8Array} args.stegId         16 bytes
     * @returns {{decoded:boolean, payload:Uint8Array, orientation:number, belowMinResolution:boolean}}
     */
    verifyImage({
      rgb,
      width,
      height,
      expectedWidth = 0,
      expectedHeight = 0,
      appVersion,
      rawBlake3,
      creatorId,
      rfc3161Der,
      stegId,
    }) {
      const mod = _ensureReady();
      _u8(rgb, 'rgb');
      _u8(rawBlake3, 'rawBlake3', 32);
      _u8(rfc3161Der, 'rfc3161Der');
      _u8(stegId, 'stegId', 16);
      if (!(width > 0 && height > 0 && width % 2 === 0 && height % 2 === 0)) {
        throw new RangeError('width/height must be positive even integers');
      }
      if (rgb.length !== width * height * 3) {
        throw new RangeError(
          `rgb.length (${rgb.length}) !== width*height*3 (${width * height * 3})`,
        );
      }
      if (typeof creatorId !== 'string' || creatorId.length !== 36) {
        throw new RangeError('creatorId must be 36-char UUID string');
      }
      if (typeof appVersion !== 'string') {
        throw new TypeError('appVersion must be string');
      }

      const result = mod.mysterion_v2_verify_image(
        rgb,
        width >>> 0, height >>> 0,
        expectedWidth >>> 0, expectedHeight >>> 0,
        appVersion, rawBlake3, creatorId, rfc3161Der, stegId,
      );

      const plain = {
        decoded: result.decoded,
        payload: result.payload,
        orientation: result.orientation,
        belowMinResolution: result.belowMinResolution,
      };
      result.free();
      return plain;
    },

    /**
     * Full Phase 1 multi-layer verify across multiple video frames with
     * soft-combining. All frames must share dimensions.
     *
     * @returns {{decoded:boolean, payload:Uint8Array, orientation:number, belowMinResolution:boolean}}
     */
    verifyVideo({
      framesRgb,
      frameCount,
      width,
      height,
      expectedWidth = 0,
      expectedHeight = 0,
      appVersion,
      rawBlake3,
      creatorId,
      rfc3161Der,
      stegId,
    }) {
      const mod = _ensureReady();
      _u8(framesRgb, 'framesRgb');
      _u8(rawBlake3, 'rawBlake3', 32);
      _u8(rfc3161Der, 'rfc3161Der');
      _u8(stegId, 'stegId', 16);
      if (!(frameCount > 0 && width > 0 && height > 0
            && width % 2 === 0 && height % 2 === 0)) {
        throw new RangeError(
          'frameCount/width/height invalid (width and height must be even)',
        );
      }
      const expected = frameCount * width * height * 3;
      if (framesRgb.length !== expected) {
        throw new RangeError(
          `framesRgb.length (${framesRgb.length}) !== frameCount*width*height*3 (${expected})`,
        );
      }
      if (typeof creatorId !== 'string' || creatorId.length !== 36) {
        throw new RangeError('creatorId must be 36-char UUID string');
      }
      if (typeof appVersion !== 'string') {
        throw new TypeError('appVersion must be string');
      }

      const result = mod.mysterion_v2_verify_video(
        framesRgb,
        frameCount >>> 0,
        width >>> 0, height >>> 0,
        expectedWidth >>> 0, expectedHeight >>> 0,
        appVersion, rawBlake3, creatorId, rfc3161Der, stegId,
      );

      const plain = {
        decoded: result.decoded,
        payload: result.payload,
        orientation: result.orientation,
        belowMinResolution: result.belowMinResolution,
      };
      result.free();
      return plain;
    },

    /**
     * Full Phase 1 multi-layer embed (sign-side counterpart to verifyImage).
     * Returns a new RGB888 Uint8Array with the watermark applied. Throws
     * on below-tier rejection.
     *
     * @returns {Uint8Array} new RGB888 of width*height*3 bytes
     */
    embedImage({
      rgb,
      width,
      height,
      appVersion,
      rawBlake3,
      creatorId,
      rfc3161Der,
      stegId,
      payload,
    }) {
      const mod = _ensureReady();
      _u8(rgb, 'rgb');
      _u8(rawBlake3, 'rawBlake3', 32);
      _u8(rfc3161Der, 'rfc3161Der');
      _u8(stegId, 'stegId', 16);
      _u8(payload, 'payload', 32);
      if (!(width > 0 && height > 0 && width % 2 === 0 && height % 2 === 0)) {
        throw new RangeError('width/height must be positive even integers');
      }
      if (rgb.length !== width * height * 3) {
        throw new RangeError(
          `rgb.length (${rgb.length}) !== width*height*3 (${width * height * 3})`,
        );
      }
      if (typeof creatorId !== 'string' || creatorId.length !== 36) {
        throw new RangeError('creatorId must be 36-char UUID string');
      }
      if (typeof appVersion !== 'string') {
        throw new TypeError('appVersion must be string');
      }

      return mod.mysterion_v2_embed_image(
        rgb,
        width >>> 0, height >>> 0,
        appVersion, rawBlake3, creatorId, rfc3161Der, stegId,
        payload,
      );
    },
  };

  globalThis.MysterionNativeV2 = MysterionNativeV2;
})();
