/* steg_lsb.js — faithful JS port of mysterion-app/lib/crypto/steg_lsb.dart
 * extractFromImage(). Recovers the blind LSB payload ("stegId|creatorId")
 * embedded in the FXX3 watermark raster.
 *
 * Layout in the image's LSBs (bit 0 of each R/G/B channel, row-major):
 *   [4 bytes magic "MYST"][4 bytes payload length (big-endian u32)][payload]
 *
 * Input: {width, height, data} where data is RGBA or RGB Uint8ClampedArray/
 *   Uint8Array of pixels (row-major). channels = 4 (RGBA) or 3 (RGB).
 * Returns: Uint8Array payload, or null if no valid MYST header.
 *
 * NOTE: The Dart source reads pixels via image.convert(numChannels:3) then
 * getPixel(x,y).r/.g/.b. We reproduce the SAME traversal order: for each
 * pixel row-major, read R LSB, G LSB, B LSB. When the source raster is RGBA
 * we skip the alpha byte (stride 4, read bytes 0,1,2).
 */
(function () {
  'use strict';

  const MAGIC = [0x4D, 0x59, 0x53, 0x54]; // "MYST"
  const HEADER_BYTES = 8;                  // 4 magic + 4 length
  const HEADER_BITS = HEADER_BYTES * 8;    // 64

  function _bitsToBytes(bits, offset, end) {
    const byteCount = (end - offset) >> 3;
    const out = new Uint8Array(byteCount);
    for (let i = 0; i < byteCount; i++) {
      const base = offset + i * 8;
      out[i] =
        (bits[base]     << 7) |
        (bits[base + 1] << 6) |
        (bits[base + 2] << 5) |
        (bits[base + 3] << 4) |
        (bits[base + 4] << 3) |
        (bits[base + 5] << 2) |
        (bits[base + 6] << 1) |
         bits[base + 7];
    }
    return out;
  }

  // pixels: Uint8Array/Uint8ClampedArray; channels: 3 or 4.
  // Returns the LSB (0/1) of the Nth *color-channel sample* in row-major
  // R,G,B order, skipping alpha when channels===4.
  function _makeSampleReader(pixels, width, height, channels) {
    // total color samples = width*height*3 (R,G,B only)
    const totalSamples = width * height * 3;
    return {
      totalSamples,
      // read LSB of color-sample index i (0-based, R,G,B,R,G,B,...)
      lsb(i) {
        const pixelIdx = (i / 3) | 0;
        const ch = i % 3;               // 0=R 1=G 2=B
        const byteIdx = pixelIdx * channels + ch;
        return pixels[byteIdx] & 1;
      },
    };
  }

  function extractFromImage(pixels, width, height, channels) {
    channels = channels || 4;
    const reader = _makeSampleReader(pixels, width, height, channels);
    const totalBits = reader.totalSamples; // 1 bit per color sample
    if (totalBits < HEADER_BITS) return null;

    // Read header bits (first 64 color-sample LSBs)
    const headerBuf = new Uint8Array(HEADER_BITS);
    for (let i = 0; i < HEADER_BITS; i++) headerBuf[i] = reader.lsb(i);

    const hBytes = _bitsToBytes(headerBuf, 0, HEADER_BITS);
    if (hBytes[0] !== MAGIC[0] || hBytes[1] !== MAGIC[1] ||
        hBytes[2] !== MAGIC[2] || hBytes[3] !== MAGIC[3]) {
      return null;
    }

    // payload length: big-endian u32 at bytes 4..8
    const length =
      ((hBytes[4] << 24) | (hBytes[5] << 16) | (hBytes[6] << 8) | hBytes[7]) >>> 0;
    if (length === 0 || length > 10000000) return null;

    const totalNeeded = HEADER_BITS + length * 8;
    if (totalNeeded > totalBits) return null;

    const allBits = new Uint8Array(totalNeeded);
    allBits.set(headerBuf, 0);
    for (let i = HEADER_BITS; i < totalNeeded; i++) allBits[i] = reader.lsb(i);

    return _bitsToBytes(allBits, HEADER_BITS, totalNeeded);
  }

  // Convenience: recover "stegId|creatorId" from raster pixels.
  // Returns {stegId, creatorId} or null.
  function recoverStegId(pixels, width, height, channels) {
    const payload = extractFromImage(pixels, width, height, channels);
    if (!payload) return null;
    let text = '';
    try { text = new TextDecoder('utf-8').decode(payload); }
    catch (_) { return null; }
    const parts = text.split('|');
    if (parts.length === 0) return null;
    const sid = parts[0].trim();
    const cid = parts.length > 1 ? parts[1].trim() : null;
    // Canonicalize to dashless 32-hex (accept 36-char dashed UUID or 32-hex).
    const hex = sid.replace(/-/g, '').toLowerCase();
    if (!/^[0-9a-f]{32}$/.test(hex)) return null;
    return { stegId: hex, creatorId: (cid && cid.length) ? cid : null };
  }

  globalThis.MysterionStegLsb = { extractFromImage, recoverStegId };
})();
