/*
 * Copyright 2023 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 { mkdtemp } from 'fs/promises';
import os from 'os';
import path from 'path';
import { CDP_WEBSOCKET_ENDPOINT_REGEX, launch, } from '@puppeteer/browsers';
import debug from 'debug';
import WebSocket from 'ws';
import { MapperCdpConnection } from '../cdp/CdpConnection.js';
import { WebSocketTransport } from '../utils/WebsocketTransport.js';
import { MapperServerCdpConnection } from './MapperCdpConnection.js';
import { PipeTransport } from './PipeTransport.js';
import { getMapperTabSource } from './reader.js';
const debugInternal = debug('bidi:mapper:internal');
/**
 * BrowserProcess is responsible for running the browser and BiDi Mapper within
 * it.
 * 1. Launch Chromium (using Puppeteer for now).
 * 2. Get `BiDi-CDP` mapper JS binaries using `MapperReader`.
 * 3. Run `BiDi-CDP` mapper in launched browser using `MapperRunner`.
 * 4. Bind `BiDi-CDP` mapper to the `BiDi server` to forward messages from BiDi
 * Mapper to the client.
 */
export class BrowserInstance {
    #mapperCdpConnection;
    #browserProcess;
    static async run(chromeOptions, verbose) {
        const profileDir = await mkdtemp(path.join(os.tmpdir(), 'web-driver-bidi-server-'));
        // See https://github.com/GoogleChrome/chrome-launcher/blob/main/docs/chrome-flags-for-tools.md
        const chromeArguments = [
            // keep-sorted start
            '--allow-browser-signin=false',
            '--disable-component-update',
            '--disable-default-apps',
            '--disable-notifications',
            '--disable-popup-blocking',
            '--disable-search-engine-choice-screen',
            '--enable-automation',
            '--no-default-browser-check',
            '--no-first-run',
            '--password-store=basic',
            '--use-mock-keychain',
            `--user-data-dir=${profileDir}`,
            // keep-sorted end
            ...(chromeOptions.chromeArgs.includes('--remote-debugging-pipe')
                ? []
                : ['--remote-debugging-port=9222']),
            ...chromeOptions.chromeArgs,
            'about:blank',
        ];
        const executablePath = chromeOptions.chromeBinary ?? process.env['BROWSER_BIN'];
        if (!executablePath) {
            throw new Error('Could not find Chrome binary');
        }
        const pipe = chromeArguments.includes('--remote-debugging-pipe');
        const launchArguments = {
            executablePath,
            args: chromeArguments,
            env: process.env,
            pipe,
        };
        debugInternal(`Launching browser`, {
            executablePath,
            args: chromeArguments,
        });
        const browserProcess = launch(launchArguments);
        let cdpConnection;
        if (pipe) {
            cdpConnection = this.#establishPipeConnection(browserProcess);
        }
        else {
            const cdpEndpoint = await browserProcess.waitForLineOutput(CDP_WEBSOCKET_ENDPOINT_REGEX);
            // There is a conflict between prettier and eslint here.
            // prettier-ignore
            cdpConnection = await this.#establishCdpConnection(cdpEndpoint);
        }
        // 2. Get `BiDi-CDP` mapper JS binaries.
        const mapperTabSource = await getMapperTabSource();
        // 3. Run `BiDi-CDP` mapper in launched browser using `MapperRunner`.
        const mapperCdpConnection = await MapperServerCdpConnection.create(cdpConnection, mapperTabSource, verbose);
        return new BrowserInstance(mapperCdpConnection, browserProcess);
    }
    constructor(mapperCdpConnection, browserProcess) {
        this.#mapperCdpConnection = mapperCdpConnection;
        this.#browserProcess = browserProcess;
    }
    async close() {
        // Close the mapper tab.
        this.#mapperCdpConnection.close();
        // Close browser.
        await this.#browserProcess.close();
    }
    bidiSession() {
        return this.#mapperCdpConnection.bidiSession();
    }
    static #establishCdpConnection(cdpUrl) {
        return new Promise((resolve, reject) => {
            debugInternal('Establishing session with cdpUrl: ', cdpUrl);
            const ws = new WebSocket(cdpUrl);
            ws.once('error', reject);
            ws.on('open', () => {
                debugInternal('Session established.');
                const transport = new WebSocketTransport(ws);
                const connection = new MapperCdpConnection(transport);
                resolve(connection);
            });
        });
    }
    static #establishPipeConnection(browserProcess) {
        debugInternal('Establishing pipe connection to browser process with pid: ', browserProcess.nodeProcess.pid);
        const { 3: pipeWrite, 4: pipeRead } = browserProcess.nodeProcess.stdio;
        const transport = new PipeTransport(pipeWrite, pipeRead);
        return new MapperCdpConnection(transport);
    }
}
//# sourceMappingURL=BrowserInstance.js.map