import { chromium } from "@playwright/test";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";

import { WP4_07_VISUAL_THRESHOLDS } from "../tests/visual-performance/wp4-07-fixture.mjs";

const evidenceIndex = process.argv.indexOf("--evidence");
const phaseIndex = process.argv.indexOf("--phase");
const evidenceDirectory = resolve(evidenceIndex >= 0 ? process.argv[evidenceIndex + 1] : "");
const phase = phaseIndex >= 0 ? process.argv[phaseIndex + 1] : "green";
if (evidenceIndex < 0 || !process.argv[evidenceIndex + 1] || !["green", "red"].includes(phase)) throw new Error("Usage: --evidence <directory> [--phase red|green]");

const scenarios = ["editor.png", "canvas.png", "export-dialog.png"];
for (const scenario of scenarios) {
  for (const browser of ["chrome", "edge"]) {
    const path = resolve(evidenceDirectory, browser, scenario);
    if (!existsSync(path)) throw new Error(`missing screenshot: ${browser}/${scenario}`);
  }
}

const browser = await chromium.launch({ channel: "msedge", headless: true });
const page = await browser.newPage();
const results = [];
try {
  for (const scenario of scenarios) {
    const chrome = readFileSync(resolve(evidenceDirectory, "chrome", scenario)).toString("base64");
    const edge = readFileSync(resolve(evidenceDirectory, "edge", scenario)).toString("base64");
    const comparison = await page.evaluate(async ({ chromeBase64, edgeBase64, threshold }) => {
      const decode = async (base64) => {
        const binary = atob(base64);
        const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
        return createImageBitmap(new Blob([bytes], { type: "image/png" }));
      };
      const [chromeImage, edgeImage] = await Promise.all([decode(chromeBase64), decode(edgeBase64)]);
      if (chromeImage.width !== edgeImage.width || chromeImage.height !== edgeImage.height) {
        return { dimensions_match: false, chrome: { height: chromeImage.height, width: chromeImage.width }, edge: { height: edgeImage.height, width: edgeImage.width } };
      }
      const surface = new OffscreenCanvas(chromeImage.width, chromeImage.height);
      const context = surface.getContext("2d", { willReadFrequently: true });
      context.drawImage(chromeImage, 0, 0);
      const chromePixels = context.getImageData(0, 0, chromeImage.width, chromeImage.height).data;
      context.clearRect(0, 0, chromeImage.width, chromeImage.height);
      context.drawImage(edgeImage, 0, 0);
      const edgePixels = context.getImageData(0, 0, edgeImage.width, edgeImage.height).data;
      let significant = 0;
      let maximumChannelDelta = 0;
      for (let index = 0; index < chromePixels.length; index += 4) {
        const deltas = [0, 1, 2, 3].map((channel) => Math.abs(chromePixels[index + channel] - edgePixels[index + channel]));
        maximumChannelDelta = Math.max(maximumChannelDelta, ...deltas);
        if (deltas.some((delta) => delta > threshold)) significant += 1;
      }
      const total = chromeImage.width * chromeImage.height;
      chromeImage.close();
      edgeImage.close();
      return {
        dimensions_match: true,
        height: surface.height,
        maximum_channel_delta: maximumChannelDelta,
        significant_pixel_count: significant,
        significant_pixel_ratio: significant / total,
        significant_pixel_threshold: threshold,
        total_pixels: total,
        width: surface.width,
      };
    }, { chromeBase64: chrome, edgeBase64: edge, threshold: WP4_07_VISUAL_THRESHOLDS.channel_delta_significant });
    results.push({ scenario, ...comparison });
  }
} finally {
  await browser.close();
}

const chromeLayout = JSON.parse(readFileSync(resolve(evidenceDirectory, "chrome", "layout-boxes.json"), "utf8"));
const edgeLayout = JSON.parse(readFileSync(resolve(evidenceDirectory, "edge", "layout-boxes.json"), "utf8"));
const greenInputsEligible = [chromeLayout, edgeLayout].every((input) => input.eligible_for_green === true && input.harness_mode === "real_archive");
const layoutComparisons = Object.keys(chromeLayout.layout_boxes).map((name) => {
  const chromeBox = chromeLayout.layout_boxes[name];
  const edgeBox = edgeLayout.layout_boxes[name];
  const deltas = Object.fromEntries(["height", "width", "x", "y"].map((field) => [field, Math.abs(chromeBox[field] - edgeBox[field])]));
  return { deltas, maximum_delta_px: Math.max(...Object.values(deltas)), name };
});
const visualPassed = results.every((item) => item.dimensions_match && item.significant_pixel_ratio <= WP4_07_VISUAL_THRESHOLDS.significant_pixel_ratio_max);
const layoutPassed = layoutComparisons.every((item) => item.maximum_delta_px <= WP4_07_VISUAL_THRESHOLDS.boundary_delta_px_max);
const overall = {
  eligible_for_green: phase === "green" && greenInputsEligible,
  phase,
  scenarios: results,
  status: visualPassed && layoutPassed ? "within_threshold" : "threshold_exceeded_manual_review_required",
  thresholds: WP4_07_VISUAL_THRESHOLDS,
};
const layout = {
  comparisons: layoutComparisons,
  eligible_for_green: phase === "green" && greenInputsEligible,
  status: layoutPassed ? "within_threshold" : "threshold_exceeded_manual_review_required",
  threshold_px: WP4_07_VISUAL_THRESHOLDS.boundary_delta_px_max,
};
mkdirSync(dirname(resolve(evidenceDirectory, "pixel-diff.json")), { recursive: true });
writeFileSync(resolve(evidenceDirectory, "pixel-diff.json"), `${JSON.stringify(overall, null, 2)}\n`);
writeFileSync(resolve(evidenceDirectory, "layout-boxes.json"), `${JSON.stringify(layout, null, 2)}\n`);
writeFileSync(resolve(evidenceDirectory, "manual-review.json"), `${JSON.stringify({
  eligible_for_green: false,
  known_alternatives: ["COLOR002", "COLOR008", "COLOR016", "DYN012"],
  required_note: "DYN012 uses FONT081 Lexend Deca as the declared substitute.",
  status: phase === "green" ? "pending_project_owner_review" : "pending_wp5_final_renderer_and_project_owner_review",
}, null, 2)}\n`);
console.log(JSON.stringify({ layout_status: layout.status, phase, visual_status: overall.status }, null, 2));
if (phase === "green" && (!greenInputsEligible || !visualPassed || !layoutPassed)) process.exit(1);
