"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PuppeteerPageHooker = void 0;
const debug_1 = __importDefault(require("debug"));
const utils_1 = require("./utils");
const puppeteer_selector_mapping_1 = require("./utils/puppeteer-selector-mapping");
const puppeteer_text_mapping_1 = require("./utils/puppeteer-text-mapping");
const loggerError = debug_1.default('root-cause:error');
const omittedPageMethods = [
    'then',
    'setViewport',
    'mainFrame',
    'screenshot',
    'close',
    'removeListener',
    'off',
    'constructor',
    'viewport',
];
const returningElementHandlesViaPromise = new Set(['$', '$$', 'waitForSelector']);
const flatGetters = new Set(['keyboard', 'mouse']);
const gettersViaFunction = new Set(['frames']);
class PuppeteerPageHooker {
    constructor(testContext, rootPage) {
        this.testContext = testContext;
        this.rootPage = rootPage;
        this.paused = false;
        this.beforeAllHooks = [];
        this.afterAllHooks = [];
        this.beforeHooks = [];
        this.afterHooks = [];
    }
    pause() {
        this.paused = true;
    }
    resume() {
        this.paused = false;
    }
    registerBeforeAllHook(hook) {
        this.beforeAllHooks.push(hook);
    }
    registerAfterAllHook(hook) {
        this.afterAllHooks.push(hook);
    }
    registerBeforeHook(hook) {
        this.beforeHooks.push(hook);
    }
    registerAfterHook(hook) {
        this.afterHooks.push(hook);
    }
    wrapWithProxy(proxiedObject) {
        return this.innerWrapWithProxy(proxiedObject, []);
    }
    innerWrapWithProxy(proxiedObject, methodCallData) {
        const pauseStateHolder = this;
        const pageHookerThis = this;
        const handler = {
            get(target, prop, receiver) {
                const reflectedProperty = Reflect.get(target, prop, receiver);
                const accessedPropAsString = prop.toString();
                if (accessedPropAsString.startsWith('_') ||
                    omittedPageMethods.includes(accessedPropAsString) ||
                    pauseStateHolder.paused) {
                    return reflectedProperty;
                }
                if (flatGetters.has(accessedPropAsString)) {
                    return pageHookerThis.wrapWithProxy(reflectedProperty);
                }
                if (gettersViaFunction.has(accessedPropAsString)) {
                    const method = reflectedProperty;
                    return function gettersViaFunctionWrapped(...args) {
                        const result = method.apply(target, args);
                        const newMethodCallData = {
                            selector: puppeteer_selector_mapping_1.extractPuppeteerSelector(proxiedObject, accessedPropAsString, args),
                            creationFunction: accessedPropAsString,
                            text: puppeteer_text_mapping_1.extractPuppeteerText(proxiedObject, accessedPropAsString, args, result),
                        };
                        return pageHookerThis.wrapReturnValueInProxy(result, methodCallData, newMethodCallData);
                    };
                }
                if (returningElementHandlesViaPromise.has(accessedPropAsString)) {
                    return utils_1.appendToFunctionName(async function returningElementHandlesViaPromiseWrappedFunction(...args) {
                        const result = await pageHookerThis.makeStep(proxiedObject, target, reflectedProperty, accessedPropAsString, args, methodCallData);
                        if (!result) {
                            return result;
                        }
                        const newMethodCallData = {
                            selector: puppeteer_selector_mapping_1.extractPuppeteerSelector(proxiedObject, accessedPropAsString, args),
                            creationFunction: accessedPropAsString,
                            text: puppeteer_text_mapping_1.extractPuppeteerText(proxiedObject, accessedPropAsString, args, result),
                        };
                        return pageHookerThis.wrapReturnValueInProxy(result, methodCallData, newMethodCallData);
                    }, `_${proxiedObject.constructor.name}_${accessedPropAsString}`);
                }
                if (typeof reflectedProperty !== 'function') {
                    return reflectedProperty;
                }
                return utils_1.appendToFunctionName(async function rootCauseWrappedFunction(...args) {
                    return await pageHookerThis.makeStep(proxiedObject, target, reflectedProperty, accessedPropAsString, args, methodCallData);
                }, `_${proxiedObject.constructor.name}_${accessedPropAsString}`);
            },
        };
        return new Proxy(proxiedObject, handler);
    }
    async start() {
        for (const beforeAllHook of this.beforeAllHooks) {
            try {
                await beforeAllHook({
                    testContext: this.testContext,
                    rootPage: this.rootPage,
                    proxyContext: this.rootPage,
                });
            }
            catch (e) {
                loggerError(e);
            }
        }
    }
    async end(endStatus) {
        for (const afterAllHook of this.afterAllHooks) {
            try {
                await afterAllHook({ testContext: this.testContext, endStatus });
            }
            catch (e) {
                loggerError(e);
            }
        }
        await this.testContext.testEnded();
    }
    wrapReturnValueInProxy(result, previousMethodCallData, methodCallData) {
        if (Array.isArray(result)) {
            return result.map((element, index) => {
                return this.innerWrapWithProxy(element, previousMethodCallData.concat({ ...methodCallData, index }));
            });
        }
        return this.innerWrapWithProxy(result, previousMethodCallData.concat(methodCallData));
    }
    async makeStep(proxyContext, target, method, fnName, args, methodCallData) {
        const { beforeHooks, afterHooks, testContext, rootPage } = this;
        testContext.stepStarted();
        const newMethodCallData = {
            selector: puppeteer_selector_mapping_1.extractPuppeteerSelector(proxyContext, fnName, args),
            creationFunction: fnName,
        };
        methodCallData = methodCallData.concat(newMethodCallData);
        const commonHookArgs = {
            testContext,
            fnName,
            proxyContext,
            rootPage,
            args,
            methodCallData,
        };
        for (const beforeHook of beforeHooks) {
            try {
                await beforeHook({ ...commonHookArgs });
            }
            catch (err) {
                loggerError(err);
            }
        }
        try {
            const result = await method.apply(target, args);
            newMethodCallData.text = puppeteer_text_mapping_1.extractPuppeteerText(proxyContext, fnName, args, result);
            for (const afterHook of afterHooks) {
                try {
                    await afterHook({
                        ...commonHookArgs,
                        instrumentedFunctionResult: {
                            success: true,
                            data: result,
                        },
                    });
                }
                catch (err) {
                    loggerError(err);
                }
            }
            return result;
        }
        catch (err) {
            for (const afterHook of afterHooks) {
                try {
                    await afterHook({
                        ...commonHookArgs,
                        instrumentedFunctionResult: {
                            success: false,
                            error: err,
                        },
                    });
                }
                catch (err) {
                    loggerError(err);
                }
            }
            throw err;
        }
        finally {
            await testContext.stepEnded();
        }
    }
}
exports.PuppeteerPageHooker = PuppeteerPageHooker;
