/* =====================================================================
   LA MORGUE — Extractor de Comentarios de YouTube
   Instituto Forense de Marketing · por Andrés Licona · @luanlicona
   Panel flotante en Shadow DOM (aislado del CSS de YouTube).
   Scroll humano con pausas variables para no gatillar límites.
   ===================================================================== */
(function () {
  "use strict";
  if (window.__MORGUE_EXTRACTOR__) return;
  window.__MORGUE_EXTRACTOR__ = true;

  const IG_URL = "https://www.instagram.com/luanlicona";

  // Rangos de espera (ms) y de scroll (fracción de la altura visible) por nivel de riesgo.
  const RANGES = {
    seguro: { lo: 2600, hi: 5200, step: [0.45, 0.90], desc: "Pausas largas y humanas. Prácticamente indetectable." },
    medio:  { lo: 1300, hi: 2600, step: [0.60, 1.15], desc: "Balance entre velocidad y seguridad." },
    alto:   { lo: 450,  hi: 1100, step: [0.85, 1.50], desc: "Rápido. Puede gatillar límites de YouTube." }
  };

  const state = {
    running: false,
    level: "seguro",
    comments: new Map(),
    scrolls: 0,
    opts: { replies: true, author: true, likes: true }
  };
  let busy = false;          // true mientras runLoop() está cerrando/abriendo (evita doble arranque)
  let declaredTotal = null;  // total de comentarios declarado por YouTube (para el progreso)

  /* ------------------------------------------------------------------ */
  /* Estilos (Shadow DOM)                                               */
  /* ------------------------------------------------------------------ */
  const CSS = `
  *{ box-sizing:border-box; margin:0; padding:0; }
  .mg-panel{
    position:fixed; right:20px; bottom:20px; width:344px;
    font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;
    color:#f4f2ee; z-index:2147483647;
    background:linear-gradient(160deg,#15181f 0%,#0a0b0d 72%);
    border:1px solid rgba(224,17,34,.35); border-radius:18px;
    box-shadow:0 20px 60px rgba(0,0,0,.55), 0 0 42px rgba(224,17,34,.10);
    overflow:hidden; animation:mgIn .35s cubic-bezier(.2,.8,.2,1);
  }
  @keyframes mgIn{ from{opacity:0; transform:translateY(16px) scale(.98);} to{opacity:1; transform:none;} }
  .mg-hidden{ display:none !important; }
  .mg-head{ display:flex; align-items:center; justify-content:space-between; padding:14px 16px;
    background:linear-gradient(100deg, rgba(224,17,34,.18), rgba(192,18,31,.05));
    border-bottom:1px solid rgba(224,17,34,.18); cursor:grab; user-select:none; }
  .mg-head:active{ cursor:grabbing; }
  .mg-logo{ font-weight:800; font-size:18px; letter-spacing:2px; color:#fff; text-transform:uppercase; }
  .mg-logo span{ color:#e11122; }
  .mg-sub{ font-size:10px; letter-spacing:.8px; color:#9aa0a8; margin-top:1px; letter-spacing:.4px; }
  .mg-icon{ background:rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.12); color:#c3c8cf;
    width:26px; height:26px; border-radius:8px; cursor:pointer; font-size:14px; line-height:22px; text-align:center; }
  .mg-icon:hover{ background:rgba(255,255,255,.14); }
  .mg-body{ padding:16px; }
  .mg-stat{ display:flex; align-items:flex-end; justify-content:space-between; margin-bottom:12px; }
  .mg-count{ font-size:44px; font-weight:800; line-height:1;
    background:linear-gradient(180deg,#ff6b74,#c0121f); -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent;
    transition:transform .2s; }
  .mg-count.pulse{ transform:scale(1.09); }
  .mg-count-label{ font-size:12px; color:#9aa0a8; margin-top:3px; letter-spacing:.5px; }
  .mg-stat-side{ display:flex; gap:16px; }
  .mg-stat-side div{ display:flex; flex-direction:column; align-items:flex-end; }
  .mg-stat-side b{ font-size:16px; color:#f4f2ee; }
  .mg-stat-side span{ font-size:10px; color:#9aa0a8; }
  .mg-statusbar{ display:flex; align-items:center; gap:8px; font-size:12px; color:#c3c8cf;
    background:rgba(255,255,255,.04); border:1px solid rgba(255,255,255,.07); border-radius:10px; padding:8px 10px; }
  .mg-dot{ width:9px; height:9px; border-radius:50%; background:#6b7280; flex:0 0 auto; }
  .mg-dot.run{ background:#c0121f; animation:mgPulse 1.2s infinite; }
  .mg-dot.ok{ background:#3ddc84; }
  .mg-dot.warn{ background:#FFB020; }
  @keyframes mgPulse{ 0%{box-shadow:0 0 0 0 rgba(224,17,34,.5);} 70%{box-shadow:0 0 0 8px rgba(224,17,34,0);} 100%{box-shadow:0 0 0 0 rgba(224,17,34,0);} }
  .mg-prog{ height:5px; background:rgba(255,255,255,.06); border-radius:6px; margin:10px 0 4px; overflow:hidden; }
  .mg-prog-bar{ height:100%; width:0%; border-radius:6px; background:linear-gradient(90deg,#c0121f,#e11122); transition:width .3s; }
  .mg-prog-bar.active{ width:100%;
    background:linear-gradient(90deg, rgba(224,17,34,.18) 25%, #e11122 50%, rgba(224,17,34,.18) 75%);
    background-size:200% 100%; animation:mgShim 1.1s linear infinite; }
  @keyframes mgShim{ from{background-position:200% 0;} to{background-position:-200% 0;} }
  .mg-section-title{ font-size:10.5px; letter-spacing:1.2px; text-transform:uppercase; color:#9aa0a8; margin:15px 0 8px; }
  .mg-risk{ display:flex; gap:6px; }
  .mg-risk button{ flex:1; padding:9px 0; border-radius:10px; cursor:pointer; font-size:12.5px; font-weight:600;
    background:rgba(255,255,255,.05); border:1px solid rgba(255,255,255,.10); color:#c3c8cf; transition:.15s; }
  .mg-risk button:hover{ background:rgba(255,255,255,.09); }
  .mg-risk button:focus-visible{ outline:2px solid #e11122; outline-offset:1px; }
  .mg-risk button.on[data-l="seguro"]{ background:linear-gradient(180deg,#1e9e5d,#137a44); border-color:#3ddc84; color:#fff; box-shadow:0 4px 14px rgba(61,220,132,.3); }
  .mg-risk button.on[data-l="medio"]{ background:linear-gradient(180deg,#d59a1b,#a8730f); border-color:#FFB020; color:#fff; box-shadow:0 4px 14px rgba(255,176,32,.3); }
  .mg-risk button.on[data-l="alto"]{ background:linear-gradient(180deg,#d94b3f,#a8302a); border-color:#FF5C52; color:#fff; box-shadow:0 4px 14px rgba(255,92,82,.3); }
  .mg-risk-desc{ font-size:11px; color:#9aa0a8; margin-top:8px; min-height:15px; line-height:1.4; }
  .mg-opts{ display:flex; flex-direction:column; gap:10px; margin:15px 0 4px; }
  .mg-tog{ display:flex; align-items:center; gap:10px; font-size:12.5px; color:#c3c8cf; cursor:pointer; user-select:none; }
  .mg-tog input{ position:absolute; opacity:0; width:1px; height:1px; }
  .mg-tog .sw{ width:36px; height:20px; border-radius:20px; background:rgba(255,255,255,.13); position:relative; transition:.2s; flex:0 0 auto; }
  .mg-tog .sw::after{ content:''; position:absolute; top:2px; left:2px; width:16px; height:16px; border-radius:50%; background:#fff; transition:.2s; }
  .mg-tog input:checked + .sw{ background:linear-gradient(90deg,#c0121f,#e11122); }
  .mg-tog input:checked + .sw::after{ transform:translateX(16px); }
  .mg-tog input:focus-visible + .sw{ outline:2px solid #e11122; outline-offset:2px; }
  .mg-actions{ display:flex; gap:8px; margin-top:16px; }
  .mg-btn{ flex:1; padding:12px 0; border-radius:12px; font-size:13px; font-weight:700; cursor:pointer; border:none; transition:.15s; color:#fff; }
  .mg-btn-go{ background:linear-gradient(180deg,#c0121f,#8f0d16); box-shadow:0 8px 20px rgba(224,17,34,.35); }
  .mg-btn-go:hover{ transform:translateY(-1px); }
  .mg-btn-go.stop{ background:linear-gradient(180deg,#e0564b,#b83a30); box-shadow:0 8px 20px rgba(224,86,75,.30); }
  .mg-btn-dl{ background:rgba(255,255,255,.08); border:1px solid rgba(225,17,34,.40); color:#ff6b74; }
  .mg-btn-dl:hover:not(:disabled){ background:rgba(225,17,34,.14); }
  .mg-btn-dl:disabled{ opacity:.4; cursor:not-allowed; }
  .mg-foot{ text-align:center; font-size:10.5px; color:#7d828a; padding:10px; border-top:1px solid rgba(255,255,255,.05); }
  .mg-foot b{ color:#ff6b74; }
  .mg-fab{ position:fixed; right:22px; bottom:22px; width:56px; height:56px; border-radius:16px; z-index:2147483647;
    background:linear-gradient(150deg,#c0121f,#0a0b0d); border:1px solid rgba(225,17,34,.45); cursor:pointer;
    display:flex; align-items:center; justify-content:center;
    box-shadow:0 12px 30px rgba(0,0,0,.5), 0 0 24px rgba(225,17,34,.22); animation:mgIn .3s; }
  .mg-fab:hover{ transform:translateY(-2px); }
  .mg-fab-logo{ display:flex; flex-direction:column; align-items:center; justify-content:center; line-height:1; font-weight:800; color:#fff; font-size:16px; }
  .mg-fab-logo b{ font-size:10.5px; color:#e11122; letter-spacing:1.5px; margin-top:2px; }
  .mg-modal{ position:absolute; inset:0; background:rgba(5,6,10,.78); display:none;
    align-items:center; justify-content:center; z-index:5; border-radius:18px; }
  .mg-modal.show{ display:flex; animation:mgIn .25s; }
  .mg-modal-card{ position:relative; width:290px; margin:0 14px; padding:26px 22px; text-align:center;
    background:linear-gradient(160deg,#1b1f28,#0a0b0d); border:1px solid rgba(225,17,34,.30); border-radius:16px; overflow:hidden; }
  .mg-x{ position:absolute; top:8px; right:10px; z-index:2; background:none; border:none; color:#9aa0a8; font-size:20px; line-height:1; cursor:pointer; }
  .mg-x:hover{ color:#fff; }
  .mg-ig-glow{ position:absolute; top:-46px; left:50%; transform:translateX(-50%); width:190px; height:130px;
    background:radial-gradient(circle, rgba(225,17,34,.40), transparent 70%); pointer-events:none; }
  .mg-ig-logo{ position:relative; font-size:32px; margin-bottom:6px; }
  .mg-modal-card h3{ position:relative; margin:0 0 8px; font-size:18px; color:#fff; }
  .mg-modal-card p{ position:relative; margin:0 0 18px; font-size:13px; color:#c3c8cf; line-height:1.5; }
  .mg-ig-btn{ position:relative; display:block; text-decoration:none; padding:13px; border-radius:50px; font-weight:700; font-size:14px; color:#fff;
    background:linear-gradient(45deg,#feda75,#fa7e1e 25%,#d62976 55%,#962fbf 80%,#4f5bd5); box-shadow:0 8px 20px rgba(214,41,118,.4); }
  .mg-ig-btn:hover{ transform:translateY(-1px); }
  .mg-ig-close{ position:relative; margin-top:12px; background:none; border:none; color:#9aa0a8; font-size:12.5px; cursor:pointer; text-decoration:underline; }
  .mg-ig-close:hover{ color:#f4f2ee; }
  `;

  /* ------------------------------------------------------------------ */
  /* HTML del panel                                                     */
  /* ------------------------------------------------------------------ */
  const PANEL_HTML = `
    <div class="mg-fab mg-hidden" id="fab" title="La Morgue — Extractor" role="button" aria-label="Abrir La Morgue"><span class="mg-fab-logo">LM<b>MORGUE</b></span></div>
    <div class="mg-panel" id="panel">
      <div class="mg-head" id="head">
        <div>
          <div class="mg-logo">LA<span>·MORGUE</span></div>
          <div class="mg-sub">INSTRUMENTAL · Extractor de comentarios de YouTube</div>
        </div>
        <button class="mg-icon" id="min" title="Minimizar" aria-label="Minimizar panel">&#8211;</button>
      </div>
      <div class="mg-body">
        <div class="mg-stat">
          <div>
            <div class="mg-count" id="count">0</div>
            <div class="mg-count-label">comentarios</div>
          </div>
          <div class="mg-stat-side">
            <div><b id="replies">0</b><span>respuestas</span></div>
            <div><b id="scrolls">0</b><span>scrolls</span></div>
          </div>
        </div>
        <div class="mg-statusbar"><span class="mg-dot" id="dot"></span><span id="statustext">Listo para empezar</span></div>
        <div class="mg-prog"><div class="mg-prog-bar" id="progbar"></div></div>

        <div class="mg-section-title">Nivel de riesgo</div>
        <div class="mg-risk" id="risk">
          <button data-l="seguro" class="on">Seguro</button>
          <button data-l="medio">Medio</button>
          <button data-l="alto">Alto</button>
        </div>
        <div class="mg-risk-desc" id="riskdesc"></div>

        <div class="mg-section-title">Qué incluir</div>
        <div class="mg-opts">
          <label class="mg-tog"><input type="checkbox" id="o-replies" checked><span class="sw"></span>Incluir respuestas (si están expandidas)</label>
          <label class="mg-tog"><input type="checkbox" id="o-author" checked><span class="sw"></span>Incluir autor</label>
          <label class="mg-tog"><input type="checkbox" id="o-likes" checked><span class="sw"></span>Incluir likes</label>
        </div>

        <div class="mg-actions">
          <button class="mg-btn mg-btn-go" id="go">&#9654;&nbsp; Iniciar</button>
          <button class="mg-btn mg-btn-dl" id="dl" disabled>&#8681;&nbsp; Descargar .txt</button>
        </div>
      </div>
      <div class="mg-foot">by <b>@luanlicona</b> &middot; LA MORGUE · INSTITUTO FORENSE DE MARKETING</div>

      <div class="mg-modal" id="modal" role="dialog" aria-modal="true" aria-labelledby="mg-modaltitle">
        <div class="mg-modal-card">
          <button class="mg-x" id="igx" aria-label="Cerrar y descargar">&times;</button>
          <div class="mg-ig-glow"></div>
          <div class="mg-ig-logo">&#128248;</div>
          <h3 id="mg-modaltitle">Antes de descargar&hellip;</h3>
          <p>Esta herramienta es un regalo. Sígueme en Instagram y desbloquea más herramientas como esta.</p>
          <a class="mg-ig-btn" id="igfollow" href="${IG_URL}" target="_blank" rel="noopener">Seguir a @luanlicona</a>
          <button class="mg-ig-close" id="igclose">No, gracias &mdash; descargar ahora &rarr;</button>
        </div>
      </div>
    </div>
  `;

  /* ------------------------------------------------------------------ */
  /* Montaje                                                            */
  /* ------------------------------------------------------------------ */
  const hostEl = document.createElement("div");
  hostEl.id = "morgue-host";
  const root = hostEl.attachShadow({ mode: "open" });
  const styleEl = document.createElement("style");
  styleEl.textContent = CSS;
  root.appendChild(styleEl);
  const wrap = document.createElement("div");
  wrap.innerHTML = PANEL_HTML;
  root.appendChild(wrap);
  (document.body || document.documentElement).appendChild(hostEl);

  const $ = (sel) => root.querySelector(sel);
  const panel = $("#panel");
  const fab = $("#fab");
  const head = $("#head");
  const countEl = $("#count");
  const repliesEl = $("#replies");
  const scrollsEl = $("#scrolls");
  const dot = $("#dot");
  const statusText = $("#statustext");
  const progbar = $("#progbar");
  const riskWrap = $("#risk");
  const riskDesc = $("#riskdesc");
  const goBtn = $("#go");
  const dlBtn = $("#dl");
  const modal = $("#modal");

  /* ------------------------------------------------------------------ */
  /* Utilidades                                                         */
  /* ------------------------------------------------------------------ */
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
  const rand = (lo, hi) => lo + Math.random() * (hi - lo);
  const clean = (s) => (s || "").replace(/\s+/g, " ").trim();

  function humanDelay() {
    const r = RANGES[state.level];
    let d = rand(r.lo, r.hi);
    if (Math.random() < 0.12) d += rand(1500, 4000); // pausa ocasional de "lectura"
    return d;
  }

  /* ------------------------------------------------------------------ */
  /* Extracción                                                         */
  /* ------------------------------------------------------------------ */
  function threadNodes() {
    const threads = document.querySelectorAll("ytd-comment-thread-renderer");
    if (threads.length) return threads;
    // Fallback: layout nuevo con el componente suelto.
    return document.querySelectorAll("ytd-comment-view-model");
  }

  function getLikes(t) {
    const el = t.querySelector("#vote-count-middle, #vote-count-left");
    let likes = el ? clean(el.textContent) : "";
    if (!likes) {
      const btn = t.querySelector(
        '#like-button button[aria-label], ytd-toggle-button-renderer button[aria-label*="gust"], button[aria-label*="like" i]'
      );
      const lab = btn && btn.getAttribute("aria-label");
      if (lab) {
        const m = lab.match(/[\d.,]+/);
        if (m) likes = m[0];
      }
    }
    return likes;
  }

  function collect() {
    const threads = threadNodes();
    threads.forEach((t) => {
      // Comentario principal: el #content-text que NO está dentro del contenedor de respuestas.
      const repliesRoot = t.querySelector("ytd-comment-replies-renderer, #replies");
      const mainEl = t.querySelector("#comment #content-text, #main #content-text, #content-text");
      if (!mainEl) return;
      const main = clean(mainEl.textContent);
      if (!main) return;

      const authorEl = t.querySelector("#author-text, #header-author #author-text, a#author-text");
      const author = authorEl ? clean(authorEl.textContent) : "";
      const likes = getLikes(t);

      const replies = [];
      if (repliesRoot) {
        repliesRoot.querySelectorAll("#content-text").forEach((rt) => {
          const txt = clean(rt.textContent);
          if (txt && txt !== main) replies.push(txt);
        });
      }
      // Clave única sin truncar (evita colisiones que descartarían comentarios).
      const key = author + "" + main;
      state.comments.set(key, { author, text: main, likes, replies });
    });
  }

  function hasSpinner() {
    return !!document.querySelector(
      "#comments ytd-continuation-item-renderer, ytd-comments ytd-continuation-item-renderer"
    );
  }

  function getDeclaredTotal() {
    const el = document.querySelector(
      "ytd-comments-header-renderer #count, ytd-comments-header-renderer #count yt-formatted-string"
    );
    if (!el) return null;
    const m = clean(el.textContent).match(/[\d.,]+/);
    if (!m) return null;
    const n = parseInt(m[0].replace(/[.,\s]/g, ""), 10);
    return isNaN(n) ? null : n;
  }

  async function ensureCommentsMounted() {
    window.scrollTo({ top: Math.max(window.innerHeight * 1.2, 640), behavior: "smooth" });
    await sleep(1300);
  }

  async function humanScroll() {
    const r = RANGES[state.level];
    // Jitter ocasional: pequeño scroll hacia arriba (como una persona).
    if (Math.random() < 0.10) {
      window.scrollBy({ top: -(60 + Math.random() * 150), behavior: "smooth" });
      await sleep(260 + Math.random() * 360);
    }
    // A veces "solo leer": una pausa sin desplazarse.
    if (Math.random() < 0.06) {
      state.scrolls++;
      return;
    }
    // Fragmentar el desplazamiento en 2-3 pasos suaves (más orgánico que un salto grande).
    const total = window.innerHeight * rand(r.step[0], r.step[1]);
    const chunks = 2 + Math.floor(Math.random() * 2);
    for (let i = 0; i < chunks; i++) {
      window.scrollBy({ top: total / chunks, behavior: "smooth" });
      await sleep(120 + Math.random() * 220);
    }
    state.scrolls++;
  }

  async function runLoop() {
    setStatus("run");
    progbar.classList.add("active");
    declaredTotal = null;
    await ensureCommentsMounted();

    // Shorts: el DOM y el scroll son distintos.
    if (location.pathname.indexOf("/shorts/") === 0) {
      progbar.classList.remove("active");
      progbar.style.width = "0%";
      state.running = false;
      setStatus("nocomments", "En Shorts no está disponible. Abre el video en formato normal.");
      syncButtons();
      return;
    }

    // Comentarios desactivados / no cargan.
    let tries = 0;
    while (threadNodes().length === 0 && tries < 3 && state.running) {
      await sleep(1500);
      tries++;
    }
    if (threadNodes().length === 0) {
      progbar.classList.remove("active");
      progbar.style.width = "0%";
      state.running = false;
      setStatus("nocomments", "Este video no tiene comentarios disponibles.");
      syncButtons();
      return;
    }

    let last = 0;
    let stable = 0;
    while (state.running) {
      collect();
      render();
      await humanScroll();
      await sleep(humanDelay());

      const now = state.comments.size; // monótono (Map acumulativo): no se ve afectado por la virtualización.
      const nearBottom =
        window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 1400;
      if (now <= last && !hasSpinner()) stable++;
      else stable = 0;
      last = now;

      if (stable >= 5 && nearBottom) break;
    }

    collect();
    render();
    progbar.classList.remove("active");
    progbar.style.width = "100%";

    if (state.running) {
      state.running = false;
      setStatus("ok");
    } else {
      setStatus("stopped");
    }
    syncButtons();
  }

  /* ------------------------------------------------------------------ */
  /* Render / estado UI                                                 */
  /* ------------------------------------------------------------------ */
  let prevCount = 0;

  function render() {
    const n = state.comments.size;
    let rep = 0;
    state.comments.forEach((c) => (rep += c.replies.length));
    countEl.textContent = n;
    repliesEl.textContent = rep;
    scrollsEl.textContent = state.scrolls;
    if (n > prevCount) {
      countEl.classList.add("pulse");
      setTimeout(() => countEl.classList.remove("pulse"), 200);
    }
    prevCount = n;
    dlBtn.disabled = n === 0;

    if (state.running) {
      dot.classList.remove("ok", "warn");
      dot.classList.add("run");
      if (declaredTotal == null) declaredTotal = getDeclaredTotal();
      const tot = declaredTotal ? " / ~" + declaredTotal : "";
      statusText.textContent = "Extrayendo… " + n + tot + (hasSpinner() ? " · cargando más" : "");
    }
  }

  function setStatus(kind, custom) {
    dot.classList.remove("run", "ok", "warn");
    if (kind === "run") {
      dot.classList.add("run");
      statusText.textContent = "Extrayendo… simulación humana";
    } else if (kind === "ok") {
      dot.classList.add("ok");
      statusText.textContent = custom || "¡Listo! " + state.comments.size + " comentarios";
    } else if (kind === "stopped") {
      dot.classList.add("warn");
      statusText.textContent = custom || "Detenido · " + state.comments.size + " comentarios";
    } else if (kind === "nocomments") {
      dot.classList.add("warn");
      statusText.textContent = custom || "Sin comentarios disponibles";
    } else {
      statusText.textContent = custom || "Listo para empezar";
    }
  }

  function syncButtons() {
    if (state.running) {
      goBtn.innerHTML = "&#9632;&nbsp; Detener";
      goBtn.classList.add("stop");
    } else {
      goBtn.innerHTML = "&#9654;&nbsp; Iniciar";
      goBtn.classList.remove("stop");
    }
    dlBtn.disabled = state.comments.size === 0;
  }

  function setMinimized(min) {
    if (min) {
      panel.classList.add("mg-hidden");
      fab.classList.remove("mg-hidden");
    } else {
      fab.classList.add("mg-hidden");
      panel.classList.remove("mg-hidden");
    }
  }

  /* ------------------------------------------------------------------ */
  /* Descarga .txt + modal Instagram                                    */
  /* ------------------------------------------------------------------ */
  function videoTitle() {
    return clean(document.title.replace(/^\(\d+\)\s*/, "").replace(/ - YouTube$/, ""));
  }

  function buildTxt() {
    const L = [];
    L.push("========================================================");
    L.push("  Comentarios de YouTube  ·  LA MORGUE");
    L.push("  Instituto Forense de Marketing  ·  por Andrés Licona · @luanlicona");
    L.push("  Video: " + videoTitle());
    L.push("  URL:   " + location.href);
    L.push("  Total: " + state.comments.size + " comentarios");
    L.push("  Fecha: " + new Date().toLocaleString());
    L.push("========================================================");
    L.push("");
    let i = 1;
    state.comments.forEach((c) => {
      let line = "[" + i + "] ";
      if (state.opts.author && c.author) line += c.author;
      if (state.opts.likes && c.likes) line += "   (" + c.likes + " likes)";
      L.push(line.trim());
      L.push(c.text);
      if (state.opts.replies && c.replies.length) {
        c.replies.forEach((r) => L.push("    > " + r));
      }
      L.push("");
      i++;
    });
    return L.join("\n");
  }

  function doDownload() {
    const blob = new Blob([buildTxt()], { type: "text/plain;charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    const stamp = new Date().toISOString().slice(0, 19).replace(/[:T]/g, "-");
    a.href = url;
    a.download = "comentarios-youtube-morgue-" + stamp + ".txt";
    (document.body || document.documentElement).appendChild(a);
    a.click();
    a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 4000);
  }

  function showModal() {
    modal.classList.add("show");
    const c = $("#igclose");
    if (c) c.focus();
  }
  function hideModalAndDownload() {
    if (!modal.classList.contains("show")) return;
    modal.classList.remove("show");
    doDownload();
  }

  /* ------------------------------------------------------------------ */
  /* Wiring                                                             */
  /* ------------------------------------------------------------------ */
  riskWrap.addEventListener("click", (e) => {
    const b = e.target.closest("button[data-l]");
    if (!b) return;
    state.level = b.getAttribute("data-l");
    riskWrap.querySelectorAll("button").forEach((x) => x.classList.remove("on"));
    b.classList.add("on");
    riskDesc.textContent = RANGES[state.level].desc;
  });
  riskDesc.textContent = RANGES[state.level].desc;

  $("#o-replies").addEventListener("change", (e) => (state.opts.replies = e.target.checked));
  $("#o-author").addEventListener("change", (e) => (state.opts.author = e.target.checked));
  $("#o-likes").addEventListener("change", (e) => (state.opts.likes = e.target.checked));

  goBtn.addEventListener("click", () => {
    if (state.running) {
      state.running = false; // el loop lo detecta y cierra
      return;
    }
    if (busy) return; // aún cerrando un loop anterior
    busy = true;
    state.running = true;
    syncButtons();
    runLoop()
      .catch((err) => {
        console.error("[LA MORGUE] Error en la extracción:", err);
        state.running = false;
        setStatus("stopped");
        syncButtons();
      })
      .finally(() => {
        busy = false;
      });
  });

  dlBtn.addEventListener("click", () => {
    if (state.comments.size === 0) return;
    showModal();
  });
  $("#igclose").addEventListener("click", hideModalAndDownload);
  $("#igx").addEventListener("click", hideModalAndDownload);
  root.addEventListener("keydown", (e) => {
    if (e.key === "Escape") hideModalAndDownload();
  });

  $("#min").addEventListener("click", () => setMinimized(true));
  fab.addEventListener("click", () => setMinimized(false));

  // Arrastrar el panel por la cabecera.
  let drag = null;
  head.addEventListener("mousedown", (e) => {
    if (e.target.closest("button")) return;
    const rect = panel.getBoundingClientRect();
    drag = { x: e.clientX, y: e.clientY, left: rect.left, top: rect.top };
    panel.style.right = "auto";
    panel.style.bottom = "auto";
    panel.style.left = rect.left + "px";
    panel.style.top = rect.top + "px";
    e.preventDefault();
  });
  window.addEventListener("mousemove", (e) => {
    if (!drag) return;
    let nl = drag.left + (e.clientX - drag.x);
    let nt = drag.top + (e.clientY - drag.y);
    nl = Math.max(6, Math.min(window.innerWidth - panel.offsetWidth - 6, nl));
    nt = Math.max(6, Math.min(window.innerHeight - 80, nt));
    panel.style.left = nl + "px";
    panel.style.top = nt + "px";
  });
  window.addEventListener("mouseup", () => (drag = null));
  window.addEventListener("resize", () => {
    if (!panel.style.left) return;
    let l = parseFloat(panel.style.left);
    let t = parseFloat(panel.style.top);
    l = Math.max(6, Math.min(window.innerWidth - panel.offsetWidth - 6, l));
    t = Math.max(6, Math.min(window.innerHeight - 80, t));
    panel.style.left = l + "px";
    panel.style.top = t + "px";
  });

  // Navegación SPA de YouTube: al cambiar de video, reiniciar el estado.
  window.addEventListener("yt-navigate-finish", () => {
    state.running = false;
    state.comments.clear();
    state.scrolls = 0;
    prevCount = 0;
    declaredTotal = null;
    progbar.classList.remove("active");
    progbar.style.width = "0%";
    render();
    setStatus("idle");
    syncButtons();
  });

  // Alternar panel desde el icono de la barra (mensaje del service worker).
  chrome.runtime.onMessage.addListener((msg) => {
    if (msg && msg.type === "MORGUE_TOGGLE") {
      setMinimized(!panel.classList.contains("mg-hidden"));
    }
  });

  render();
  syncButtons();
  console.log("[LA MORGUE] Extractor de comentarios cargado. by @luanlicona");
})();
