import { buildPageViewerNotOpenFailure, validateBrowserBoundCaptureRequest, } from "./browserBoundCapture";
import { createCaptureRequestId, createHiddenCaptureIframe, } from "./captureShared";
const MAX_TOOL_RESULT_IMAGE_DIMENSION = 1280;
const PNG_SIZE_FALLBACK_THRESHOLD = 2_500_000;
const JPEG_QUALITY = 0.9;
const DEFAULT_CAPTURE_BACKGROUND_COLOR = "#ffffff";
function normalizeScope(scope) {
    switch (scope) {
        case "editor-ui":
        case "selection":
        case "component":
        case "component-range":
        case "page":
            return scope;
        case "viewport":
        default:
            return "viewport";
    }
}
function getPageDimensions(doc) {
    const body = doc.body;
    const root = doc.documentElement;
    return {
        width: Math.max(root?.scrollWidth ?? 0, root?.clientWidth ?? 0, body?.scrollWidth ?? 0, body?.clientWidth ?? 0),
        height: Math.max(root?.scrollHeight ?? 0, root?.clientHeight ?? 0, body?.scrollHeight ?? 0, body?.clientHeight ?? 0),
    };
}
function getCaptureBackgroundColor(targetElement) {
    const doc = targetElement.ownerDocument;
    const targetWindow = targetElement.ownerDocument.defaultView;
    if (!targetWindow)
        return DEFAULT_CAPTURE_BACKGROUND_COLOR;
    const probe = doc.createElement("div");
    probe.style.backgroundColor = "var(--color-white)";
    probe.style.position = "absolute";
    probe.style.visibility = "hidden";
    (doc.body ?? doc.documentElement).appendChild(probe);
    const cssColor = targetWindow.getComputedStyle(probe).backgroundColor.trim();
    probe.remove();
    return cssColor && cssColor !== "rgba(0, 0, 0, 0)"
        ? cssColor
        : DEFAULT_CAPTURE_BACKGROUND_COLOR;
}
function getWindowViewportBounds(targetWindow, zoom = 1, viewportWidth = targetWindow.innerWidth, viewportHeight = targetWindow.innerHeight) {
    const safeZoom = zoom > 0 ? zoom : 1;
    return {
        x: Math.floor(targetWindow.scrollX ?? 0),
        y: Math.floor(targetWindow.scrollY ?? 0),
        width: Math.max(1, Math.ceil(viewportWidth / safeZoom)),
        height: Math.max(1, Math.ceil(viewportHeight / safeZoom)),
    };
}
function clampBounds(bounds, pageSize, padding = 0) {
    const x = Math.max(0, Math.floor(bounds.x - padding));
    const y = Math.max(0, Math.floor(bounds.y - padding));
    const right = Math.min(pageSize.width, Math.ceil(bounds.x + bounds.width + padding));
    const bottom = Math.min(pageSize.height, Math.ceil(bounds.y + bounds.height + padding));
    return {
        x,
        y,
        width: Math.max(1, right - x),
        height: Math.max(1, bottom - y),
    };
}
function unionBounds(boundsList) {
    const minX = Math.min(...boundsList.map((bounds) => bounds.x));
    const minY = Math.min(...boundsList.map((bounds) => bounds.y));
    const maxRight = Math.max(...boundsList.map((bounds) => bounds.x + bounds.width));
    const maxBottom = Math.max(...boundsList.map((bounds) => bounds.y + bounds.height));
    return {
        x: minX,
        y: minY,
        width: maxRight - minX,
        height: maxBottom - minY,
    };
}
function normalizeComponentIdentifier(value) {
    return value?.trim().replace(/[{}]/g, "").toLowerCase() ?? "";
}
function matchesComponentIdentifier(candidate, target) {
    const normalizedCandidate = normalizeComponentIdentifier(candidate);
    const normalizedTarget = normalizeComponentIdentifier(target);
    return !!normalizedCandidate && normalizedCandidate === normalizedTarget;
}
function bridgeRectToDocumentBounds(geometry, rect) {
    if (!rect || rect.width <= 0 || rect.height <= 0) {
        return null;
    }
    return {
        x: Math.floor(rect.left + geometry.scroll.x),
        y: Math.floor(rect.top + geometry.scroll.y),
        width: Math.ceil(rect.width),
        height: Math.ceil(rect.height),
    };
}
function getComponentBoundsFromBridgeGeometry(geometry, componentId) {
    if (!geometry)
        return null;
    const target = geometry.targets.find((candidate) => {
        if (candidate.kind !== "component")
            return false;
        return (matchesComponentIdentifier(candidate.componentId, componentId) ||
            matchesComponentIdentifier(candidate.key, componentId));
    });
    return target?.rect
        ? bridgeRectToDocumentBounds(geometry, target.rect)
        : null;
}
function getBridgeGeometryDocumentSize(geometry) {
    if (!geometry)
        return { width: 0, height: 0 };
    const width = Math.max(geometry.viewport.width + geometry.scroll.x, ...geometry.targets.map((target) => target.rect.right + geometry.scroll.x));
    const height = Math.max(geometry.viewport.height + geometry.scroll.y, ...geometry.targets.map((target) => target.rect.bottom + geometry.scroll.y));
    return { width, height };
}
function getBridgeSnapshotPageSize(pageViewContext, snapshot) {
    const geometrySize = getBridgeGeometryDocumentSize(pageViewContext.bridgeGeometry);
    const viewportWidth = snapshot?.viewport.width ||
        pageViewContext.bridgeGeometry?.viewport.width ||
        pageViewContext.viewport.width ||
        pageViewContext.editorIframe?.clientWidth ||
        0;
    const viewportHeight = snapshot?.viewport.height ||
        pageViewContext.bridgeGeometry?.viewport.height ||
        pageViewContext.viewport.height ||
        pageViewContext.editorIframe?.clientHeight ||
        0;
    return {
        width: Math.max(1, Math.ceil(snapshot?.scrollWidth || geometrySize.width || viewportWidth)),
        height: Math.max(1, Math.ceil(snapshot?.scrollHeight || geometrySize.height || viewportHeight)),
    };
}
function getBridgeViewportBounds(pageViewContext, snapshot) {
    const geometry = pageViewContext.bridgeGeometry;
    const viewport = snapshot?.viewport ?? geometry?.viewport;
    const scroll = snapshot?.scroll ?? geometry?.scroll;
    return {
        x: Math.floor(scroll?.x ?? 0),
        y: Math.floor(scroll?.y ?? 0),
        width: Math.max(1, Math.ceil(viewport?.width ||
            pageViewContext.viewport.width ||
            pageViewContext.editorIframe?.clientWidth ||
            1)),
        height: Math.max(1, Math.ceil(viewport?.height ||
            pageViewContext.viewport.height ||
            pageViewContext.editorIframe?.clientHeight ||
            1)),
    };
}
function resolveBridgeCaptureArea(pageViewContext, snapshot, selection, params, pageSize) {
    const scope = normalizeScope(params.scope);
    const padding = Math.max(0, params.padding ?? 0);
    if (scope === "page") {
        return {
            scope,
            bounds: clampBounds({
                x: 0,
                y: 0,
                width: pageSize.width,
                height: pageSize.height,
            }, pageSize, padding),
        };
    }
    const fromComponentId = params.fromComponentId?.trim();
    const toComponentId = params.toComponentId?.trim();
    if (scope === "selection") {
        const bounds = selection
            .map((componentId) => getComponentBoundsFromBridgeGeometry(pageViewContext.bridgeGeometry, componentId))
            .filter((item) => !!item && item.width > 0 && item.height > 0);
        if (!bounds.length) {
            throw new Error("There are no selected components to capture.");
        }
        return {
            scope,
            bounds: clampBounds(unionBounds(bounds), pageSize, padding),
        };
    }
    if (fromComponentId && toComponentId) {
        const bounds = [fromComponentId, toComponentId]
            .map((componentId) => getComponentBoundsFromBridgeGeometry(pageViewContext.bridgeGeometry, componentId))
            .filter((item) => !!item && item.width > 0 && item.height > 0);
        if (bounds.length !== 2) {
            throw new Error("Could not resolve both component boundaries for the requested capture range.");
        }
        return {
            scope: "component-range",
            bounds: clampBounds(unionBounds(bounds), pageSize, padding),
        };
    }
    if (fromComponentId) {
        const bounds = getComponentBoundsFromBridgeGeometry(pageViewContext.bridgeGeometry, fromComponentId);
        if (!bounds) {
            throw new Error(`Could not resolve a visible component for '${fromComponentId}'.`);
        }
        return {
            scope: "component",
            bounds: clampBounds(bounds, pageSize, padding),
        };
    }
    if (scope === "component" || scope === "component-range") {
        return {
            scope: "viewport",
            bounds: clampBounds(getBridgeViewportBounds(pageViewContext, snapshot), pageSize, padding),
        };
    }
    return {
        scope: "viewport",
        bounds: clampBounds(getBridgeViewportBounds(pageViewContext, snapshot), pageSize, padding),
    };
}
async function canvasToBlob(canvas, contentType, quality) {
    const blob = await new Promise((resolve, reject) => {
        canvas.toBlob((value) => {
            if (value) {
                resolve(value);
            }
            else {
                reject(new Error("Failed to convert screenshot canvas into an image."));
            }
        }, contentType, quality);
    });
    return blob;
}
async function blobToDataUrl(blob) {
    return await new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => {
            const value = reader.result;
            if (typeof value === "string" && value.length > 0) {
                resolve(value);
                return;
            }
            reject(new Error("Failed to encode screenshot image data."));
        };
        reader.onerror = () => {
            reject(reader.error ?? new Error("Failed to encode screenshot image data."));
        };
        reader.readAsDataURL(blob);
    });
}
function buildFileName(scope, extension = "png") {
    const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
    return `page-screenshot-${scope}-${timestamp}.${extension}`;
}
function scaleCanvasForToolResult(canvas) {
    const maxDimension = Math.max(canvas.width, canvas.height);
    if (maxDimension <= MAX_TOOL_RESULT_IMAGE_DIMENSION) {
        return canvas;
    }
    const scale = MAX_TOOL_RESULT_IMAGE_DIMENSION / maxDimension;
    const scaledCanvas = document.createElement("canvas");
    scaledCanvas.width = Math.max(1, Math.round(canvas.width * scale));
    scaledCanvas.height = Math.max(1, Math.round(canvas.height * scale));
    const context = scaledCanvas.getContext("2d");
    if (!context) {
        throw new Error("Failed to prepare the screenshot for the tool result.");
    }
    context.drawImage(canvas, 0, 0, scaledCanvas.width, scaledCanvas.height);
    return scaledCanvas;
}
function replaceFileExtension(fileName, extension) {
    return fileName.replace(/\.[^.]+$/, "") + `.${extension}`;
}
async function canvasToResultImage(canvas, scope) {
    const processedCanvas = scaleCanvasForToolResult(canvas);
    let contentType = "image/png";
    let fileName = buildFileName(scope, "png");
    let blob = await canvasToBlob(processedCanvas, contentType);
    if (blob.size > PNG_SIZE_FALLBACK_THRESHOLD) {
        contentType = "image/jpeg";
        fileName = replaceFileExtension(fileName, "jpg");
        blob = await canvasToBlob(processedCanvas, contentType, JPEG_QUALITY);
    }
    return {
        image: {
            dataUrl: await blobToDataUrl(blob),
            fileName,
            fileSize: blob.size,
            contentType,
        },
        width: processedCanvas.width,
        height: processedCanvas.height,
    };
}
async function renderCaptureAreaToCanvas(targetElement, pageSize, captureArea) {
    const rootStyle = targetElement.style;
    const previousTransform = rootStyle.transform;
    const previousTransformOrigin = rootStyle.transformOrigin;
    const previousOverflow = rootStyle.overflow;
    try {
        const { default: html2canvas } = await import("html2canvas-pro");
        rootStyle.transform = "none";
        rootStyle.transformOrigin = "";
        rootStyle.overflow = previousOverflow || "auto";
        await new Promise((resolve) => requestAnimationFrame(() => resolve(undefined)));
        return await html2canvas(targetElement, {
            backgroundColor: getCaptureBackgroundColor(targetElement),
            logging: false,
            removeContainer: true,
            scale: Math.min(globalThis.devicePixelRatio || 1, 2),
            useCORS: true,
            windowWidth: pageSize.width,
            windowHeight: pageSize.height,
            scrollX: 0,
            scrollY: 0,
            x: captureArea.bounds.x,
            y: captureArea.bounds.y,
            width: captureArea.bounds.width,
            height: captureArea.bounds.height,
        });
    }
    finally {
        rootStyle.transform = previousTransform;
        rootStyle.transformOrigin = previousTransformOrigin;
        rootStyle.overflow = previousOverflow;
    }
}
async function waitForSnapshotDocumentToSettle(doc) {
    const fontsReady = doc.fonts?.ready ?? Promise.resolve();
    await Promise.race([fontsReady.catch(() => undefined), wait(1_000)]);
    const pendingImages = Array.from(doc.images || []).filter((image) => !image.complete);
    if (pendingImages.length > 0) {
        await Promise.race([
            Promise.all(pendingImages.map((image) => new Promise((resolve) => {
                image.addEventListener("load", () => resolve(), { once: true });
                image.addEventListener("error", () => resolve(), { once: true });
            }))),
            wait(1_500),
        ]);
    }
    await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve(undefined))));
}
function wait(timeoutMs) {
    return new Promise((resolve) => window.setTimeout(resolve, timeoutMs));
}
async function renderBridgeSnapshotToCanvas(html, pageSize, captureArea) {
    if (!document.body) {
        throw new Error("The Parhelia UI has not finished loading yet.");
    }
    const iframe = createHiddenCaptureIframe(pageSize.width, pageSize.height);
    const loadPromise = new Promise((resolve) => {
        let settled = false;
        const finish = () => {
            if (settled)
                return;
            settled = true;
            resolve();
        };
        iframe.addEventListener("load", finish, { once: true });
        window.setTimeout(finish, 1_000);
    });
    iframe.srcdoc = html;
    document.body.appendChild(iframe);
    try {
        await loadPromise;
        const iframeDocument = iframe.contentDocument;
        const targetElement = iframeDocument?.documentElement;
        if (!iframeDocument || !targetElement || !iframeDocument.body) {
            throw new Error("Failed to prepare the captured DOM snapshot.");
        }
        targetElement.style.width = `${pageSize.width}px`;
        targetElement.style.minHeight = `${pageSize.height}px`;
        iframeDocument.body.style.minHeight = `${pageSize.height}px`;
        await waitForSnapshotDocumentToSettle(iframeDocument);
        return await renderCaptureAreaToCanvas(targetElement, pageSize, captureArea);
    }
    finally {
        iframe.remove();
    }
}
async function getBridgeDomForCapture(pageViewContext) {
    if (pageViewContext.requestBridgeCaptureDom) {
        const result = await pageViewContext.requestBridgeCaptureDom({
            requestId: createCaptureRequestId("capture-screenshot"),
        });
        if (result.error) {
            throw new Error(result.error);
        }
        if (result.html?.trim()) {
            return {
                html: result.html,
                snapshot: pageViewContext.bridgeDom,
            };
        }
    }
    if (pageViewContext.bridgeDom?.html.trim()) {
        return {
            html: pageViewContext.bridgeDom.html,
            snapshot: pageViewContext.bridgeDom,
        };
    }
    return null;
}
/**
 * Renders a screenshot from any bridge source: the live editor page-view context, or a transient
 * BridgeClient driving a hidden iframe (background/offscreen capture). Returns null only when the
 * source has no usable bridge DOM, so callers can fall back.
 */
export async function captureScreenshotFromBridgeSource(pageViewContext, params, selection, normalizedScope) {
    if (!pageViewContext?.bridgeReady)
        return null;
    let bridgeDom;
    try {
        bridgeDom = await getBridgeDomForCapture(pageViewContext);
    }
    catch (error) {
        return {
            success: false,
            scope: normalizedScope,
            error: error instanceof Error
                ? error.message
                : "Failed to capture the current page DOM through the host bridge.",
        };
    }
    if (!bridgeDom)
        return null;
    const pageSize = getBridgeSnapshotPageSize(pageViewContext, bridgeDom.snapshot);
    let captureArea;
    try {
        captureArea = resolveBridgeCaptureArea(pageViewContext, bridgeDom.snapshot, selection, params, pageSize);
    }
    catch (error) {
        return {
            success: false,
            scope: normalizedScope,
            error: error instanceof Error
                ? error.message
                : "Failed to resolve capture bounds.",
        };
    }
    let canvas;
    try {
        canvas = await renderBridgeSnapshotToCanvas(bridgeDom.html, pageSize, captureArea);
    }
    catch (error) {
        return {
            success: false,
            scope: captureArea.scope,
            error: error instanceof Error
                ? error.message
                : "Failed to render the requested screenshot.",
        };
    }
    try {
        const resultImage = await canvasToResultImage(canvas, captureArea.scope);
        return {
            success: true,
            scope: captureArea.scope,
            image: resultImage.image,
            bounds: captureArea.bounds,
            imageWidth: resultImage.width,
            imageHeight: resultImage.height,
        };
    }
    catch (error) {
        return {
            success: false,
            scope: captureArea.scope,
            error: error instanceof Error
                ? error.message
                : "Failed to encode the screenshot for the tool result.",
        };
    }
}
function getActivePageViewContext(editContext) {
    return editContext.getActiveSlotContext?.()?.primaryPageViewContext;
}
export async function capturePageScreenshot(request, params, editContext) {
    const normalizedScope = normalizeScope(params.scope);
    const captureEditorUi = normalizedScope === "editor-ui";
    if (!editContext) {
        return buildPageViewerNotOpenFailure({
            request,
            editContext,
            scope: normalizedScope,
        });
    }
    const validationFailure = validateBrowserBoundCaptureRequest({
        request,
        editContext,
        scope: normalizedScope,
    });
    if (validationFailure) {
        return validationFailure;
    }
    let targetElement;
    let pageSize;
    let captureArea;
    const activePageViewContext = getActivePageViewContext(editContext);
    if (captureEditorUi) {
        if (!document.documentElement || !document.body) {
            return {
                success: false,
                scope: normalizedScope,
                error: "The Parhelia UI has not finished loading yet.",
            };
        }
        targetElement = document.documentElement;
        pageSize = getPageDimensions(document);
        const padding = Math.max(0, params.padding ?? 0);
        captureArea = {
            scope: "editor-ui",
            bounds: clampBounds(getWindowViewportBounds(window), pageSize, padding),
        };
    }
    else {
        if (!activePageViewContext) {
            return buildPageViewerNotOpenFailure({
                request,
                editContext,
                scope: normalizedScope,
            });
        }
        const bridgeResult = await captureScreenshotFromBridgeSource(activePageViewContext, params, editContext.selection ?? [], normalizedScope);
        if (bridgeResult)
            return bridgeResult;
        return {
            success: false,
            scope: normalizedScope,
            error: "The current page bridge is not ready to capture screenshots.",
        };
    }
    let canvas;
    try {
        canvas = await renderCaptureAreaToCanvas(targetElement, pageSize, captureArea);
    }
    catch (error) {
        return {
            success: false,
            scope: captureArea.scope,
            error: error instanceof Error
                ? error.message
                : "Failed to render the requested screenshot.",
        };
    }
    try {
        const resultImage = await canvasToResultImage(canvas, captureArea.scope);
        return {
            success: true,
            scope: captureArea.scope,
            image: resultImage.image,
            bounds: captureArea.bounds,
            imageWidth: resultImage.width,
            imageHeight: resultImage.height,
        };
    }
    catch (error) {
        return {
            success: false,
            scope: captureArea.scope,
            error: error instanceof Error
                ? error.message
                : "Failed to encode the screenshot for the tool result.",
        };
    }
}
//# sourceMappingURL=capturePageScreenshot.js.map