/**
 * Copyright Microsoft Corporation. All rights reserved.
 *
 * 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 util from 'util';
import debug from 'debug';
import open from 'open';
import { server as coreServer } from 'playwright-core/lib/coreBundle';
import { ManualPromise } from '@isomorphic/manualPromise';
import { isUnderTest } from '@utils/debug';
import { gracefullyProcessExitDoNotHang } from '@utils/processLauncher';
import { configLoader } from '../common';
import ListReporter from '../reporters/list';
import { SigIntWatcher } from './sigIntWatcher';
import { TestRunner, TestRunnerEvent } from './testRunner';
import UIModeReporter from './uiModeReporter';
import { loadReporter } from './loadUtils';
import { wrapReporterAsV2 } from '../reporters/reporterV2';
const originalDebugLog = debug.log;
// eslint-disable-next-line no-restricted-properties
const originalStdoutWrite = process.stdout.write;
// eslint-disable-next-line no-restricted-properties
const originalStderrWrite = process.stderr.write;
const originalStdinIsTTY = process.stdin.isTTY;
class TestServer {
    _configLocation;
    _configCLIOverrides;
    _dispatcher;
    constructor(configLocation, configCLIOverrides) {
        this._configLocation = configLocation;
        this._configCLIOverrides = configCLIOverrides;
    }
    async start(options) {
        this._dispatcher = new TestServerDispatcher(this._configLocation, this._configCLIOverrides);
        return await coreServer.startTraceViewerServer({ ...options, transport: this._dispatcher.transport });
    }
    async stop() {
        await this._dispatcher?.stop();
    }
}
export class TestServerDispatcher {
    transport;
    _serializer;
    _closeOnDisconnect = false;
    _testRunner;
    _globalSetupReport;
    _dispatchEvent;
    constructor(configLocation, configCLIOverrides) {
        this._testRunner = new TestRunner(configLocation, configCLIOverrides);
        this.transport = {
            onconnect: () => { },
            dispatch: (method, params) => this[method](params),
            onclose: () => {
                if (this._closeOnDisconnect)
                    gracefullyProcessExitDoNotHang(0);
            },
        };
        this._dispatchEvent = (method, params) => this.transport.sendEvent?.(method, params);
        this._testRunner.on(TestRunnerEvent.TestFilesChanged, testFiles => this._dispatchEvent('testFilesChanged', { testFiles }));
        this._testRunner.on(TestRunnerEvent.TestPaused, params => this._dispatchEvent('testPaused', { errors: params.errors }));
    }
    async _wireReporter(messageSink) {
        return await createReporterForTestServer(this._serializer, messageSink);
    }
    async _collectingReporter() {
        const report = [];
        return {
            reporter: await createReporterForTestServer(this._serializer, e => report.push(e)),
            report,
        };
    }
    async initialize(params) {
        // Note: this method can be called multiple times, for example from a new connection after UI mode reload.
        this._serializer = params.serializer;
        this._closeOnDisconnect = !!params.closeOnDisconnect;
        await this._testRunner.initialize({
            ...params,
        });
        this._setInterceptStdio(!!params.interceptStdio);
    }
    async ping() { }
    async open(params) {
        if (isUnderTest())
            return;
        // eslint-disable-next-line no-console
        open('vscode://file/' + params.location.file + ':' + params.location.line).catch(e => console.error(e));
    }
    async resizeTerminal(params) {
        this._testRunner.resizeTerminal(params);
    }
    async checkBrowsers() {
        return { hasBrowsers: this._testRunner.hasSomeBrowsers() };
    }
    async installBrowsers() {
        await this._testRunner.installBrowsers();
    }
    async runGlobalSetup(params) {
        const { reporter, report } = await this._collectingReporter();
        this._globalSetupReport = report;
        const { status, env } = await this._testRunner.runGlobalSetup([reporter, new ListReporter()]);
        return { report, status, env };
    }
    async runGlobalTeardown() {
        const { status } = await this._testRunner.runGlobalTeardown();
        const report = this._globalSetupReport || [];
        this._globalSetupReport = undefined;
        return { status, report };
    }
    async clearCache(params) {
        await this._testRunner.clearCache();
    }
    async listFiles(params) {
        const { reporter, report } = await this._collectingReporter();
        const { status } = await this._testRunner.listFiles(reporter, params.projects);
        return { report, status };
    }
    async listTests(params) {
        const { reporter, report } = await this._collectingReporter();
        const { status } = await this._testRunner.listTests(reporter, params);
        return { report, status };
    }
    async runTests(params) {
        const wireReporter = await this._wireReporter(e => this._dispatchEvent('report', e));
        const { status } = await this._testRunner.runTests(wireReporter, {
            ...params,
            doNotRunDepsOutsideProjectFilter: true,
            pauseAtEnd: params.pauseAtEnd,
            pauseOnError: params.pauseOnError,
        });
        return { status };
    }
    async watch(params) {
        await this._testRunner.watch(params.fileNames);
    }
    async findRelatedTestFiles(params) {
        return this._testRunner.findRelatedTestFiles(params.files);
    }
    async stopTests() {
        await this._testRunner.stopTests();
    }
    async stop() {
        this._setInterceptStdio(false);
        await this._testRunner.stop();
    }
    async closeGracefully() {
        await this._testRunner.closeGracefully();
    }
    _setInterceptStdio(interceptStdio) {
        /* eslint-disable no-restricted-properties */
        if (process.env.PWTEST_DEBUG)
            return;
        if (interceptStdio) {
            if (debug.log === originalDebugLog) {
                // Only if debug.log hasn't already been tampered with, don't intercept any DEBUG=* logging
                debug.log = (...args) => {
                    const string = util.format(...args) + '\n';
                    return originalStderrWrite.apply(process.stderr, [string]);
                };
            }
            const stdoutWrite = (chunk) => {
                this._dispatchEvent('stdio', chunkToPayload('stdout', chunk));
                return true;
            };
            const stderrWrite = (chunk) => {
                this._dispatchEvent('stdio', chunkToPayload('stderr', chunk));
                return true;
            };
            process.stdout.write = stdoutWrite;
            process.stderr.write = stderrWrite;
            // Override isTTY to prevent reporters from thinking they can block and wait for user SIGINT.
            // We don't have a test for this, so be careful!
            // https://github.com/microsoft/playwright/issues/37867
            // @ts-expect-error types are wrong, isTTY can be undefined
            process.stdin.isTTY = undefined;
        }
        else {
            debug.log = originalDebugLog;
            process.stdout.write = originalStdoutWrite;
            process.stderr.write = originalStderrWrite;
            process.stdin.isTTY = originalStdinIsTTY;
        }
        /* eslint-enable no-restricted-properties */
    }
}
export async function runUIMode(configFile, configCLIOverrides, options) {
    const configLocation = configLoader.resolveConfigLocation(configFile);
    return await innerRunTestServer(configLocation, configCLIOverrides, options, async (server, cancelPromise) => {
        await coreServer.installRootRedirect(server, undefined, { ...options, webApp: 'uiMode.html' });
        if (options.host !== undefined || options.port !== undefined) {
            await coreServer.openTraceInBrowser(server.urlPrefix('human-readable'));
        }
        else {
            const channel = await installedChromiumChannelForUI(configLocation, configCLIOverrides);
            const page = await coreServer.openTraceViewerApp(server.urlPrefix('precise'), 'chromium', {
                headless: isUnderTest() && process.env.PWTEST_HEADED_FOR_TEST !== '1',
                persistentContextOptions: {
                    handleSIGINT: false,
                    channel,
                },
            });
            page.on('close', () => cancelPromise.resolve());
        }
    });
}
// Pick first channel that is used by one of the projects, to ensure it is installed on the machine.
async function installedChromiumChannelForUI(configLocation, configCLIOverrides) {
    const config = await configLoader.loadConfig(configLocation, configCLIOverrides).catch(e => null);
    if (!config)
        return undefined;
    if (config.projects.some(p => (!p.project.use.browserName || p.project.use.browserName === 'chromium') && !p.project.use.channel))
        return undefined;
    for (const channel of ['chromium', 'chrome', 'msedge']) {
        if (config.projects.some(p => p.project.use.channel === channel))
            return channel;
    }
    return undefined;
}
export async function runTestServer(configFile, configCLIOverrides, options) {
    const configLocation = configLoader.resolveConfigLocation(configFile);
    return await innerRunTestServer(configLocation, configCLIOverrides, options, async (server) => {
        // eslint-disable-next-line no-console
        console.log('Listening on ' + server.urlPrefix('precise').replace('http:', 'ws:') + '/' + server.wsGuid());
    });
}
async function innerRunTestServer(configLocation, configCLIOverrides, options, openUI) {
    const testServer = new TestServer(configLocation, configCLIOverrides);
    const cancelPromise = new ManualPromise();
    const sigintWatcher = new SigIntWatcher();
    process.stdin.on('close', () => gracefullyProcessExitDoNotHang(0));
    void sigintWatcher.promise().then(() => cancelPromise.resolve());
    try {
        const server = await testServer.start(options);
        await openUI(server, cancelPromise);
        await cancelPromise;
    }
    finally {
        await testServer.stop();
        sigintWatcher.disarm();
    }
    return sigintWatcher.hadSignal() ? 'interrupted' : 'passed';
}
function chunkToPayload(type, chunk) {
    if (chunk instanceof Uint8Array)
        return { type, buffer: chunk.toString('base64') };
    return { type, text: chunk };
}
async function createReporterForTestServer(file, messageSink) {
    const reporterConstructor = file ? await loadReporter(null, file) : UIModeReporter;
    return wrapReporterAsV2(new reporterConstructor({
        _send: messageSink,
    }));
}
//# sourceMappingURL=testServer.js.map