// src/shared/binary.ts — Portable Blob↔base64 helpers for the
// offscreen↔SW port wire-format (D-12 fix).
//
// Why this file exists:
//   chrome.runtime.Port.postMessage JSON-serializes payloads across
//   extension contexts. JSON.stringify(blob) === "{}" (Blob has no
//   enumerable own properties), so any Blob passed through a port
//   arrives as a plain empty object on the other side. The SW then
//   calls `new Blob([{}, {}, ...])` and the constructor coerces each
//   non-Blob member via String({}) === "[object Object]", producing
//   exactly 75 bytes of garbage for 5 chunks — matching the observed
//   ffprobe failure forensics in debug session d12.
//
// Fix: encode each chunk's binary payload to base64 in offscreen
// BEFORE port.postMessage; decode back to Blob in SW AFTER receive.
// base64 strings round-trip cleanly through JSON.
//
// The wire-format contract is pinned by tests/offscreen/port-serialization.test.ts
// (GREEN describe block). This module's two helpers MUST satisfy:
//   1. `await blobToBase64(blob)` returns just the base64 payload
//      (no `data:...;base64,` prefix).
//   2. `base64ToBlob(b64, mimeType)` returns a Blob whose bytes
//      equal `Uint8Array(await blob.arrayBuffer())` of the original.
//   3. EBML magic bytes 0x1A 0x45 0xDF 0xA3 round-trip intact through
//      JSON.parse(JSON.stringify({dataBase64: await blobToBase64(...)})).
//
// Portability: both `btoa`/`atob` and `arrayBuffer()` are available in
// the modern browser/extension runtime AND in the Node-based vitest
// environment (Node 18+ has both as globals). No FileReader dependency
// (FileReader is browser-only — would break Node tests).

/**
 * Encode a Blob's binary content to a base64 string suitable for
 * JSON-friendly transport over chrome.runtime.Port.
 *
 * Returns the raw base64 payload only (no `data:<type>;base64,` prefix).
 *
 * @param blob - The binary blob to encode (typically a MediaRecorder
 *               dataavailable chunk).
 * @returns Base64-encoded representation of the blob's bytes.
 */
export async function blobToBase64(blob: Blob): Promise<string> {
  const buf = await blob.arrayBuffer();
  const bytes = new Uint8Array(buf);
  // Per-byte concat is the portable way: avoids the
  // `String.fromCharCode(...bytes)` apply-spread argument-length limit
  // (typically ~64 KiB on some engines) for large chunks.
  let binary = '';
  for (let i = 0; i < bytes.length; i++) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary);
}

/**
 * Decode a base64 string back into a Blob with the given MIME type.
 *
 * Synchronous because atob + Uint8Array population requires no I/O.
 *
 * @param b64 - The base64-encoded payload (as produced by
 *              {@link blobToBase64}). Must NOT include a
 *              `data:<type>;base64,` prefix.
 * @param mimeType - The MIME type to assign to the reconstructed Blob
 *                   (e.g. `video/webm;codecs=vp9`).
 * @returns A Blob whose bytes match the original encoded blob exactly.
 */
export function base64ToBlob(b64: string, mimeType: string): Blob {
  // WR-07 fix: defensive early-return for empty input. `atob('')` returns
  // an empty string and the resulting Blob has size 0 — currently no
  // caller filters zero-size segments, which would corrupt the
  // concatenated WebM with a stray empty EBML segment. Return an
  // explicit empty Blob here so the SW-side filter on
  // `segment.data.size > 0` (see src/background/index.ts
  // getVideoBufferFromOffscreen) can pre-filter cleanly without an
  // extra atob round-trip.
  if (b64.length === 0) {
    return new Blob([], { type: mimeType });
  }
  const binary = atob(b64);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return new Blob([bytes], { type: mimeType });
}
