// ==================== UTILITY / PRIMITIVE FUNCTIONS ====================

/**
 * Loads an image from a URL (via Chrome extension message passing) and returns an Image element.
 */
async function urlToImage(url) {
    if (!url) throw new Error('Image URL is required');
    const response = await fetchImageAsBase64(url);
    const b64 = response.b64Image;
    if (!b64 || !b64.startsWith('data:image')) throw new Error('Failed to fetch image from URL');
    return await loadImageFromDataUrl(b64);
}

/**
 * Sends a message to the Chrome extension background script to convert a URL to base64.
 */
function fetchImageAsBase64(url) {
    return new Promise((resolve, reject) => {
        chrome.runtime.sendMessage({ type: 'url-to-b64', url }, response => {
            if (response && response.error) reject(new Error(response.error));
            else resolve(response);
        });
    });
}

/**
 * Creates an Image element from a data URL by drawing it onto a canvas first.
 */
function loadImageFromDataUrl(dataUrl) {
    return new Promise((resolve) => {
        const ctx = document.createElement('canvas').getContext('2d');
        const img = new Image();
        img.onload = function () {
            ctx.drawImage(img, 0, 0);
            resolve(img);
        };
        img.src = dataUrl;
    });
}

/**
 * Scales an image so its longest side equals maxSize, maintaining aspect ratio.
 * Canvas background is white.
 */
function scaleImageToMax(image, maxSize, _unused) {
    return new Promise((resolve) => {
        let scale = 1;
        if (image.width > image.height) scale = maxSize / image.width;
        else if (image.width < image.height) scale = maxSize / image.height;
        else scale = maxSize / image.height;

        const newWidth = image.width * scale;
        const newHeight = image.height * scale;
        const canvas = document.createElement('canvas');
        canvas.id = 'myCanvas';
        canvas.width = newWidth;
        canvas.height = newHeight;
        canvas.style = 'border:2px solid black;';
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.globalAlpha = 1;
        ctx.drawImage(image, 0, 0, newWidth, newHeight);

        const result = new Image();
        result.crossOrigin = 'anonymous';
        result.src = canvas.toDataURL();
        result.onload = function () { resolve(result); };
    });
}

/**
 * Resizes an image to fit within (targetH x targetW) using a canvas with white background,
 * centering the scaled image.
 */
function resizeImageCentered(image, targetH, targetW) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.id = 'myCanvas';
        canvas.width = targetH;
        canvas.height = targetW;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        const centerX = targetH / 2;
        const centerY = targetW / 2;
        let scale = 1;
        if (image.width > image.height) scale = targetH / image.width;
        else if (image.width < image.height) scale = targetH / image.height;
        else scale = targetH / image.height;

        const scaledW = image.width * scale;
        const scaledH = image.height * scale;
        ctx.globalAlpha = 1;
        ctx.drawImage(image, centerX - scaledW / 2, centerY - scaledH / 2, scaledW, scaledH);

        const result = new Image();
        result.crossOrigin = 'anonymous';
        result.src = canvas.toDataURL();
        result.onload = function () { resolve(result); };
    });
}

/**
 * Scales an image so its longest side is exactly maxSide, iterating down if needed.
 */
function scaleImageToFit(image, maxSide, _unused2) {
    return new Promise((resolve) => {
        let scale = 1;
        if (image.width > image.height) scale = maxSide / image.width;
        else if (image.width < image.height) scale = maxSide / image.height;
        else scale = maxSide / image.height;

        let scaledW = image.width * scale;
        let scaledH = image.height * scale;
        let fine = 1;
        while (scaledW > maxSide) {
            scaledW = image.width * fine;
            scaledH = image.height * fine;
            fine -= 0.01;
        }

        const canvas = document.createElement('canvas');
        canvas.id = 'myCanvas';
        canvas.width = scaledW;
        canvas.height = scaledH;
        canvas.style = 'border:2px solid black;';
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.globalAlpha = 1;
        ctx.drawImage(image, 0, 0, scaledW, scaledH);

        const result = new Image();
        result.crossOrigin = 'anonymous';
        result.src = canvas.toDataURL();
        result.onload = function () { resolve(result); };
    });
}

/**
 * Makes an image square by padding the shorter dimension with white.
 * Centers the image on the resulting canvas.
 */
function makeImageSquare(image) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        console.log('Making image aspect same');

        if (image.width > image.height) {
            canvas.width = image.width;
            canvas.height = image.height + (image.width - image.height);
        } else if (image.width < image.height) {
            canvas.height = image.height;
            canvas.width = image.width + (image.height - image.width);
        } else {
            console.log('Image is already a square');
            resolve(image);
            return;
        }

        console.log('Done making image aspect same');
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(image, (canvas.width - image.width) / 2, (canvas.height - image.height) / 2);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Makes an image square (bottom-right aligned variant — no centering).
 */
function makeImageSquareBottomRight(image) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        if (image.width > image.height) {
            canvas.width = image.width;
            canvas.height = image.height + (image.width - image.height);
        } else if (image.width < image.height) {
            canvas.height = image.height;
            canvas.width = image.width + (image.height - image.width);
        } else {
            resolve(image);
            return;
        }

        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(image, canvas.width - image.width, canvas.height - image.height);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Adds white padding to an image.
 * paddingDirection: 'all' | 'height' | 'width'
 */
function addPaddingToImage({ imageObject, padding, paddingDirection = 'all' }) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.id = 'myCanvas';
        const ctx = canvas.getContext('2d');

        if (paddingDirection === 'all') {
            canvas.width = imageObject.width + padding;
            canvas.height = imageObject.height + padding;
            ctx.fillStyle = 'white';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.drawImage(imageObject, padding / 2, padding / 2);
        } else if (paddingDirection === 'height') {
            canvas.width = imageObject.width;
            canvas.height = imageObject.height + padding;
            ctx.fillStyle = 'white';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.drawImage(imageObject, 0, padding / 2);
        } else if (paddingDirection === 'width') {
            canvas.width = imageObject.width + padding;
            canvas.height = imageObject.height;
            ctx.fillStyle = 'white';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.drawImage(imageObject, padding / 2, 0);
        }

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Upscales an image if either dimension is smaller than minWidth / minHeight.
 */
function ensureMinimumSize(image, minWidth, minHeight) {
    return new Promise((resolve) => {
        let w = image.width;
        let h = image.height;

        if (w < minWidth || h < minHeight) {
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            const scale = Math.max(minWidth / w, minHeight / h);
            w *= scale;
            h *= scale;
            canvas.width = w;
            canvas.height = h;
            ctx.drawImage(image, 0, 0, w, h);

            const result = new Image();
            result.src = canvas.toDataURL();
            result.onload = function () { resolve(result); };
        } else {
            resolve(image);
        }
    });
}

/**
 * Crops white borders from an image using pixel scanning.
 */
function cropWhiteBorders(image) {
    return new Promise((resolve) => {
        const img = new Image();
        img.onload = function () {
            const canvas = document.createElement('canvas');
            canvas.width = img.width;
            canvas.height = img.height;
            const ctx = canvas.getContext('2d');
            ctx.drawImage(img, 0, 0, img.width, img.height);
            const pixelData = ctx.getImageData(0, 0, img.width, img.height).data;

            const top = findRowBoundary(true, img, pixelData);
            const bottom = findRowBoundary(false, img, pixelData);
            const left = findColBoundary(true, img, pixelData);
            const cropWidth = findColBoundary(false, img, pixelData) - left;
            const cropHeight = bottom - top;

            canvas.width = cropWidth;
            canvas.height = cropHeight;
            ctx.drawImage(img, left, top, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);

            const result = new Image();
            result.crossOrigin = 'anonymous';
            result.onload = function () { resolve(result); };
            result.src = canvas.toDataURL();
        };
        img.src = image.src;
    });
}

function isWhitePixel(pixel) {
    return pixel.red === 0xff && pixel.green === 0xff && pixel.blue === 0xff;
}

function getPixel(x, y, pixelData, image) {
    return {
        red: pixelData[4 * (image.width * y + x)],
        green: pixelData[4 * (image.width * y + x) + 1],
        blue: pixelData[4 * (image.width * y + x) + 2]
    };
}

function findRowBoundary(fromTop, image, pixelData) {
    const dir = fromTop ? 1 : -1;
    for (let y = fromTop ? 0 : image.height - 1; fromTop ? y < image.height : y > -1; y += dir)
        for (let x = 0; x < image.width; x++)
            if (!isWhitePixel(getPixel(x, y, pixelData, image)))
                return fromTop ? y : Math.min(y + 1, image.height);
    return null;
}

function findColBoundary(fromLeft, image, pixelData) {
    const dir = fromLeft ? 1 : -1;
    for (let x = fromLeft ? 0 : image.width - 1; fromLeft ? x < image.width : x > -1; x += dir)
        for (let y = 0; y < image.height; y++)
            if (!isWhitePixel(getPixel(x, y, pixelData, image)))
                return fromLeft ? x : Math.min(x + 1, image.width);
    return null;
}

/**
 * Makes near-white pixels transparent (alpha = 0).
 * Pixels where R, G, B are all between 245–255 become fully transparent.
 */
function makeWhiteTransparent(image) {
    return new Promise((resolve) => {
        const img = new Image();
        img.onload = function () {
            const canvas = document.createElement('canvas');
            canvas.width = img.width;
            canvas.height = img.height;
            const ctx = canvas.getContext('2d');
            ctx.drawImage(img, 0, 0, img.width, img.height);
            const imageData = ctx.getImageData(0, 0, img.width, img.height);
            const data = imageData.data;

            for (let i = 0; i < data.length; i += 4) {
                if ([data[i], data[i + 1], data[i + 2]].every(v => v < 256 && v > 245)) {
                    data[i + 3] = 0;
                }
            }

            ctx.putImageData(imageData, 0, 0);
            const result = new Image();
            result.onload = function () { resolve(result); };
            result.src = canvas.toDataURL();
        };
        img.src = image.src;
    });
}

/**
 * Rotates an image by `degrees` degrees.
 */
function rotateImage(image, degrees) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        const rad = degrees * Math.PI / 180;
        let cos = Math.cos(rad);
        let sin = Math.sin(rad);
        if (sin < 0) sin = -sin;
        if (cos < 0) cos = -cos;

        canvas.width = image.height * sin + image.width * cos;
        canvas.height = image.height * cos + image.width * sin;

        const cx = canvas.width / 2;
        const cy = canvas.height / 2;
        ctx.translate(cx, cy);
        ctx.rotate(degrees * Math.PI / 180);
        ctx.drawImage(image, -image.width / 2, -image.height / 2, image.width, image.height);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Flips an image horizontally (mirror).
 */
function flipImageHorizontal(image) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.id = 'myCanvas';
        canvas.width = image.width;
        canvas.height = image.height;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.translate(canvas.width, 0);
        ctx.scale(-1, 1);
        ctx.drawImage(image, 0, 0);

        const result = new Image();
        result.crossOrigin = 'anonymous';
        result.src = canvas.toDataURL();
        result.onload = function () { resolve(result); };
    });
}

/**
 * Draws a rounded rectangle clipping path on a canvas context.
 */
function drawRoundedRect(ctx, x, y, width, height, radius) {
    ctx.beginPath();
    ctx.moveTo(x + radius, y);
    ctx.lineTo(x + width - radius, y);
    ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
    ctx.lineTo(x + width, y + height - radius);
    ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
    ctx.lineTo(x + radius, y + height);
    ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
    ctx.lineTo(x, y + radius);
    ctx.quadraticCurveTo(x, y, x + radius, y);
    ctx.closePath();
}

/**
 * Rounds the corners of an image with a given radius.
 */
function roundImageCorners(image, radius) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.width = image.width;
        canvas.height = image.height;
        const ctx = canvas.getContext('2d');
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.save();
        ctx.beginPath();
        ctx.moveTo(radius, 0);
        ctx.lineTo(canvas.width - radius, 0);
        ctx.quadraticCurveTo(canvas.width, 0, canvas.width, radius);
        ctx.lineTo(canvas.width, canvas.height - radius);
        ctx.quadraticCurveTo(canvas.width, canvas.height, canvas.width - radius, canvas.height);
        ctx.lineTo(radius, canvas.height);
        ctx.quadraticCurveTo(0, canvas.height, 0, canvas.height - radius);
        ctx.lineTo(0, radius);
        ctx.quadraticCurveTo(0, 0, radius, 0);
        ctx.clip();
        ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
        ctx.restore();

        const result = new Image();
        result.onload = () => { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Adds a background colour behind a transparent image.
 */
function applyBackgroundColor(image, _unused, color) {
    return new Promise(async (resolve) => {
        const canvas = document.createElement('canvas');
        canvas.width = image.width;
        canvas.height = image.height;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = color;
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(image, 0, 0, image.width, image.height);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Applies a global alpha to an image (opacity).
 */
function applyOpacity(image, opacity) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.width = image.width;
        canvas.height = image.height;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.globalAlpha = opacity;
        ctx.drawImage(image, 0, 0);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Draws a black border / stroke rectangle around an image.
 */
function addBorderStroke(image, color, lineWidth) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.width = image.width;
        canvas.height = image.height;
        const ctx = canvas.getContext('2d');
        ctx.drawImage(image, 0, 0);
        ctx.strokeStyle = color;
        ctx.lineWidth = lineWidth;
        ctx.strokeRect(0, 0, canvas.width, canvas.height);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Adds a thick black border + white background fill around image (30px line width).
 */
function addThickBorderWithWhiteBg(image) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.id = 'myCanvas';
        canvas.width = image.width;
        canvas.height = image.height;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
        ctx.strokeStyle = 'black';
        ctx.lineWidth = 30;
        ctx.strokeRect(0, 0, canvas.width, canvas.height);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

// ==================== COMPOSITING FUNCTIONS ====================

/**
 * Places a watermark image in the bottom-right corner of a base image.
 * opacity: transparency of the watermark
 */
function overlayBottomRight(baseImage, watermark, opacity) {
    return new Promise(async (resolve) => {
        const canvas = document.createElement('canvas');
        canvas.width = baseImage.width;
        canvas.height = baseImage.height;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(baseImage, 0, 0);
        ctx.globalAlpha = opacity;
        ctx.drawImage(watermark, canvas.width - watermark.width, 0, watermark.width, watermark.height);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Places a watermark image in the bottom-right corner at a small offset from the edge.
 * (position: width - wmWidth - width/50, height - wmHeight - height/divisor)
 */
function overlayBottomRightWithOffset(baseImage, watermark, opacity, divisor) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.width = baseImage.width;
        canvas.height = baseImage.height;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(baseImage, 0, 0);
        ctx.globalAlpha = opacity;

        const x = canvas.width - watermark.width - canvas.width / 50;
        const y = canvas.height - watermark.height - canvas.height / divisor;
        drawRoundedRect(ctx, x, y, watermark.width, watermark.height, 10);
        ctx.clip();
        ctx.drawImage(watermark, x, y);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Places a secondary image in the bottom-left corner at a small offset.
 */
function overlayBottomLeft(baseImage, overlay, opacity, divisor) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.width = baseImage.width;
        canvas.height = baseImage.height;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(baseImage, 0, 0);
        ctx.globalAlpha = opacity;

        const x = canvas.width / 50;
        const y = canvas.height - overlay.height - canvas.height / divisor;
        ctx.drawImage(overlay, x, y);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Places an overlay at the top-left corner with a small x offset.
 */
function overlayTopLeft(baseImage, overlay, opacity) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.width = baseImage.width;
        canvas.height = baseImage.height;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(baseImage, 0, 0);
        ctx.globalAlpha = opacity;
        const x = canvas.width / 50;
        ctx.drawImage(overlay, x, 0, overlay.width, overlay.height);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Places a secondary image in the bottom-right (no-clip variant). Identical layout to overlayBottomRight.
 */
function overlayBottomRightNoClip(baseImage, overlay, opacity) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.width = baseImage.width;
        canvas.height = baseImage.height;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(baseImage, 0, 0);
        ctx.globalAlpha = opacity;

        const x = canvas.width - overlay.width;
        const y = canvas.height - overlay.height;
        // (unused params kept for API compat)
        ctx.drawImage(overlay, canvas.width - overlay.width, 0, overlay.width, overlay.height);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Appends a secondary image to the RIGHT side of a base image (extends canvas width).
 * position: 'left' shifts base right; 'right' keeps base left.
 */
function appendImageHorizontal(baseImage, appendImage, position) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.width = baseImage.width + appendImage.width;
        canvas.height = baseImage.height;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        if (position === 'left') ctx.drawImage(baseImage, appendImage.width, 0);
        else if (position === 'right') ctx.drawImage(baseImage, 0, 0);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Same as appendImageHorizontal but with a fixed extra pixel-width instead of another image.
 */
function addHorizontalSpace(image, extraWidth, _position) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.width = image.width + extraWidth;
        canvas.height = image.height;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(image, extraWidth, 0); // always places image after the gap

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Appends vertical space (above or below) to an image.
 * direction: 'top' | 'bottom'
 */
function addVerticalSpace(image, extraHeight, direction) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.width = image.width;
        canvas.height = image.height + extraHeight;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        if (direction === 'top') ctx.drawImage(image, 0, extraHeight);
        else if (direction === 'bottom') ctx.drawImage(image, 0, 0);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Appends an image side-by-side at the RIGHT of a base image, with the secondary placed
 * at 90% of its dimensions in the top-right corner.
 */
function appendImageToRight(baseImage, sideImage, opacity) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.width = baseImage.width + sideImage.width;
        canvas.height = baseImage.height;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(baseImage, 0, 0);
        ctx.globalAlpha = opacity;

        const drawW = 0.9 * sideImage.width;
        const drawH = 0.9 * sideImage.height;
        ctx.drawImage(sideImage, canvas.width - drawW, 0, drawW, drawH);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Draws image at bottom with a row of sub-images along the bottom edge,
 * evenly spaced horizontally.
 */
function placeImageAtBottom(baseImage, subImage, xOffset) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.width = baseImage.width;
        canvas.height = baseImage.height;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(baseImage, 0, 0);
        ctx.drawImage(subImage, xOffset, canvas.height - subImage.height);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

// ==================== TEXT OVERLAY FUNCTIONS ====================

/**
 * Word-wraps `text` across `maxWidth` using the given canvas context.
 * Returns the number of lines needed.
 */
function measureWrappedLineCount(ctx, text, maxWidth, lineHeight, fontSizePx, fontFamily) {
    return new Promise(resolve => {
        ctx.font = fontSizePx + 'px ' + fontFamily;
        const words = text.split(' ');
        let lineCount = 1;
        let line = '';
        for (let i = 0; i < words.length; i++) {
            const test = line + words[i] + ' ';
            if (ctx.measureText(test).width > maxWidth && i > 0) {
                lineCount++;
                line = words[i] + ' ';
            } else {
                line = test;
            }
        }
        resolve(lineCount);
    });
}

/**
 * Draws word-wrapped text onto a canvas context.
 */
function drawWrappedText(ctx, text, x, y, maxWidth, lineHeight) {
    const words = text.split(' ');
    let line = '';
    for (let i = 0; i < words.length; i++) {
        const test = line + words[i] + ' ';
        if (ctx.measureText(test).width > maxWidth && i > 0) {
            ctx.fillText(line, x, y);
            line = words[i] + ' ';
            y += lineHeight;
        } else {
            line = test;
        }
    }
    ctx.fillText(line, x, y);
}

/**
 * Prepends a text caption ("Look Inside ⤸" style) above the image.
 * firstWord in #34B8FF, rest in #F79148.
 */
function addLookInsideTextAbove(image) {
    const fontSize = image.height / 10 * 1.5;
    const blockHeight = fontSize + fontSize / 2;
    const baselineY = fontSize;

    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.id = 'myCanvas';
        canvas.width = image.width;
        canvas.height = image.height + blockHeight;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        ctx.fillStyle = '#F79148';
        ctx.font = 'bold ' + fontSize + 'px Arial';
        ctx.fillText('Look ', 0, baselineY);

        const lookWidth = ctx.measureText('Look ').width;
        ctx.fillStyle = '#34B8FF';
        ctx.font = 'bold ' + fontSize + 'px Arial';
        ctx.fillText('Inside ⤸', lookWidth, baselineY);

        ctx.drawImage(image, 0, blockHeight);

        const result = new Image();
        result.crossOrigin = 'anonymous';
        result.src = canvas.toDataURL();
        result.onload = function () { resolve(result); };
    });
}

/**
 * Prepends dynamic two-word buyer-intent text above the image.
 * firstWord in #34B8FF, remaining words in #F79148 (auto-sized to fill width).
 */
function addDynamicBuyerIntentText(image, firstWord, remainingWords) {
    remainingWords += ' ⤸';
    const canvasWidth = image.width;
    const fontSize = image.width / 10;
    const blockHeight = fontSize + fontSize / 2;
    const baselineY = fontSize;

    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.id = 'myCanvas';
        canvas.width = image.width;
        canvas.height = image.height + blockHeight;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        ctx.fillStyle = '#34B8FF';
        ctx.font = 'bold ' + fontSize + 'px Arial';
        ctx.fillText(firstWord, 0, baselineY);

        const firstWordWidth = ctx.measureText(firstWord).width;
        const remainingFontSize = (canvasWidth - firstWordWidth) / remainingWords.length * 2;
        ctx.fillStyle = '#F79148';
        ctx.font = 'bold ' + remainingFontSize + 'px Arial';
        ctx.fillText(remainingWords, firstWordWidth, baselineY);

        ctx.drawImage(image, 0, blockHeight);

        const result = new Image();
        result.crossOrigin = 'anonymous';
        result.src = canvas.toDataURL();
        result.onload = function () { resolve(result); };
    });
}

/**
 * Prepends a text caption (computed from lineCount rows) above the image.
 * Used together with measureWrappedLineCount().
 */
async function addTextAboveImage(image, text, lineCount, textColor, fontFamily) {
    const linePx = image.height / 10 * 0.45;
    const blockHeight = linePx + linePx / 2;
    const baselineY = linePx;

    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.id = 'myCanvas';
        canvas.width = image.width;
        canvas.height = image.height + blockHeight * lineCount;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        const lineHeight = canvas.height * (70 / 1500);
        ctx.fillStyle = textColor;
        ctx.font = linePx + 'px ' + fontFamily;
        drawWrappedText(ctx, text, 0, baselineY, canvas.width, lineHeight);
        ctx.drawImage(image, 0, blockHeight * lineCount);

        const result = new Image();
        result.crossOrigin = 'anonymous';
        result.src = canvas.toDataURL();
        result.onload = function () { resolve(result); };
    });
}

// ==================== BADGE FUNCTIONS ====================

/**
 * Prepends a "Best Seller" badge image above the main image.
 */
async function addBestSellerBadge(image, badgePath = '/Image_Badges/Best_Seller.png') {
    const badgeUrl = chrome.runtime.getURL(badgePath);
    const badge = await urlToImage(badgeUrl);
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = image.width;
    canvas.height = image.height + badge.height;
    ctx.fillStyle = 'white';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.drawImage(image, 0, badge.height);
    ctx.drawImage(badge, 0, 0, badge.width, badge.height);

    const result = new Image();
    result.src = canvas.toDataURL();
    return result;
}

/**
 * Overlays a "Limited Time Deal" badge in the bottom-right corner of the image.
 */
async function addLimitedTimeDealBadge(image, badgePath = '/Image_Badges/Limited-Time-Deal-Badge.jpg') {
    const badgeUrl = chrome.runtime.getURL(badgePath);
    const badge = await urlToImage(badgeUrl);
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = image.width;
    canvas.height = image.height;
    ctx.fillStyle = 'white';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.drawImage(image, 0, 0);

    const badgeW = image.width / 2.5;
    const badgeH = badge.height * (badgeW / badge.width);
    ctx.drawImage(badge, image.width - badgeW, image.height - badgeH, badgeW, badgeH);

    const result = new Image();
    result.src = canvas.toDataURL();
    return result;
}

// ==================== PRODUCT SAFETY CARD ====================

/**
 * Generates a 500×500 German-language product safety information card (eBay-style).
 * Shows manufacturer and EU responsible person.
 */
async function createProductSafetyCard(manufacturer, euResponsiblePerson) {
    const canvas = document.createElement('canvas');
    canvas.width = 500;
    canvas.height = 500;
    const ctx = canvas.getContext('2d');

    // Coloured border strips
    ctx.fillStyle = '#E53238'; ctx.fillRect(0, 0, canvas.width, 20);
    ctx.fillStyle = '#0074E8'; ctx.fillRect(canvas.width - 20, 0, 20, canvas.height);
    ctx.fillStyle = '#F5AF02'; ctx.fillRect(0, canvas.height - 20, canvas.width, 20);
    ctx.fillStyle = '#86B817'; ctx.fillRect(0, 0, 20, canvas.height);

    const innerW = canvas.width - 40;
    const innerH = canvas.height - 40;
    ctx.fillStyle = '#ffffff';
    ctx.fillRect(20, 20, innerW, innerH);

    const title = 'Informationen zur Produktsicherheit';
    ctx.font = 'bold 24px Arial';
    ctx.fillStyle = '#000';
    const titleX = 20 + (innerW - ctx.measureText(title).width) / 2;
    ctx.fillText(title, titleX, 40);

    ctx.strokeStyle = '#cccccc';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(30, 75);
    ctx.lineTo(20 + innerW - 10, 75);
    ctx.stroke();

    function drawSection(ctx, x, y, maxW, label, value, maxY) {
        ctx.fillStyle = '#000';
        ctx.font = 'bold 16px Arial';
        ctx.textBaseline = 'top';
        ctx.fillText(label, x + 8, y);
        y += 20;
        ctx.font = '14px Arial';

        const wrapText = (ctx, text, maxWidth) => {
            const paragraphs = text.split('\n');
            const lines = [];
            for (const para of paragraphs) {
                const words = para.split(' ');
                let line = '';
                for (let i = 0; i < words.length; i++) {
                    const test = line + words[i] + ' ';
                    if (ctx.measureText(test).width > maxWidth && i > 0) {
                        lines.push(line.trim());
                        line = words[i] + ' ';
                    } else {
                        line = test;
                    }
                }
                lines.push(line.trim());
            }
            return lines;
        };

        const lines = wrapText(ctx, value, maxW - 16);
        for (let i = 0; i < lines.length; i++) {
            if (y + 16 > maxY) break;
            ctx.fillText(lines[i], x + 8, y);
            y += 16;
        }
        return y;
    }

    const bottomY = 20 + innerH - 10;
    const halfH = (20 + innerH - 85 - 10) / 2;

    let y = drawSection(ctx, 20, 85, innerW, 'Hersteller:', manufacturer, 85 + halfH);
    y += 10;
    drawSection(ctx, 20, y, innerW, 'EU Verantwortliche Person:', euResponsiblePerson, bottomY);

    ctx.strokeStyle = '#eeeeee';
    ctx.lineWidth = 2;
    ctx.strokeRect(20, 20, innerW, innerH);

    return new Promise((resolve) => {
        const result = new Image();
        result.src = canvas.toDataURL('image/png');
        result.onload = function () { resolve(result); };
    });
}

// ==================== FILE / INPUT HELPERS ====================

const ImageSourceType = { URL: 'URL', BASE_64: 'BASE_64' };

/**
 * Converts a base64 string (or data URL) to a File object (image/jpeg).
 */
const base64ToFile = (base64, filename) => {
    let raw = base64;
    if (base64.startsWith('data')) raw = base64.split(',')[1];

    const binary = atob(raw);
    const buffer = new ArrayBuffer(binary.length);
    const bytes = new Uint8Array(buffer);
    for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);

    const blob = new Blob([buffer], { type: 'image/jpeg' });
    return new File([blob], filename, { lastModified: new Date().getTime(), type: 'image/jpeg' });
};

/**
 * Injects a File into an <input type="file"> element and fires a change event.
 */
const injectFileIntoInput = (file, inputElement) => {
    const dt = new DataTransfer();
    dt.items.add(file);
    inputElement.files = dt.files;
    inputElement.dispatchEvent(new Event('change', { bubbles: true }));
};

/**
 * Loads a URL or base64 string as a File and injects it into a CSS-selected input.
 */
const loadAndInjectImage = async (source, inputSelector, filename, sourceType) => {
    let file;
    if (sourceType === ImageSourceType.URL) {
        const { b64Image } = await fetchImageAsBase64(source);
        file = base64ToFile(b64Image, filename);
    } else if (sourceType === ImageSourceType.BASE_64) {
        file = base64ToFile(source, filename);
    }
    const input = document.querySelector(inputSelector);
    injectFileIntoInput(file, input);
};

// ==================== HIGH-LEVEL IMAGE PIPELINE FUNCTIONS ====================

/**
 * Pipeline A: Resizes main image, overlays a secondary image (top-right side panel),
 * then places watermark bottom-right.
 */
async function createListingImageWithSidePanel(
    mainImageUrl, sidePanelUrl, watermarkUrl,
    _unused3, _unused4,
    targetH, targetW,
    _unused5
) {
    let main = await urlToImage(mainImageUrl);
    main = await resizeImageCentered(main, targetH, targetW);

    let side = await urlToImage(sidePanelUrl);
    side = await resizeImageCentered(side, 0.45 * targetW, 0.45 * targetH);
    main = await appendImageHorizontal(main, side, 'right');
    side = await resizeImageCentered(side, 0.4 * targetH, 0.4 * targetW);

    let watermark = await urlToImage(watermarkUrl);
    watermark = await resizeImageCentered(watermark, 0.45 * targetW, 0.45 * targetH);
    return await overlayBottomRight(main, watermark, 0.95);
}

/**
 * Pipeline B: Similar to A but with rounded-corner clipping for the side panel and
 * watermark placed via overlayBottomRight (0.7 opacity).
 */
async function createListingImageWithRoundedSidePanel(
    mainImageUrl, sidePanelUrl,
    _unused6, targetH, targetW, _unused7
) {
    let main = await urlToImage(mainImageUrl);
    main = await scaleImageToMax(main, targetH, targetW);

    let side = await urlToImage(sidePanelUrl);
    side = await scaleImageToMax(side, 0.45 * targetW, 0.45 * targetH);
    main = await appendImageHorizontal(main, side, 'right');
    side = await scaleImageToMax(side, 0.4 * targetW, 0.4 * targetH);
    return await overlayBottomRight(main, side, 0.7);
}

/**
 * Pipeline C: Adds a watermark to a single product image and makes it square.
 * If landscape: pads height. If portrait/square: pads width.
 */
async function createWatermarkedSquareImage({ imageSource, waterMarkUrl }) {
    let main = await urlToImage(imageSource);
    main = await makeWhiteTransparent(main);
    main = await makeImageSquareBottomRight(main);

    let wm = await urlToImage(waterMarkUrl);
    wm = await scaleImageToMax(wm, 0.2 * main.height, 0.2 * main.width);
    wm = await makeWhiteTransparent(wm);
    wm = await makeImageSquareBottomRight(wm);
    main = await appendImageHorizontal(main, wm, 'right');
    main = await overlayBottomRight(main, wm, 0.7);

    if (main.width > main.height) {
        main = await addPaddingToImage({ imageObject: main, padding: main.width - main.height, paddingDirection: 'height' });
    } else {
        main = await addPaddingToImage({ imageObject: main, padding: main.height - main.width, paddingDirection: 'width' });
    }
    return main;
}

/**
 * Pipeline D: Fetches buyer intent from server, adds "Look Inside"-style text,
 * appends watermark, applies square crop, returns final image.
 */
async function createLookInsideImage(
    { imageSource, waterMarkUrl },
    firstWord = 'Look',
    remainingWords = 'Inside'
) {
    let main = await urlToImage(imageSource);
    main = await makeWhiteTransparent(main);
    main = await makeImageSquareBottomRight(main);

    let wm = await urlToImage(waterMarkUrl);
    wm = await scaleImageToMax(wm, 0.2 * main.height, 0.2 * main.width);
    wm = await makeWhiteTransparent(wm);
    wm = await makeImageSquareBottomRight(wm);

    // Fetch two-word buyer intent from API
    const description = getProductDescriptionAndFeatures();
    const prompt = getFilteredTitle() + '\n\n' + description;
    const buyerIntent = await postToServer('http://1.1.1.66:1102/api/ItemSpecifics/GetBuyerIntent', {
        request_type: 'get_buyer_intent',
        productDescription: prompt
    });
    console.log('twoWordBuyerIntent', buyerIntent);

    const words = buyerIntent.split(' ');
    firstWord = words[0];
    remainingWords = 'Inside';
    for (let i = 1; i < words.length; i++) remainingWords += ' ' + words[i];
    remainingWords = remainingWords.trim();
    firstWord = firstWord.trim();

    main = await addDynamicBuyerIntentText(main, firstWord, remainingWords);
    main = await appendImageHorizontal(main, wm, 'right');
    main = await makeImageSquare(main);
    return await overlayBottomRight(main, wm, 0.7);
}

/**
 * Pipeline E: Watermark + make square + resize to min 500×500.
 */
async function createSquareResizedImage({ imageSource, waterMarkUrl }) {
    let main = await urlToImage(imageSource);
    main = await makeWhiteTransparent(main);
    main = await makeImageSquareBottomRight(main);

    let wm = await urlToImage(waterMarkUrl);
    wm = await scaleImageToMax(wm, 0.2 * main.height, 0.2 * main.width);
    wm = await makeWhiteTransparent(wm);
    wm = await makeImageSquareBottomRight(wm);
    main = await appendImageHorizontal(main, wm, 'right');
    main = await makeImageSquare(main);
    main = await overlayBottomRight(main, wm, 0.7);
    return await ensureMinimumSize(main, 500, 500);
}

/**
 * Pipeline F: Full listing image with side panel, border, best-seller and limited-deal badges + 500px min.
 */
async function createListingImageWithBadges(
    mainImageUrl, sidePanelUrl, watermarkUrl,
    _unused8, targetH, targetW, _unused9, _unused10
) {
    let main = await urlToImage(mainImageUrl);
    main = await scaleImageToMax(main, targetH, targetW);
    main = await makeWhiteTransparent(main);

    let wm = await urlToImage(watermarkUrl);
    wm = await scaleImageToMax(wm, 0.45 * targetW, 0.45 * targetH);

    let side = await urlToImage(sidePanelUrl);
    side = await scaleImageToMax(side, 0.45 * targetW, 0.45 * targetH);

    main = await appendImageHorizontal(main, side, 'right');
    side = await scaleImageToMax(side, 0.4 * targetH, 0.4 * targetW);
    main = await overlayBottomRightWithOffset(main, side, 1, 12);
    wm = await scaleImageToFit(wm, 0.65 * side.width, 0.65 * side.height);
    wm = await makeWhiteTransparent(wm);
    main = await overlayBottomRight(main, wm, 0.7);
    main = await makeImageSquare(main);
    main = await addBestSellerBadge(main);
    main = await addLimitedTimeDealBadge(main);
    return await ensureMinimumSize(main, 500, 500);
}

/**
 * Pipeline G: Listing image with side panel + watermark (variant ordering).
 */
async function createListingImageVariantG(
    mainImageUrl, sidePanelUrl, watermarkUrl,
    _unused11, targetH, targetW, _unused12, _unused13
) {
    let main = await urlToImage(mainImageUrl);
    main = await scaleImageToMax(main, targetH, targetW);
    main = await makeWhiteTransparent(main);

    let wm = await urlToImage(watermarkUrl);
    wm = await scaleImageToMax(wm, 0.45 * targetW, 0.45 * targetH);
    wm = await makeWhiteTransparent(wm);
    wm = await makeImageSquareBottomRight(wm);

    let side = await urlToImage(sidePanelUrl);
    side = await scaleImageToMax(side, 0.45 * targetW, 0.45 * targetH);
    main = await appendImageHorizontal(main, side, 'right');
    wm = await scaleImageToFit(wm, 0.65 * side.width, 0.65 * side.height);
    wm = await makeImageSquareBottomRight(wm);
    main = await overlayBottomRight(main, wm, 0.7);
    main = await makeImageSquare(main);
    side = await scaleImageToMax(side, 0.4 * targetH, 0.4 * targetW);
    main = await overlayBottomRightWithOffset(main, side, 1, 12);
    return await ensureMinimumSize(main, 500, 500);
}

/**
 * Pipeline H: Listing image with beige background on side panel + badges.
 */
async function createListingImageWithBeigeSidePanel(
    mainImageUrl, sidePanelUrl, watermarkUrl,
    _unused14, targetH, targetW, _unused15, _unused16
) {
    let main = await urlToImage(mainImageUrl);
    main = await scaleImageToMax(main, targetH, targetW);
    main = await makeWhiteTransparent(main);

    let wm = await urlToImage(watermarkUrl);
    wm = await scaleImageToMax(wm, 0.45 * targetW, 0.45 * targetH);

    let side = await urlToImage(sidePanelUrl);
    side = await scaleImageToMax(side, 0.45 * targetW, 0.45 * targetH);
    side = await makeImageSquareBottomRight(side);
    side = await applyBackgroundColor(side, sidePanelUrl, 'beige');
    main = await appendImageHorizontal(main, side, 'right');
    side = await scaleImageToMax(side, 0.4 * targetH, 0.4 * targetW);
    main = await overlayBottomRightWithOffset(main, side, 1, 12);
    wm = await makeWhiteTransparent(wm);
    wm = await scaleImageToFit(wm, side.width, side.height);
    wm = await makeImageSquareBottomRight(wm);
    main = await overlayBottomRight(main, wm, 0.7, watermarkUrl);
    main = await makeImageSquare(main);
    main = await addBestSellerBadge(main);
    main = await addLimitedTimeDealBadge(main);
    return await ensureMinimumSize(main, 500, 500);
}

/**
 * Pipeline I: Multi-image with two secondary images (badges/thumbnails) placed left,
 * chosen based on relative widths.
 */
async function createMultiImageWithTwoBadges(
    mainImageUrl, badge1Url, badge2Url, thumb1Url, thumb2Url,
    _label, targetW, targetH, textColor, fontFamily
) {
    let main = await urlToImage(mainImageUrl);
    main = await resizeImageCentered(main, targetW, targetH);

    let wm = await urlToImage(badge2Url);
    wm = await scaleImageToMax(wm, 0.45 * targetH, 0.45 * targetW);

    let side = await urlToImage(badge1Url);
    side = await resizeImageCentered(side, 0.45 * targetH, 0.45 * targetW);
    main = await appendImageHorizontal(main, side, 'right');
    side = await resizeImageCentered(side, 0.4 * targetH, 0.4 * targetW);
    wm = await resizeImageCentered(wm, 0.4 * targetH, 0.4 * targetW);
    main = await overlayBottomRightWithOffset(main, side, 1, 12);
    wm = await makeImageSquareBottomRight(wm);
    main = await overlayBottomRight(main, wm, 0.7);

    let t1 = await urlToImage(thumb1Url);
    t1 = await addThickBorderWithWhiteBg(t1);
    t1 = await scaleImageToMax(t1, 0.45 * targetH, 0.45 * targetW);

    let t2 = await urlToImage(thumb2Url);
    t2 = await addThickBorderWithWhiteBg(t2);
    t2 = await scaleImageToMax(t2, 0.45 * targetH, 0.45 * targetW);

    if (t2.width >= t1.width) {
        main = await appendImageHorizontal(main, t2, 'left');
        t2 = await scaleImageToMax(t2, 0.4 * targetH, 0.4 * targetW);
        t1 = await scaleImageToMax(t1, 0.4 * targetH, 0.4 * targetW);
        main = await overlayBottomLeft(main, t2, 1, 12);
        main = await overlayTopLeft(main, t1, 1);
    } else {
        main = await appendImageHorizontal(main, t1, 'left');
        t2 = await scaleImageToMax(t2, 0.4 * targetH, 0.4 * targetW);
        t1 = await scaleImageToMax(t1, 0.4 * targetH, 0.4 * targetW);
        main = await overlayTopLeft(main, t1, 1);
        main = await overlayBottomLeft(main, t2, 1, 12);
    }
    return await ensureMinimumSize(main, 500, 500);
}

/**
 * Pipeline J: Creates a multi-image with 3 thumbnails stacked on the right side.
 */
async function createMultiImageV2(
    mainImageUrl, thumb1Url, watermarkUrl, thumb2Url, thumb3Url,
    _unused17, targetW, targetH, _unused18, _unused19,
    addBadges = false
) {
    console.log('Creating multi image V2');
    console.log('imageSource', mainImageUrl);

    let main = await urlToImage(mainImageUrl);
    main = await scaleImageToMax(main, targetW, targetH);
    main = await cropWhiteBorders(main);

    let t1 = await urlToImage(thumb1Url);
    let t2 = await urlToImage(thumb2Url);
    let t3 = await urlToImage(thumb3Url);
    t1 = await scaleImageToMax(t1, 0.45 * targetW, 0.45 * targetH);
    t2 = await scaleImageToMax(t2, 0.45 * targetW, 0.45 * targetH);
    t3 = await scaleImageToMax(t3, 0.45 * targetW, 0.45 * targetH);

    const maxThumbWidth = Math.max(t1.width, t2.width, t3.width);
    main = await addHorizontalSpace(main, maxThumbWidth, 'right');
    main = await makeImageSquare(main);

    t1 = await scaleImageToMax(t1, 0.4 * targetW, 0.4 * targetH);
    t2 = await scaleImageToMax(t2, 0.4 * targetW, 0.4 * targetH);
    t3 = await scaleImageToMax(t3, 0.4 * targetW, 0.4 * targetH);

    let yOffset = 0;
    main = await overlayThumbOnRight(main, t1, yOffset);
    yOffset += t1.height + main.height / 25;
    main = await overlayThumbOnRight(main, t2, yOffset);
    yOffset += t2.height + main.height / 25;
    main = await overlayThumbOnRight(main, t3, yOffset);

    let wm = await urlToImage(watermarkUrl);
    wm = await scaleImageToMax(wm, 0.2 * main.height, 0.2 * main.width);

    const originalHeight = main.height;
    const badgeUrl = chrome.runtime.getURL('/Image_Badges/Best_Seller.png');
    let badge = await urlToImage(badgeUrl);
    const badgeH = main.height / 5;
    const badgeW = badge.width * (badgeH / badge.height);
    badge = await scaleImageToMax(badge, badgeW, badgeH);

    if (addBadges) {
        main = await addBestSellerBadge(main);
        main = await addLimitedTimeDealBadge(main);
        const extraH = main.height - originalHeight;
        wm = await scaleImageToMax(wm, extraH, 0.2 * main.width);
        main = await overlayBottomRightNoClip(main, wm, 0.7);
    }
    return await ensureMinimumSize(main, 500, 500);
}

/**
 * Places a thumbnail with rounded corners at a fixed y-offset on the right side of an image.
 */
function overlayThumbOnRight(baseImage, thumb, yOffset) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.width = baseImage.width;
        canvas.height = baseImage.height;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(baseImage, 0, 0);
        ctx.globalAlpha = 0.9;

        const x = canvas.width - thumb.width - canvas.width / 50;
        const y = yOffset;
        drawRoundedRect(ctx, x, y, thumb.width, thumb.height, 10);
        ctx.clip();
        ctx.drawImage(thumb, x, y);

        const result = new Image();
        result.onload = function () { resolve(result); };
        result.src = canvas.toDataURL();
    });
}

/**
 * Pipeline K: Basic image with watermark + text caption above, upload on completion.
 */
async function uploadWithWatermarkAndText(mainImageUrl, itemId, uploadTarget, targetH, targetW) {
    const watermarkUrl = localStorage.getItem('waterMarkUrl');
    let main = await urlToImage(mainImageUrl);
    main = await resizeImageCentered(main, targetH, targetW);

    let wm = await urlToImage(watermarkUrl);
    wm = await resizeImageCentered(wm, 0.4 * targetW, 0.4 * targetH);
    main = await appendToRight(main, wm, 0.75);
    main = await addTextAboveImage(main, itemId);
    upload(main.src, itemId, uploadTarget);
}

/**
 * Pipeline L: Simple resize + rotation + upload (no watermark).
 */
async function uploadResizedRotated(mainImageUrl, itemId, uploadTarget, targetH, targetW) {
    let main = await urlToImage(mainImageUrl);
    main = await rotateImage(main, 0);
    main = await resizeImageCentered(main, targetH, targetW);
    upload(main.src, itemId, uploadTarget);
}

/**
 * Pipeline M: Fixed size image pipeline for a pre-processed image object (not URL).
 * Resizes + crops + sets up side + watermark.
 */
async function createFixedSizeListingImage(imageObj, sidePanelUrl, watermarkUrl) {
    let main = imageObj;
    main = await scaleImageToMax(main, 1500, 1500);
    main = await cropWhiteBorders(main);

    let wm = await urlToImage(watermarkUrl);
    wm = await cropWhiteBorders(wm);
    wm = await scaleImageToMax(wm, 675, 675);

    let side = await urlToImage(sidePanelUrl);
    side = await scaleImageToMax(side, 675, 675);

    main = await appendImageHorizontal(main, side, 'right');
    side = await scaleImageToMax(side, 600, 600);
    main = await overlayBottomRightWithOffset(main, side, 1, 12);
    wm = await scaleImageToFit(wm, 0.65 * side.width, 0.65 * side.height);
    wm = await makeWhiteTransparent(wm);
    main = await overlayBottomRight(main, wm, 0.7);
    main = await makeImageSquare(main);
    return await ensureMinimumSize(main, 500, 500);
}

/**
 * Pipeline N: Canada-flag composite + text caption.
 * Overlays Canadian flag image and a flipped/rotated secondary image.
 */
async function createCanadaFlagComposite(
    mainImageUrl, secondaryImageUrl, caption,
    targetH, targetW, textColor, fontFamily
) {
    let main = await urlToImage(mainImageUrl);
    main = await resizeImageCentered(main, targetH, targetW);

    const flagImg = await urlToImage('https://centreforinquiry.ca/wp-content/uploads/2020/05/68353859-canadian-map-with-canada-flag.jpg');
    const flagResized = await resizeImageCentered(flagImg, 0.45 * targetW, 0.45 * targetH);
    main = await appendToRight(main, flagResized, 0.75);

    let secondary = await urlToImage(secondaryImageUrl);
    secondary = await addThickBorderWithWhiteBg(secondary);
    secondary = await flipImageHorizontal(secondary);
    secondary = await rotateImage(secondary, 10);
    secondary = await resizeImageCentered(secondary, 0.45 * targetW, 0.45 * targetH);
    main = await placeBottomRight(main, secondary, 0.95);
    main = await addTextAboveImage(main, caption, textColor, fontFamily);
    return await addThickBorderWithWhiteBg(main);
}

/**
 * Pipeline O: Thumbnail strip — main image with a row of sub-images along the bottom.
 */
async function createThumbnailStripImage(mainImageUrl, thumbnailUrls) {
    let main = await urlToImage(mainImageUrl);
    main = await scaleImageToMax(main, 1500, 1500);

    const thumbs = [];
    for (let i = 0; i < thumbnailUrls.length; i++) {
        let t = await urlToImage(thumbnailUrls[i]);
        t = await scaleImageToMax(t, main.width / thumbnailUrls.length, main.height / thumbnailUrls.length);
        thumbs.push(t);
    }

    let widest = thumbs[0];
    for (let i = 0; i < thumbs.length; i++)
        if (thumbs[i].width > widest.width) widest = thumbs[i];

    main = await addVerticalSpace(main, widest.height, 'bottom');
    for (let i = 0; i < thumbs.length; i++)
        main = await placeImageAtBottom(main, thumbs[i], i * (main.width / thumbs.length));

    return main;
}

// ==================== UTILITIES ====================

/**
 * Appends an img element to the document for quick debugging.
 */
function debugAppendImage(src) {
    const img = document.createElement('img');
    img.src = src;
    document.getElementsByTagName('html')[0].appendChild(img);
}

/**
 * Loads an image from a src string (cross-origin) and resolves with the Image element.
 */
function loadImageFromSrc(src) {
    return new Promise((resolve) => {
        const img = new Image();
        img.src = src;
        img.crossOrigin = 'anonymous';
        img.onload = function () { resolve(img); };
    });
}

/**
 * Crops whitespace from an image using pixel-colour scanning (legacy version using canvas pixel data directly).
 * Returns a data URL string (not an Image element).
 */
function cropWhiteBordersLegacy(image) {
    const imgWidth = image.width;
    const imgHeight = image.height;
    const canvas = document.createElement('canvas');
    canvas.setAttribute('width', imgWidth);
    canvas.setAttribute('height', imgHeight);
    const ctx = canvas.getContext('2d');
    ctx.drawImage(image, 0, 0);
    const pixelData = ctx.getImageData(0, 0, imgWidth, imgHeight).data;

    const getPixel = (x, y) => {
        const idx = imgWidth * y + x;
        return {
            red: pixelData[4 * idx],
            green: pixelData[4 * idx + 1],
            blue: pixelData[4 * idx + 2],
            opacity: pixelData[4 * idx + 3]
        };
    };
    const isLight = p => p.red > 200 && p.green > 200 && p.blue > 200;

    const scanRowFromEdge = (fromTop) => {
        const dir = fromTop ? 1 : -1;
        for (let y = fromTop ? 0 : imgHeight - 1; fromTop ? y < imgHeight : y > -1; y += dir)
            for (let x = 0; x < imgWidth; x++)
                if (!isLight(getPixel(x, y)))
                    return fromTop ? y : Math.min(y + 1, imgHeight);
        return null;
    };
    const scanColFromEdge = (fromLeft) => {
        const dir = fromLeft ? 1 : -1;
        for (let x = fromLeft ? 0 : imgWidth - 1; fromLeft ? x < imgWidth : x > -1; x += dir)
            for (let y = 0; y < imgHeight; y++)
                if (!isLight(getPixel(x, y)))
                    return fromLeft ? x : Math.min(x + 1, imgWidth);
        return null;
    };

    const top = scanRowFromEdge(true);
    const bottom = scanRowFromEdge(false);
    const left = scanColFromEdge(true);
    const cropW = scanColFromEdge(false) - left;
    const cropH = bottom - top;

    canvas.setAttribute('width', cropW);
    canvas.setAttribute('height', cropH);
    canvas.getContext('2d').drawImage(image, left, top, cropW, cropH, 0, 0, cropW, cropH);
    return canvas.toDataURL();
}

/**
 * Converts an Image element to a Canvas element.
 */
function imageToCanvas(image) {
    return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        canvas.width = image.width;
        canvas.height = image.height;
        const ctx = canvas.getContext('2d');
        const img = new Image();
        img.onload = function () {
            ctx.drawImage(image, 0, 0);
            resolve(canvas);
        };
        img.src = image.src;
    });
}

/**
 * Converts a Canvas element to an Image element.
 */
function canvasToImage(canvas) {
    return new Promise((resolve) => {
        const img = new Image();
        img.onload = function () { resolve(img); };
        img.src = canvas.toDataURL();
    });
}

// ==================== ENTRY POINT ====================

console.log('image_utils.js');
console.log('test/script.js');

document.querySelector('#test').addEventListener('click', async () => {
    console.log('Test button clicked');

    const result = await createMultiImageWithTwoBadges(
        'https://i.ebayimg.com/images/g/264AAeSwJD5oZa~l/s-l1600.webp',
        'https://i.ebayimg.com/images/g/264AAeSwJD5oZa~l/s-l1600.webp',
        'https://www.ebay.com/itm/357214288914',
        'https://i.ebayimg.com/images/g/264AAeSwJD5oZa~l/s-l1600.webp',
        'https://i.ebayimg.com/images/g/264AAeSwJD5oZa~l/s-l1600.webp',
        'Sample Image',
        800, 800,
        '#FFFFFF', 'Arial'
    );

    console.log('Multi Image created promise resolved');
    console.log('Multi Image created:', result);
    document.body.appendChild(result);
    console.log('Multi Image appended to body');
});



