/**
 * 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 path from 'path';
import { createGuid } from '@utils/crypto';
import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
export class TeleReporterEmitter {
    _messageSink;
    _rootDir;
    _emitterOptions;
    _resultKnownAttachmentCounts = new Map();
    _resultKnownErrorCounts = new Map();
    // In case there is blob reporter and UI mode, make sure one doesn't override
    // the id assigned by the other.
    _idSymbol = Symbol('id');
    constructor(messageSink, options = {}) {
        this._messageSink = messageSink;
        this._emitterOptions = options;
    }
    version() {
        return 'v2';
    }
    onConfigure(config) {
        this._rootDir = config.rootDir;
        this._messageSink({ method: 'onConfigure', params: { config: this._serializeConfig(config) } });
    }
    onBegin(suite) {
        const projects = suite.suites.map(projectSuite => this._serializeProject(projectSuite));
        for (const project of projects)
            this._messageSink({ method: 'onProject', params: { project } });
        this._messageSink({ method: 'onBegin', params: undefined });
    }
    onTestBegin(test, result) {
        result[this._idSymbol] = createGuid();
        this._messageSink({
            method: 'onTestBegin',
            params: {
                testId: test.id,
                result: this._serializeResultStart(result)
            }
        });
    }
    async onTestPaused(test, result) {
        const resultId = result[this._idSymbol];
        this._resultKnownErrorCounts.set(resultId, result.errors.length);
        this._messageSink({
            method: 'onTestPaused',
            params: {
                testId: test.id,
                resultId,
                errors: result.errors,
            }
        });
        // keep the test paused until externally resumed
        await new Promise(() => { });
    }
    onTestEnd(test, result) {
        const testEnd = {
            testId: test.id,
            expectedStatus: test.expectedStatus,
            timeout: test.timeout,
            annotations: []
        };
        this._sendNewAttachments(result, test.id);
        this._messageSink({
            method: 'onTestEnd',
            params: {
                test: testEnd,
                result: this._serializeResultEnd(result),
            }
        });
        const resultId = result[this._idSymbol];
        this._resultKnownAttachmentCounts.delete(resultId);
        this._resultKnownErrorCounts.delete(resultId);
    }
    onStepBegin(test, result, step) {
        step[this._idSymbol] = createGuid();
        this._messageSink({
            method: 'onStepBegin',
            params: {
                testId: test.id,
                resultId: result[this._idSymbol],
                step: this._serializeStepStart(step)
            }
        });
    }
    onStepEnd(test, result, step) {
        // Create synthetic onAttach event so we serialize the entire attachment along with the step
        const resultId = result[this._idSymbol];
        this._sendNewAttachments(result, test.id);
        this._messageSink({
            method: 'onStepEnd',
            params: {
                testId: test.id,
                resultId,
                step: this._serializeStepEnd(step, result)
            }
        });
    }
    onError(error, workerInfo) {
        this._messageSink({
            method: 'onError',
            params: {
                error,
                workerInfo: workerInfo ? {
                    workerIndex: workerInfo.workerIndex,
                    parallelIndex: workerInfo.parallelIndex,
                    projectName: workerInfo.project.name,
                } : undefined,
            },
        });
    }
    onStdOut(chunk, test, result) {
        this._onStdIO('stdout', chunk, test, result);
    }
    onStdErr(chunk, test, result) {
        this._onStdIO('stderr', chunk, test, result);
    }
    _onStdIO(type, chunk, test, result) {
        if (this._emitterOptions.omitOutput)
            return;
        const isBase64 = typeof chunk !== 'string';
        const data = isBase64 ? chunk.toString('base64') : chunk;
        this._messageSink({
            method: 'onStdIO',
            params: { testId: test?.id, resultId: result ? result[this._idSymbol] : undefined, type, data, isBase64 }
        });
    }
    async onEnd(result) {
        const resultPayload = {
            status: result.status,
            startTime: result.startTime.getTime(),
            duration: result.duration,
        };
        this._messageSink({
            method: 'onEnd',
            params: {
                result: resultPayload
            }
        });
    }
    printsToStdio() {
        return false;
    }
    _serializeConfig(config) {
        return {
            configFile: this._relativePath(config.configFile),
            globalTimeout: config.globalTimeout,
            maxFailures: config.maxFailures,
            metadata: config.metadata,
            rootDir: config.rootDir,
            shard: config.shard,
            version: config.version,
            workers: config.workers,
            globalSetup: config.globalSetup,
            globalTeardown: config.globalTeardown,
            tags: config.tags,
            webServer: config.webServer,
        };
    }
    _serializeProject(suite) {
        const project = suite.project();
        const report = {
            metadata: project.metadata,
            name: project.name,
            outputDir: this._relativePath(project.outputDir),
            repeatEach: project.repeatEach,
            retries: project.retries,
            testDir: this._relativePath(project.testDir),
            testIgnore: serializeRegexPatterns(project.testIgnore),
            testMatch: serializeRegexPatterns(project.testMatch),
            timeout: project.timeout,
            suites: suite.suites.map(fileSuite => {
                return this._serializeSuite(fileSuite);
            }),
            grep: serializeRegexPatterns(project.grep),
            grepInvert: serializeRegexPatterns(project.grepInvert || []),
            dependencies: project.dependencies,
            snapshotDir: this._relativePath(project.snapshotDir),
            teardown: project.teardown,
            ignoreSnapshots: project.ignoreSnapshots ? true : undefined,
            use: this._serializeProjectUseOptions(project.use),
        };
        return report;
    }
    _serializeProjectUseOptions(use) {
        return {
            testIdAttribute: use.testIdAttribute,
        };
    }
    _serializeSuite(suite) {
        const result = {
            title: suite.title,
            location: this._relativeLocation(suite.location),
            entries: suite.entries().map(e => {
                if (e.type === 'test')
                    return this._serializeTest(e);
                return this._serializeSuite(e);
            })
        };
        return result;
    }
    _serializeTest(test) {
        return {
            testId: test.id,
            title: test.title,
            location: this._relativeLocation(test.location),
            retries: test.retries,
            tags: test.tags,
            repeatEachIndex: test.repeatEachIndex,
            annotations: this._relativeAnnotationLocations(test.annotations),
        };
    }
    _serializeResultStart(result) {
        return {
            id: result[this._idSymbol],
            retry: result.retry,
            workerIndex: result.workerIndex,
            parallelIndex: result.parallelIndex,
            startTime: +result.startTime,
        };
    }
    _serializeResultEnd(result) {
        const id = result[this._idSymbol];
        return {
            id,
            duration: result.duration,
            status: result.status,
            errors: this._resultKnownErrorCounts.has(id) ? result.errors.slice(this._resultKnownAttachmentCounts.get(id)) : result.errors,
            annotations: result.annotations?.length ? this._relativeAnnotationLocations(result.annotations) : undefined,
        };
    }
    _sendNewAttachments(result, testId) {
        const resultId = result[this._idSymbol];
        // Track whether this step (or something else since the last step) has added attachments and send them
        const knownAttachmentCount = this._resultKnownAttachmentCounts.get(resultId) ?? 0;
        if (result.attachments.length > knownAttachmentCount) {
            this._messageSink({
                method: 'onAttach',
                params: {
                    testId,
                    resultId,
                    attachments: this._serializeAttachments((result.attachments.slice(knownAttachmentCount))),
                }
            });
        }
        this._resultKnownAttachmentCounts.set(resultId, result.attachments.length);
    }
    _serializeAttachments(attachments) {
        return attachments.map(a => {
            const { body, ...rest } = a;
            return {
                ...rest,
                // There is no Buffer in the browser, so there is no point in sending the data there.
                base64: (body && !this._emitterOptions.omitBuffers) ? body.toString('base64') : undefined,
            };
        });
    }
    _serializeStepStart(step) {
        return {
            id: step[this._idSymbol],
            parentStepId: step.parent?.[this._idSymbol],
            title: step.title,
            category: step.category,
            startTime: +step.startTime,
            location: this._relativeLocation(step.location),
        };
    }
    _serializeStepEnd(step, result) {
        return {
            id: step[this._idSymbol],
            duration: step.duration,
            error: step.error,
            attachments: step.attachments.length ? step.attachments.map(a => result.attachments.indexOf(a)) : undefined,
            annotations: step.annotations.length ? this._relativeAnnotationLocations(step.annotations) : undefined,
        };
    }
    _relativeAnnotationLocations(annotations) {
        return annotations.map(annotation => ({
            ...annotation,
            location: annotation.location ? this._relativeLocation(annotation.location) : undefined,
        }));
    }
    _relativeLocation(location) {
        if (!location)
            return location;
        return {
            ...location,
            file: this._relativePath(location.file),
        };
    }
    _relativePath(absolutePath) {
        if (!absolutePath)
            return absolutePath;
        return path.relative(this._rootDir, absolutePath);
    }
}
//# sourceMappingURL=teleEmitter.js.map