#!/usr/bin/env node
/**
 * Generate pixel diffs for the side-by-side Angular/React screenshots
 * captured into `comparison-report/visual/screenshots/full/` and emit a
 * JSON file the report.html consumes for the quantitative side of the
 * parity comparison.
 *
 * Pairs are inferred by filename: every `react-{stem}.png` is matched
 * against `angular-{stem}.png` in the same directory. Diffs are written
 * to `comparison-report/visual/diffs-full/{stem}.png` (white = match,
 * red overlay = mismatched pixel) and stats land in
 * `comparison-report/visual/diff-stats.json`.
 *
 * Image-dimension mismatches are handled by padding both sides to the
 * larger canvas with white before pixel-matching — same approach as
 * `tests/parity/visual/screenshot-diff-multi.ts`.
 */

import { readdirSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { resolve, join } from "node:path";
import { PNG } from "pngjs";
import pixelmatch from "pixelmatch";

const ROOT = resolve(process.cwd());
const SRC_DIR = join(ROOT, "comparison-report/visual/screenshots/full");
const DIFFS_DIR = join(ROOT, "comparison-report/visual/diffs-full");
const STATS_PATH = join(ROOT, "comparison-report/visual/diff-stats.json");

mkdirSync(DIFFS_DIR, { recursive: true });

/**
 * Paint a rectangle white. Used to mask Angular-only test-env chrome
 * (orange `Тестовая версия` badge top-left, `rc/2026-04-06` build tag
 * top-right, chat-widget bubble bottom-right) before pixel-matching
 * so those regions don't pollute the parity score.
 */
function maskRect(png, x, y, w, h) {
  const x0 = Math.max(0, x);
  const y0 = Math.max(0, y);
  const x1 = Math.min(png.width, x + w);
  const y1 = Math.min(png.height, y + h);
  for (let yy = y0; yy < y1; yy++) {
    for (let xx = x0; xx < x1; xx++) {
      const idx = (yy * png.width + xx) * 4;
      png.data[idx] = 255;
      png.data[idx + 1] = 255;
      png.data[idx + 2] = 255;
      png.data[idx + 3] = 255;
    }
  }
}

/** Apply the standard chrome masks to both PNGs in-place. */
function applyChromeMasks(png) {
  // Top-left debug counter / orange "Тестовая версия" badge.
  maskRect(png, 0, 0, 200, 90);
  // Top-right `rc/...` build tag.
  maskRect(png, png.width - 240, 0, 240, 50);
  // Bottom-right chat widget.
  maskRect(png, png.width - 90, png.height - 90, 90, 90);
}

/**
 * Shift the contents of a PNG vertically by `dy` pixels in-place.
 * Negative `dy` moves content up (rows above are dropped, the gap at
 * the bottom is filled white). Used to compensate for the Angular
 * test-env chrome that pushes the main layout down by a fixed
 * amount, so that content rows align with React's chrome-less render
 * before pixel-matching.
 */
function shiftUp(png, dy) {
  if (dy <= 0) return;
  const w = png.width;
  const h = png.height;
  const stride = w * 4;
  // Move row y+dy → row y.
  for (let y = 0; y < h - dy; y++) {
    png.data.copyWithin(y * stride, (y + dy) * stride, (y + dy + 1) * stride);
  }
  // Fill last `dy` rows with white.
  for (let y = h - dy; y < h; y++) {
    for (let x = 0; x < w; x++) {
      const idx = (y * w + x) * 4;
      png.data[idx] = 255;
      png.data[idx + 1] = 255;
      png.data[idx + 2] = 255;
      png.data[idx + 3] = 255;
    }
  }
}

/**
 * Find the y-coordinate of the orange `Тестовая версия` badge in an
 * Angular screenshot, if present. The badge is solid #ff9000 / similar
 * orange — distinctive enough to detect by row-scanning. Returns -1
 * when no badge band is found.
 */
function findOrangeBadgeBottom(png) {
  const w = png.width;
  const h = Math.min(png.height, 200);
  let bandStart = -1;
  let bandEnd = -1;
  for (let y = 0; y < h; y++) {
    let orangeCount = 0;
    // Sample first 200px of the row for orange pixels.
    for (let x = 0; x < Math.min(200, w); x++) {
      const idx = (y * w + x) * 4;
      const r = png.data[idx];
      const g = png.data[idx + 1];
      const b = png.data[idx + 2];
      // ~ #ff9000 / #f37b09 family.
      if (r > 220 && g > 100 && g < 180 && b < 60) orangeCount++;
    }
    if (orangeCount > 30) {
      if (bandStart === -1) bandStart = y;
      bandEnd = y;
    }
  }
  return bandEnd === -1 ? -1 : bandEnd;
}

const files = readdirSync(SRC_DIR);
const reactFiles = files.filter((f) => f.startsWith("react-") && f.endsWith(".png"));

const stats = {};
let processed = 0;

for (const reactFile of reactFiles) {
  const stem = reactFile.slice("react-".length); // e.g. "onlineboard-start.png"
  const angularFile = `angular-${stem}`;
  if (!files.includes(angularFile)) {
    console.log(`  skip (no angular pair): ${reactFile}`);
    continue;
  }

  const aBuf = readFileSync(join(SRC_DIR, angularFile));
  const rBuf = readFileSync(join(SRC_DIR, reactFile));
  const aPng = PNG.sync.read(aBuf);
  const rPng = PNG.sync.read(rBuf);

  const width = Math.max(aPng.width, rPng.width);
  const height = Math.max(aPng.height, rPng.height);

  const pad = (src) => {
    const out = new PNG({ width, height });
    for (let i = 0; i < out.data.length; i += 4) {
      out.data[i] = 255; out.data[i + 1] = 255; out.data[i + 2] = 255; out.data[i + 3] = 255;
    }
    for (let y = 0; y < src.height; y++) {
      for (let x = 0; x < src.width; x++) {
        const srcIdx = (y * src.width + x) * 4;
        const dstIdx = (y * width + x) * 4;
        out.data[dstIdx]     = src.data[srcIdx];
        out.data[dstIdx + 1] = src.data[srcIdx + 1];
        out.data[dstIdx + 2] = src.data[srcIdx + 2];
        out.data[dstIdx + 3] = src.data[srcIdx + 3];
      }
    }
    return PNG.sync.read(PNG.sync.write(out));
  };

  const aP = pad(aPng);
  const rP = pad(rPng);

  // Strip Angular-only chrome (test-env badge, build tag, chat widget)
  // from both sides — those regions are environmental noise that
  // shouldn't drag the parity score down.
  applyChromeMasks(aP);
  applyChromeMasks(rP);

  const diffPng = new PNG({ width, height });

  const mismatchCount = pixelmatch(
    aP.data, rP.data, diffPng.data, width, height,
    { threshold: 0.1, alpha: 0.3 },
  );

  const totalPixels = width * height;
  const mismatchPct = (mismatchCount / totalPixels) * 100;
  const heightDiff = rPng.height - aPng.height;

  const diffName = stem;
  writeFileSync(join(DIFFS_DIR, diffName), PNG.sync.write(diffPng));

  const key = stem.replace(/\.png$/, "");
  stats[key] = {
    angular: `screenshots/full/${angularFile}`,
    react: `screenshots/full/${reactFile}`,
    diff: `diffs-full/${diffName}`,
    mismatchPct: Number(mismatchPct.toFixed(3)),
    mismatchCount,
    totalPixels,
    heightDiff,
    angular_w: aPng.width, angular_h: aPng.height,
    react_w: rPng.width, react_h: rPng.height,
  };

  processed++;
  console.log(`  ${stem}: ${mismatchPct.toFixed(2)}% diff (${mismatchCount.toLocaleString()} / ${totalPixels.toLocaleString()} px)`);
}

writeFileSync(STATS_PATH, JSON.stringify(stats, null, 2));
console.log(`\nDone — ${processed} pairs diffed.`);
console.log(`  Diffs: ${DIFFS_DIR}/`);
console.log(`  Stats: ${STATS_PATH}`);
