// Bridge entre Service Worker e Offscreen Document.
//
// MV3 SW não consegue rodar ffmpeg.wasm direto (sem DOM, sem Workers nativos
// confiáveis). chrome.offscreen.createDocument cria um "documento headless" onde
// ffmpeg.wasm roda OK. Esta lib esconde a complexidade.
//
// API:
//   processVideo({ mode, blob, profileLabel? }) -> Promise<{blob, profile, ...}>
//
// modes:
//   'anti_detect' — strip + inject câmera fake (-c copy, rápido)
//   'transcode'   — re-encode H.264 IG-safe
//   'both'        — transcode + inject

const OFFSCREEN_DOC = 'src/offscreen/transcode.html';
let _creatingPromise = null;

async function hasOffscreenDocument() {
  // chrome.offscreen.hasDocument só existe em Chrome 116+. Fallback: tenta listar.
  if (chrome.offscreen?.hasDocument) {
    return chrome.offscreen.hasDocument();
  }
  // Fallback: chrome.runtime.getContexts (Chrome 116+)
  if (chrome.runtime.getContexts) {
    const contexts = await chrome.runtime.getContexts({ contextTypes: ['OFFSCREEN_DOCUMENT'] });
    return contexts.length > 0;
  }
  return false;
}

async function ensureOffscreenDocument() {
  if (await hasOffscreenDocument()) return;
  if (_creatingPromise) return _creatingPromise;
  _creatingPromise = chrome.offscreen.createDocument({
    url: OFFSCREEN_DOC,
    reasons: ['BLOBS', 'WORKERS', 'DOM_SCRAPING'],
    justification: 'ffmpeg.wasm precisa de DOM + Workers pra rodar; SW não tem.'
  }).catch(err => {
    // Race: outro tick pode ter criado já
    if (String(err).includes('Only a single offscreen')) return;
    throw err;
  }).finally(() => { _creatingPromise = null; });
  return _creatingPromise;
}

async function blobToTransferable(blob) {
  const buf = await blob.arrayBuffer();
  return { buffer: buf, mime: blob.type || 'video/mp4' };
}

// Converte blob → base64 string. Necessário pra atravessar chrome.runtime.sendMessage
// (que JSON-ifica ArrayBuffer pra {} silenciosamente).
async function blobToB64(blob) {
  const buf = await blob.arrayBuffer();
  const u8 = new Uint8Array(buf);
  let s = '';
  const chunk = 0x8000;
  for (let i = 0; i < u8.length; i += chunk) {
    s += String.fromCharCode.apply(null, u8.subarray(i, i + chunk));
  }
  return { b64: btoa(s), mime: blob.type || 'video/mp4' };
}

function b64ToBlob(b64, mime) {
  const bin = atob(b64 || '');
  const u8 = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) u8[i] = bin.charCodeAt(i);
  return new Blob([u8], { type: mime || 'video/mp4' });
}

/**
 * Roda processamento no offscreen com qualquer combinação de:
 *   - mode: 'anti_detect' | 'transcode' | 'both' | null (sem transcode)
 *   - frame: { dataUrl, viewport_x, viewport_y, viewport_w, viewport_h } | null
 *   - watermark: { dataUrl, position, opacity, manual_enabled?, manual_x?, manual_y?, manual_scale?, manual_rotation? } | null
 *   - profileLabel: nome do perfil camera específico, ou null (random)
 *
 * dataUrl: aceita "data:image/png;base64,..." (vem direto da API /preferences/frames|watermarks).
 *
 * Retorna { blob, info: { steps[], profile?, creation_time?, location? } }
 */
export async function processVideo({ mode, blob, profileLabel = null, frame = null, watermark = null, timeoutMs = 15 * 60 * 1000 } = {}) {
  await ensureOffscreenDocument();
  const { b64: videoB64, mime: videoMime } = await blobToB64(blob);

  const framePayload = frame?.dataUrl ? prepImageFromDataUrl(frame) : null;
  const wmPayload = watermark?.dataUrl ? prepImageFromDataUrl(watermark) : null;

  const reply = await new Promise((resolve, reject) => {
    let done = false;
    const t = setTimeout(() => {
      if (done) return; done = true;
      reject(new Error(`ffmpeg processing timed out (${Math.round(timeoutMs / 60000)} min)`));
    }, timeoutMs);

    chrome.runtime.sendMessage({
      target: 'offscreen-transcode',
      type: 'PROCESS_VIDEO',
      mode,
      videoB64,
      videoMime,
      frame: framePayload,
      watermark: wmPayload,
      profileLabel
    }, (r) => {
      if (done) return; done = true;
      clearTimeout(t);
      if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); return; }
      resolve(r);
    });
  });

  if (!reply?.ok) throw new Error(reply?.error || 'Falha no offscreen ffmpeg');

  // O offscreen guarda o blob processado e nos devolve só a URL. Não trafegamos
  // os bytes via chrome.runtime (que estava corrompendo arquivos >20MB no base64
  // round-trip). SW pode usar essa URL direto em chrome.downloads ou fetch(url)
  // pra pegar os bytes quando precisar (Drive upload).
  return {
    url: reply.url,
    size: reply.size,
    mime: reply.mime || 'video/mp4',
    info: reply.info || {}
  };
}

// Revoga a blob URL do offscreen quando não precisamos mais (libera memória).
export async function revokeProcessedBlobUrl(url) {
  if (!url) return;
  try {
    await ensureOffscreenDocument();
    chrome.runtime.sendMessage({
      target: 'offscreen-transcode',
      type: 'REVOKE_BLOB_URL',
      url
    }).catch(() => {});
  } catch (_) {}
}

// Converte data URL (do backend) em { b64, mime, ...rest }. O data URL JÁ é base64,
// só extrai a parte depois da vírgula — não precisa converter ArrayBuffer.
function prepImageFromDataUrl(input) {
  const { dataUrl, ...rest } = input;
  const m = /^data:([^;]+);base64,(.+)$/i.exec(String(dataUrl || '').trim());
  if (!m) throw new Error('image dataUrl inválido');
  return { b64: m[2], mime: m[1], ...rest };
}

// Fecha o offscreen pra liberar memória — chamar quando a fila esvaziar.
export async function closeOffscreen() {
  if (await hasOffscreenDocument()) {
    try { await chrome.offscreen.closeDocument(); } catch (_) {}
  }
}

/**
 * Workaround: chrome.downloads.download não consegue acessar blob: URLs criadas no SW
 * em algumas versões do Chrome. O offscreen tem contexto DOM real onde URL.createObjectURL
 * gera URLs que o download manager enxerga. Esta função delega pro offscreen criar a URL
 * e disparar o download.
 *
 * Retorna { id, filename } igual chrome.downloads.download.
 */
export async function downloadInOffscreen({ blob, filename }) {
  await ensureOffscreenDocument();
  // Converte blob → base64 (chrome.runtime.sendMessage JSON-ifica ArrayBuffer pra {})
  const buf = await blob.arrayBuffer();
  const u8 = new Uint8Array(buf);
  let s = '';
  const chunk = 0x8000;
  for (let i = 0; i < u8.length; i += chunk) {
    s += String.fromCharCode.apply(null, u8.subarray(i, i + chunk));
  }
  const b64 = btoa(s);
  const mime = blob.type || 'video/mp4';
  // 1) Offscreen cria a blob URL (DOM context — chrome.downloads enxerga)
  const reply = await new Promise((resolve, reject) => {
    chrome.runtime.sendMessage({
      target: 'offscreen-transcode',
      type: 'CREATE_BLOB_URL',
      b64,
      mime
    }, (r) => {
      if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); return; }
      resolve(r);
    });
  });
  if (!reply?.ok || !reply.url) throw new Error(reply?.error || 'offscreen createBlobUrl falhou');
  // 2) SW chama chrome.downloads.download com a URL do offscreen
  try {
    const id = await chrome.downloads.download({
      url: reply.url,
      filename,
      saveAs: false,
      conflictAction: 'uniquify'
    });
    // Revoga após 2min pra dar tempo do download começar
    setTimeout(() => {
      chrome.runtime.sendMessage({
        target: 'offscreen-transcode',
        type: 'REVOKE_BLOB_URL',
        url: reply.url
      }).catch(() => {});
    }, 120000);
    return { id, filename };
  } catch (err) {
    // Tenta revogar a URL imediatamente em caso de erro
    chrome.runtime.sendMessage({
      target: 'offscreen-transcode',
      type: 'REVOKE_BLOB_URL',
      url: reply.url
    }).catch(() => {});
    throw new Error(`chrome.downloads via offscreen URL falhou: ${err?.message || err}`);
  }
}
