/**
 * 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 fs from 'fs';
import os from 'os';
import path from 'path';
import { tools } from 'playwright-core/lib/coreBundle';
import debug from 'debug';
import { noColors } from '@isomorphic/colors';
import { ManualPromise } from '@isomorphic/manualPromise';
import { escapeRegExp } from '@isomorphic/stringUtils';
import { toPosixPath } from '@utils/fileUtils';
import { base, ListReporter, testRunner } from '../../runner';
import { StringWriteStream } from './streams';
import { fileExistsAsync } from '../../util';
import { ensureSeedFile, seedProject } from './seed';
import { configLoader } from '../../common';
export class GeneratorJournal {
    _rootPath;
    _plan;
    _seed;
    _steps;
    constructor(rootPath, plan, seed) {
        this._rootPath = rootPath;
        this._plan = plan;
        this._seed = seed;
        this._steps = [];
    }
    logStep(title, code) {
        if (title)
            this._steps.push({ title, code });
    }
    journal() {
        const result = [];
        result.push(`# Plan`);
        result.push(this._plan);
        result.push(`# Seed file: ${toPosixPath(path.relative(this._rootPath, this._seed.file))}`);
        result.push('```ts');
        result.push(this._seed.content);
        result.push('```');
        result.push(`# Steps`);
        result.push(this._steps.map(step => `### ${step.title}
\`\`\`ts
${step.code}
\`\`\``).join('\n\n'));
        result.push(bestPracticesMarkdown);
        return result.join('\n\n');
    }
}
export class TestContext {
    _clientInfo;
    _testRunnerAndScreen;
    computedHeaded;
    _configLocation;
    rootPath;
    generatorJournal;
    constructor(clientInfo, configPath, options) {
        this._clientInfo = clientInfo;
        this._configLocation = configLoader.resolveConfigLocation(configPath || clientInfo.cwd);
        this.rootPath = clientInfo.cwd || this._configLocation.configDir;
        if (options?.headless !== undefined)
            this.computedHeaded = !options.headless;
        else
            this.computedHeaded = !process.env.CI && !(os.platform() === 'linux' && !process.env.DISPLAY);
    }
    existingTestRunner() {
        return this._testRunnerAndScreen?.testRunner;
    }
    async _cleanupTestRunner() {
        if (!this._testRunnerAndScreen)
            return;
        await this._testRunnerAndScreen.testRunner.stopTests();
        this._testRunnerAndScreen.claimStdio();
        try {
            await this._testRunnerAndScreen.testRunner.runGlobalTeardown();
        }
        finally {
            this._testRunnerAndScreen.releaseStdio();
            this._testRunnerAndScreen = undefined;
        }
    }
    async createTestRunner() {
        await this._cleanupTestRunner();
        const runner = new testRunner.TestRunner(this._configLocation, {});
        await runner.initialize({});
        const testPaused = new ManualPromise();
        const testRunnerAndScreen = {
            ...createScreen(),
            testRunner: runner,
            waitForTestPaused: () => testPaused,
        };
        this._testRunnerAndScreen = testRunnerAndScreen;
        runner.on(testRunner.TestRunnerEvent.TestPaused, params => {
            testRunnerAndScreen.sendMessageToPausedTest = params.sendMessage;
            testPaused.resolve();
        });
        return testRunnerAndScreen;
    }
    async getOrCreateSeedFile(seedFile, projectName) {
        const configDir = this._configLocation.configDir;
        const { testRunner: runner } = await this.createTestRunner();
        const config = await runner.loadConfig();
        const project = seedProject(config, projectName);
        if (!seedFile) {
            seedFile = await ensureSeedFile(project);
        }
        else {
            const candidateFiles = [];
            const testDir = project.project.testDir;
            candidateFiles.push(path.resolve(testDir, seedFile));
            candidateFiles.push(path.resolve(configDir, seedFile));
            candidateFiles.push(path.resolve(this.rootPath, seedFile));
            let resolvedSeedFile;
            for (const candidateFile of candidateFiles) {
                if (await fileExistsAsync(candidateFile)) {
                    resolvedSeedFile = candidateFile;
                    break;
                }
            }
            if (!resolvedSeedFile)
                throw new Error('seed test not found.');
            seedFile = resolvedSeedFile;
        }
        const seedFileContent = await fs.promises.readFile(seedFile, 'utf8');
        return {
            file: seedFile,
            content: seedFileContent,
            projectName: project.project.name,
        };
    }
    async runSeedTest(seedFile, projectName) {
        const result = await this.runTestsWithGlobalSetupAndPossiblePause({
            headed: this.computedHeaded,
            locations: ['/' + escapeRegExp(seedFile) + '/'],
            projects: [projectName],
            timeout: 0,
            workers: 1,
            pauseAtEnd: true,
            disableConfigReporters: true,
            failOnLoadErrors: true,
        });
        if (result.status === 'passed')
            result.output += '\nError: seed test not found.';
        else if (result.status !== 'paused')
            result.output += '\nError while running the seed test.';
        return result;
    }
    async runTestsWithGlobalSetupAndPossiblePause(params) {
        const configDir = this._configLocation.configDir;
        const testRunnerAndScreen = await this.createTestRunner();
        const { testRunner: runner, screen, claimStdio, releaseStdio } = testRunnerAndScreen;
        claimStdio();
        try {
            const setupReporter = new MCPListReporter({ configDir, screen, includeTestId: true });
            const { status } = await runner.runGlobalSetup([setupReporter]);
            if (status !== 'passed')
                return { output: testRunnerAndScreen.output.join('\n'), status };
        }
        finally {
            releaseStdio();
        }
        let status = 'passed';
        const cleanup = async () => {
            claimStdio();
            try {
                const result = await runner.runGlobalTeardown();
                if (status === 'passed')
                    status = result.status;
            }
            finally {
                releaseStdio();
            }
        };
        try {
            const reporter = new MCPListReporter({ configDir, screen, includeTestId: true });
            status = await Promise.race([
                runner.runTests(reporter, params).then(result => result.status),
                testRunnerAndScreen.waitForTestPaused().then(() => 'paused'),
            ]);
            if (status === 'paused') {
                const response = await testRunnerAndScreen.sendMessageToPausedTest({ request: { initialize: { clientInfo: this._clientInfo } } });
                if (response.error)
                    throw new Error(response.error.message);
                testRunnerAndScreen.output.push(response.response.initialize.pausedMessage);
                return { output: testRunnerAndScreen.output.join('\n'), status };
            }
        }
        catch (e) {
            status = 'failed';
            testRunnerAndScreen.output.push(String(e));
            await cleanup();
            return { output: testRunnerAndScreen.output.join('\n'), status };
        }
        await cleanup();
        return { output: testRunnerAndScreen.output.join('\n'), status };
    }
    async close() {
        await this._cleanupTestRunner().catch(e => debug('pw:mcp:error')(e));
    }
    async sendMessageToPausedTest(request) {
        const sendMessage = this._testRunnerAndScreen?.sendMessageToPausedTest;
        if (!sendMessage)
            throw new Error('Must setup test before interacting with the page');
        const result = await sendMessage({ request });
        if (result.error)
            throw new Error(result.error.message);
        if (typeof request?.callTool?.arguments?.['intent'] === 'string') {
            const response = tools.parseResponse(result.response.callTool);
            if (response && !response.isError && response.code)
                this.generatorJournal?.logStep(request.callTool.arguments['intent'], response.code);
        }
        return result.response;
    }
}
export function createScreen() {
    const output = [];
    const stdout = new StringWriteStream(output, 'stdout');
    const stderr = new StringWriteStream(output, 'stderr');
    const screen = {
        ...base.terminalScreen,
        isTTY: false,
        colors: noColors,
        stdout: stdout,
        stderr: stderr,
    };
    /* eslint-disable no-restricted-properties */
    const originalStdoutWrite = process.stdout.write;
    const originalStderrWrite = process.stderr.write;
    const claimStdio = () => {
        process.stdout.write = (chunk) => {
            stdout.write(chunk);
            return true;
        };
        process.stderr.write = (chunk) => {
            stderr.write(chunk);
            return true;
        };
    };
    const releaseStdio = () => {
        process.stdout.write = originalStdoutWrite;
        process.stderr.write = originalStderrWrite;
    };
    /* eslint-enable no-restricted-properties */
    return { screen, claimStdio, releaseStdio, output };
}
const bestPracticesMarkdown = `
# Best practices
- Do not improvise, do not add directives that were not asked for
- Use clear, descriptive assertions to validate the expected behavior
- Use reliable locators from this log
- Use local variables for locators that are used multiple times
- Use Playwright waiting assertions and best practices from this log
- NEVER! use page.waitForLoadState()
- NEVER! use page.waitForNavigation()
- NEVER! use page.waitForTimeout()
- NEVER! use page.evaluate()
`;
class MCPListReporter extends ListReporter {
    async onTestPaused() {
        // ListReporter waits for user input to resume, we don't want that in MCP.
        await new Promise(() => { });
    }
}
//# sourceMappingURL=testContext.js.map