import { findComponentRect, findFieldElement } from "../../utils";
import { getAccessibleIframeDocument, getAccessibleIframeWindow, } from "./iframeAccess";
export function getOverlayScrollMetrics(iframe, scrollScale, visualScale) {
    const iframeWindow = getAccessibleIframeWindow(iframe);
    const iframeDocument = getAccessibleIframeDocument(iframe);
    const scrollContainers = [
        iframeDocument?.scrollingElement,
        iframeDocument?.documentElement,
        iframeDocument?.body,
    ].filter((element) => !!element);
    return {
        scrollLeft: Math.max(iframeWindow?.scrollX || 0, ...scrollContainers.map((element) => element.scrollLeft || 0)),
        scrollTop: Math.max(iframeWindow?.scrollY || 0, ...scrollContainers.map((element) => element.scrollTop || 0)),
        scrollScale,
        visualScale,
    };
}
export function getOverlayMetrics(iframe, scrollScale, visualScale) {
    const iframeDocument = getAccessibleIframeDocument(iframe);
    const scrollContainers = [
        iframeDocument?.scrollingElement,
        iframeDocument?.documentElement,
        iframeDocument?.body,
    ].filter((element) => !!element);
    const scrollMetrics = getOverlayScrollMetrics(iframe, scrollScale, visualScale);
    const scrollWidth = Math.max(iframe?.clientWidth || 0, ...scrollContainers.map((element) => element.scrollWidth || 0));
    const scrollHeight = Math.max(iframe?.clientHeight || 0, ...scrollContainers.map((element) => element.scrollHeight || 0));
    return {
        ...scrollMetrics,
        viewportWidth: iframe?.clientWidth || 0,
        viewportHeight: iframe?.clientHeight || 0,
        scrollWidth,
        scrollHeight,
    };
}
export function toOverlayRect(viewportRect, metrics) {
    return {
        x: (viewportRect.left + metrics.scrollLeft * metrics.scrollScale) *
            metrics.visualScale,
        y: (viewportRect.top + metrics.scrollTop * metrics.scrollScale) *
            metrics.visualScale,
        width: viewportRect.width * metrics.visualScale,
        height: viewportRect.height * metrics.visualScale,
    };
}
export function rectEquals(a, b, tolerance = 0.5) {
    if (a === b)
        return true;
    if (!a || !b)
        return false;
    return (Math.abs(a.x - b.x) <= tolerance &&
        Math.abs(a.y - b.y) <= tolerance &&
        Math.abs(a.width - b.width) <= tolerance &&
        Math.abs(a.height - b.height) <= tolerance);
}
export function rectListEquals(a, b, tolerance = 0.5) {
    if (a === b)
        return true;
    if (!a || !b)
        return false;
    if (a.length !== b.length)
        return false;
    for (let i = 0; i < a.length; i++) {
        if (!rectEquals(a[i], b[i], tolerance))
            return false;
    }
    return true;
}
export function getTextNodeAtOffset(root, offset, affinity = "backward") {
    const ownerDocument = root.ownerDocument || document;
    const walker = ownerDocument.createTreeWalker(root, NodeFilter.SHOW_TEXT);
    let currentNode;
    let boundaryNode = null;
    while ((currentNode = walker.nextNode())) {
        const nodeLength = currentNode.textContent?.length || 0;
        if (offset < nodeLength) {
            return { node: currentNode, offset };
        }
        if (offset === nodeLength) {
            if (affinity === "backward") {
                return { node: currentNode, offset };
            }
            boundaryNode = currentNode;
            offset = 0;
            continue;
        }
        offset -= nodeLength;
    }
    return boundaryNode
        ? { node: boundaryNode, offset: boundaryNode.textContent?.length || 0 }
        : null;
}
export function createRangeFromTextOffsets(iframeDocument, root, start, end) {
    const startData = getTextNodeAtOffset(root, start, "forward");
    const endData = getTextNodeAtOffset(root, end, "backward");
    if (!startData || !endData)
        return null;
    try {
        const textRange = iframeDocument.createRange();
        textRange.setStart(startData.node, startData.offset);
        textRange.setEnd(endData.node, endData.offset);
        return textRange;
    }
    catch {
        return null;
    }
}
export function measureTextRangeRects(iframeDocument, root, start, end, metrics) {
    if (start >= end)
        return [];
    const textRange = createRangeFromTextOffsets(iframeDocument, root, start, end);
    return textRange
        ? Array.from(textRange.getClientRects()).map((rect) => toOverlayRect(rect, metrics))
        : [];
}
export function measureCollapsedTextOffsetRect(iframeDocument, root, offset, metrics) {
    try {
        const caretRange = createRangeFromTextOffsets(iframeDocument, root, offset, offset);
        if (!caretRange)
            return null;
        const collapsedRect = Array.from(caretRange.getClientRects()).find((rect) => rect.height > 0);
        if (collapsedRect) {
            return toOverlayRect({
                left: collapsedRect.left,
                top: collapsedRect.top,
                width: Math.max(collapsedRect.width, 1),
                height: collapsedRect.height,
            }, metrics);
        }
        const textLength = root.textContent?.length ?? 0;
        const fallbackRects = offset > 0
            ? measureTextRangeRects(iframeDocument, root, offset - 1, offset, metrics)
            : offset < textLength
                ? measureTextRangeRects(iframeDocument, root, offset, offset + 1, metrics)
                : [];
        const fallbackRect = fallbackRects[0];
        if (!fallbackRect)
            return null;
        return {
            x: offset > 0 ? fallbackRect.x + fallbackRect.width : fallbackRect.x,
            y: fallbackRect.y,
            width: 1,
            height: fallbackRect.height,
        };
    }
    catch {
        return null;
    }
}
export function measureElementRect(element, metrics) {
    if (!element || !element.isConnected)
        return null;
    try {
        return toOverlayRect(element.getBoundingClientRect(), metrics);
    }
    catch {
        return null;
    }
}
export function measureFieldElementRect(iframe, fieldDescriptor, metrics) {
    return measureElementRect(findFieldElement(iframe, fieldDescriptor), metrics);
}
export function measureComponentRect(iframe, component, metrics) {
    const result = findComponentRect(iframe, component);
    if (!result?.rect)
        return null;
    return {
        rect: toOverlayRect({
            left: result.rect.x,
            top: result.rect.y,
            width: result.rect.width,
            height: result.rect.height,
        }, metrics),
        elements: result.elements,
    };
}
//# sourceMappingURL=geometry.js.map