"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ScrollExecutor = void 0;
const ScrollStepDescriptor_1 = require("@mablhq/test-model/types/mobile/ScrollStepDescriptor");
const execution_1 = require("@mablhq-internal/runtime/execution");
const execution_2 = require("@mablhq-internal/runtime/execution");
const FindMobileElement_1 = require("../actions/FindMobileElement");
const scrollingExecutionUtil_1 = require("../util/scrollingExecutionUtil");
const LocatorStrategy_1 = require("../actions/find/strategies/LocatorStrategy");
const messaging_1 = require("@mablhq-internal/runtime/core/messaging/messaging");
const pageSourceUtil_1 = require("../util/pageSourceUtil");
const PERCENTAGE_PROXIMITY_TO_EDGE = 0.2;
class ScrollExecutor {
    run(step, executionContext) {
        return (0, execution_1.runActionWithElementReadyRetriesForMobile)(() => this.scroll(step, executionContext), executionContext);
    }
    async scroll(step, executionContext) {
        const driver = executionContext.mobileTestEnvironment.getDriver();
        const windowSize = await executionContext.mobileTestEnvironment.getWindowSize();
        const platform = executionContext.mobileTestEnvironment.getConfig().app.platformName;
        await executionContext.observeDeferredFunction();
        try {
            let scrollExecutionCoordinates;
            switch (step.scrollType) {
                case ScrollStepDescriptor_1.ScrollType.ScrollDeviceScreen: {
                    const { coordinates, scrollDirection } = step;
                    this.validateCoordinates(coordinates);
                    scrollExecutionCoordinates = await (0, scrollingExecutionUtil_1.getScrollExecutionCoordinates)(windowSize, { scrollDirection, scrollPercentages: coordinates });
                    break;
                }
                case ScrollStepDescriptor_1.ScrollType.ScrollToElement: {
                    const { coordinates, find, maxScrollAttempts, scrollDirection, targetElementDescriptor, } = step;
                    let containerElement;
                    if (!!find) {
                        containerElement =
                            await FindMobileElement_1.FindMobileElement.normalizeDescriptorAndFindOneElement(find, executionContext);
                        if (!containerElement?.mobileElement &&
                            !containerElement.boundingBox) {
                            return {
                                status: execution_2.StepStatus.Failed,
                                logInfo: 'Unable to find the element to scroll within',
                            };
                        }
                    }
                    const containerLocationAndSize = await (0, scrollingExecutionUtil_1.getElementLocationAndSizeLimitedToDeviceScreen)(windowSize, containerElement?.mobileElement, containerElement?.boundingBox);
                    return this.scrollToElement(driver, executionContext, targetElementDescriptor, containerLocationAndSize, coordinates, maxScrollAttempts, scrollDirection, windowSize, platform);
                }
                case ScrollStepDescriptor_1.ScrollType.ScrollWithinElement: {
                    const { find, scrollDirection } = step;
                    const containerElement = await FindMobileElement_1.FindMobileElement.normalizeDescriptorAndFindOneElement(find, executionContext);
                    if (!containerElement.mobileElement &&
                        !containerElement.boundingBox) {
                        return {
                            status: execution_2.StepStatus.Failed,
                            logInfo: 'Unable to find the element to scroll within',
                        };
                    }
                    const { coordinates } = step;
                    this.validateCoordinates(coordinates);
                    scrollExecutionCoordinates = await (0, scrollingExecutionUtil_1.getScrollExecutionCoordinates)(windowSize, {
                        boundingBox: containerElement.boundingBox,
                        element: containerElement.mobileElement,
                        scrollDirection,
                        scrollPercentages: coordinates,
                    });
                    break;
                }
                default:
                    return {
                        status: execution_2.StepStatus.Failed,
                        logInfo: 'Unknown scroll type',
                    };
            }
            if (!scrollExecutionCoordinates) {
                executionContext.log.error('Unable to determine scroll execution coordinates');
                return {
                    status: execution_2.StepStatus.Failed,
                    logInfo: 'Unable to determine coordinates for scrolling ',
                };
            }
            await (0, scrollingExecutionUtil_1.scrollByCoordinates)(driver, scrollExecutionCoordinates);
        }
        catch (error) {
            executionContext.log.error(error);
            return {
                status: execution_2.StepStatus.Failed,
                logInfo: 'Error trying to scroll',
            };
        }
        return { status: execution_2.StepStatus.Passed };
    }
    async scrollToElement(driver, executionContext, targetDescriptor, containerLocationAndSize, scrollPercentages, maxStepScrollAttempts, scrollDirection, windowSize, platform) {
        const maxScrollAttempts = Math.min(scrollingExecutionUtil_1.MAX_SCROLL_ATTEMPTS, maxStepScrollAttempts);
        const shouldStopFind = () => !executionContext.isRunningStep() ||
            executionContext.isFindExecutionShortCircuited();
        let scrollAttempts = 0;
        let previousPageSource = '';
        let findResult;
        let targetFound = false;
        while (!targetFound &&
            scrollAttempts <= maxScrollAttempts &&
            !shouldStopFind()) {
            findResult = await FindMobileElement_1.FindMobileElement.normalizeDescriptorAndFindOneElement(targetDescriptor, executionContext, undefined, LocatorStrategy_1.DEFAULT_MOBILE_FIND_CONFIG.ScrollGroupStrategyWaitTimeoutMs);
            targetFound = !!findResult?.mobileElement || !!findResult?.boundingBox;
            if (targetFound) {
                const targetElementLocationAndSize = await (0, scrollingExecutionUtil_1.getElementLocationAndSizeLimitedToDeviceScreen)(windowSize, findResult.mobileElement, findResult.boundingBox);
                await this.maybeScrollMoreToCenter(driver, executionContext, scrollDirection, windowSize, targetElementLocationAndSize, containerLocationAndSize);
            }
            if (shouldStopFind() || scrollAttempts >= maxScrollAttempts) {
                break;
            }
            if (!targetFound) {
                executionContext.log.info(`Did not find the element at scroll attempt #${scrollAttempts}. Scrolling ${scrollDirection}...`, {
                    executionPhase: messaging_1.ExecutionPhase.DURING_ACTION,
                });
                await (0, scrollingExecutionUtil_1.scrollInDirection)(driver, windowSize, {
                    scrollPercentages,
                    boundingBox: (0, scrollingExecutionUtil_1.convertElementLocationAndSizeToBoundingBox)(containerLocationAndSize),
                    scrollDirection,
                });
                scrollAttempts++;
                const currentPageSource = await (0, pageSourceUtil_1.getLimitedPageSource)(driver, platform);
                if (currentPageSource !== previousPageSource) {
                    previousPageSource = currentPageSource;
                }
                else {
                    executionContext.log.info('Determined that the page source between scrolls has not changed. Stopping...', {
                        executionPhase: messaging_1.ExecutionPhase.DURING_ACTION,
                    });
                    break;
                }
            }
        }
        if (!targetFound) {
            return {
                status: execution_2.StepStatus.Failed,
                logInfo: 'Unable to find the element to scroll to',
            };
        }
        return { status: execution_2.StepStatus.Passed };
    }
    async maybeScrollMoreToCenter(driver, executionContext, scrollDirection, windowSize, targetElementLocationAndSize, containerLocationAndSize) {
        switch (scrollDirection) {
            case ScrollStepDescriptor_1.ScrollDirection.Down:
                if (targetElementLocationAndSize.elementLocation.y >
                    (1 - PERCENTAGE_PROXIMITY_TO_EDGE) * windowSize.height) {
                    executionContext.log.debug('Scrolling down to bring the element closer to the center of the screen', {
                        executionPhase: messaging_1.ExecutionPhase.DURING_ACTION,
                    });
                    await (0, scrollingExecutionUtil_1.scrollInDirection)(driver, windowSize, {
                        boundingBox: (0, scrollingExecutionUtil_1.convertElementLocationAndSizeToBoundingBox)(containerLocationAndSize),
                        scrollDirection,
                        scrollPercentages: {
                            from: { xPercent: 0.5, yPercent: 0.6 },
                            to: { xPercent: 0.5, yPercent: 0.4 },
                        },
                    });
                }
                break;
            case ScrollStepDescriptor_1.ScrollDirection.Up:
                if (targetElementLocationAndSize.elementLocation.y <
                    PERCENTAGE_PROXIMITY_TO_EDGE * windowSize.height) {
                    executionContext.log.debug('Scrolling up to bring the element closer to the center of the screen', {
                        executionPhase: messaging_1.ExecutionPhase.DURING_ACTION,
                    });
                    await (0, scrollingExecutionUtil_1.scrollInDirection)(driver, windowSize, {
                        boundingBox: (0, scrollingExecutionUtil_1.convertElementLocationAndSizeToBoundingBox)(containerLocationAndSize),
                        scrollDirection,
                        scrollPercentages: {
                            from: { xPercent: 0.5, yPercent: 0.4 },
                            to: { xPercent: 0.5, yPercent: 0.6 },
                        },
                    });
                }
                break;
            default:
                break;
        }
    }
    validateCoordinates(coordinates) {
        const { from, to } = coordinates;
        if (!this.isBetweenZeroAndOne(from.xPercent) ||
            !this.isBetweenZeroAndOne(from.yPercent) ||
            !this.isBetweenZeroAndOne(to.xPercent) ||
            !this.isBetweenZeroAndOne(to.yPercent)) {
            throw new Error('Invalid scrolling percentages');
        }
    }
    isBetweenZeroAndOne(value) {
        return value >= 0 && value <= 1;
    }
}
exports.ScrollExecutor = ScrollExecutor;
