function isAppleWebViewDialogFallbackTarget() {
    try {
        const userAgent = navigator.userAgent || '';
        const touchPoints = navigator.maxTouchPoints || 0;
        return /iPhone|iPad|iPod/i.test(userAgent)
            || (/Macintosh/i.test(userAgent) && touchPoints > 1);
    } catch {
        return false;
    }
}

// Dialog helper functions for Blazor interop
window.showDialog = function(element) {
    if (!element) {
        return false;
    }

    if (!isAppleWebViewDialogFallbackTarget() && typeof element.showModal === 'function') {
        element.showModal();
        return true;
    }

    element.setAttribute('open', '');
    element.style.display = 'flex';
    return true;
};

window.closeDialog = function(element) {
    if (!element) {
        return false;
    }

    if (!isAppleWebViewDialogFallbackTarget() && typeof element.close === 'function') {
        element.close();
        return true;
    }

    element.removeAttribute('open');
    element.style.removeProperty('display');
    return true;
};

window.getWindowDimensions = function() {
    return {
        width: window.innerWidth || 0,
        height: window.innerHeight || 0
    };
};

window.getViewportWidth = function() {
    return window.innerWidth || document.documentElement?.clientWidth || 0;
};

window.getElementRect = function(element) {
    if (!element) {
        return null;
    }

    const rect = element.getBoundingClientRect();
    return {
        left: rect.left,
        top: rect.top,
        width: rect.width,
        height: rect.height
    };
};

window.getImageMetrics = function(element) {
    if (!element) {
        return null;
    }

    const rect = element.getBoundingClientRect();
    return {
        naturalWidth: element.naturalWidth || 0,
        naturalHeight: element.naturalHeight || 0,
        width: rect.width,
        height: rect.height
    };
};

window.registerImagePreviewViewportObserver = function(element, dotNetRef, callbackMethodName) {
    if (!element || !dotNetRef || !callbackMethodName) {
        return false;
    }

    if (element.__amPreviewViewportCleanup) {
        element.__amPreviewViewportCleanup();
    }

    let disposed = false;
    let frameHandle = 0;

    const notify = () => {
        if (disposed) {
            return;
        }

        if (frameHandle) {
            window.cancelAnimationFrame(frameHandle);
        }

        frameHandle = window.requestAnimationFrame(() => {
            frameHandle = 0;
            dotNetRef.invokeMethodAsync(callbackMethodName).catch(() => {});
        });
    };

    const resizeHandler = () => notify();
    window.addEventListener('resize', resizeHandler, { passive: true });

    let observer = null;
    if (window.ResizeObserver) {
        observer = new ResizeObserver(() => notify());
        observer.observe(element);
    }

    element.__amPreviewViewportCleanup = () => {
        if (disposed) {
            return;
        }

        disposed = true;
        if (frameHandle) {
            window.cancelAnimationFrame(frameHandle);
            frameHandle = 0;
        }

        window.removeEventListener('resize', resizeHandler);
        observer?.disconnect();
        observer = null;
        delete element.__amPreviewViewportCleanup;
    };

    notify();
    return true;
};

window.unregisterImagePreviewViewportObserver = function(element) {
    if (element && element.__amPreviewViewportCleanup) {
        element.__amPreviewViewportCleanup();
        return true;
    }

    return false;
};

function configureAstroPreviewCanvas(canvas) {
    if (!canvas || typeof canvas.getBoundingClientRect !== 'function') {
        return null;
    }

    const rect = canvas.getBoundingClientRect();
    if (!rect || rect.width <= 0 || rect.height <= 0) {
        return null;
    }

    const maxBackingPixels = 36000000;
    const baseDpr = Math.max(1, Math.min(window.devicePixelRatio || 1, 3));
    const requestedPixels = rect.width * rect.height * baseDpr * baseDpr;
    const dpr = requestedPixels > maxBackingPixels
        ? Math.max(1, Math.sqrt(maxBackingPixels / (rect.width * rect.height)))
        : baseDpr;

    const backingWidth = Math.max(1, Math.round(rect.width * dpr));
    const backingHeight = Math.max(1, Math.round(rect.height * dpr));

    if (canvas.width !== backingWidth || canvas.height !== backingHeight) {
        canvas.width = backingWidth;
        canvas.height = backingHeight;
    }

    const context = canvas.getContext('2d', { alpha: true });
    if (!context) {
        return null;
    }

    context.setTransform(dpr, 0, 0, dpr, 0, 0);
    context.clearRect(0, 0, rect.width, rect.height);
    return { context, rect, dpr };
}

function isAstroPreviewImageReady(image) {
    return !!image
        && typeof image.getBoundingClientRect === 'function'
        && image.complete
        && (image.naturalWidth || 0) > 0
        && (image.naturalHeight || 0) > 0;
}

function snapAstroPreviewCanvasPixel(value, dpr) {
    const safeDpr = Math.max(1, dpr || 1);
    return Math.round(value * safeDpr) / safeDpr;
}

function drawAstroPreviewImage(context, image, destination, canvasRect, rotate180, dpr) {
    let visibleLeft = Math.max(0, destination.x);
    let visibleTop = Math.max(0, destination.y);
    let visibleRight = Math.min(canvasRect.width, destination.x + destination.width);
    let visibleBottom = Math.min(canvasRect.height, destination.y + destination.height);
    let visibleWidth = visibleRight - visibleLeft;
    let visibleHeight = visibleBottom - visibleTop;

    if (visibleWidth <= 0 || visibleHeight <= 0) {
        return;
    }

    const naturalWidth = Math.max(1, image.naturalWidth || 1);
    const naturalHeight = Math.max(1, image.naturalHeight || 1);

    if (rotate180) {
        const scale = Math.max(destination.width / naturalWidth, destination.height / naturalHeight);
        context.imageSmoothingEnabled = scale < 1;
        context.imageSmoothingQuality = 'high';
        context.save();
        context.translate(destination.x + (destination.width / 2), destination.y + (destination.height / 2));
        context.rotate(Math.PI);
        context.drawImage(image, -destination.width / 2, -destination.height / 2, destination.width, destination.height);
        context.restore();
        return;
    }

    let sourceLeft = ((visibleLeft - destination.x) / destination.width) * naturalWidth;
    let sourceTop = ((visibleTop - destination.y) / destination.height) * naturalHeight;
    let sourceWidth = (visibleWidth / destination.width) * naturalWidth;
    let sourceHeight = (visibleHeight / destination.height) * naturalHeight;
    const scale = Math.max(visibleWidth / Math.max(1, sourceWidth), visibleHeight / Math.max(1, sourceHeight));

    context.imageSmoothingEnabled = scale < 1;

    if (context.imageSmoothingEnabled) {
        context.imageSmoothingQuality = 'high';
    } else {
        const sourceRight = Math.min(naturalWidth, Math.max(sourceLeft + sourceWidth, sourceLeft + 1));
        const sourceBottom = Math.min(naturalHeight, Math.max(sourceTop + sourceHeight, sourceTop + 1));
        const snappedSourceLeft = Math.max(0, Math.floor(sourceLeft));
        const snappedSourceTop = Math.max(0, Math.floor(sourceTop));
        const snappedSourceRight = Math.min(naturalWidth, Math.ceil(sourceRight));
        const snappedSourceBottom = Math.min(naturalHeight, Math.ceil(sourceBottom));

        sourceLeft = snappedSourceLeft;
        sourceTop = snappedSourceTop;
        sourceWidth = Math.max(1, snappedSourceRight - snappedSourceLeft);
        sourceHeight = Math.max(1, snappedSourceBottom - snappedSourceTop);

        visibleLeft = destination.x + (sourceLeft / naturalWidth) * destination.width;
        visibleTop = destination.y + (sourceTop / naturalHeight) * destination.height;
        visibleRight = destination.x + ((sourceLeft + sourceWidth) / naturalWidth) * destination.width;
        visibleBottom = destination.y + ((sourceTop + sourceHeight) / naturalHeight) * destination.height;

        visibleLeft = snapAstroPreviewCanvasPixel(visibleLeft, dpr);
        visibleTop = snapAstroPreviewCanvasPixel(visibleTop, dpr);
        visibleRight = snapAstroPreviewCanvasPixel(visibleRight, dpr);
        visibleBottom = snapAstroPreviewCanvasPixel(visibleBottom, dpr);
        visibleWidth = Math.max(1 / Math.max(1, dpr || 1), visibleRight - visibleLeft);
        visibleHeight = Math.max(1 / Math.max(1, dpr || 1), visibleBottom - visibleTop);
    }

    context.drawImage(
        image,
        sourceLeft,
        sourceTop,
        sourceWidth,
        sourceHeight,
        visibleLeft,
        visibleTop,
        visibleWidth,
        visibleHeight);
}

function drawAstroPreviewDetailOverlay(context, detailImage, destination, canvasRect, overlay, dpr) {
    if (!isAstroPreviewImageReady(detailImage) || !overlay) {
        return;
    }

    const left = Math.max(0, Math.min(1, Number(overlay.left ?? overlay.Left ?? 0)));
    const top = Math.max(0, Math.min(1, Number(overlay.top ?? overlay.Top ?? 0)));
    const width = Math.max(0, Math.min(1 - left, Number(overlay.width ?? overlay.Width ?? 0)));
    const height = Math.max(0, Math.min(1 - top, Number(overlay.height ?? overlay.Height ?? 0)));

    if (width <= 0 || height <= 0) {
        return;
    }

    const overlayDestination = {
        x: destination.x + (left * destination.width),
        y: destination.y + (top * destination.height),
        width: width * destination.width,
        height: height * destination.height
    };

    let visibleLeft = Math.max(0, overlayDestination.x);
    let visibleTop = Math.max(0, overlayDestination.y);
    let visibleRight = Math.min(canvasRect.width, overlayDestination.x + overlayDestination.width);
    let visibleBottom = Math.min(canvasRect.height, overlayDestination.y + overlayDestination.height);
    let visibleWidth = visibleRight - visibleLeft;
    let visibleHeight = visibleBottom - visibleTop;

    if (visibleWidth <= 0 || visibleHeight <= 0) {
        return;
    }

    const naturalWidth = Math.max(1, detailImage.naturalWidth || 1);
    const naturalHeight = Math.max(1, detailImage.naturalHeight || 1);
    let sourceLeft = ((visibleLeft - overlayDestination.x) / overlayDestination.width) * naturalWidth;
    let sourceTop = ((visibleTop - overlayDestination.y) / overlayDestination.height) * naturalHeight;
    let sourceWidth = (visibleWidth / overlayDestination.width) * naturalWidth;
    let sourceHeight = (visibleHeight / overlayDestination.height) * naturalHeight;
    const scale = Math.max(visibleWidth / Math.max(1, sourceWidth), visibleHeight / Math.max(1, sourceHeight));

    context.save();
    context.imageSmoothingEnabled = scale < 1;
    if (context.imageSmoothingEnabled) {
        context.imageSmoothingQuality = 'high';
    } else {
        const sourceRight = Math.min(naturalWidth, Math.max(sourceLeft + sourceWidth, sourceLeft + 1));
        const sourceBottom = Math.min(naturalHeight, Math.max(sourceTop + sourceHeight, sourceTop + 1));
        const snappedSourceLeft = Math.max(0, Math.floor(sourceLeft));
        const snappedSourceTop = Math.max(0, Math.floor(sourceTop));
        const snappedSourceRight = Math.min(naturalWidth, Math.ceil(sourceRight));
        const snappedSourceBottom = Math.min(naturalHeight, Math.ceil(sourceBottom));

        sourceLeft = snappedSourceLeft;
        sourceTop = snappedSourceTop;
        sourceWidth = Math.max(1, snappedSourceRight - snappedSourceLeft);
        sourceHeight = Math.max(1, snappedSourceBottom - snappedSourceTop);

        visibleLeft = overlayDestination.x + (sourceLeft / naturalWidth) * overlayDestination.width;
        visibleTop = overlayDestination.y + (sourceTop / naturalHeight) * overlayDestination.height;
        visibleRight = overlayDestination.x + ((sourceLeft + sourceWidth) / naturalWidth) * overlayDestination.width;
        visibleBottom = overlayDestination.y + ((sourceTop + sourceHeight) / naturalHeight) * overlayDestination.height;

        visibleLeft = snapAstroPreviewCanvasPixel(visibleLeft, dpr);
        visibleTop = snapAstroPreviewCanvasPixel(visibleTop, dpr);
        visibleRight = snapAstroPreviewCanvasPixel(visibleRight, dpr);
        visibleBottom = snapAstroPreviewCanvasPixel(visibleBottom, dpr);
        visibleWidth = Math.max(1 / Math.max(1, dpr || 1), visibleRight - visibleLeft);
        visibleHeight = Math.max(1 / Math.max(1, dpr || 1), visibleBottom - visibleTop);
    }

    context.drawImage(
        detailImage,
        sourceLeft,
        sourceTop,
        sourceWidth,
        sourceHeight,
        visibleLeft,
        visibleTop,
        visibleWidth,
        visibleHeight);
    context.restore();
}

function renderAstroImagePreviewCanvasNow(canvas, primaryImage, compareImage, options) {
    const configured = configureAstroPreviewCanvas(canvas);
    if (!configured || !isAstroPreviewImageReady(primaryImage)) {
        return false;
    }

    const imageRect = primaryImage.getBoundingClientRect();
    if (!imageRect || imageRect.width <= 0 || imageRect.height <= 0) {
        return false;
    }

    const { context, rect: canvasRect, dpr } = configured;
    const destination = {
        x: imageRect.left - canvasRect.left,
        y: imageRect.top - canvasRect.top,
        width: imageRect.width,
        height: imageRect.height
    };

    drawAstroPreviewImage(context, primaryImage, destination, canvasRect, false, dpr);

    const detailOverlayImage = options?.detailOverlayImage ?? options?.DetailOverlayImage;
    const detailOverlay = options?.detailOverlay ?? options?.DetailOverlay;
    drawAstroPreviewDetailOverlay(context, detailOverlayImage, destination, canvasRect, detailOverlay, dpr);

    if (isAstroPreviewImageReady(compareImage)) {
        const splitPercent = Math.max(0, Math.min(100, Number(options?.compareSplitPercent ?? options?.CompareSplitPercent ?? 50)));
        const splitX = destination.x + (destination.width * splitPercent / 100);

        context.save();
        context.beginPath();
        context.rect(splitX, destination.y, Math.max(0, destination.x + destination.width - splitX), destination.height);
        context.clip();
        drawAstroPreviewImage(context, compareImage, destination, canvasRect, !!(options?.rotateCompare180 ?? options?.RotateCompare180), dpr);
        context.restore();
    }

    return true;
}

window.__astroImagePreviewCanvasRenderers = window.__astroImagePreviewCanvasRenderers || new WeakMap();

window.renderAstroImagePreviewCanvas = function(canvas, primaryImage, compareImage, options) {
    if (!canvas || typeof window.requestAnimationFrame !== 'function') {
        return renderAstroImagePreviewCanvasNow(canvas, primaryImage, compareImage, options);
    }

    let state = window.__astroImagePreviewCanvasRenderers.get(canvas);
    if (!state) {
        state = { frameHandle: 0, primaryImage: null, compareImage: null, options: null };
        window.__astroImagePreviewCanvasRenderers.set(canvas, state);
    }

    state.primaryImage = primaryImage;
    state.compareImage = compareImage;
    state.options = options || {};

    if (!state.frameHandle) {
        state.frameHandle = window.requestAnimationFrame(() => {
            state.frameHandle = 0;
            renderAstroImagePreviewCanvasNow(canvas, state.primaryImage, state.compareImage, state.options);
        });
    }

    return true;
};

window.clearAstroImagePreviewCanvas = function(canvas) {
    const state = canvas && window.__astroImagePreviewCanvasRenderers
        ? window.__astroImagePreviewCanvasRenderers.get(canvas)
        : null;
    if (state?.frameHandle) {
        window.cancelAnimationFrame(state.frameHandle);
        state.frameHandle = 0;
    }

    const configured = configureAstroPreviewCanvas(canvas);
    if (!configured) {
        return false;
    }

    configured.context.clearRect(0, 0, configured.rect.width, configured.rect.height);
    window.__astroImagePreviewCanvasRenderers?.delete(canvas);
    return true;
};
