/**
 * 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 { debuglog } from 'node:util';
import { SimpleTransport } from './SimpleTransport.js';
const debugInternal = debuglog('bidi:mapper:internal');
const debugInfo = debuglog('bidi:mapper:info');
const debugOthers = debuglog('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 = debuglog(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();
        const { targetId: mapperTargetId } = await browserClient.sendCommand('Target.createTarget', {
            url: 'about:blank#MAPPER_TARGET',
            hidden: true,
            background: true,
        });
        const { sessionId: mapperSessionId } = await browserClient.sendCommand('Target.attachToTarget', { targetId: mapperTargetId, flatten: true });
        const mapperCdpClient = cdpConnection.getCdpClient(mapperSessionId);
        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: mapperTargetId,
            inheritPermissions: true,
        });
        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('${mapperTargetId}')`,
            awaitPromise: true,
        });
        debugInternal('Mapper is launched!');
        return bidiSession;
    }
}
//# sourceMappingURL=MapperCdpConnection.js.map