/**
 * Offscreen document: the only extension context where WebCodecs lives
 * (VideoEncoder is not exposed to service workers). The background ships the
 * screencast frames over a dedicated runtime port in base64 batches, this
 * page encodes them into a silent WebM, verifies it with a local <video>
 * element and ships the result back. A failed verification is reported as an
 * error so the background falls back to the GIF path instead of uploading a
 * broken file.
 */
import browser from "webextension-polyfill";
import { buildVideoWebm } from "../background/video";
import type { VideoFrame } from "../background/gif";

/** base64 (wire format) → bytes for the encoder */
function bytesFromBase64(base64: string): Uint8Array {
  const binary = atob(base64);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
  return bytes;
}

function bytesToBase64(bytes: Uint8Array): string {
  let binary = "";
  const chunk = 0x8000;
  for (let i = 0; i < bytes.length; i += chunk) {
    binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
  }
  return btoa(binary);
}

/** can the local <video> element actually open what we just encoded? */
function probePlayback(blob: Blob): Promise<{ ok: boolean; error: string }> {
  return new Promise((resolve) => {
    const url = URL.createObjectURL(blob);
    const video = document.createElement("video");
    video.muted = true;
    video.preload = "metadata";
    const done = (ok: boolean, error: string) => {
      URL.revokeObjectURL(url);
      video.removeAttribute("src");
      video.load();
      resolve({ ok, error });
    };
    video.addEventListener("loadedmetadata", () => done(true, ""));
    video.addEventListener("error", () =>
      done(false, video.error ? `${video.error.code} ${video.error.message}` : "unknown")
    );
    video.src = url;
  });
}

browser.runtime.onConnect.addListener((port) => {
  if (port.name !== "video-encode") return;
  let frames: VideoFrame[] = [];

  port.onMessage.addListener(async (raw: unknown) => {
    const msg = raw as {
      type: string;
      frames?: { at: number; b64: string; w: number; h: number }[];
      crop?: import("../background/gif").CursorCropOptions | null;
    };
    if (msg.type === "encode_video_start") {
      frames = [];
      return;
    }
    if (msg.type === "encode_video_frames") {
      for (const frame of msg.frames ?? []) {
        frames.push({ at: frame.at, jpeg: bytesFromBase64(frame.b64), w: frame.w, h: frame.h });
      }
      return;
    }
    if (msg.type === "encode_video_run") {
      try {
        const blob = await buildVideoWebm(frames, msg.crop ?? undefined);
        if (!blob) {
          port.postMessage({ type: "encode_video_result", ok: false, error: "encoding produced nothing" });
          return;
        }
        const probe = await probePlayback(blob);
        if (!probe.ok) {
          port.postMessage({ type: "encode_video_result", ok: false, error: `encoded video failed playback check: ${probe.error}` });
          return;
        }
        const raw = new Uint8Array(await blob.arrayBuffer());
        port.postMessage({ type: "encode_video_result", ok: true, b64: bytesToBase64(raw) });
      } catch (error) {
        port.postMessage({ type: "encode_video_result", ok: false, error: error instanceof Error ? error.message : String(error) });
      }
    }
  });
});