import { Muxer, ArrayBufferTarget } from './vendor/mp4-muxer.mjs';

function bytesToBase64(bytes) {
  let binary = "";
  const chunkSize = 0x8000;
  for (let i = 0; i < bytes.length; i += chunkSize) {
    binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunkSize));
  }
  return btoa(binary);
}

function even(n) {
  const v = Math.max(2, Math.round(n));
  return v % 2 === 0 ? v : v - 1;
}

function pickVideoSize(bitmap, aspect) {
  if (aspect === "3:4") return { width: 720, height: 960 };
  const maxSide = 960;
  const ratio = bitmap.width / Math.max(1, bitmap.height);
  if (ratio >= 1) return { width: even(maxSide), height: even(maxSide / ratio) };
  return { width: even(maxSide * ratio), height: even(maxSide) };
}

function drawVideoFrame(ctx, canvas, bitmap, aspect, frameIndex, frameCount) {
  ctx.fillStyle = "#f6f7f9";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  const progress = frameCount > 1 ? frameIndex / (frameCount - 1) : 0;
  const zoom = 1.0 + progress * 0.018;
  if (aspect === "3:4") {
    const targetRatio = canvas.width / canvas.height;
    const sourceRatio = bitmap.width / Math.max(1, bitmap.height);
    let sx = 0, sy = 0, sw = bitmap.width, sh = bitmap.height;
    if (sourceRatio > targetRatio) {
      sw = Math.round(bitmap.height * targetRatio);
      sx = Math.round((bitmap.width - sw) / 2);
    } else {
      sh = Math.round(bitmap.width / targetRatio);
      sy = Math.round((bitmap.height - sh) / 2);
    }
    const dw = canvas.width * zoom;
    const dh = canvas.height * zoom;
    ctx.drawImage(bitmap, sx, sy, sw, sh, (canvas.width - dw) / 2, (canvas.height - dh) / 2, dw, dh);
    return;
  }
  const dw = canvas.width * zoom;
  const dh = canvas.height * zoom;
  ctx.drawImage(bitmap, (canvas.width - dw) / 2, (canvas.height - dh) / 2, dw, dh);
}

async function generateVideo(task) {
  if (!globalThis.VideoEncoder || !globalThis.VideoFrame) {
    throw new Error("WebCodecs VideoEncoder/VideoFrame is not available in offscreen document");
  }
  const res = await fetch(task.source_url, { credentials: "omit", cache: "no-store" });
  if (!res.ok) throw new Error(`video source image fetch failed ${res.status}: ${task.source_url}`);
  const sourceBlob = await res.blob();
  const bitmap = await createImageBitmap(sourceBlob);
  const aspect = task.aspect || "source";
  const size = pickVideoSize(bitmap, aspect);
  const fps = Math.max(1, Math.min(60, Number(task.fps || 24)));
  const durationSec = Math.max(1, Math.min(60, Number(task.duration_sec || 10)));
  const frameCount = Math.max(1, Math.round(fps * durationSec));
  const config = {
    codec: "avc1.42E01F",
    width: size.width,
    height: size.height,
    bitrate: 1200000,
    framerate: fps,
    avc: { format: "avc" },
  };
  const support = await VideoEncoder.isConfigSupported(config);
  if (!support.supported) throw new Error("H.264 WebCodecs config is not supported");

  const target = new ArrayBufferTarget();
  const muxer = new Muxer({
    target,
    video: { codec: "avc", width: size.width, height: size.height, frameRate: fps },
    fastStart: "in-memory",
    firstTimestampBehavior: "strict",
  });
  const canvas = document.createElement("canvas");
  canvas.width = size.width;
  canvas.height = size.height;
  const ctx = canvas.getContext("2d");
  let encoderError = null;
  const encoder = new VideoEncoder({
    output: (chunk, meta) => muxer.addVideoChunk(chunk, meta),
    error: (e) => { encoderError = e; },
  });
  encoder.configure(config);
  for (let i = 0; i < frameCount; i++) {
    drawVideoFrame(ctx, canvas, bitmap, aspect, i, frameCount);
    const frame = new VideoFrame(canvas, {
      timestamp: Math.round(i * 1_000_000 / fps),
      duration: Math.round(1_000_000 / fps),
    });
    encoder.encode(frame, { keyFrame: i % Math.max(1, fps * 2) === 0 });
    frame.close();
    if (encoderError) throw encoderError;
  }
  await encoder.flush();
  encoder.close();
  if (encoderError) throw encoderError;
  muxer.finalize();
  const bytes = new Uint8Array(target.buffer);
  return {
    content_base64: bytesToBase64(bytes),
    width: size.width,
    height: size.height,
    size_bytes: bytes.byteLength,
  };
}

chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
  if (!message || message.type !== "rb_process_video_offscreen") return false;
  generateVideo(message.task || {})
    .then((result) => sendResponse({ ok: true, result }))
    .catch((err) => sendResponse({ ok: false, error: err && err.message ? err.message : String(err) }));
  return true;
});
