/* =========================================================================
   La Morgue — TikTok Comments · content script (mundo aislado)
   Escucha los comentarios que captura hook.js, hace scroll SOLO por el panel
   de comentarios (nunca por el feed de videos), los ordena por interacción
   (likes + respuestas) y exporta la publicación y los comentarios a un .txt.
   ========================================================================= */

(() => {
  if (window.__ttcCollectorLoaded) return;
  window.__ttcCollectorLoaded = true;

  // ---------- Configuración (ajústala a tu gusto) ----------
  const CFG = {
    topN: 0,               // 0 = exporta todos los capturados; o pon 100 para el top 100
    replyWeight: 5,        // cuánto pesa cada respuesta frente a 1 like
    scrollMinMs: 650,      // pausa mínima entre pasos de scroll
    scrollMaxMs: 1100,     // pausa máxima entre pasos de scroll
    stableLimit: 6,        // ciclos sin progreso para dar por terminado
  };

  // ---------- Estado ----------
  const commentsMap = new Map(); // cid -> registro (top-level)
  const repliesMap = new Map();  // parentCid -> [registros de respuesta]
  let isCapturing = false;
  let isFinished = false;
  let noMoreSeen = false;
  let manualMode = false;
  let noProgress = 0;
  let lastCount = 0;
  let lastScrollSig = -1;
  let scroller = null;

  const rand = (a, b) => Math.random() * (b - a) + a;
  const sleep = (ms) =>
    new Promise((res) => {
      const t0 = performance.now();
      (function c() {
        if (!isCapturing) return res();
        if (performance.now() - t0 >= ms) return res();
        setTimeout(c, 100);
      })();
    });

  // ---------- Normalización de un comentario de la API ----------
  function toRecord(c) {
    const cid = c.cid || c.comment_id || c.id;
    if (!cid) return null;
    const u = c.user || {};
    return {
      cid: String(cid),
      nickname: u.nickname || u.unique_id || 'Desconocido',
      handle: u.unique_id || '',
      text: c.text || '',
      likes: c.digg_count != null ? c.digg_count : (c.like_count != null ? c.like_count : 0),
      replyCount: c.reply_comment_total != null ? c.reply_comment_total : 0,
      time: c.create_time != null ? c.create_time : 0,
      parent: c.reply_id || c.reply_to_reply_id || c.parent_comment_id || '',
    };
  }

  function ingest(list, isReply) {
    if (!Array.isArray(list)) return 0;
    let added = 0;
    for (const c of list) {
      const rec = toRecord(c);
      if (!rec) continue;
      if (isReply) {
        const key = rec.parent || 'sueltas';
        if (!repliesMap.has(key)) repliesMap.set(key, []);
        const arr = repliesMap.get(key);
        if (!arr.some((r) => r.cid === rec.cid)) {
          arr.push(rec);
          added++;
        }
      } else if (!commentsMap.has(rec.cid)) {
        commentsMap.set(rec.cid, rec);
        added++;
      }
    }
    return added;
  }

  // ---------- Escucha del hook ----------
  window.addEventListener('message', (ev) => {
    if (ev.source !== window) return;
    const d = ev.data;
    if (!d || d.source !== 'ttc-hook') return;
    const json = d.data || {};
    const list = json.comments || json.comment_list || [];
    ingest(list, d.kind === 'replies');
    if (d.kind === 'comments') {
      if (json.has_more === 0 || json.has_more === false) noMoreSeen = true;
    }
  });

  // ---------- Localizar el panel de comentarios (NO el feed de video) ----------
  // Elementos que son parte de la LISTA de comentarios (no el botón del video).
  function commentItems() {
    return document.querySelectorAll(
      '[data-e2e^="comment-level"], [data-e2e^="comment-username"], [data-e2e="comment-text"]'
    );
  }

  // Un contenedor que tenga un <video> dentro es el feed, no los comentarios.
  function hasVideo(el) {
    return !!el.querySelector('video');
  }

  function isScrollableStyle(el) {
    const s = getComputedStyle(el);
    return /(auto|scroll)/.test(s.overflowY);
  }

  // Sube desde cada comentario hasta el ancestro scrolleable que NO contenga video.
  function findCommentsScroller() {
    const items = commentItems();
    for (const item of items) {
      let el = item.parentElement;
      while (el && el !== document.body) {
        if (isScrollableStyle(el) && el.clientHeight > 120 && !hasVideo(el)) {
          return el;
        }
        el = el.parentElement;
      }
    }
    // Respaldo: escanear contenedores scrolleables que tengan comentarios y no video
    let best = null;
    let bestH = -1;
    document.querySelectorAll('div, ul, section').forEach((el) => {
      if (!isScrollableStyle(el)) return;
      if (el.clientHeight < 150) return;
      if (hasVideo(el)) return;
      if (!el.querySelector('[data-e2e^="comment-"]')) return;
      if (el.scrollHeight > bestH) {
        bestH = el.scrollHeight;
        best = el;
      }
    });
    return best;
  }

  function ensureScroller() {
    if (scroller && document.contains(scroller) && !hasVideo(scroller)) return scroller;
    scroller = findCommentsScroller();
    return scroller;
  }

  function dispatchWheel(el) {
    if (!el) return;
    try {
      el.dispatchEvent(new WheelEvent('wheel', { deltaY: 800, bubbles: true, cancelable: true }));
    } catch (e) {}
  }

  function scrollSignature() {
    return scroller ? Math.round(scroller.scrollTop) : -1;
  }

  // Empujón de scroll SOLO dentro del panel de comentarios
  function doScroll() {
    const sc = ensureScroller();
    if (!sc) return false; // sin panel: modo manual (no tocamos la ventana/feed)

    const before = sc.scrollTop;
    sc.scrollTop = Math.min(sc.scrollHeight, sc.scrollTop + Math.max(600, sc.clientHeight * 0.9));
    dispatchWheel(sc);

    // Si ya no avanza (tope), intentar traer el último comentario del panel a la vista
    if (sc.scrollTop <= before + 2) {
      const items = sc.querySelectorAll('[data-e2e^="comment-level"], [data-e2e^="comment-username"]');
      if (items.length) {
        try {
          items[items.length - 1].scrollIntoView({ block: 'end' });
        } catch (e) {}
      }
    }
    return true;
  }

  // ---------- Bucle de captura ----------
  async function captureLoop() {
    ensureScroller();
    while (isCapturing) {
      const canAuto = doScroll();
      manualMode = !canAuto;

      await sleep(rand(CFG.scrollMinMs, CFG.scrollMaxMs));
      if (!isCapturing) break;

      const count = commentsMap.size;
      const sig = scrollSignature();
      const grew = count > lastCount;
      const moved = sig !== lastScrollSig;
      lastCount = count;
      lastScrollSig = sig;

      if (grew || moved) noProgress = 0;
      else noProgress++;

      if ((noMoreSeen && !grew && noProgress >= 2) || noProgress >= CFG.stableLimit) {
        finish();
        break;
      }
    }
  }

  function start() {
    if (isCapturing) return;
    isCapturing = true;
    isFinished = false;
    noProgress = 0;
    lastCount = commentsMap.size;
    lastScrollSig = -1;
    scroller = null;
    captureLoop();
  }

  function stop() {
    isCapturing = false;
  }

  function finish() {
    isCapturing = false;
    isFinished = true;
  }

  // ---------- Metadatos del video (mejor esfuerzo) ----------
  function textOf(sel) {
    const el = document.querySelector(sel);
    return el ? (el.textContent || '').trim() : '';
  }

  function videoMeta() {
    const m = location.pathname.match(/@([^/]+)\/video\/(\d+)/);
    const handle = m ? m[1] : '';
    const videoId = m ? m[2] : '';
    const desc =
      textOf('[data-e2e="browse-video-desc"]') ||
      textOf('[data-e2e="video-desc"]') ||
      '';
    const likes = textOf('[data-e2e="like-count"]') || textOf('[data-e2e="browse-like-count"]');
    const commentCount =
      textOf('[data-e2e="comment-count"]') || textOf('[data-e2e="browse-comment-count"]');
    return { handle, videoId, desc, likes, commentCount };
  }

  // ---------- Construcción del .txt ----------
  function interaction(rec) {
    return (rec.likes || 0) + CFG.replyWeight * (rec.replyCount || 0);
  }

  function indentBlock(text, pad) {
    return (text || '').split('\n').map((l) => pad + l).join('\n');
  }

  function rankedComments() {
    const arr = Array.from(commentsMap.values());
    arr.forEach((r) => (r.__interaction = interaction(r)));
    arr.sort((a, b) => b.__interaction - a.__interaction);
    return CFG.topN > 0 ? arr.slice(0, CFG.topN) : arr;
  }

  function buildTxt() {
    if (commentsMap.size === 0) return null;
    const meta = videoMeta();
    const ranked = rankedComments();
    const L = [];
    const sep = '='.repeat(66);
    const sub = '-'.repeat(66);

    L.push(sep);
    L.push('LA MORGUE · INSTITUTO FORENSE DE MARKETING');
    L.push('EVIDENCIA · VIDEO DE TIKTOK');
    L.push(sep);
    L.push('Autor: @' + (meta.handle || 'desconocido'));
    if (meta.desc) L.push('Descripción: ' + meta.desc);
    if (meta.likes) L.push('Likes del video: ' + meta.likes);
    if (meta.commentCount) L.push('Comentarios (según TikTok): ' + meta.commentCount);
    L.push('Comentarios capturados: ' + commentsMap.size);
    L.push(
      'URL: https://www.tiktok.com/@' + (meta.handle || '') + '/video/' + (meta.videoId || '')
    );
    L.push('Exportado: ' + new Date().toLocaleString());
    L.push('');
    L.push(sep);
    L.push(
      (CFG.topN > 0 ? 'TOP ' + ranked.length : ranked.length + ' COMENTARIOS') +
        ' POR INTERACCIÓN'
    );
    L.push('(interacción = likes + ' + CFG.replyWeight + ' × respuestas)');
    L.push(sep);
    L.push('');

    ranked.forEach((c, i) => {
      L.push(
        '#' + (i + 1) +
          '  @' + (c.handle || c.nickname) +
          '  ·  ' + c.likes + ' likes' +
          '  ·  ' + c.replyCount + ' respuestas' +
          '  ·  interacción ' + c.__interaction
      );
      L.push(c.text || '');

      const reps = repliesMap.get(c.cid);
      if (reps && reps.length) {
        reps
          .slice()
          .sort((a, b) => (b.likes || 0) - (a.likes || 0))
          .forEach((r) => {
            L.push('');
            L.push('    ↳ @' + (r.handle || r.nickname) + ' · ' + r.likes + ' likes');
            L.push(indentBlock(r.text || '', '      '));
          });
      }

      L.push('');
      L.push(sub);
      L.push('');
    });

    return L.join('\n');
  }

  function safeName(handle, id) {
    const base = (handle || 'tiktok').replace(/[^\w-]/gi, '').slice(0, 40);
    return 'tiktok_comentarios_' + (base || 'video') + (id ? '_' + id : '') + '.txt';
  }

  function download() {
    const txt = buildTxt();
    if (!txt) return { ok: false, error: 'no_data' };
    const meta = videoMeta();
    const blob = new Blob([txt], { type: 'text/plain;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = safeName(meta.handle, meta.videoId);
    document.body.appendChild(a);
    a.click();
    a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 1500);
    return { ok: true, count: commentsMap.size };
  }

  // ---------- Detección de página ----------
  function isVideoPage() {
    return /@[^/]+\/video\/\d+/.test(location.pathname);
  }

  // ---------- Mensajería con el popup ----------
  chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
    switch (msg && msg.type) {
      case 'PING':
        sendResponse({ ok: true, isVideo: isVideoPage() });
        break;
      case 'START':
        start();
        sendResponse({ ok: true, count: commentsMap.size, manual: manualMode });
        break;
      case 'STOP':
        stop();
        sendResponse({ ok: true, count: commentsMap.size });
        break;
      case 'EXPORT':
        sendResponse(download());
        break;
      case 'STATUS':
        sendResponse({
          capturing: isCapturing,
          finished: isFinished,
          manual: manualMode,
          count: commentsMap.size,
        });
        break;
      default:
        sendResponse({ ok: false, error: 'unknown' });
    }
    return true;
  });
})();
