"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.walkAllIframes = walkAllIframes;
exports.findByLocation = findByLocation;
exports.sendScrollDownMessage = sendScrollDownMessage;
exports.sendScrollUpMessage = sendScrollUpMessage;
exports.findScrollableElements = findScrollableElements;
const mablscriptFind_1 = require("@mablhq/test-model/mablscriptFind");
const messages_1 = require("../../sharedUtils/messages");
const MAX_SCROLLABLE_ELEMENTS = 5;
async function safelyDispose(handle, context) {
    if (handle) {
        try {
            await handle.dispose();
        }
        catch (error) {
            console.warn(`Failed to dispose ${context}:`, error);
        }
    }
}
async function processFindResponseHandle(handle) {
    let findResponse;
    let frameId;
    let foundSelector;
    try {
        const [payloadHandle, targetHandle] = await Promise.all([
            handle.getProperty('payload'),
            handle.getProperty('target'),
        ]);
        findResponse = await payloadHandle?.jsonValue();
        const elementHandle = targetHandle?.asElement();
        const frame = await elementHandle?.contentFrame();
        frameId = frame?.id();
        foundSelector = findResponse?.elementSelectors
            ? (0, mablscriptFind_1.getTargetSelector)(findResponse.elementSelectors)
            : undefined;
    }
    finally {
        await handle.dispose();
    }
    return { findResponse, frameId, foundSelector };
}
function convertRelativeBoundingBoxToAbsolute(boundingBox) {
    return {
        x: boundingBox.x * window.innerWidth,
        y: boundingBox.y * window.innerHeight,
        width: boundingBox.width * window.innerWidth,
        height: boundingBox.height * window.innerHeight,
    };
}
function convertRelativePointToAbsolute(point) {
    return {
        x: point.x * window.innerWidth,
        y: point.y * window.innerHeight,
    };
}
async function walkIFrames(trainingContext, boundingBox, targetPoint, toolCall, generatePageMessage, combiner) {
    const page = await trainingContext.getActivePage();
    if (!page) {
        return;
    }
    let relativeBoundingBox = boundingBox
        ? await page.evaluate(convertRelativeBoundingBoxToAbsolute, boundingBox)
        : undefined;
    let relativePoint = targetPoint
        ? await page.evaluate(convertRelativePointToAbsolute, targetPoint)
        : undefined;
    let messagePromise;
    if (relativeBoundingBox) {
        const message = (0, messages_1.findByLocationMessage)(relativeBoundingBox, relativePoint, toolCall);
        messagePromise = trainingContext.sendMessageToRootFrameOfActiveTab(message, true);
    }
    else {
        messagePromise = Promise.resolve(undefined);
    }
    const customMessage = generatePageMessage
        ? generatePageMessage(relativeBoundingBox, relativePoint, toolCall)
        : undefined;
    const customMessagePromise = customMessage
        ? trainingContext.sendMessageToRootFrameOfActiveTab(customMessage, true)
        : Promise.resolve(undefined);
    const [findResponseHandle, customResponse] = await Promise.all([
        messagePromise,
        customMessagePromise,
    ]);
    if (!findResponseHandle) {
        return combiner
            ? combiner(undefined, customResponse, undefined)
            : undefined;
    }
    let { frameId, findResponse, foundSelector } = await processFindResponseHandle(findResponseHandle);
    let combinedResponse = combiner
        ? await combiner(undefined, customResponse, findResponse)
        : findResponse;
    while (frameId && foundSelector?.tag_name === 'iframe') {
        const iFrameX = parseFloat(foundSelector.bounding_x ?? '0');
        const iFrameY = parseFloat(foundSelector.bounding_y ?? '0');
        relativeBoundingBox = {
            x: relativeBoundingBox.x - iFrameX,
            y: relativeBoundingBox.y - iFrameY,
            width: relativeBoundingBox.width,
            height: relativeBoundingBox.height,
        };
        relativePoint = relativePoint
            ? {
                x: relativePoint.x - iFrameX,
                y: relativePoint.y - iFrameY,
            }
            : undefined;
        const message = (0, messages_1.findByLocationMessage)(relativeBoundingBox, relativePoint, toolCall);
        const iframeResponse = await trainingContext.sendMessageToTabAndFrame(message, page.getPageId(), frameId, true);
        const customMessageIframeResponse = customMessage
            ? await trainingContext.sendMessageToTabAndFrame(customMessage, page.getPageId(), frameId, true)
            : Promise.resolve(undefined);
        const [iframeResponseHandle, customIframeResponse] = await Promise.all([
            iframeResponse,
            customMessageIframeResponse,
        ]);
        if (!iframeResponseHandle) {
            break;
        }
        ({ frameId, findResponse, foundSelector } = await processFindResponseHandle(iframeResponseHandle));
        combinedResponse = combiner
            ? await combiner(combinedResponse, customIframeResponse, findResponse)
            : findResponse;
    }
    return combinedResponse;
}
async function processIframesRecursively(trainingContext, page, currentResponse, generatePageMessage, combiner, frameId) {
    const findAllIframesMsg = (0, messages_1.findAllIframesMessage)();
    let iframesResponse;
    if (frameId) {
        iframesResponse = await trainingContext.sendMessageToTabAndFrame(findAllIframesMsg, page.getPageId(), frameId, true);
    }
    else {
        iframesResponse = await trainingContext.sendMessageToRootFrameOfActiveTab(findAllIframesMsg, true);
    }
    if (!iframesResponse) {
        return currentResponse;
    }
    let combinedResponse = currentResponse;
    try {
        const iframesData = (await (await iframesResponse.getProperty('message'))?.jsonValue());
        if (iframesData?.success && iframesData?.count > 0) {
            const iframesHandle = await iframesResponse.getProperty('iframes');
            if (iframesHandle) {
                for (let i = 0; i < iframesData.count; i++) {
                    const iframeElementHandle = await iframesHandle.getProperty(i.toString());
                    if (iframeElementHandle) {
                        try {
                            const elementHandle = iframeElementHandle.asElement();
                            const frame = await elementHandle?.contentFrame();
                            const iframeId = frame?.id();
                            if (generatePageMessage && iframeId) {
                                try {
                                    const customMessage = generatePageMessage();
                                    const iframeResponse = await trainingContext.sendMessageToTabAndFrame(customMessage, page.getPageId(), iframeId, true);
                                    if (combiner) {
                                        combinedResponse = await combiner(combinedResponse, iframeResponse, iframeId);
                                    }
                                    combinedResponse = await processIframesRecursively(trainingContext, page, combinedResponse, generatePageMessage, combiner, iframeId);
                                }
                                catch (error) {
                                    console.warn(`Failed to send message to iframe ${iframeId}, frame may have been removed:`, error);
                                }
                            }
                        }
                        catch (error) {
                            console.warn(`Failed to access iframe element ${i}, element may have been removed:`, error);
                        }
                        finally {
                            await iframeElementHandle.dispose();
                        }
                    }
                }
                await iframesHandle.dispose();
            }
        }
    }
    finally {
        await iframesResponse.dispose();
    }
    return combinedResponse;
}
async function walkAllIframes(trainingContext, generatePageMessage, combiner) {
    const page = await trainingContext.getActivePage();
    if (!page) {
        return;
    }
    let combinedResponse;
    const customMessage = generatePageMessage ? generatePageMessage() : undefined;
    if (customMessage) {
        const customResponse = await trainingContext.sendMessageToRootFrameOfActiveTab(customMessage, true);
        combinedResponse = combiner
            ? await combiner(undefined, customResponse, 'main')
            : customResponse;
    }
    combinedResponse = await processIframesRecursively(trainingContext, page, combinedResponse, generatePageMessage, combiner);
    return combinedResponse;
}
async function sendScrollMessageWithIframeSupport(trainingContext, scrollDirection, scrollFactor, scrollPoint, scrollBoundingBox) {
    const pageOnly = !scrollBoundingBox && !scrollPoint;
    const pageMessageFunction = (relativeBoundingBox, relativePoint) => (0, messages_1.findScrollableElementsMessage)(MAX_SCROLLABLE_ELEMENTS, relativePoint, relativeBoundingBox, true, pageOnly);
    const combiner = async (bestScrollableElement, messageResponse) => {
        if (messageResponse) {
            let elementsArrayHandle;
            try {
                elementsArrayHandle = await messageResponse.getProperty('elements');
                if (elementsArrayHandle) {
                    let length;
                    try {
                        const lengthHandle = await elementsArrayHandle.getProperty('length');
                        length = (await lengthHandle?.jsonValue());
                        await safelyDispose(lengthHandle, 'length handle');
                    }
                    catch (error) {
                        console.warn('Failed to get elements array length:', error);
                        return bestScrollableElement;
                    }
                    for (let i = length - 1; i >= 0; i--) {
                        const elementHandle = await elementsArrayHandle.getProperty(i.toString());
                        if (elementHandle) {
                            const scrollableElement = await elementHandle?.jsonValue();
                            if (scrollableElement) {
                                const canScroll = scrollDirection === 'down'
                                    ? (scrollableElement.scrollability?.scrollDown ?? 0) > 0
                                    : (scrollableElement.scrollability?.scrollUp ?? 0) > 0;
                                if (canScroll) {
                                    if (bestScrollableElement &&
                                        bestScrollableElement !== elementHandle) {
                                        await safelyDispose(bestScrollableElement, 'previous best scrollable element');
                                    }
                                    bestScrollableElement = elementHandle;
                                    break;
                                }
                                else {
                                    await safelyDispose(elementHandle, `element handle ${i}`);
                                }
                            }
                            else {
                                await safelyDispose(elementHandle, `element handle ${i} (no data)`);
                            }
                        }
                    }
                }
            }
            catch (error) {
                console.warn('Failed to process elements array:', error);
            }
            finally {
                await safelyDispose(elementsArrayHandle, 'elements array handle');
            }
        }
        return bestScrollableElement;
    };
    const scrollableElement = await walkIFrames(trainingContext, scrollBoundingBox, scrollPoint, undefined, pageMessageFunction, combiner);
    if (scrollableElement) {
        try {
            const scrollSuccessMessage = await scrollableElement.evaluate((scrollableElement, scrollParams) => {
                const height = scrollableElement.elementBoundingBox?.height ?? window.innerHeight;
                const amount = height *
                    (scrollParams.scrollDirection === 'down'
                        ? scrollParams.scrollFactor
                        : -1 * scrollParams.scrollFactor);
                scrollableElement.element.scrollBy({
                    top: amount,
                    left: 0,
                    behavior: 'auto',
                });
                const scrollAvailable = scrollParams.scrollDirection === 'up'
                    ? scrollableElement.scrollability.scrollUp
                    : scrollableElement.scrollability.scrollDown;
                const actualScrolledDistance = Math.min(Math.abs(amount), scrollAvailable);
                return {
                    scrolledDistance: actualScrolledDistance,
                    success: actualScrolledDistance > 0,
                };
            }, { scrollDirection, scrollFactor });
            return scrollSuccessMessage;
        }
        catch (error) {
            console.error('Failed to scroll element:', error);
            return { scrolledDistance: 0, success: false };
        }
        finally {
            try {
                await scrollableElement.dispose();
            }
            catch (disposeError) {
                console.warn('Failed to dispose scrollable element handle:', disposeError);
            }
        }
    }
    console.warn('No scrollable element found');
    return { scrolledDistance: 0, success: false };
}
async function findByLocation(trainingContext, boundingBox, targetPoint, toolCall) {
    return walkIFrames(trainingContext, boundingBox, targetPoint, toolCall);
}
async function sendScrollDownMessage(trainingContext, scrollFactor = 0.75, scrollPoint, scrollBoundingBox) {
    return sendScrollMessageWithIframeSupport(trainingContext, 'down', scrollFactor, scrollPoint, scrollBoundingBox);
}
async function sendScrollUpMessage(trainingContext, scrollFactor = 0.75, scrollPoint, scrollBoundingBox) {
    return sendScrollMessageWithIframeSupport(trainingContext, 'up', scrollFactor, scrollPoint, scrollBoundingBox);
}
async function findScrollableElements(trainingContext, maxElements = 5) {
    const pageMessageFunction = () => (0, messages_1.findScrollableElementsMessage)(maxElements);
    const combiner = async (scrollableElements, messageResponse) => {
        const messageResponseValue = await messageResponse?.jsonValue();
        let updatedScrollableElements;
        if (messageResponseValue) {
            if (scrollableElements) {
                const mainPages = scrollableElements.elements.filter((element) => element.type === 'document');
                const currentElements = scrollableElements.elements.filter((element) => element.type === 'element');
                const newElements = (messageResponseValue.elements || []).map((element) => ({
                    ...element,
                    type: 'element',
                }));
                const combinedElements = [...currentElements, ...newElements]
                    .sort((a, b) => (a.elementBoundingBox?.height ?? 0) *
                    (a.elementBoundingBox?.width ?? 0) -
                    (b.elementBoundingBox?.height ?? 0) *
                        (b.elementBoundingBox?.width ?? 0))
                    .slice(0, maxElements);
                updatedScrollableElements = {
                    elements: [...mainPages, ...combinedElements],
                    success: true,
                };
            }
            else {
                updatedScrollableElements = messageResponseValue;
            }
        }
        return updatedScrollableElements;
    };
    try {
        const response = await walkAllIframes(trainingContext, pageMessageFunction, combiner);
        return response;
    }
    catch (e) {
        console.error('Error finding scrollable elements', e);
        return undefined;
    }
}
