// Prism Translate — PDF bilingual viewer. Renders pages with pdf.js, extracts text,
// translates paragraphs via the background worker. All local.
import * as pdfjsLib from "../vendor/pdfjs/pdf.min.mjs";
import { LANGS } from "../lib/storage.js";
import { mountBrand } from "../lib/brand.js";

pdfjsLib.GlobalWorkerOptions.workerSrc = chrome.runtime.getURL("vendor/pdfjs/pdf.worker.min.mjs");

const $ = (s) => document.querySelector(s);
const pagesEl = $("#pages");
const statusEl = $("#status");
const dropEl = $("#drop");
const MAX_PAGES = 60;

let cfg = { targetLang: "zh-CN", sourceLang: "auto", scenario: "general" };

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 });
  }));
}
const status = (t) => (statusEl.textContent = t);

async function init() {
  mountBrand();
  const r = await sendBg({ cmd: "getState" });
  if (r.ok) {
    cfg.targetLang = r.settings.targetLang;
    cfg.sourceLang = r.settings.sourceLang;
    cfg.scenario = r.settings.scenario;
    const t = $("#target");
    LANGS.filter((l) => l.code !== "auto").forEach((l) => {
      const o = document.createElement("option");
      o.value = l.code; o.textContent = l.name; if (l.code === cfg.targetLang) o.selected = true;
      t.appendChild(o);
    });
    const sc = $("#scenario");
    r.prompts.forEach((p) => {
      const o = document.createElement("option");
      o.value = p.id; o.textContent = p.name; if (p.id === cfg.scenario) o.selected = true;
      sc.appendChild(o);
    });
    t.addEventListener("change", (e) => (cfg.targetLang = e.target.value));
    sc.addEventListener("change", (e) => (cfg.scenario = e.target.value));
  }

  $("#open").addEventListener("click", () => $("#file").click());
  $("#file").addEventListener("change", (e) => { if (e.target.files[0]) openFile(e.target.files[0]); });

  dropEl.addEventListener("dragover", (e) => { e.preventDefault(); dropEl.classList.add("over"); });
  dropEl.addEventListener("dragleave", () => dropEl.classList.remove("over"));
  dropEl.addEventListener("drop", (e) => {
    e.preventDefault(); dropEl.classList.remove("over");
    const f = e.dataTransfer.files[0];
    if (f && f.type === "application/pdf") openFile(f);
  });

  // ?url= or ?file= to auto-load a PDF (e.g. from a context-menu link)
  const url = new URLSearchParams(location.search).get("url");
  if (url) openUrl(url);
}

async function openFile(file) {
  const buf = await file.arrayBuffer();
  renderPdf(new Uint8Array(buf));
}
async function openUrl(url) {
  try {
    status("下载 PDF…");
    const res = await fetch(url);
    if (!res.ok) { status("下载失败：HTTP " + res.status); return; }
    const buf = await res.arrayBuffer();
    renderPdf(new Uint8Array(buf));
  } catch (e) { status("下载失败：" + e.message); }
}

function buildParagraphs(items) {
  let line = "";
  const lines = [];
  for (const it of items) {
    line += it.str;
    if (it.hasEOL) { lines.push(line.trim()); line = ""; }
  }
  if (line.trim()) lines.push(line.trim());

  const cjk = (ch) => !!ch && /[⺀-鿿　-〿＀-￯]/.test(ch);
  const paras = [];
  let cur = "";
  for (const ln of lines) {
    if (!ln) { if (cur) { paras.push(cur); cur = ""; } continue; }
    if (!cur) cur = ln;
    else {
      // boundary-aware spacing: only omit the space when BOTH sides are CJK
      const a = cur[cur.length - 1], b = ln[0];
      cur += (cjk(a) && cjk(b)) ? "" : " ";
      cur += ln;
    }
    if (cur.length > 600) { paras.push(cur); cur = ""; } // relaxed cap to avoid mid-sentence chops
  }
  if (cur) paras.push(cur);
  // keep letters AND numbers (so numeric/table rows aren't silently dropped)
  return paras.filter((p) => /[\p{L}\p{N}]/u.test(p));
}

async function renderPdf(data) {
  dropEl.classList.add("hidden");
  pagesEl.innerHTML = "";
  status("解析中…");
  let pdf;
  try {
    pdf = await pdfjsLib.getDocument({ data, disableFontFace: true }).promise;
  } catch (e) { status("无法打开 PDF：" + e.message); dropEl.classList.remove("hidden"); return; }

  const total = Math.min(pdf.numPages, MAX_PAGES);
  status(`共 ${pdf.numPages} 页${pdf.numPages > MAX_PAGES ? `（仅翻译前 ${MAX_PAGES} 页）` : ""}`);

  for (let i = 1; i <= total; i++) {
    try {
      const page = await pdf.getPage(i);
      const viewport = page.getViewport({ scale: 1.5 });

      const row = document.createElement("div");
      row.className = "page-row";
      row.innerHTML =
        `<div class="page-canvas-wrap"><div class="page-num">第 ${i} / ${pdf.numPages} 页</div><canvas></canvas></div>` +
        `<div class="trans-col"><div class="trans-head"><span class="page-num">译文</span><span class="toggle-orig">显示/隐藏原文</span></div></div>`;
      pagesEl.appendChild(row);

      const col = row.querySelector(".trans-col");
      row.querySelector(".toggle-orig").addEventListener("click", () =>
        col.querySelectorAll(".tblock").forEach((b) => b.classList.toggle("show-orig"))
      );

      // 1) Extract + translate text FIRST — the core feature, never blocked by painting.
      const tc = await page.getTextContent();
      const paras = buildParagraphs(tc.items);
      const blocks = paras.map((p) => {
        const b = document.createElement("div");
        b.className = "tblock";
        b.innerHTML = `<div class="tt loading">翻译中…</div><div class="to">${escapeHtml(p)}</div>`;
        col.appendChild(b);
        return b;
      });
      translatePage(paras, blocks);
      status(`第 ${i}/${total} 页…`);

      // 2) Paint the page image in the background; a slow/stalled paint must not block translation.
      const canvas = row.querySelector("canvas");
      const ctx = canvas.getContext("2d");
      canvas.width = viewport.width;
      canvas.height = viewport.height;
      page.render({ canvasContext: ctx, viewport }).promise.catch((e) => console.warn("[prism-pdf] render", i, e));
    } catch (e) {
      console.error("[prism-pdf] page", i, "failed:", e);
      status(`第 ${i} 页失败：${e && (e.message || e)}`);
    }
  }
  status(`完成 · ${total} 页`);
}

async function translatePage(paras, blocks) {
  const chunkSize = 10;
  for (let i = 0; i < paras.length; i += chunkSize) {
    const slice = paras.slice(i, i + chunkSize);
    const r = await sendBg({
      cmd: "translateBatch",
      texts: slice,
      targetLang: cfg.targetLang,
      sourceLang: cfg.sourceLang,
      scenario: cfg.scenario,
    });
    slice.forEach((_, j) => {
      const tt = blocks[i + j].querySelector(".tt");
      if (r.ok) { tt.classList.remove("loading"); tt.textContent = r.translations[j] || ""; }
      else { tt.classList.remove("loading"); tt.classList.add("err"); tt.textContent = "⚠ " + (r.error || "翻译失败"); }
    });
  }
}

function escapeHtml(s) {
  return String(s ?? "").replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" }[c]));
}

init();
