#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
import { createRequire } from "node:module";
import { pathToFileURL } from "node:url";

const require = createRequire(import.meta.url);

const [target, outDir = "deck_qa"] = process.argv.slice(2);

if (!target) {
  console.error("Usage: node scripts/check_deck.mjs <deck.html-or-url> <out-dir>");
  process.exit(1);
}

const puppeteer = await loadPuppeteer();

const chromePath = findChrome();
if (!chromePath) {
  console.error("Chrome was not found. Set CHROME_PATH to your Chrome executable.");
  process.exit(1);
}

fs.mkdirSync(outDir, { recursive: true });

const url = /^https?:\/\//i.test(target) || /^file:\/\//i.test(target)
  ? target
  : pathToFileURL(path.resolve(target)).href;

const browser = await puppeteer.launch({
  executablePath: chromePath,
  headless: "new",
  args: ["--no-sandbox", "--disable-setuid-sandbox"]
});

const page = await browser.newPage();
await page.setViewport({ width: 1600, height: 1000, deviceScaleFactor: 1 });
await page.goto(url, { waitUntil: "networkidle0" });

const slideCount = await page.$$eval(".slide", (nodes) => nodes.length);
if (!slideCount) {
  await browser.close();
  throw new Error("No .slide elements found.");
}

const reports = [];
for (let i = 0; i < slideCount; i += 1) {
  await page.evaluate((index) => {
    if (typeof window.show === "function") {
      window.show(index);
      return;
    }
    const slides = [...document.querySelectorAll(".slide")];
    slides.forEach((slide, n) => slide.classList.toggle("active", n === index));
  }, i);
  await sleep(80);

  const deck = await page.$(".deck");
  const screenshotPath = path.join(outDir, `slide-${String(i + 1).padStart(2, "0")}.png`);
  await deck.screenshot({ path: screenshotPath });

  reports.push(await page.evaluate((slideNumber) => {
    const badWords = ["旧文", "你之前", "基于既有", "引用", "批准", "内部来源", "詹老师", "会前沟通"];
    const deck = document.querySelector(".deck");
    const active = document.querySelector(".slide.active");
    const deckRect = rect(deck);
    const slideRect = rect(active);
    const text = active?.innerText || "";
    const boxes = [...active.querySelectorAll([
      "h1", "h2", ".lead", ".body", ".note", ".footer span",
      ".cover-panel .row", ".section-title p", ".card h3", ".card p",
      ".answer h3", ".answer p", ".answer .quote", ".detail h3", ".detail li",
      ".step b", ".step span", ".matrix div", ".check", ".emphasis",
      ".plain-list .item b", ".plain-list .item span"
    ].join(","))]
      .filter((node) => {
        const style = getComputedStyle(node);
        const box = rect(node);
        return style.display !== "none" && style.visibility !== "hidden" && box.width > 1 && box.height > 1;
      })
      .map((node) => ({ tag: node.tagName, className: node.className, text: node.innerText, box: rect(node) }));

    const outOfDeck = boxes.filter(({ box }) =>
      box.left < deckRect.left - 2 ||
      box.top < deckRect.top - 2 ||
      box.right > deckRect.right + 2 ||
      box.bottom > deckRect.bottom + 2
    ).length;

    let overlaps = 0;
    for (let a = 0; a < boxes.length; a += 1) {
      for (let b = a + 1; b < boxes.length; b += 1) {
        const area = intersectionArea(boxes[a].box, boxes[b].box);
        if (area > 48) overlaps += 1;
      }
    }

    return {
      slide: slideNumber,
      title: active?.querySelector("h1,h2")?.innerText?.trim() || "",
      overflow: {
        vertical: Math.max(0, active.scrollHeight - active.clientHeight),
        horizontal: Math.max(0, active.scrollWidth - active.clientWidth)
      },
      outOfDeck,
      overlaps,
      badWords: badWords.filter((word) => text.includes(word)),
      slideFrame: {
        width: Math.round(slideRect.width),
        height: Math.round(slideRect.height)
      }
    };

    function rect(node) {
      const r = node.getBoundingClientRect();
      return {
        left: r.left,
        top: r.top,
        right: r.right,
        bottom: r.bottom,
        width: r.width,
        height: r.height
      };
    }

    function intersectionArea(a, b) {
      const width = Math.max(0, Math.min(a.right, b.right) - Math.max(a.left, b.left));
      const height = Math.max(0, Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top));
      return width * height;
    }
  }, i + 1));
}

const palette = await page.evaluate(() => {
  const root = getComputedStyle(document.documentElement);
  return {
    ink: root.getPropertyValue("--ink").trim(),
    primary: root.getPropertyValue("--teal").trim(),
    accent: root.getPropertyValue("--gold").trim(),
    paper: root.getPropertyValue("--paper").trim()
  };
});

await browser.close();

const problems = reports.flatMap((report) => {
  const list = [];
  if (report.overflow.vertical > 2 || report.overflow.horizontal > 2) list.push(`slide ${report.slide}: overflow`);
  if (report.outOfDeck > 0) list.push(`slide ${report.slide}: ${report.outOfDeck} elements out of deck`);
  if (report.overlaps > 0) list.push(`slide ${report.slide}: ${report.overlaps} possible overlaps`);
  if (report.badWords.length) list.push(`slide ${report.slide}: bad words ${report.badWords.join(", ")}`);
  return list;
});

const report = {
  target: url,
  slideCount,
  palette,
  problems,
  reports
};

fs.writeFileSync(path.join(outDir, "qa-report.json"), JSON.stringify(report, null, 2), "utf8");
fs.writeFileSync(path.join(outDir, "contact-sheet.html"), contactSheet(slideCount), "utf8");

console.log(JSON.stringify({
  slideCount,
  problems: problems.length,
  report: path.join(outDir, "qa-report.json"),
  contactSheet: path.join(outDir, "contact-sheet.html")
}, null, 2));

if (problems.length) process.exitCode = 2;

function contactSheet(count) {
  const images = Array.from({ length: count }, (_, i) => {
    const n = String(i + 1).padStart(2, "0");
    return `<figure><img src="slide-${n}.png"><figcaption>${n}</figcaption></figure>`;
  }).join("\n");
  return `<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8">
<title>Deck Contact Sheet</title>
<style>
body{margin:24px;background:#eef3f6;font-family:-apple-system,BlinkMacSystemFont,"PingFang SC",sans-serif;color:#253142}
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
figure{margin:0;background:#fffdf8;border:1px solid #dbe1e6;border-radius:8px;padding:8px}
img{display:block;width:100%;border-radius:4px}
figcaption{font-size:12px;color:#657184;margin-top:6px;text-align:center}
</style>
<div class="grid">
${images}
</div>
</html>`;
}

function findChrome() {
  const candidates = [
    process.env.CHROME_PATH,
    "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
    "/Applications/Chrome.app/Contents/MacOS/Chrome",
    "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
    "/usr/bin/google-chrome",
    "/usr/bin/chromium-browser",
    "/usr/bin/chromium"
  ].filter(Boolean);
  return candidates.find((candidate) => fs.existsSync(candidate));
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function loadPuppeteer() {
  try {
    const mod = await import("puppeteer-core");
    return mod.default || mod;
  } catch {
    const candidates = [
      path.resolve(process.cwd(), "node_modules", "puppeteer-core"),
      path.resolve(path.dirname(new URL(import.meta.url).pathname), "..", "node_modules", "puppeteer-core"),
      "/tmp/deckqa/node_modules/puppeteer-core"
    ];
    for (const candidate of candidates) {
      if (!fs.existsSync(path.join(candidate, "package.json"))) continue;
      try {
        return require(candidate);
      } catch {
        // Try the next known dependency location.
      }
    }
  }
  console.error("Missing dependency: puppeteer-core. Run `npm install puppeteer-core` in this skill folder or use an existing workspace install.");
  process.exit(1);
}
