import { NativeImage, nativeImage } from 'electron/common';
import { BrowserWindow } from 'electron/main';

import { AssertionError, expect } from 'chai';

import path = require('node:path');

import { createArtifact } from './lib/artifacts';
import { closeAllWindows } from './lib/window-helpers';

const FIXTURE_PATH = path.resolve(
  __dirname,
  'fixtures',
  'api',
  'corner-smoothing'
);

/**
 * Rendered images may "match" but slightly differ due to rendering artifacts
 * like anti-aliasing and vector path resolution, among others. This tolerance
 * is the cutoff for whether two images "match" or not.
 *
 * From testing, matching images were found to have an average global difference
 * up to about 1.3 and mismatched images were found to have a difference of at
 * least about 7.3.
 *
 * See the documentation on `compareImages` for more information.
 */
const COMPARISON_TOLERANCE = 2.5;

/**
 * Compares the average global difference of two images to determine if they
 * are similar enough to "match" each other.
 *
 * "Average global difference" is the average difference of pixel components
 * (RGB each) across an entire image.
 *
 * The cutoff for matching/not-matching is defined by the `COMPARISON_TOLERANCE`
 * constant.
 */
function compareImages (img1: NativeImage, img2: NativeImage): boolean {
  expect(img1.getSize()).to.deep.equal(
    img2.getSize(),
    'Cannot compare images with different sizes. Run tests with --force-device-scale-factor=1'
  );

  const bitmap1 = img1.toBitmap();
  const bitmap2 = img2.toBitmap();

  const { width, height } = img1.getSize();

  let totalDiff = 0;
  for (let x = 0; x < width; x++) {
    for (let y = 0; y < height; y++) {
      const index = (x + y * width) * 4;
      const pixel1 = bitmap1.subarray(index, index + 4);
      const pixel2 = bitmap2.subarray(index, index + 4);
      const diff =
        Math.abs(pixel1[0] - pixel2[0]) +
        Math.abs(pixel1[1] - pixel2[1]) +
        Math.abs(pixel1[2] - pixel2[2]);
      totalDiff += diff;
    }
  }

  const avgDiff = totalDiff / (width * height);
  return avgDiff <= COMPARISON_TOLERANCE;
}

/**
 * Recipe for tests.
 *
 * The page is rendered, captured as an image, then compared to an expected
 * result image.
 */
async function pageCaptureTestRecipe (
  pagePath: string,
  expectedImgPath: string,
  artifactName: string,
  cornerSmoothingAvailable: boolean = true
): Promise<void> {
  const w = new BrowserWindow({
    show: false,
    width: 800,
    height: 600,
    useContentSize: true,
    webPreferences: {
      disableBlinkFeatures: cornerSmoothingAvailable ? undefined : 'ElectronCSSCornerSmoothing'
    }
  });
  await w.loadFile(pagePath);
  w.show();

  // Wait for a render frame to prepare the page.
  await w.webContents.executeJavaScript(
    'new Promise((resolve) => { requestAnimationFrame(() => resolve()); })'
  );

  const actualImg = await w.webContents.capturePage();
  expect(actualImg.isEmpty()).to.be.false('Failed to capture page image');

  const expectedImg = nativeImage.createFromPath(expectedImgPath);
  expect(expectedImg.isEmpty()).to.be.false(
    'Failed to read expected reference image'
  );

  const matches = compareImages(actualImg, expectedImg);
  if (!matches) {
    const artifactFileName = `corner-rounding-expected-${artifactName}.png`;
    await createArtifact(artifactFileName, actualImg.toPNG());

    throw new AssertionError(
      `Actual image did not match expected reference image. Actual: "${artifactFileName}" in artifacts, Expected: "${path.relative(
        path.resolve(__dirname, '..'),
        expectedImgPath
      )}" in source`
    );
  }
}

describe('-electron-corner-smoothing', () => {
  afterEach(async () => {
    await closeAllWindows();
  });

  describe('shape', () => {
    for (const available of [true, false]) {
      it(`matches the reference with web preference = ${available}`, async () => {
        await pageCaptureTestRecipe(
          path.join(FIXTURE_PATH, 'shape', 'test.html'),
          path.join(FIXTURE_PATH, 'shape', `expected-${available}.png`),
          `shape-${available}`,
          available
        );
      });
    }
  });

  describe('system-ui keyword', () => {
    const { platform } = process;
    it(`matches the reference for platform = ${platform}`, async () => {
      await pageCaptureTestRecipe(
        path.join(FIXTURE_PATH, 'system-ui-keyword', 'test.html'),
        path.join(
          FIXTURE_PATH,
          'system-ui-keyword',
          `expected-${platform}.png`
        ),
        `system-ui-${platform}`
      );
    });
  });
});
