/**
 * Copyright 2021 Google LLC.
 * Copyright (c) Microsoft Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import debug from 'debug';
import { SimpleTransport } from './SimpleTransport.js';
const debugInternal = debug('bidi:mapper:internal');
const debugInfo = debug('bidi:mapper:info');
const debugOthers = debug('bidi:mapper:debug:others');
// Memorizes a debug creation
const loggers = new Map();
const getLogger = (type) => {
    const prefix = `bidi:mapper:${type}`;
    let logger = loggers.get(prefix);
    if (!logger) {
        logger = debug(prefix);
        loggers.set(prefix, logger);
    }
    return logger;
};
export class MapperServerCdpConnection {
    #cdpConnection;
    #bidiSession;
    static async create(cdpConnection, mapperTabSource, verbose) {
        try {
            const bidiSession = await this.#initMapper(cdpConnection, mapperTabSource, verbose);
            return new MapperServerCdpConnection(cdpConnection, bidiSession);
        }
        catch (e) {
            cdpConnection.close();
            throw e;
        }
    }
    constructor(cdpConnection, bidiSession) {
        this.#cdpConnection = cdpConnection;
        this.#bidiSession = bidiSession;
    }
    static async #sendMessage(mapperCdpClient, message) {
        try {
            await mapperCdpClient.sendCommand('Runtime.evaluate', {
                expression: `onBidiMessage(${JSON.stringify(message)})`,
            });
        }
        catch (error) {
            debugInternal('Call to onBidiMessage failed', error);
        }
    }
    close() {
        this.#cdpConnection.close();
    }
    bidiSession() {
        return this.#bidiSession;
    }
    static #onBindingCalled = (params, bidiSession) => {
        if (params.name === 'sendBidiResponse') {
            bidiSession.emit('message', params.payload);
        }
        else if (params.name === 'sendDebugMessage') {
            this.#onDebugMessage(params.payload);
        }
    };
    static #onDebugMessage = (json) => {
        try {
            const log = JSON.parse(json);
            if (log.logType !== undefined && log.messages !== undefined) {
                const logger = getLogger(log.logType);
                logger(log.messages);
            }
        }
        catch {
            // Fall back to raw log in case of unknown
            debugOthers(json);
        }
    };
    static #onConsoleAPICalled = (params) => {
        debugInfo('consoleAPICalled: %s %O', params.type, params.args.map((arg) => arg.value));
    };
    static #onRuntimeExceptionThrown = (params) => {
        debugInfo('exceptionThrown:', params);
    };
    static async #initMapper(cdpConnection, mapperTabSource, verbose) {
        debugInternal('Initializing Mapper.');
        const browserClient = await cdpConnection.createBrowserSession();
        // Run mapper in the first open tab.
        const targets = (await cdpConnection.sendCommand('Target.getTargets', {}));
        const mapperTabTargetId = targets.targetInfos.filter((target) => target.type === 'page')[0].targetId;
        const { sessionId: mapperSessionId } = await browserClient.sendCommand('Target.attachToTarget', { targetId: mapperTabTargetId, flatten: true });
        const mapperCdpClient = cdpConnection.getCdpClient(mapperSessionId);
        // Click on the body to interact with the page in order to "beforeunload" being
        // triggered when the tab is closed.
        await mapperCdpClient.sendCommand('Runtime.evaluate', {
            expression: 'document.body.click()',
            userGesture: true,
        });
        // Create and activate new tab with a blank page.
        await browserClient.sendCommand('Target.createTarget', {
            url: 'about:blank',
        });
        const bidiSession = new SimpleTransport(async (message) => await this.#sendMessage(mapperCdpClient, message));
        // Process responses from the mapper tab.
        mapperCdpClient.on('Runtime.bindingCalled', (params) => this.#onBindingCalled(params, bidiSession));
        // Forward console messages from the mapper tab.
        mapperCdpClient.on('Runtime.consoleAPICalled', this.#onConsoleAPICalled);
        // Catch unhandled exceptions in the mapper.
        mapperCdpClient.on('Runtime.exceptionThrown', this.#onRuntimeExceptionThrown);
        await mapperCdpClient.sendCommand('Runtime.enable');
        await browserClient.sendCommand('Target.exposeDevToolsProtocol', {
            bindingName: 'cdp',
            targetId: mapperTabTargetId,
        });
        await mapperCdpClient.sendCommand('Runtime.addBinding', {
            name: 'sendBidiResponse',
        });
        if (verbose) {
            // Needed to request verbose logs from Mapper.
            await mapperCdpClient.sendCommand('Runtime.addBinding', {
                name: 'sendDebugMessage',
            });
        }
        // Evaluate Mapper Tab sources in the tab.
        await mapperCdpClient.sendCommand('Runtime.evaluate', {
            expression: mapperTabSource,
        });
        // TODO: handle errors in all these evaluate calls!
        await mapperCdpClient.sendCommand('Runtime.evaluate', {
            expression: `window.runMapperInstance('${mapperTabTargetId}')`,
            awaitPromise: true,
        });
        debugInternal('Mapper is launched!');
        return bidiSession;
    }
}
//# sourceMappingURL=MapperCdpConnection.js.map