#!/usr/bin/env tsx
import sharp from 'sharp';
import * as fs from 'fs';
import * as path from 'path';
import { parse } from 'svg-parser';
import { PNG } from 'pngjs';
import open from 'open';
import tempDir from 'temp-dir'; // Import the temp-dir package
import { KiCAD, kicad_cli_path, kicad_path } from './kicad';
import { exec } from "node:child_process";
import yoctoSpinner from 'yocto-spinner';

// Function to parse SVG dimensions
function parseSvgDimensions(svgPath: string): { width: number; height: number } {
    const svgContent = fs.readFileSync(svgPath, 'utf-8');
    const parsedSvg = parse(svgContent);
    const svgElement = parsedSvg.children[0] as any;
    const svgAttributes = svgElement?.properties || {};
    const widthAttr = svgAttributes.width;
    const heightAttr = svgAttributes.height;
    const viewBox = svgAttributes.viewBox;
    let width = 0;
    let height = 0;

    if (typeof widthAttr === 'string') {
        width = parseFloat(widthAttr.replace(/px$/, '')) || 0;
    }
    if (typeof heightAttr === 'string') {
        height = parseFloat(heightAttr.replace(/px$/, '')) || 0;
    }
    if ((!width || !height) && typeof viewBox === 'string') {
        const [_, __, vbWidth, vbHeight] = viewBox.split(/\s+/).map(Number);
        width = width || vbWidth;
        height = height || vbHeight;
    }
    if (!width || !height) {
        console.warn(`No dimensions found in SVG. Using default size: 800x600.`);
        width = 800;
        height = 600;
    }
    return { width: Math.round(width), height: Math.round(height) };
}

async function createBinaryMaskFromSharp(imageBuffer: Buffer): Promise<Uint8Array> {
    const { data, info } = await sharp(imageBuffer)
        .raw()
        .ensureAlpha()
        .toBuffer({ resolveWithObject: true });
    const { width, height } = info;
    const mask = new Uint8Array(width * height);

    for (let i = 0; i < data.length; i += 4) {
        const alpha = data[i + 3];
        mask[i / 4] = alpha > 0 ? 1 : 0;
    }

    return mask;
}

function compareBinaryMasks(
    originalPng: PNG,
    mask1: Uint8Array,
    mask2: Uint8Array,
    width: number,
    height: number
): PNG {
    const diff = new PNG({ width, height });

    for (let y = 0; y < height; y++) {
        const rowStart = y * width;
        const rowEnd = rowStart + width;

        // Check if the entire row is identical
        let rowChanged = false;
        for (let x = rowStart; x < rowEnd; x++) {
            if (mask1[x] !== mask2[x]) {
                rowChanged = true;
                break;
            }
        }

        if (!rowChanged) {
            // Copy the entire row as semi-transparent
            for (let x = 0; x < width; x++) {
                const idx = (y * width + x) << 2;
                diff.data[idx] = originalPng.data[idx];
                diff.data[idx + 1] = originalPng.data[idx + 1];
                diff.data[idx + 2] = originalPng.data[idx + 2];
                diff.data[idx + 3] = 127; // Semi-transparent
            }
        } else {
            // Compare pixel-by-pixel for this row
            for (let x = 0; x < width; x++) {
                const idx = (y * width + x) << 2;
                const mask1Value = mask1[rowStart + x];
                const mask2Value = mask2[rowStart + x];

                if (mask1Value === 1 && mask2Value === 0) {
                    // Pixel was present in mask1 but not in mask2 (removed)
                    diff.data[idx] = 255;     // Red
                    diff.data[idx + 1] = 0;   // Green
                    diff.data[idx + 2] = 0;   // Blue
                    diff.data[idx + 3] = 255; // Alpha
                } else if (mask1Value === 0 && mask2Value === 1) {
                    // Pixel was present in mask2 but not in mask1 (added)
                    diff.data[idx] = 0;       // Red
                    diff.data[idx + 1] = 255; // Green
                    diff.data[idx + 2] = 0;   // Blue
                    diff.data[idx + 3] = 255; // Alpha
                } else {
                    // No change, copy original image with semi-transparency
                    diff.data[idx] = originalPng.data[idx];
                    diff.data[idx + 1] = originalPng.data[idx + 1];
                    diff.data[idx + 2] = originalPng.data[idx + 2];
                    diff.data[idx + 3] = 127; // Semi-transparent
                }
            }
        }
    }

    return diff;
}

function areMasksIdentical(mask1: Uint8Array, mask2: Uint8Array): boolean {
    if (mask1.length !== mask2.length) return false;
    for (let i = 0; i < mask1.length; i++) {
        if (mask1[i] !== mask2[i]) return false;
    }
    return true;
}

async function convertSvgToPngBuffer(svgPath: string): Promise<Buffer> {
    const { width, height } = parseSvgDimensions(svgPath);

    // Define the target width (1000px)
    const targetWidth = 1000;

    // Calculate the scaling factor to maintain the aspect ratio
    const scaleFactor = targetWidth / width;
    const targetHeight = Math.round(height * scaleFactor);

    // Resize the SVG to the target dimensions
    const pngBuffer = await sharp(svgPath)
        .resize(targetWidth, targetHeight) // Resize to 1000px wide while maintaining aspect ratio
        .png()
        .toBuffer();

    return pngBuffer;
}

function compareColorImages(image1: PNG, image2: PNG): PNG {
    const width = image1.width;
    const height = image1.height;

    // Ensure both images have the same dimensions
    if (width !== image2.width || height !== image2.height) {
        throw new Error("Images must have the same dimensions for comparison.");
    }

    const diff = new PNG({ width, height });

    // Define tolerance thresholds for color and alpha
    const colorThreshold = 30; // Allow small color differences (0-255 range)
    const alphaThreshold = 30; // Allow small alpha differences (0-255 range)

    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const idx = (y * width + x) << 2;

            const r1 = image1.data[idx];
            const g1 = image1.data[idx + 1];
            const b1 = image1.data[idx + 2];
            const a1 = image1.data[idx + 3];

            const r2 = image2.data[idx];
            const g2 = image2.data[idx + 1];
            const b2 = image2.data[idx + 2];
            const a2 = image2.data[idx + 3];

            // Calculate the absolute differences for each channel
            const colorDiff = Math.abs(r1 - r2) + Math.abs(g1 - g2) + Math.abs(b1 - b2);
            const alphaDiff = Math.abs(a1 - a2);

            if (colorDiff <= colorThreshold && alphaDiff <= alphaThreshold) {
                // No significant change, copy original pixel with semi-transparency
                diff.data[idx] = r1;
                diff.data[idx + 1] = g1;
                diff.data[idx + 2] = b1;
                diff.data[idx + 3] = 127; // Semi-transparent
            } else {
                // Significant change detected, highlight in red, green, or yellow
                    // Removed feature
                    diff.data[idx] = 255;     // Red
                    diff.data[idx + 1] = 255;   // Green
                    diff.data[idx + 2] = 0;   // Blue
                    diff.data[idx + 3] = 255; // Alpha

            }
        }
    }

    return diff;
}

const layers = [
    "F.Cu", "F.Adhesive", "F.Paste", "F.Silkscreen", "F.Mask", "F.Courtyard", "F.Fab",
    "B.Cu", "B.Adhesive", "B.Paste", "B.Silkscreen", "B.Mask", "B.Courtyard", "B.Fab",
    "User.1", "User.2", "User.3", "User.4", "User.5", "User.6", "User.7", "User.8", "User.9",
    "Edge.Cuts", "User.Drawings", "User.Comments", "User.Eco1", "User.Eco2", "Margin"
];


(async () => {
    const tempFiles: string[] = []; // Track all temporary files for cleanup
    const htmlLayers: { name: string; originalImage: string | null, diffImage: string | null, modifiedImage: string | null }[] = [];

    console.log("🔃 typeCAD gitdiff starting...");
    if (process.argv.length < 3) {
        console.error("Usage: typecad-gitdiff <original.kicad_pcb> <modified.kicad_pcb>");
        console.log("typeCAD-gitdiff version 1.0.0");
        process.exit(1);
    }
    const spinner = yoctoSpinner({text: 'processing files...', spinner: {frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'], interval: 100}, color: 'green'}).start();
    try {
        new KiCAD();
        // Export SVGs for both boards to temporary files in parallel
        const exportPromises = layers.flatMap(layer => {
            const tempSvgPath1 = path.join(tempDir, `1_${layer}.svg`);
            const tempSvgPath2 = path.join(tempDir, `2_${layer}.svg`);
            tempFiles.push(tempSvgPath1, tempSvgPath2);
            return [
                new Promise<void>((resolve, reject) => {
                    exec(`"${kicad_cli_path}" pcb export svg --exclude-drawing-sheet --page-size-mode 2 --layers "${layer}" --output "${tempSvgPath1}" ${process.argv[2]}`, (err) => {
                        if (err) reject(err);
                        else resolve();
                    });
                }),
                new Promise<void>((resolve, reject) => {
                    exec(`"${kicad_cli_path}" pcb export svg --exclude-drawing-sheet --page-size-mode 2 --layers "${layer}" --output "${tempSvgPath2}" ${process.argv[3]}`, (err) => {
                        if (err) reject(err);
                        else resolve();
                    });
                })
            ];
        });
        await Promise.all(exportPromises);

        // Generate paths for all SVGs
        const svgPaths1 = layers.map(layer => path.join(tempDir, `1_${layer}.svg`));
        const svgPaths2 = layers.map(layer => path.join(tempDir, `2_${layer}.svg`));

        // Convert all SVGs to PNG buffers in parallel
        const [pngBuffers1, pngBuffers2] = await Promise.all([
            Promise.all(svgPaths1.map(svgPath => convertSvgToPngBuffer(svgPath))),
            Promise.all(svgPaths2.map(svgPath => convertSvgToPngBuffer(svgPath)))
        ]);

        // Render PNG files for "Render" layer
        const renderPngPath1 = path.join(tempDir, '1_render.png');
        const renderPngPath2 = path.join(tempDir, '2_render.png');
        tempFiles.push(renderPngPath1, renderPngPath2);

        // Execute rendering commands for "Render" layer
        await new Promise<void>((resolve, reject) => {
            exec(`"${kicad_cli_path}" pcb render --output "${renderPngPath1}" ${process.argv[2]}`, (err) => {
                if (err) reject(err);
                else resolve();
            });
        });
        await new Promise<void>((resolve, reject) => {
            exec(`"${kicad_cli_path}" pcb render --output "${renderPngPath2}" ${process.argv[3]}`, (err) => {
                if (err) reject(err);
                else resolve();
            });
        });

        // Read the rendered PNG files into buffers
        const renderPngBuffer1 = fs.readFileSync(renderPngPath1);
        const renderPngBuffer2 = fs.readFileSync(renderPngPath2);

        // Add "Render" layer to the layers array
        layers.push("Render");
        
        // Process layers in parallel
        // Process layers in parallel
        await Promise.all(layers.map(async (layer, index) => {
            let originalPng1, pngBuffer1, pngBuffer2;
            
            if (layer === "Render") {
                // Use the rendered PNG buffers for the "Render" layer
                originalPng1 = PNG.sync.read(renderPngBuffer1);
                pngBuffer1 = renderPngBuffer1;
                pngBuffer2 = renderPngBuffer2;
            } else {
                // Use the SVG-derived PNG buffers for other layers
                originalPng1 = PNG.sync.read(pngBuffers1[index]);
                pngBuffer1 = pngBuffers1[index];
                pngBuffer2 = pngBuffers2[index];
            }
            
            // Convert original images to Base64
            const originalImageBase64 = pngBuffer1.toString('base64');
            const originalImageSrc = `data:image/png;base64,${originalImageBase64}`;
            
            const modifiedImageBase64 = pngBuffer2.toString('base64');
            const modifiedImageSrc = `data:image/png;base64,${modifiedImageBase64}`;
            
            if (layer === "Render") {
                // Compare the two PNGs directly for the "Render" layer
                const diff = compareColorImages(originalPng1, PNG.sync.read(pngBuffer2));
                const diffBuffer = PNG.sync.write(diff); // Get the buffer of the difference image
            
                // Convert the buffer to a Base64-encoded string
                const diffImageBase64 = diffBuffer.toString('base64');
                const diffImageSrc = `data:image/png;base64,${diffImageBase64}`;
            
                htmlLayers.push({
                    name: layer,
                    originalImage: originalImageSrc,
                    diffImage: diffImageSrc,
                    modifiedImage: modifiedImageSrc
                });
            } else {
                // Create binary masks and compare them for other layers
                const [mask1, mask2] = await Promise.all([
                    createBinaryMaskFromSharp(pngBuffer1),
                    createBinaryMaskFromSharp(pngBuffer2)
                ]);
            
                if (areMasksIdentical(mask1, mask2)) {
                    htmlLayers.push({
                        name: layer,
                        originalImage: originalImageSrc,
                        diffImage: null,
                        modifiedImage: modifiedImageSrc
                    });
                    return;
                }
            
                const diff = compareBinaryMasks(originalPng1, mask1, mask2, originalPng1.width, originalPng1.height);
                const diffBuffer = PNG.sync.write(diff); // Get the buffer of the difference image
            
                // Convert the buffer to a Base64-encoded string
                const diffImageBase64 = diffBuffer.toString('base64');
                const diffImageSrc = `data:image/png;base64,${diffImageBase64}`;
            
                htmlLayers.push({
                    name: layer,
                    originalImage: originalImageSrc,
                    diffImage: diffImageSrc,
                    modifiedImage: modifiedImageSrc
                });
            }
        }));

        // Generate HTML file with embedded images and enhanced styling
        const htmlContent = `
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Layer Comparison Results</title>
    <style>
        /* General Styling */
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 0;
            padding: 0;
            background: linear-gradient(135deg, #f5f7fa, #c3cfe2);
            color: #333;
            line-height: 1.6;
        }
        h1 {
            text-align: center;
            font-size: 2.25em;
            font-family: Inter VF, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"
            font-weight: 800;
            color: #111827;
            margin-top: 20px;
            margin-bottom: 20px;
            text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
        }
        .layer-container {
            background: #fff;
            border-radius: 8px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            margin-bottom: 20px;
            padding: 15px;
            transition: transform 0.3s ease, box-shadow 0.3s ease;
        }
        .layer-container:hover {
            transform: translateY(-5px);
            box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
        }
        .layer-title {
            font-size: 1.2em;
            font-weight: bold;
            color: #2980b9;
            margin-bottom: 10px;
        }
        .image-grid {
            display: flex;
            gap: 15px;
            justify-content: center;
            align-items: center;
        }
        .image-wrapper {
            position: relative;
            display: inline-block;
            background-color: #fff; /* White background for transparency */
            border-radius: 4px;
            overflow: hidden;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        .image-grid img {
            max-width: 100%;
            height: auto;
            display: block;
            cursor: pointer;
            transition: transform 0.3s ease;
            background: gray
        }
        .image-grid img:hover {
            transform: scale(1.05);
        }
        .image-caption {
            text-align: center;
            font-size: 0.9em;
            color: #555;
            margin-top: 5px;
        }
        .no-changes {
            color: #888;
            font-style: italic;
            text-align: center;
        }

        /* Lightbox Styling */
        .lightbox {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.8);
            display: none;
            justify-content: center;
            align-items: center;
            z-index: 1000;
        }
        .lightbox img {
            max-width: 90%;
            max-height: 90%;
            border-radius: 8px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
            background: white;
        }
        .lightbox.active {
            display: flex;
        }

        /* Legend Styling */
        .legend {
            margin-top: 40px;
            padding: 20px;
            background: #fff;
            border-radius: 8px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            font-size: 1em;
            line-height: 1.5;
            max-width: 800px;
            margin-left: auto;
            margin-right: auto;
        }
        .legend h2 {
            text-align: center;
            font-size: 1.5em;
            color: #2c3e50;
            margin-bottom: 15px;
        }
        .legend div {
            display: flex;
            align-items: center;
            margin-bottom: 10px;
        }
        .legend span.color-box {
            width: 20px;
            height: 20px;
            margin-right: 15px;
            border-radius: 2px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        .red { background-color: #ff4d4d; }
        .green { background-color: #4caf50; }
        .yellow { background-color:rgb(255, 255, 0); }
        .legend span.description {
            flex-grow: 1;
            color: #333;
        }

        /* Footer */
        footer {
            text-align: center;
            margin-top: 40px;
            padding: 10px;
            font-size: 0.9em;
            color: #888;
        }
        footer a {
            color: #2980b9;
            text-decoration: none;
        }
        footer a:hover {
            text-decoration: underline;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Layer Comparison Results</h1>
        <div id="results">
            ${htmlLayers.map(layer => `
            <div class="layer-container">
                <div class="layer-title">${layer.name}</div>
                ${layer.diffImage
                ? `
                    <div class="image-grid">
                        <div class="image-wrapper">
                            <img src="${layer.originalImage}" alt="Original Image" class="clickable-image" data-src="${layer.originalImage}">
                            <div class="image-caption">Original</div>
                        </div>
                        <div class="image-wrapper">
                            <img src="${layer.diffImage}" alt="Difference Image" class="clickable-image" data-src="${layer.diffImage}">
                            <div class="image-caption">Difference</div>
                        </div>
                        <div class="image-wrapper">
                            <img src="${layer.modifiedImage}" alt="Modified Image" class="clickable-image" data-src="${layer.modifiedImage}">
                            <div class="image-caption">Modified</div>
                        </div>
                    </div>
                    `
                : `<div class="no-changes">No changes detected.</div>`}
            </div>
            `).join('')}
        </div>

        <!-- Lightbox -->
        <div class="lightbox" id="lightbox">
            <img src="" alt="Expanded Image" id="lightbox-image">
        </div>

        <!-- Legend Section -->
        <div class="legend">
            <h2>Legend</h2>
            <div>
                <span class="color-box red"></span>
                <span class="description">Features removed</span>
            </div>
            <div>
                <span class="color-box green"></span>
                <span class="description">Features added</span>
            </div>
            <div>
                <span class="color-box yellow"></span>
                <span class="description">Render features modified</span>
            </div>
        </div>

        <!-- Footer -->
        <footer>
            Generated by <a href="https://typecad.net" target="_blank">typecad.net</a>
        </footer>
    </div>

    <script>
        // Lightbox functionality
        document.addEventListener('DOMContentLoaded', () => {
            const lightbox = document.getElementById('lightbox');
            const lightboxImage = document.getElementById('lightbox-image');

            // Open lightbox when an image is clicked
            document.querySelectorAll('.clickable-image').forEach(img => {
                img.addEventListener('click', () => {
                    lightboxImage.src = img.dataset.src;
                    lightbox.classList.add('active');
                });
            });

            // Close lightbox when clicking outside the image or pressing Esc
            lightbox.addEventListener('click', (e) => {
                if (e.target === lightbox || e.target === lightboxImage) {
                    lightbox.classList.remove('active');
                }
            });

            document.addEventListener('keydown', (e) => {
                if (e.key === 'Escape') {
                    lightbox.classList.remove('active');
                }
            });
        });
    </script>
</body>
</html>
`;
        const htmlFilePath = path.join(tempDir, 'results.html');
        fs.writeFileSync(htmlFilePath, htmlContent);
        await open(htmlFilePath); // Open the HTML file in the default browser

    } catch (error) {
        console.error('Error:', error);
    } finally {
        // Clean up all temporary files
        spinner.success('Finished');
        for (const filePath of tempFiles) {
            try {
                if (fs.existsSync(filePath)) {
                    fs.unlinkSync(filePath);
                }
            } catch (cleanupError) {
                console.error(`Failed to delete temporary file: ${filePath}`, cleanupError);
            }
        }
    }
})();