/**
 * 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 { isRegExp, isString } from '@isomorphic/rtti';
import { Artifact } from './artifact';
import { ChannelOwner } from './channelOwner';
import { DisposableStub } from './disposable';
export class Tracing extends ChannelOwner {
    _includeSources = false;
    _additionalSources = new Set();
    _isLive = false;
    _tracesDir;
    _stacksId;
    _isTracing = false;
    _harId;
    _harRecorders = new Map();
    static from(channel) {
        return channel._object;
    }
    constructor(parent, type, guid, initializer) {
        super(parent, type, guid, initializer);
    }
    async start(options = {}) {
        await this._wrapApiCall(async () => {
            this._includeSources = !!options.sources;
            this._isLive = !!options.live;
            await this._channel.tracingStart({
                name: options.name,
                snapshots: options.snapshots,
                screenshots: options.screenshots,
                live: options.live,
            });
            const { traceName } = await this._channel.tracingStartChunk({ name: options.name, title: options.title });
            await this._startCollectingStacks(traceName, this._isLive);
        });
    }
    async startChunk(options = {}) {
        await this._wrapApiCall(async () => {
            const { traceName } = await this._channel.tracingStartChunk(options);
            await this._startCollectingStacks(traceName, this._isLive);
        });
    }
    async group(name, options = {}) {
        if (options.location)
            this._additionalSources.add(options.location.file);
        await this._channel.tracingGroup({ name, location: options.location });
        return new DisposableStub(() => this.groupEnd());
    }
    async groupEnd() {
        await this._channel.tracingGroupEnd();
    }
    async _startCollectingStacks(traceName, live) {
        if (!this._isTracing) {
            this._isTracing = true;
            this._connection.setIsTracing(true);
        }
        const result = await this._connection.localUtils()?.tracingStarted({ tracesDir: this._tracesDir, traceName, live });
        this._stacksId = result?.stacksId;
    }
    async stopChunk(options = {}) {
        await this._wrapApiCall(async () => {
            await this._doStopChunk(options.path);
        });
    }
    async stop(options = {}) {
        await this._wrapApiCall(async () => {
            await this._doStopChunk(options.path);
            await this._channel.tracingStop();
        });
    }
    async startHar(path, options = {}) {
        await this._wrapApiCall(async () => {
            if (this._harId)
                throw new Error('HAR recording has already been started');
            if (options.resourcesDir && path.endsWith('.zip'))
                throw new Error('resourcesDir option is not compatible with a .zip har file');
            const defaultContent = path.endsWith('.zip') ? 'attach' : 'embed';
            this._harId = await this._recordIntoHAR(path, null, {
                url: options.urlFilter,
                updateContent: options.content ?? defaultContent,
                updateMode: options.mode ?? 'full',
                resourcesDir: options.resourcesDir,
            });
        });
        return new DisposableStub(() => this.stopHar());
    }
    async stopHar() {
        await this._wrapApiCall(async () => {
            const harId = this._harId;
            if (!harId)
                throw new Error('HAR recording has not been started');
            this._harId = undefined;
            await this._exportHAR(harId);
        });
    }
    async _recordIntoHAR(har, page, options = {}) {
        const isZip = har.endsWith('.zip');
        const { harId } = await this._channel.harStart({
            page: page?._channel,
            options: {
                content: options.updateContent ?? 'attach',
                urlGlob: isString(options.url) ? options.url : undefined,
                urlRegexSource: isRegExp(options.url) ? options.url.source : undefined,
                urlRegexFlags: isRegExp(options.url) ? options.url.flags : undefined,
                mode: options.updateMode ?? 'minimal',
                harPath: isZip ? undefined : har,
                resourcesDir: options.resourcesDir,
            },
        });
        this._harRecorders.set(harId, { path: har, resourcesDir: options.resourcesDir });
        return harId;
    }
    async _exportHAR(harId) {
        const harParams = this._harRecorders.get(harId);
        if (!harParams)
            return;
        this._harRecorders.delete(harId);
        const isLocal = !this._connection.isRemote();
        const isZip = harParams.path.endsWith('.zip');
        if (isLocal) {
            const { entries } = await this._channel.harExport({ harId, mode: 'entries' });
            if (!isZip) {
                // Server wrote HAR and resources to the user's chosen paths.
                return;
            }
            const localUtils = this._connection.localUtils();
            if (!localUtils)
                throw new Error('Cannot save zipped HAR in thin clients');
            await localUtils.zip({ zipFile: harParams.path, entries: entries, mode: 'write', includeSources: false, additionalSources: [] });
            return;
        }
        const { artifact: artifactChannel } = await this._channel.harExport({ harId, mode: 'archive' });
        const artifact = Artifact.from(artifactChannel);
        if (isZip) {
            await artifact.saveAs(harParams.path);
            await artifact.delete();
            return;
        }
        const localUtils = this._connection.localUtils();
        if (!localUtils)
            throw new Error('Uncompressed har is not supported in thin clients');
        await artifact.saveAs(harParams.path + '.tmp');
        await localUtils.harUnzip({ zipFile: harParams.path + '.tmp', harFile: harParams.path, resourcesDir: harParams.resourcesDir });
        await artifact.delete();
    }
    async _exportAllHars() {
        await this._wrapApiCall(async () => {
            await Promise.all([...this._harRecorders.keys()].map(harId => this._exportHAR(harId)));
        }, { internal: true });
    }
    async _doStopChunk(filePath) {
        this._resetStackCounter();
        const additionalSources = [...this._additionalSources];
        this._additionalSources.clear();
        if (!filePath) {
            // Not interested in artifacts.
            await this._channel.tracingStopChunk({ mode: 'discard' });
            if (this._stacksId)
                await this._connection.localUtils().traceDiscarded({ stacksId: this._stacksId });
            return;
        }
        const localUtils = this._connection.localUtils();
        if (!localUtils)
            throw new Error('Cannot save trace in thin clients');
        const isLocal = !this._connection.isRemote();
        if (isLocal) {
            const result = await this._channel.tracingStopChunk({ mode: 'entries' });
            await localUtils.zip({ zipFile: filePath, entries: result.entries, mode: 'write', stacksId: this._stacksId, includeSources: this._includeSources, additionalSources });
            return;
        }
        const result = await this._channel.tracingStopChunk({ mode: 'archive' });
        // The artifact may be missing if the browser closed while stopping tracing.
        if (!result.artifact) {
            if (this._stacksId)
                await localUtils.traceDiscarded({ stacksId: this._stacksId });
            return;
        }
        // Save trace to the final local file.
        const artifact = Artifact.from(result.artifact);
        await artifact.saveAs(filePath);
        await artifact.delete();
        await localUtils.zip({ zipFile: filePath, entries: [], mode: 'append', stacksId: this._stacksId, includeSources: this._includeSources, additionalSources });
    }
    _resetStackCounter() {
        if (this._isTracing) {
            this._isTracing = false;
            this._connection.setIsTracing(false);
        }
    }
}
//# sourceMappingURL=tracing.js.map