// Prism Translate — live bilingual subtitles for YouTube.
// Observes the caption DOM, translates each line, renders original + translation over the player.
(() => {
  if (window.__prismVideoSubs) return;
  window.__prismVideoSubs = true;

  let enabled = true;
  let target = "zh-CN", source = "auto", scenario = "general";
  let overlay = null, lastText = "", seq = 0, started = false;
  const cache = new Map();

  function sendBg(msg) {
    return new Promise((res) =>
      chrome.runtime.sendMessage(msg, (r) => {
        if (chrome.runtime.lastError) res({ ok: false, error: chrome.runtime.lastError.message });
        else res(r || { ok: false });
      })
    );
  }

  async function loadCfg() {
    const r = await sendBg({ cmd: "getState" });
    if (r.ok) {
      target = r.settings.targetLang;
      source = r.settings.sourceLang;
      scenario = r.settings.scenario;
      enabled = r.settings.videoSubs !== false;
    }
  }

  const player = () => document.querySelector("#movie_player, .html5-video-player");

  function ensureOverlay() {
    const p = player();
    if (!p) return null;
    if (overlay && overlay.isConnected) return overlay;
    overlay = document.createElement("div");
    overlay.id = "prism-vsub";
    overlay.innerHTML = '<div class="prism-vsub-orig"></div><div class="prism-vsub-trans"></div>';
    if (getComputedStyle(p).position === "static") p.style.position = "relative";
    p.appendChild(overlay);
    return overlay;
  }

  function currentCaption() {
    const segs = document.querySelectorAll(".ytp-caption-segment");
    if (!segs.length) return "";
    return Array.from(segs).map((s) => s.textContent).join(" ").replace(/\s+/g, " ").trim();
  }

  async function translate(text) {
    if (cache.has(text)) return cache.get(text);
    const r = await sendBg({ cmd: "translateOne", text, targetLang: target, sourceLang: source, scenario });
    const tr = r.ok ? r.text : "";
    if (tr) cache.set(text, tr);
    return tr;
  }

  function onCaptionChanged() {
    if (!enabled) return;
    const text = currentCaption();
    if (text === lastText) return;
    lastText = text;
    const o = ensureOverlay();
    if (!o) return;
    if (!text) { o.style.display = "none"; return; }
    o.style.display = "block";
    o.querySelector(".prism-vsub-orig").textContent = text;
    o.querySelector(".prism-vsub-trans").textContent = "…";
    const mySeq = ++seq;
    const forText = text;
    translate(text).then((tr) => {
      if (mySeq !== seq) return; // a newer caption superseded this one
      if (tr) {
        o.querySelector(".prism-vsub-trans").textContent = tr;
      } else {
        // failed/empty translation: clear and allow this same line to retry later
        o.querySelector(".prism-vsub-trans").textContent = "";
        if (lastText === forText) lastText = "";
      }
    });
  }

  // Observe ONLY the player subtree (never the whole page) to avoid a mutation firehose.
  let timer = null;
  let observedNode = null;
  const obs = new MutationObserver(() => {
    clearTimeout(timer);
    timer = setTimeout(onCaptionChanged, 120);
  });
  function attachObserver() {
    const node = player();
    if (!node || node === observedNode) return;
    obs.disconnect();
    obs.observe(node, { subtree: true, childList: true, characterData: true });
    observedNode = node;
  }

  function start() {
    if (started) return;
    started = true;
    document.documentElement.classList.add("prism-vsub-on");
    attachObserver();
    addToggle();
    // player may hydrate late (cold load / nav from home); keep trying briefly.
    let tries = 0;
    const iv = setInterval(() => {
      attachObserver();
      addToggle();
      if ((observedNode && document.getElementById("prism-vsub-toggle")) || ++tries > 20) clearInterval(iv);
    }, 700);
  }
  function stop() {
    started = false;
    obs.disconnect();
    observedNode = null;
    document.documentElement.classList.remove("prism-vsub-on");
    overlay?.remove();
    overlay = null;
    lastText = "";
  }

  // Single source of truth for flipping state; optionally persists to settings.
  async function setEnabled(next, persist) {
    enabled = next;
    document.getElementById("prism-vsub-toggle")?.classList.toggle("on", enabled);
    if (enabled) { start(); onCaptionChanged(); } else { stop(); }
    if (persist) {
      const r = await sendBg({ cmd: "getState" });
      if (r.ok) sendBg({ cmd: "saveSettings", settings: { ...r.settings, videoSubs: enabled } });
    }
  }

  function addToggle() {
    const p = player();
    if (!p) return; // never attach to body — the visibility CSS needs the player ancestor
    const existing = document.getElementById("prism-vsub-toggle");
    if (existing) {
      if (existing.parentElement === p) return;
      existing.remove(); // stale button from a torn-down player
    }
    const btn = document.createElement("button");
    btn.id = "prism-vsub-toggle";
    btn.title = "Prism 双语字幕开关";
    btn.innerHTML = '<span class="prism-vsub-dot"></span>双语字幕';
    btn.classList.toggle("on", enabled);
    btn.addEventListener("click", (e) => { e.stopPropagation(); setEnabled(!enabled, true); });
    p.appendChild(btn);
  }

  chrome.runtime.onMessage.addListener((msg, s, resp) => {
    if (msg.cmd === "toggleVideoSubs") { setEnabled(!enabled, true); resp({ ok: true, enabled }); }
    else if (msg.cmd === "ping") { resp({ ok: true, video: true }); }
  });

  // YouTube is an SPA; re-acquire player/observer/toggle after navigations.
  let lastUrl = location.href;
  setInterval(() => {
    if (location.href !== lastUrl) {
      lastUrl = location.href; lastText = ""; observedNode = null;
      if (enabled) setTimeout(() => { attachObserver(); addToggle(); }, 1000);
    }
  }, 1500);

  loadCfg().then(() => {
    if (enabled) start();
    else setTimeout(addToggle, 1500);
  });
})();
