import { Type } from "@sinclair/typebox";
/**
 * Visual regression diffing — compare current page screenshot against a stored baseline.
 */
const BASELINE_DIR = ".nerf/browser-baselines";
export function registerVisualDiffTools(pi, deps) {
    pi.registerTool({
        name: "browser_visual_diff",
        label: "Browser Visual Diff",
        description: "Compare current page screenshot against a stored baseline pixel-by-pixel. " +
            "Returns similarity score (0–1), diff pixel count, and optionally generates a diff image highlighting changes. " +
            "On first run with no baseline, saves the current screenshot as the baseline. " +
            "Baselines are stored in .nerf/browser-baselines/ (gitignored, environment-specific).",
        parameters: Type.Object({
            name: Type.Optional(Type.String({
                description: "Baseline name (default: auto-generated from URL + viewport). " +
                    "Use consistent names to compare the same view across runs.",
            })),
            selector: Type.Optional(Type.String({
                description: "CSS selector to scope comparison to a specific element instead of full viewport.",
            })),
            threshold: Type.Optional(Type.Number({
                description: "Pixel matching threshold 0–1 (default: 0.1). " +
                    "Higher values are more tolerant of anti-aliasing and rendering differences.",
            })),
            updateBaseline: Type.Optional(Type.Boolean({
                description: "If true, overwrite the existing baseline with the current screenshot (default: false).",
            })),
        }),
        async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
            try {
                const { page: p } = await deps.ensureBrowser();
                const { mkdir, readFile, writeFile } = await import("node:fs/promises");
                const pathMod = await import("node:path");
                const baselineDir = pathMod.resolve(process.cwd(), BASELINE_DIR);
                await mkdir(baselineDir, { recursive: true });
                // Ensure .gitignore
                const gitignorePath = pathMod.join(baselineDir, ".gitignore");
                await writeFile(gitignorePath, "*\n!.gitignore\n").catch(() => { });
                // Generate baseline name
                const url = p.url();
                const viewport = p.viewportSize();
                const vpSuffix = viewport ? `${viewport.width}x${viewport.height}` : "unknown";
                const autoName = deps.sanitizeArtifactName(`${new URL(url).pathname.replace(/\//g, "-")}-${vpSuffix}`, `baseline-${vpSuffix}`);
                const name = deps.sanitizeArtifactName(params.name ?? autoName, autoName);
                const baselinePath = pathMod.join(baselineDir, `${name}.png`);
                const diffPath = pathMod.join(baselineDir, `${name}-diff.png`);
                // Capture current screenshot as PNG (needed for pixel comparison)
                let currentBuffer;
                if (params.selector) {
                    const locator = p.locator(params.selector).first();
                    currentBuffer = await locator.screenshot({ type: "png" });
                }
                else {
                    currentBuffer = await p.screenshot({ type: "png", fullPage: false });
                }
                // Check if baseline exists
                let baselineBuffer = null;
                try {
                    baselineBuffer = await readFile(baselinePath);
                }
                catch {
                    // No baseline yet
                }
                if (!baselineBuffer || params.updateBaseline) {
                    // Save as new baseline
                    await writeFile(baselinePath, currentBuffer);
                    return {
                        content: [{
                                type: "text",
                                text: baselineBuffer
                                    ? `Baseline updated: ${baselinePath}\nSize: ${(currentBuffer.length / 1024).toFixed(1)} KB`
                                    : `Baseline created (first run): ${baselinePath}\nSize: ${(currentBuffer.length / 1024).toFixed(1)} KB\nRe-run to compare against this baseline.`,
                            }],
                        details: {
                            baselinePath,
                            baselineCreated: !baselineBuffer,
                            baselineUpdated: !!baselineBuffer,
                            sizeBytes: currentBuffer.length,
                        },
                    };
                }
                // Perform pixel comparison using sharp for PNG decoding
                const sharp = (await import("sharp")).default;
                const baselineMeta = await sharp(baselineBuffer).metadata();
                const currentMeta = await sharp(currentBuffer).metadata();
                const bWidth = baselineMeta.width ?? 0;
                const bHeight = baselineMeta.height ?? 0;
                const cWidth = currentMeta.width ?? 0;
                const cHeight = currentMeta.height ?? 0;
                // If dimensions differ, report mismatch
                if (bWidth !== cWidth || bHeight !== cHeight) {
                    return {
                        content: [{
                                type: "text",
                                text: `Dimension mismatch: baseline is ${bWidth}x${bHeight}, current is ${cWidth}x${cHeight}. Cannot compare.\nUse updateBaseline: true to reset.`,
                            }],
                        details: {
                            match: false,
                            dimensionMismatch: true,
                            baselineDimensions: { width: bWidth, height: bHeight },
                            currentDimensions: { width: cWidth, height: cHeight },
                        },
                    };
                }
                // Extract raw RGBA pixel data
                const baselineRaw = await sharp(baselineBuffer).ensureAlpha().raw().toBuffer();
                const currentRaw = await sharp(currentBuffer).ensureAlpha().raw().toBuffer();
                const width = bWidth;
                const height = bHeight;
                const totalPixels = width * height;
                const threshold = params.threshold ?? 0.1;
                // Simple pixel-by-pixel comparison (avoiding pixelmatch dependency)
                const diffData = Buffer.alloc(width * height * 4);
                let diffPixels = 0;
                const thresholdSq = threshold * threshold * 255 * 255 * 3;
                for (let i = 0; i < totalPixels; i++) {
                    const offset = i * 4;
                    const dr = baselineRaw[offset] - currentRaw[offset];
                    const dg = baselineRaw[offset + 1] - currentRaw[offset + 1];
                    const db = baselineRaw[offset + 2] - currentRaw[offset + 2];
                    const distSq = dr * dr + dg * dg + db * db;
                    if (distSq > thresholdSq) {
                        diffPixels++;
                        // Mark diff pixels as red
                        diffData[offset] = 255; // R
                        diffData[offset + 1] = 0; // G
                        diffData[offset + 2] = 0; // B
                        diffData[offset + 3] = 255; // A
                    }
                    else {
                        // Dim unchanged pixels
                        diffData[offset] = currentRaw[offset] >> 1;
                        diffData[offset + 1] = currentRaw[offset + 1] >> 1;
                        diffData[offset + 2] = currentRaw[offset + 2] >> 1;
                        diffData[offset + 3] = 255;
                    }
                }
                const similarity = 1 - (diffPixels / totalPixels);
                const match = diffPixels === 0;
                // Save diff image
                await sharp(diffData, { raw: { width, height, channels: 4 } })
                    .png()
                    .toFile(diffPath);
                return {
                    content: [{
                            type: "text",
                            text: match
                                ? `Visual diff: MATCH (100% similar)\nBaseline: ${baselinePath}`
                                : `Visual diff: ${(similarity * 100).toFixed(2)}% similar\nDiff pixels: ${diffPixels} of ${totalPixels} (${((diffPixels / totalPixels) * 100).toFixed(2)}%)\nDiff image: ${diffPath}\nBaseline: ${baselinePath}`,
                        }],
                    details: {
                        match,
                        similarity,
                        diffPixels,
                        totalPixels,
                        diffPercentage: (diffPixels / totalPixels) * 100,
                        dimensions: { width, height },
                        baselinePath,
                        diffImagePath: match ? undefined : diffPath,
                        threshold,
                    },
                };
            }
            catch (err) {
                return {
                    content: [{ type: "text", text: `Visual diff failed: ${err.message}` }],
                    details: { error: err.message },
                    isError: true,
                };
            }
        },
    });
}
