"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ANDROID_CLOUD_IMAGE_CHROMEDRIVER_EXECUTABLE_DIR = exports.NO_WEBVIEW_CONTEXT_FOUND = exports.NATIVE_APP_CONTEXT = exports.WEBVIEW_CONTEXT_PREFIX = void 0;
exports.executeFindInWebView = executeFindInWebView;
exports.doesWebviewMatch = doesWebviewMatch;
exports.isDetailedContext = isDetailedContext;
exports.waitForWebViewContextLoaded = waitForWebViewContextLoaded;
exports.waitForDocumentFullyLoaded = waitForDocumentFullyLoaded;
exports.getIOSWebViewContexts = getIOSWebViewContexts;
exports.getAndroidWebViewWindowHandles = getAndroidWebViewWindowHandles;
exports.getAndroidWebViewContextId = getAndroidWebViewContextId;
exports.translateWebViewElementCoordinatesToNativeCoordinates = translateWebViewElementCoordinatesToNativeCoordinates;
const fs = __importStar(require("fs"));
const resourceUtil_1 = require("@mablhq-internal/runtime/util/resourceUtil");
const execution_1 = require("@mablhq-internal/runtime/execution");
const mablApi_1 = require("@mablhq-internal/runtime/mablApi");
const utilities_1 = require("@mablhq-internal/runtime/trainer/utilities");
exports.WEBVIEW_CONTEXT_PREFIX = 'WEBVIEW_';
exports.NATIVE_APP_CONTEXT = 'NATIVE_APP';
const ANDROID_WINDOW_HANDLE_PREFIX = 'CDwindow-';
exports.NO_WEBVIEW_CONTEXT_FOUND = 'No webview context found. Please check that webviews can be inspected for this application.';
exports.ANDROID_CLOUD_IMAGE_CHROMEDRIVER_EXECUTABLE_DIR = '/opt/selenium';
const MABL_FIND_FILE_LOCATION = (0, resourceUtil_1.findResource)('mablFind.js');
const PAGE_STABILIZATION_WAIT_TIME_MS = 300;
let mablFindScript;
function getMablFindScript() {
    if (mablFindScript === undefined) {
        mablFindScript = fs.readFileSync(MABL_FIND_FILE_LOCATION, 'utf8');
    }
    return mablFindScript;
}
async function executeFindInWebView(driver, mobileExecutionContext, webTargetDescriptor) {
    await waitForDocumentFullyLoaded(driver);
    await injectMablFindIntoWebView(driver);
    await (0, utilities_1.sleep)(PAGE_STABILIZATION_WAIT_TIME_MS);
    const stringifiedNormalizedDescriptor = JSON.stringify(webTargetDescriptor);
    const resolvedFindDescriptorString = mobileExecutionContext.variables.resolveToString(stringifiedNormalizedDescriptor);
    if (mobileExecutionContext.mobileEnvConfig.app.platformName ===
        mablApi_1.MobilePlatformEnum.Ios) {
        await driver.setTimeouts(undefined, undefined, execution_1.FIND_TIMEOUT_MILLIS);
    }
    const findResultOrError = await driver.executeAsync((args, done) => {
        const { resolvedFindDescriptorString, maxWaitTimeMs, pageInteractionSpeed, useFindModels, returnSimilarityMatrix, } = args;
        const { createLocalExecutionStrategy, createUnifiedExecutionStrategy, FindAction, FindExecutionConfiguration, } = mablFind;
        const configuration = new FindExecutionConfiguration(maxWaitTimeMs, undefined, undefined, undefined, returnSimilarityMatrix);
        configuration.pageInteractionSpeed = pageInteractionSpeed;
        const findDescriptorObject = JSON.parse(resolvedFindDescriptorString);
        const strategy = useFindModels
            ? createUnifiedExecutionStrategy(maxWaitTimeMs, findDescriptorObject)
            : createLocalExecutionStrategy(maxWaitTimeMs, findDescriptorObject);
        const action = new FindAction(strategy, maxWaitTimeMs);
        let stopCheckingCircuit = false;
        action
            .execute(findDescriptorObject, configuration, useFindModels)
            .then((result) => {
            stopCheckingCircuit = true;
            const { foundElement } = result;
            foundElement?.scrollIntoViewIfNeeded
                ?
                    foundElement?.scrollIntoViewIfNeeded()
                : foundElement?.scrollIntoView();
            function removeNodes(_key, value) {
                if (value instanceof Node) {
                    return;
                }
                return value;
            }
            const findResult = JSON.parse(JSON.stringify(result, removeNodes));
            done({
                findResult,
                elementBoundingClientRect: foundElement
                    ?.getBoundingClientRect()
                    .toJSON(),
            });
        })
            .catch((error) => done(error));
        const checkFindShortCircuit = () => {
            if (globalThis.__MABL_STEP_SHORT_CIRCUIT) {
                delete globalThis.__MABL_STEP_SHORT_CIRCUIT;
                done(undefined);
            }
            else if (!stopCheckingCircuit) {
                setTimeout(checkFindShortCircuit, 1000);
            }
        };
        setTimeout(checkFindShortCircuit, 1000);
    }, {
        resolvedFindDescriptorString,
        maxWaitTimeMs: execution_1.FIND_TIMEOUT_MILLIS,
        pageInteractionSpeed: mobileExecutionContext.pageInteractionSpeed ??
            mablApi_1.TestParameters.PageLoadWaitEnum.NORMAL,
        useFindModels: true,
        returnSimilarityMatrix: false,
    });
    if (findResultOrError instanceof Error) {
        throw findResultOrError;
    }
    if (!findResultOrError.findResult.success) {
        throw new Error('Unable to find element in webview');
    }
    const elementXPath = findResultOrError.findResult.foundSelector?.xpath;
    if (!elementXPath) {
        throw new Error('Unable to retrieve xpath of element in webview');
    }
    const elementReference = await driver.findElement('xpath', elementXPath);
    return {
        elementReference,
        elementBoundingClientRect: findResultOrError.elementBoundingClientRect,
        elementSelector: findResultOrError.findResult.foundSelector,
    };
}
async function injectMablFindIntoWebView(driver) {
    const isScriptAlreadyInjected = await driver.execute(() => !!window.mablFind);
    if (isScriptAlreadyInjected) {
        return;
    }
    return driver.executeScript(getMablFindScript(), []);
}
function doesWebviewMatch(webViewUrl, elementDescriptorUrl) {
    return webViewUrl === elementDescriptorUrl;
}
function isDetailedContext(context) {
    return typeof context !== 'string';
}
const WAIT_FOR_WEB_VIEW_CONTEXT_LOADED_MILLIS = 20_000;
async function waitForWebViewContextLoaded(driver) {
    try {
        await driver.waitUntil(async () => {
            const currentContexts = await driver.getContexts();
            return currentContexts.length > 1;
        }, {
            timeout: WAIT_FOR_WEB_VIEW_CONTEXT_LOADED_MILLIS,
            interval: 200,
        });
        return true;
    }
    catch (error) {
        return false;
    }
}
const WAIT_FOR_DOC_FULLY_LOADED_MILLIS = 15_000;
async function waitForDocumentFullyLoaded(driver) {
    await driver.waitUntil(async () => (await driver.execute(() => document.readyState)) === 'complete', {
        timeout: WAIT_FOR_DOC_FULLY_LOADED_MILLIS,
        timeoutMsg: 'Website not loaded',
        interval: 100,
    });
}
async function getIOSWebViewContexts(driver) {
    const contexts = (await driver.execute('mobile: getContexts'));
    return removeDuplicateContexts(contexts
        .filter((context) => context.id !== exports.NATIVE_APP_CONTEXT)
        .reverse());
}
async function getAndroidWebViewWindowHandles(driver) {
    const contextsResponse = (await driver.execute('mobile: getContexts'));
    if (contextsResponse.length === 0) {
        return [];
    }
    const contexts = removeDuplicateContexts(contextsResponse[0].pages);
    const windowHandles = await driver.getWindowHandles();
    for (const context of contexts) {
        if (!windowHandles.includes(context.id)) {
            context.id = ANDROID_WINDOW_HANDLE_PREFIX + context.id;
        }
    }
    return contexts;
}
async function getAndroidWebViewContextId(driver) {
    const contexts = await driver.getContexts();
    const webViewContexts = contexts
        .filter((c) => !isDetailedContext(c))
        .filter((c) => c.startsWith(exports.WEBVIEW_CONTEXT_PREFIX));
    if (webViewContexts.length === 0) {
        return undefined;
    }
    return webViewContexts[0];
}
function removeDuplicateContexts(contexts) {
    const urlsSet = new Set();
    return contexts.filter((context) => {
        if (context.url === undefined) {
            return false;
        }
        if (urlsSet.has(context.url)) {
            return false;
        }
        urlsSet.add(context.url);
        return true;
    });
}
function translateWebViewElementCoordinatesToNativeCoordinates(elementBoundingClientRect, webViewWindowCoordinatesInfo, parentWebViewElRect, platform) {
    const visualViewportOffsetLeft = platform === mablApi_1.MobilePlatformEnum.Android
        ? webViewWindowCoordinatesInfo.visualViewportOffsetLeft
        : 0;
    const visualViewportOffsetTop = platform === mablApi_1.MobilePlatformEnum.Android
        ? webViewWindowCoordinatesInfo.visualViewportOffsetTop
        : 0;
    const webViewToGlobalWidthRatio = platform === mablApi_1.MobilePlatformEnum.Android
        ? parentWebViewElRect.width /
            webViewWindowCoordinatesInfo.visualViewportWidth
        : webViewWindowCoordinatesInfo.visualViewportScale;
    const translatedRect = {
        x: (elementBoundingClientRect.x - visualViewportOffsetLeft) *
            webViewToGlobalWidthRatio +
            parentWebViewElRect.x,
        y: (elementBoundingClientRect.y - visualViewportOffsetTop) *
            webViewToGlobalWidthRatio +
            parentWebViewElRect.y,
        width: elementBoundingClientRect.width * webViewToGlobalWidthRatio,
        height: elementBoundingClientRect.height * webViewToGlobalWidthRatio,
    };
    if (translatedRect.x < parentWebViewElRect.x) {
        translatedRect.width =
            translatedRect.width - (parentWebViewElRect.x - translatedRect.x);
        translatedRect.x = parentWebViewElRect.x;
    }
    if (translatedRect.y < parentWebViewElRect.y) {
        translatedRect.height =
            translatedRect.height - (parentWebViewElRect.y - translatedRect.y);
        translatedRect.y = parentWebViewElRect.y;
    }
    if (translatedRect.x + translatedRect.width >
        parentWebViewElRect.x + parentWebViewElRect.width) {
        translatedRect.width =
            parentWebViewElRect.x + parentWebViewElRect.width - translatedRect.x;
    }
    if (translatedRect.y + translatedRect.height >
        parentWebViewElRect.y + parentWebViewElRect.height) {
        translatedRect.height =
            parentWebViewElRect.y + parentWebViewElRect.height - translatedRect.y;
    }
    return translatedRect;
}
