"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ScreenshotComparisonError = void 0;
exports.takeFullPageScreenshot = takeFullPageScreenshot;
exports.compareScreenScreenshot = compareScreenScreenshot;
exports.compareElementScreenshot = compareElementScreenshot;
const promises_1 = __importDefault(require("node:fs/promises"));
const node_path_1 = __importDefault(require("node:path"));
const pixelmatch_1 = __importDefault(require("pixelmatch"));
const pngjs_1 = require("pngjs");
const locator_1 = require("./locator");
class ScreenshotComparisonError extends Error {
    expectedPath;
    actualPath;
    diffPath;
    constructor(message, details = {}) {
        super(message);
        this.name = 'ScreenshotComparisonError';
        this.expectedPath = details.expectedPath;
        this.actualPath = details.actualPath;
        this.diffPath = details.diffPath;
    }
}
exports.ScreenshotComparisonError = ScreenshotComparisonError;
async function takeFullPageScreenshot(page) {
    return page.screenshot({ fullPage: true });
}
async function compareScreenScreenshot(page, screenshotPath, options) {
    const paths = resolveScreenshotPaths(screenshotPath, options);
    const actual = await page.screenshot({ fullPage: false });
    return finalizeScreenshotComparison(actual, paths, options);
}
async function compareElementScreenshot(page, target, screenshotPath, options) {
    const locator = resolveElementLocator(page, target, options.selectors, options.index ?? 0);
    await locator.scrollIntoViewIfNeeded();
    const masks = await resolveMaskLocators(page, options.mask ?? [], options.selectors);
    const actual = await locator.screenshot({ mask: masks });
    return finalizeScreenshotComparison(actual, resolveScreenshotPaths(screenshotPath, options), options);
}
function resolveElementLocator(page, target, selectors, index) {
    const selector = (0, locator_1.resolveSelectorAlias)(target, selectors);
    const base = selector ? page.locator(selector) : (0, locator_1.locatorByAny)(page, target, selectors);
    return (0, locator_1.atIndex)(base, index);
}
async function resolveMaskLocators(page, maskTargets, selectors) {
    const masks = [];
    for (const maskTarget of maskTargets) {
        const locator = resolveElementLocator(page, maskTarget, selectors, 0);
        const box = await locator.boundingBox().catch(() => null);
        if (box) {
            masks.push(locator);
        }
    }
    return masks;
}
async function finalizeScreenshotComparison(actual, paths, options) {
    await promises_1.default.mkdir(node_path_1.default.dirname(paths.actualPath), { recursive: true });
    await promises_1.default.writeFile(paths.actualPath, actual);
    const expectedExists = await pathExists(paths.expectedPath);
    if (!expectedExists) {
        throw new ScreenshotComparisonError(`Screenshot ${paths.relativePath} baseline not found: ${paths.expectedPath}`, {
            expectedPath: paths.expectedPath,
            actualPath: paths.actualPath
        });
    }
    const expected = await promises_1.default.readFile(paths.expectedPath);
    const comparison = compareImageBuffers(actual, expected, options.threshold ?? 0.1);
    if (comparison.diffPixels > (options.maxDiffPixels ?? 0)) {
        await promises_1.default.mkdir(node_path_1.default.dirname(paths.diffPath), { recursive: true });
        await promises_1.default.writeFile(paths.diffPath, comparison.diffBuffer);
        throw new ScreenshotComparisonError(`Screenshot mismatch for ${paths.expectedPath}: diff pixels ${comparison.diffPixels} exceeded allowed ${options.maxDiffPixels ?? 0}.`, {
            expectedPath: paths.expectedPath,
            actualPath: paths.actualPath,
            diffPath: paths.diffPath
        });
    }
    return {
        match: true,
        diffPixels: comparison.diffPixels,
        expectedPath: paths.expectedPath,
        actualPath: paths.actualPath,
        diffPath: await pathExists(paths.diffPath) ? paths.diffPath : undefined
    };
}
function resolveScreenshotPaths(screenshotPath, options) {
    const relative = normalizeRelativeScreenshotPath(screenshotPath);
    const cwd = options.cwd ?? process.cwd();
    return {
        relativePath: relative,
        expectedPath: node_path_1.default.join(node_path_1.default.resolve(cwd, options.expectedDir), relative),
        actualPath: node_path_1.default.join(node_path_1.default.resolve(cwd, options.actualDir), relative),
        diffPath: node_path_1.default.join(node_path_1.default.resolve(cwd, options.diffDir), relative)
    };
}
function normalizeRelativeScreenshotPath(value) {
    const normalized = value.replace(/\\/g, '/').trim();
    if (!normalized) {
        throw new Error('A relative screenshot path is required.');
    }
    if (node_path_1.default.isAbsolute(normalized) || normalized.split('/').some((segment) => segment === '..')) {
        throw new Error(`Invalid relative screenshot path "${value}".`);
    }
    return normalized.endsWith('.png') ? normalized : `${normalized}.png`;
}
async function pathExists(filePath) {
    try {
        await promises_1.default.access(filePath);
        return true;
    }
    catch {
        return false;
    }
}
function compareImageBuffers(actual, expected, threshold) {
    try {
        const actualPng = pngjs_1.PNG.sync.read(actual);
        const expectedPng = pngjs_1.PNG.sync.read(expected);
        if (actualPng.width !== expectedPng.width || actualPng.height !== expectedPng.height) {
            return {
                diffPixels: Math.max(actualPng.width * actualPng.height, expectedPng.width * expectedPng.height),
                diffBuffer: actual
            };
        }
        const diff = new pngjs_1.PNG({ width: actualPng.width, height: actualPng.height });
        const diffPixels = (0, pixelmatch_1.default)(actualPng.data, expectedPng.data, diff.data, actualPng.width, actualPng.height, { threshold });
        return {
            diffPixels,
            diffBuffer: pngjs_1.PNG.sync.write(diff)
        };
    }
    catch {
        const diffPixels = actual.equals(expected) ? 0 : 1;
        return {
            diffPixels,
            diffBuffer: actual
        };
    }
}
