"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;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppiumServerInstance = exports.InferredAppiumServerStatusEvent = exports.AppiumSeverEvent = exports.ServerStatus = void 0;
const utilities_1 = require("@mablhq-internal/runtime/utilities");
const child_process_1 = require("child_process");
const async_retry_1 = __importDefault(require("async-retry"));
const path = __importStar(require("path"));
const APPIUM_SERVER_READY = 'APPIUM_SERVER_READY';
const APPIUM_SERVER_SHUTDOWN = 'APPIUM_SERVER_SHUTDOWN';
const APPIUM_SERVER_LOG = 'APPIUM_SERVER_LOG';
const APPIUM_SERVER_CHECK_IN = 'APPIUM_SERVER_CHECK_IN';
const APPIUM_SERVER_PARENT_ALIVE = 'APPIUM_SERVER_PARENT_ALIVE';
var ServerStatus;
(function (ServerStatus) {
    ServerStatus["running"] = "running";
    ServerStatus["stopped"] = "stopped";
})(ServerStatus || (exports.ServerStatus = ServerStatus = {}));
var AppiumSeverEvent;
(function (AppiumSeverEvent) {
    AppiumSeverEvent["RUN_APPIUM_SERVER_DEAD"] = "run-appium-server-dead";
    AppiumSeverEvent["RUN_APPIUM_SERVER_SHUTTING_DOWN"] = "run-appium-server-shutting-down";
    AppiumSeverEvent["RUN_APPIUM_SERVER_STARTED"] = "run-appium-server-started";
    AppiumSeverEvent["RUN_APPIUM_SERVER_STARTING"] = "run-appium-server-starting";
    AppiumSeverEvent["RUN_APPIUM_SERVER_STILL_SHUTTING_DOWN"] = "run-appium-server-still-shutting-down";
    AppiumSeverEvent["RUN_APPIUM_SERVER_STILL_STARTING"] = "run-appium-server-still-starting";
})(AppiumSeverEvent || (exports.AppiumSeverEvent = AppiumSeverEvent = {}));
var InferredAppiumServerStatusEvent;
(function (InferredAppiumServerStatusEvent) {
    InferredAppiumServerStatusEvent["APK_INSTALLED"] = "apk-installed";
    InferredAppiumServerStatusEvent["CONNECTED_TO_DEVICE"] = "connected-to-device";
    InferredAppiumServerStatusEvent["CONNECTING_TO_ANDROID_DEVICE"] = "connecting-to-android-device";
    InferredAppiumServerStatusEvent["CONNECTING_TO_IOS_DEVICE"] = "connecting-to-ios-device";
    InferredAppiumServerStatusEvent["DRIVERS_LOADED"] = "drivers-found";
    InferredAppiumServerStatusEvent["LOADING_DRIVERS"] = "loading-drivers";
    InferredAppiumServerStatusEvent["SETTING_UP_SIMULATOR"] = "setting-up-simulator";
    InferredAppiumServerStatusEvent["SIMULATOR_BOOTED"] = "simulator-booted";
    InferredAppiumServerStatusEvent["STARTING_MJPEG_STREAM"] = "starting-mjpeg-stream";
    InferredAppiumServerStatusEvent["STILL_CONNECTING_TO_ANDROID_DEVICE"] = "still-connecting-to-android-device";
    InferredAppiumServerStatusEvent["STILL_CONNECTING_TO_IOS_DEVICE"] = "still-connecting-to-ios-device";
    InferredAppiumServerStatusEvent["WEBVIEW_CHROME_DRIVER_DOWNLOAD"] = "webview-chrome-driver-download";
    InferredAppiumServerStatusEvent["WEBVIEW_CHROME_DRIVER_DOWNLOAD_COMPLETE"] = "webview-chrome-driver-download-complete";
})(InferredAppiumServerStatusEvent || (exports.InferredAppiumServerStatusEvent = InferredAppiumServerStatusEvent = {}));
const inferredRegExpMap = {
    [InferredAppiumServerStatusEvent.APK_INSTALLED]: /.*The installation of.*/gim,
    [InferredAppiumServerStatusEvent.CONNECTED_TO_DEVICE]: /.*(AndroidUiautomator2Driver|XCUITestDriver).*Determined the downstream protocol.*/gim,
    [InferredAppiumServerStatusEvent.CONNECTING_TO_ANDROID_DEVICE]: /.*Waiting up to .* for UiAutomator2 to be online.*/gim,
    [InferredAppiumServerStatusEvent.STILL_CONNECTING_TO_ANDROID_DEVICE]: /.*AndroidUiautomator2Driver.*socket hang up.*/gim,
    [InferredAppiumServerStatusEvent.CONNECTING_TO_IOS_DEVICE]: /.*Launching WebDriverAgent on the device.*/gim,
    [InferredAppiumServerStatusEvent.STILL_CONNECTING_TO_IOS_DEVICE]: /.*XCUITestDriver.*connect ECONNREFUSED.*/gim,
    [InferredAppiumServerStatusEvent.STARTING_MJPEG_STREAM]: /.*Starting MJPEG stream.*/gim,
    [InferredAppiumServerStatusEvent.SETTING_UP_SIMULATOR]: /.*Setting up simulator.*/gim,
    [InferredAppiumServerStatusEvent.SIMULATOR_BOOTED]: /.*Simulator with UDID .* booted.*/gim,
    [InferredAppiumServerStatusEvent.DRIVERS_LOADED]: /.*Available drivers.*/gim,
    [InferredAppiumServerStatusEvent.LOADING_DRIVERS]: /.*Attempting to load driver.*/gim,
    [InferredAppiumServerStatusEvent.WEBVIEW_CHROME_DRIVER_DOWNLOAD]: /.*\[ChromedriverGoogleapisStorageClient\].*Parsed \d+ entries from storage XML.*/gim,
    [InferredAppiumServerStatusEvent.WEBVIEW_CHROME_DRIVER_DOWNLOAD_COMPLETE]: /.*\[ChromedriverStorageClient\].*Successfully synchronized \d+ chromedriver.*/gim,
};
const iterableInferredRegExpMap = Object.entries(inferredRegExpMap);
class AppiumServerInstance {
    server;
    serverConfig;
    serverStatus = ServerStatus.stopped;
    serverEventRateLimitTracker;
    minTimeBetweenRepeatedEventsInMillis = 3_500;
    constructor(serverOptions) {
        this.serverConfig = serverOptions;
        this.serverEventRateLimitTracker = {};
    }
    getStatus() {
        return this.serverStatus;
    }
    getServer() {
        return this.server;
    }
    async start() {
        this.serverEventRateLimitTracker = {};
        if ((0, utilities_1.areWeTestingInJest)() && globalThis.__APPIUM_SERVER__) {
            this.server = globalThis.__APPIUM_SERVER__.server;
            return;
        }
        const serverRunScript = path.resolve(this.serverConfig.runAppiumServerPath ?? __dirname, 'runAppiumServer.js');
        this.serverConfig.allowCors = true;
        console.log('Calling appium server run script at: ', serverRunScript);
        let forkFunc = child_process_1.fork;
        let runningAsUtilityProcess = false;
        if (this.serverConfig.alternativeFork) {
            runningAsUtilityProcess = true;
            forkFunc = this.serverConfig.alternativeFork;
            console.log('Using an alternative fork function');
        }
        this.server = forkFunc(serverRunScript, [
            JSON.stringify({ ...this.serverConfig, runningAsUtilityProcess }),
            this.serverConfig.androidHome ?? '',
        ], {
            env: process.env,
            silent: true,
            serviceName: 'mablMobileServer',
        });
        this.maybeEmitAppiumServerEvent(AppiumSeverEvent.RUN_APPIUM_SERVER_STARTING);
        if (process.env.MABL_DEBUG_APPIUM_SERVER === '1') {
            this.server.stdout?.on('data', (chunk) => {
                console.log('[runAppiumServer.js] ', chunk.toString());
            });
            this.server.stderr?.on('data', (chunk) => {
                console.error('[runAppiumServer.js] ', chunk.toString());
            });
        }
        process.on('SIGINT', () => {
            this.server?.kill('SIGINT');
        });
        if (!isUtilityProcess(this.server)) {
            this.server.on('error', (error) => {
                console.error('Appium server ERROR - ', error);
            });
        }
        this.server.on('message', (raw) => {
            const message = raw;
            switch (message.type) {
                case APPIUM_SERVER_CHECK_IN:
                    this.sendMessageToServer(APPIUM_SERVER_PARENT_ALIVE);
                    break;
                case APPIUM_SERVER_READY:
                    this.serverStatus = ServerStatus.running;
                    break;
                case APPIUM_SERVER_LOG:
                    if (message.payload) {
                        AppiumServerInstance.maybeEmitInferredEventFromMessage(message, (event) => {
                            this.emitRateLimitedEvent(event, this.serverEventRateLimitTracker, this.minTimeBetweenRepeatedEventsInMillis);
                        });
                        if (this.serverConfig?.logHandler) {
                            const meetsLogLevel = meetsLogLevelRequirements(this.serverConfig.loglevel ?? 'error', message.payload.logLevel);
                            if (meetsLogLevel) {
                                this.serverConfig?.logHandler(message.payload.logLevel, message.payload.log);
                            }
                        }
                    }
                    break;
                default:
                    console.warn(`unsupported message type from server: [${message.type}]`);
                    break;
            }
        });
        this.server.stderr?.setEncoding('utf8');
        this.server.stderr?.on('data', (data) => {
            console.log('[runAppiumServer.js] - ERROR: ' + data);
            if (this.serverConfig?.logHandler) {
                this.serverConfig?.logHandler('error', data);
            }
        });
        await this.waitForServerStatusMessage(ServerStatus.running);
        this.maybeEmitAppiumServerEvent(AppiumSeverEvent.RUN_APPIUM_SERVER_STARTED);
    }
    static maybeEmitInferredEventFromMessage(message, serverEventHandlerFunc) {
        if (!serverEventHandlerFunc ||
            !message.payload?.log ||
            message.payload.log.length > 5000) {
            return;
        }
        const messagePayload = message.payload;
        iterableInferredRegExpMap.find(([inferredEvent, regExp]) => {
            if ((messagePayload.logLevel === 'info' ||
                messagePayload.logLevel === 'debug') &&
                regExp.test(messagePayload.log)) {
                serverEventHandlerFunc(inferredEvent);
                return true;
            }
            return false;
        });
    }
    emitRateLimitedEvent(event, serverEventRateLimitTracker, minTimeBetweenRepeatedEventsInMillis) {
        if (!this.serverConfig?.serverEventHandler) {
            return;
        }
        const currTime = Date.now();
        const lastSent = serverEventRateLimitTracker[event];
        if (!lastSent ||
            (lastSent && currTime - lastSent >= minTimeBetweenRepeatedEventsInMillis)) {
            this.serverConfig.serverEventHandler(event);
            this.serverEventRateLimitTracker[event] = currTime;
        }
    }
    maybeEmitAppiumServerEvent(event) {
        this.emitRateLimitedEvent(event, this.serverEventRateLimitTracker, this.minTimeBetweenRepeatedEventsInMillis);
    }
    async waitForServerStatusMessage(status, maxTimeMillis) {
        return (0, async_retry_1.default)(() => {
            if (this.serverStatus !== status) {
                if (status === ServerStatus.running) {
                    this.maybeEmitAppiumServerEvent(AppiumSeverEvent.RUN_APPIUM_SERVER_STILL_STARTING);
                }
                else if (status === ServerStatus.stopped) {
                    this.maybeEmitAppiumServerEvent(AppiumSeverEvent.RUN_APPIUM_SERVER_STILL_SHUTTING_DOWN);
                }
                throw new Error(`Waiting for appium server status to reach [${status}] has failed`);
            }
        }, {
            retries: 100,
            minTimeout: 500,
            maxTimeout: 1_000,
            maxRetryTime: maxTimeMillis ?? 60_000,
        });
    }
    async stop() {
        if ((0, utilities_1.areWeTestingInJest)() && globalThis.__APPIUM_SERVER__) {
            return;
        }
        if (!this.server) {
            throw new Error('No server found to close');
        }
        this.sendMessageToServer(APPIUM_SERVER_SHUTDOWN);
        this.maybeEmitAppiumServerEvent(AppiumSeverEvent.RUN_APPIUM_SERVER_SHUTTING_DOWN);
        try {
            await this.waitForServerStatusMessage(ServerStatus.stopped, 3_000);
            this.maybeEmitAppiumServerEvent(AppiumSeverEvent.RUN_APPIUM_SERVER_DEAD);
        }
        catch (error) {
            this.server?.kill('SIGINT');
            this.serverStatus = ServerStatus.stopped;
            this.maybeEmitAppiumServerEvent(AppiumSeverEvent.RUN_APPIUM_SERVER_DEAD);
        }
        this.server = undefined;
    }
    sendMessageToServer(message) {
        if (isUtilityProcess(this.server)) {
            this.server?.postMessage(message);
        }
        else {
            this.server?.send(message);
        }
    }
}
exports.AppiumServerInstance = AppiumServerInstance;
function isUtilityProcess(value) {
    return value?.postMessage && value?.pid;
}
const logLevelMap = {
    debug: 0,
    'debug:debug': 0,
    'debug:info': 0,
    'debug:warn': 0,
    'debug:error': 0,
    info: 1,
    'info:debug': 1,
    'info:info': 1,
    'info:warn': 1,
    'info:error': 1,
    warn: 2,
    'warn:debug': 2,
    'warn:info': 2,
    'warn:warn': 2,
    'warn:error': 2,
    error: 3,
    'error:debug': 3,
    'error:info': 3,
    'error:warn': 3,
    'error:error': 3,
};
function meetsLogLevelRequirements(configuredLogLevel, incomingLogLevel) {
    return (logLevelMap[incomingLogLevel] >=
        logLevelMap[configuredLogLevel]);
}
