// inject-tt.js — roda no PAGE WORLD do TikTok (não no isolated world da extensão).
// Patcha window.fetch e XHR pra capturar respostas do endpoint /api/post/item_list/
// e variantes. As URLs assinadas dos vídeos vivem dentro da resposta — uma vez
// capturadas, postamos pro content script via window.postMessage.
//
// Por que page-world: o isolated world tem seu próprio `window.fetch` e não vê
// as chamadas que o app React do TikTok faz. Patches em isolated não interceptam nada.

(function () {
  if (window.__IS_TT_INJECT__) return;
  window.__IS_TT_INJECT__ = true;

  const POST_TYPE = 'IS_EXT_TIKTOK_ITEMS';
  const ENDPOINTS_RE = /\/api\/(post|recommend|challenge|search|comment|user|favorite)\/item|item_list\b/i;

  function safeParse(text) {
    try { return JSON.parse(text); } catch (_) { return null; }
  }

  function extractItems(body) {
    if (!body || typeof body !== 'object') return [];
    const list = Array.isArray(body.itemList) ? body.itemList
              : Array.isArray(body.items) ? body.items
              : Array.isArray(body.data) ? body.data
              : [];
    const out = [];
    for (const item of list) {
      // TikTok item shapes variam — tenta uma série de paths conhecidos.
      const id = item.id || item.aweme_id || item.item?.id;
      const video = item.video || item.item?.video || item.aweme_info?.video;
      if (!id || !video) continue;

      // playAddr é a versão SEM watermark; downloadAddr é com.
      const playUrl = video.playAddr || video.play_addr?.url_list?.[0] || video.playAddrList?.[0];
      const downloadUrl = video.downloadAddr || video.download_addr?.url_list?.[0];
      const cover = video.cover || video.cover?.url_list?.[0] || video.originCover;

      if (!playUrl && !downloadUrl) continue;

      out.push({
        platform: 'tiktok',
        id: String(id),
        url: playUrl || downloadUrl,
        url_watermark: downloadUrl || null,
        cover: typeof cover === 'string' ? cover : (Array.isArray(cover) ? cover[0] : null),
        duration_ms: (video.duration || 0) * 1000,
        width: video.width || video.ratio?.width || null,
        height: video.height || video.ratio?.height || null,
        author: item.author?.uniqueId || item.author?.unique_id || item.author?.username || null,
        author_name: item.author?.nickname || null,
        desc: item.desc || item.content || '',
        stats: {
          play_count: item.stats?.playCount ?? item.statistics?.play_count ?? null,
          digg_count: item.stats?.diggCount ?? item.statistics?.digg_count ?? null,
          comment_count: item.stats?.commentCount ?? null,
          share_count: item.stats?.shareCount ?? null
        },
        captured_at: Date.now()
      });
    }
    return out;
  }

  // Cache de items vistos pra o overlay Save (botão no card do vídeo).
  // Quando o user clica em Save, o overlay precisa do item completo (url do
  // vídeo, author, etc), e isso vem desse cache cross-reference por video_id.
  const _seenItems = new Map(); // video_id → item

  function postItems(items, source_url) {
    if (!items?.length) return;
    for (const it of items) {
      if (it?.id) _seenItems.set(String(it.id), it);
    }
    window.postMessage({ __is_ext: true, type: POST_TYPE, items, source_url }, '*');
  }

  // Listener pro tt-overlay.js (isolated world) requisitar um item por ID.
  // Responde com window.postMessage pq isolated→page só rola via DOM events.
  window.addEventListener('message', (event) => {
    if (event.source !== window) return;
    const d = event.data;
    if (!d || d.__is_ext_tt_get_item !== true) return;
    const item = _seenItems.get(String(d.video_id));
    window.postMessage({
      __is_ext_tt_get_item_reply: true,
      requestId: d.requestId,
      item: item || null
    }, '*');
  });

  // ── Patch fetch ─────────────────────────────────────────────────────────
  const origFetch = window.fetch;
  window.fetch = async function (input, init) {
    const url = typeof input === 'string' ? input : (input?.url || '');
    const response = await origFetch.apply(this, arguments);
    try {
      if (ENDPOINTS_RE.test(url)) {
        // Clona pra ler sem consumir o stream original (o app TikTok ainda vai consumi-lo)
        const clone = response.clone();
        clone.text().then((text) => {
          const body = safeParse(text);
          const items = extractItems(body);
          if (items.length) postItems(items, url);
        }).catch(() => {});
      }
    } catch (_) { /* nunca pode travar o site */ }
    return response;
  };

  // ── Patch XHR ──────────────────────────────────────────────────────────
  const origOpen = XMLHttpRequest.prototype.open;
  const origSend = XMLHttpRequest.prototype.send;
  XMLHttpRequest.prototype.open = function (method, url) {
    this.__is_url = url;
    return origOpen.apply(this, arguments);
  };
  XMLHttpRequest.prototype.send = function () {
    const xhr = this;
    if (ENDPOINTS_RE.test(xhr.__is_url || '')) {
      xhr.addEventListener('load', () => {
        try {
          const text = xhr.responseText;
          if (!text) return;
          const body = safeParse(text);
          const items = extractItems(body);
          if (items.length) postItems(items, xhr.__is_url);
        } catch (_) {}
      });
    }
    return origSend.apply(this, arguments);
  };

  // ── Fetch bridge: tenta credentials modes em ordem ────────────────────
  // TikTok CDN aceita 'include' (usa cookies); IG CDN só 'omit' (signed URL).
  // Tentamos os 2 — se um falha por CORS/credentials, o outro pode passar.
  //
  // VALIDAÇÕES (vieram depois de um vídeo corrompido ter ido pro Drive):
  //  - content-length vs buf.byteLength: detecta download truncado
  //  - magic bytes 'ftyp' (offset 4): detecta arquivo não-MP4 / dados quebrados
  //  - tamanho mínimo 1KB: detecta resposta de erro disfarçada
  //  - retry até 3x com backoff curto: cobre falhas transientes do CDN
  async function doFetch(url, { maxAttempts = 3 } = {}) {
    const errors = [];
    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      for (const credentials of ['include', 'omit']) {
        const tag = `A${attempt}/${credentials}`;
        try {
          const r = await origFetch.call(window, url, { credentials });
          if (!r.ok) { errors.push(`${tag}:HTTP ${r.status}`); continue; }
          const ct = r.headers.get('content-type') || 'video/mp4';
          if (/^text\/html/i.test(ct)) { errors.push(`${tag}:CT=${ct}`); continue; }
          const buf = await r.arrayBuffer();
          // Validação mínima: só rejeita se for obviamente lixo (< 100 bytes).
          // Removidas as checagens de content-length e ftyp magic que estavam
          // rejeitando downloads legítimos (streaming, chunked transfer, etc).
          if (buf.byteLength < 100) {
            errors.push(`${tag}:empty ${buf.byteLength}b`);
            continue;
          }
          return { buf, mime: ct };
        } catch (e) {
          errors.push(`${tag}:${e?.message || e}`);
        }
      }
      if (attempt < maxAttempts) await new Promise(r => setTimeout(r, 300 * attempt));
    }
    throw new Error(`download falhou após ${maxAttempts} tentativas: ${errors.join(' | ')}`);
  }

  window.addEventListener('message', async (event) => {
    if (event.source !== window) return;
    const d = event.data;
    if (!d || d.__is_ext_fetch !== true) return;
    const { url, requestId } = d;
    try {
      const { buf, mime } = await doFetch(url);
      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);
      window.postMessage({ __is_ext_fetch_reply: true, requestId, b64, mime }, '*');
    } catch (e) {
      window.postMessage({ __is_ext_fetch_reply: true, requestId, error: e?.message || String(e) }, '*');
    }
  });

  // Notifica que o inject tá ativo
  window.postMessage({ __is_ext: true, type: 'IS_EXT_TT_READY' }, '*');
  console.log('[IS-Ext/TT inject] patches + fetch bridge instalados');
})();
