import { __decorate, __param } from '../../../../../../external/tslib/tslib.es6.js';
import { RunOnceScheduler } from '../../../base/common/async.js';
import { VSBuffer } from '../../../base/common/buffer.js';
import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js';
import { Emitter, Event } from '../../../base/common/event.js';
import { createSingleCallFunction } from '../../../base/common/functional.js';
import { hash } from '../../../base/common/hash.js';
import { Disposable, toDisposable, DisposableStore } from '../../../base/common/lifecycle.js';
import { MarshalledId } from '../../../base/common/marshallingIds.js';
import { isDefined } from '../../../base/common/types.js';
import { generateUuid } from '../../../base/common/uuid.js';
import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';
import { ILogService } from '../../../platform/log/common/log.service.js';
import { MainContext } from './extHost.protocol.js';
import { IExtHostCommands } from './extHostCommands.js';
import { IExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors.js';
import { IExtHostRpcService } from './extHostRpcService.js';
import { toItemFromContext, ExtHostTestItemCollection, TestItemImpl, TestItemRootImpl } from './extHostTestItem.js';
import { TestMessage, TestCoverage, TestResults, TestTag, location, TestItem, Range } from './extHostTypeConverters.js';
import { TestRunProfileKind, TestRunRequest, FileCoverage } from './extHostTypes.js';
import { TestCommandId } from '../../contrib/testing/common/constants.js';
import { TestId, TestPosition } from '../../contrib/testing/common/testId.js';
import { InvalidTestItemError } from '../../contrib/testing/common/testItemCollection.js';
import { TestsDiffOp, isStartControllerTests, TestRunProfileBitset, AbstractIncrementalTestCollection, TestResultState } from '../../contrib/testing/common/testTypes.js';
import { checkProposedApiEnabled } from '../../services/extensions/common/extensions.js';
let followupCounter = 0;
const testResultInternalIDs = ( new WeakMap());
const IExtHostTesting = ( createDecorator('IExtHostTesting'));
let ExtHostTesting = class ExtHostTesting extends Disposable {
    constructor(rpc, logService, commands, editors) {
        super();
        this.logService = logService;
        this.commands = commands;
        this.editors = editors;
        this.resultsChangedEmitter = this._register(( new Emitter()));
        this.controllers = ( new Map());
        this.defaultProfilesChangedEmitter = this._register(( new Emitter()));
        this.followupProviders = ( new Set());
        this.testFollowups = ( new Map());
        this.onResultsChanged = this.resultsChangedEmitter.event;
        this.results = [];
        this.proxy = ( rpc.getProxy(MainContext.MainThreadTesting));
        this.observer = ( new TestObservers(this.proxy));
        this.runTracker = ( new TestRunCoordinator(this.proxy, logService));
        commands.registerArgumentProcessor({
            processArgument: arg => {
                switch (arg?.$mid) {
                    case MarshalledId.TestItemContext: {
                        const cast = arg;
                        const targetTest = cast.tests[cast.tests.length - 1].item.extId;
                        const controller = this.controllers.get(TestId.root(targetTest));
                        return controller?.collection.tree.get(targetTest)?.actual ?? toItemFromContext(arg);
                    }
                    case MarshalledId.TestMessageMenuArgs: {
                        const { test, message } = arg;
                        const extId = test.item.extId;
                        return {
                            test: this.controllers.get(TestId.root(extId))?.collection.tree.get(extId)?.actual
                                ?? toItemFromContext({ $mid: MarshalledId.TestItemContext, tests: [test] }),
                            message: TestMessage.to(message),
                        };
                    }
                    default: return arg;
                }
            }
        });
        commands.registerCommand(false, 'testing.getExplorerSelection', async () => {
            const inner = await commands.executeCommand(TestCommandId.GetExplorerSelection);
            const lookup = (i) => {
                const controller = this.controllers.get(TestId.root(i));
                if (!controller) {
                    return undefined;
                }
                return TestId.isRoot(i) ? controller.controller : controller.collection.tree.get(i)?.actual;
            };
            return {
                include: inner?.include.map(lookup).filter(isDefined) || [],
                exclude: inner?.exclude.map(lookup).filter(isDefined) || [],
            };
        });
    }
    createTestController(extension, controllerId, label, refreshHandler) {
        if (( this.controllers.has(controllerId))) {
            throw ( new Error(`Attempt to insert a duplicate controller with ID "${controllerId}"`));
        }
        const disposable = ( new DisposableStore());
        const collection = disposable.add(( new ExtHostTestItemCollection(controllerId, label, this.editors)));
        collection.root.label = label;
        const profiles = ( new Map());
        const activeProfiles = ( new Set());
        const proxy = this.proxy;
        const controller = {
            items: collection.root.children,
            get label() {
                return label;
            },
            set label(value) {
                label = value;
                collection.root.label = value;
                proxy.$updateController(controllerId, { label });
            },
            get refreshHandler() {
                return refreshHandler;
            },
            set refreshHandler(value) {
                refreshHandler = value;
                proxy.$updateController(controllerId, { canRefresh: !!value });
            },
            get id() {
                return controllerId;
            },
            createRunProfile: (label, group, runHandler, isDefault, tag, supportsContinuousRun) => {
                let profileId = hash(label);
                while (( profiles.has(profileId))) {
                    profileId++;
                }
                return ( new TestRunProfileImpl(
                    this.proxy,
                    profiles,
                    activeProfiles,
                    this.defaultProfilesChangedEmitter.event,
                    controllerId,
                    profileId,
                    label,
                    group,
                    runHandler,
                    isDefault,
                    tag,
                    supportsContinuousRun
                ));
            },
            createTestItem(id, label, uri) {
                return ( new TestItemImpl(controllerId, id, label, uri));
            },
            createTestRun: (request, name, persist = true) => {
                return this.runTracker.createTestRun(extension, controllerId, collection, request, name, persist);
            },
            invalidateTestResults: items => {
                if (items === undefined) {
                    this.proxy.$markTestRetired(undefined);
                }
                else {
                    const itemsArr = items instanceof Array ? items : [items];
                    this.proxy.$markTestRetired(( itemsArr.map(i => ( TestId.fromExtHostTestItem(i, controllerId).toString()))));
                }
            },
            set resolveHandler(fn) {
                collection.resolveHandler = fn;
            },
            get resolveHandler() {
                return collection.resolveHandler;
            },
            dispose: () => {
                disposable.dispose();
            },
        };
        proxy.$registerTestController(controllerId, label, !!refreshHandler);
        disposable.add(toDisposable(() => proxy.$unregisterTestController(controllerId)));
        const info = { controller, collection, profiles, extension, activeProfiles };
        this.controllers.set(controllerId, info);
        disposable.add(toDisposable(() => this.controllers.delete(controllerId)));
        disposable.add(collection.onDidGenerateDiff(diff => proxy.$publishDiff(controllerId, ( diff.map(TestsDiffOp.serialize)))));
        return controller;
    }
    createTestObserver() {
        return this.observer.checkout();
    }
    async runTests(req, token = CancellationToken.None) {
        const profile = tryGetProfileFromTestRunReq(req);
        if (!profile) {
            throw ( new Error('The request passed to `vscode.test.runTests` must include a profile'));
        }
        const controller = this.controllers.get(profile.controllerId);
        if (!controller) {
            throw ( new Error('Controller not found'));
        }
        await this.proxy.$runTests({
            preserveFocus: req.preserveFocus ?? true,
            group: profileGroupToBitset[profile.kind],
            targets: [{
                    testIds: req.include?.map(t => ( TestId.fromExtHostTestItem(t, controller.collection.root.id).toString())) ?? [controller.collection.root.id],
                    profileId: profile.profileId,
                    controllerId: profile.controllerId,
                }],
            exclude: req.exclude?.map(t => t.id),
        }, token);
    }
    registerTestFollowupProvider(provider) {
        this.followupProviders.add(provider);
        return { dispose: () => { this.followupProviders.delete(provider); } };
    }
    $syncTests() {
        for (const { collection } of ( this.controllers.values())) {
            collection.flushDiff();
        }
        return Promise.resolve();
    }
    async $getCoverageDetails(coverageId, testId, token) {
        const details = await this.runTracker.getCoverageDetails(coverageId, testId, token);
        return details?.map(TestCoverage.fromDetails);
    }
    async $disposeRun(runId) {
        this.runTracker.disposeTestRun(runId);
    }
    $configureRunProfile(controllerId, profileId) {
        this.controllers.get(controllerId)?.profiles.get(profileId)?.configureHandler?.();
    }
    $setDefaultRunProfiles(profiles) {
        const evt = ( new Map());
        for (const [controllerId, profileIds] of Object.entries(profiles)) {
            const ctrl = this.controllers.get(controllerId);
            if (!ctrl) {
                continue;
            }
            const changes = ( new Map());
            const added = profileIds.filter(id => !( ctrl.activeProfiles.has(id)));
            const removed = [...ctrl.activeProfiles].filter(id => !profileIds.includes(id));
            for (const id of added) {
                changes.set(id, true);
                ctrl.activeProfiles.add(id);
            }
            for (const id of removed) {
                changes.set(id, false);
                ctrl.activeProfiles.delete(id);
            }
            if (changes.size) {
                evt.set(controllerId, changes);
            }
        }
        this.defaultProfilesChangedEmitter.fire(evt);
    }
    async $refreshTests(controllerId, token) {
        await this.controllers.get(controllerId)?.controller.refreshHandler?.(token);
    }
    $publishTestResults(results) {
        this.results = ( Object.freeze(( results
            .map(r => {
            const o = TestResults.to(r);
            testResultInternalIDs.set(o, r.id);
            return o;
        }))
            .concat(this.results)
            .sort((a, b) => b.completedAt - a.completedAt)
            .slice(0, 32)));
        this.resultsChangedEmitter.fire();
    }
    async $expandTest(testId, levels) {
        const collection = this.controllers.get(TestId.fromString(testId).controllerId)?.collection;
        if (collection) {
            await collection.expand(testId, levels < 0 ? Infinity : levels);
            collection.flushDiff();
        }
    }
    $acceptDiff(diff) {
        this.observer.applyDiff(( diff.map(d => TestsDiffOp.deserialize({ asCanonicalUri: u => u }, d))));
    }
    async $runControllerTests(reqs, token) {
        return Promise.all(( reqs.map(req => this.runControllerTestRequest(req, false, token))));
    }
    async $startContinuousRun(reqs, token) {
        const cts = ( new CancellationTokenSource(token));
        const res = await Promise.all(( reqs.map(req => this.runControllerTestRequest(req, true, cts.token))));
        if (!token.isCancellationRequested && !( res.some(r => r.error))) {
            await ( new Promise(r => token.onCancellationRequested(r)));
        }
        cts.dispose(true);
        return res;
    }
    async $provideTestFollowups(req, token) {
        const results = this.results.find(r => testResultInternalIDs.get(r) === req.resultId);
        const test = results && findTestInResultSnapshot(TestId.fromString(req.extId), results?.results);
        if (!test) {
            return [];
        }
        let followups = [];
        await Promise.all(( [...this.followupProviders].map(async (provider) => {
            try {
                const r = await provider.provideFollowup(results, test, req.taskIndex, req.messageIndex, token);
                if (r) {
                    followups = followups.concat(r);
                }
            }
            catch (e) {
                this.logService.error(`Error thrown while providing followup for test message`, e);
            }
        })));
        if (token.isCancellationRequested) {
            return [];
        }
        return ( followups.map(command => {
            const id = followupCounter++;
            this.testFollowups.set(id, command);
            return { title: command.title, id };
        }));
    }
    $disposeTestFollowups(id) {
        for (const i of id) {
            this.testFollowups.delete(i);
        }
    }
    $executeTestFollowup(id) {
        const command = this.testFollowups.get(id);
        if (!command) {
            return Promise.resolve();
        }
        return this.commands.executeCommand(command.command, ...(command.arguments || []));
    }
    $cancelExtensionTestRun(runId) {
        if (runId === undefined) {
            this.runTracker.cancelAllRuns();
        }
        else {
            this.runTracker.cancelRunById(runId);
        }
    }
    getMetadataForRun(run) {
        for (const tracker of this.runTracker.trackers) {
            const taskId = tracker.getTaskIdForRun(run);
            if (taskId) {
                return { taskId, runId: tracker.id };
            }
        }
        return undefined;
    }
    async runControllerTestRequest(req, isContinuous, token) {
        const lookup = this.controllers.get(req.controllerId);
        if (!lookup) {
            return {};
        }
        const { collection, profiles, extension } = lookup;
        const profile = profiles.get(req.profileId);
        if (!profile) {
            return {};
        }
        const includeTests = ( req.testIds
            .map((testId) => collection.tree.get(testId)))
            .filter(isDefined);
        const excludeTests = ( req.excludeExtIds
            .map(id => lookup.collection.tree.get(id)))
            .filter(isDefined)
            .filter(exclude => ( includeTests.some(include => include.fullId.compare(exclude.fullId) === TestPosition.IsChild)));
        if (!includeTests.length) {
            return {};
        }
        const publicReq = ( new TestRunRequest(( includeTests.some(i => i.actual instanceof TestItemRootImpl)) ? undefined : ( includeTests.map(t => t.actual)), ( excludeTests.map(t => t.actual)), profile, isContinuous));
        const tracker = isStartControllerTests(req) && this.runTracker.prepareForMainThreadTestRun(extension, publicReq, TestRunDto.fromInternal(req, lookup.collection), profile, token);
        try {
            await profile.runHandler(publicReq, token);
            return {};
        }
        catch (e) {
            return { error: String(e) };
        }
        finally {
            if (tracker) {
                if (tracker.hasRunningTasks && !token.isCancellationRequested) {
                    await Event.toPromise(tracker.onEnd);
                }
            }
        }
    }
};
ExtHostTesting = ( __decorate([
    ( __param(0, IExtHostRpcService)),
    ( __param(1, ILogService)),
    ( __param(2, IExtHostCommands)),
    ( __param(3, IExtHostDocumentsAndEditors))
], ExtHostTesting));
const RUN_CANCEL_DEADLINE = 10_000;
var TestRunTrackerState;
( (function(TestRunTrackerState) {
    TestRunTrackerState[TestRunTrackerState["Running"] = 0] = "Running";
    TestRunTrackerState[TestRunTrackerState["Cancelling"] = 1] = "Cancelling";
    TestRunTrackerState[TestRunTrackerState["Ended"] = 2] = "Ended";
})(TestRunTrackerState || (TestRunTrackerState = {})));
class TestRunTracker extends Disposable {
    get hasRunningTasks() {
        return this.running > 0;
    }
    get id() {
        return this.dto.id;
    }
    constructor(dto, proxy, logService, profile, extension, parentToken) {
        super();
        this.dto = dto;
        this.proxy = proxy;
        this.logService = logService;
        this.profile = profile;
        this.extension = extension;
        this.state = TestRunTrackerState.Running;
        this.running = 0;
        this.tasks = ( new Map());
        this.sharedTestIds = ( new Set());
        this.endEmitter = this._register(( new Emitter()));
        this.publishedCoverage = ( new Map());
        this.onEnd = this.endEmitter.event;
        this.cts = this._register(( new CancellationTokenSource(parentToken)));
        const forciblyEnd = this._register(( new RunOnceScheduler(() => this.forciblyEndTasks(), RUN_CANCEL_DEADLINE)));
        this._register(this.cts.token.onCancellationRequested(() => forciblyEnd.schedule()));
        const didDisposeEmitter = ( new Emitter());
        this.onDidDispose = didDisposeEmitter.event;
        this._register(toDisposable(() => {
            didDisposeEmitter.fire();
            didDisposeEmitter.dispose();
        }));
    }
    getTaskIdForRun(run) {
        for (const [taskId, { run: r }] of this.tasks) {
            if (r === run) {
                return taskId;
            }
        }
        return undefined;
    }
    cancel() {
        if (this.state === TestRunTrackerState.Running) {
            this.cts.cancel();
            this.state = TestRunTrackerState.Cancelling;
        }
        else if (this.state === TestRunTrackerState.Cancelling) {
            this.forciblyEndTasks();
        }
    }
    async getCoverageDetails(id, testId, token) {
        const [, taskId] = TestId.fromString(id).path;
        const coverage = this.publishedCoverage.get(id);
        if (!coverage) {
            return [];
        }
        const { report, extIds } = coverage;
        const task = this.tasks.get(taskId);
        if (!task) {
            throw ( new Error('unreachable: run task was not found'));
        }
        let testItem;
        if (testId && report instanceof FileCoverage) {
            const index = extIds.indexOf(testId);
            if (index === -1) {
                return [];
            }
            testItem = report.fromTests[index];
        }
        const details = testItem
            ? this.profile?.loadDetailedCoverageForTest?.(task.run, report, testItem, token)
            : this.profile?.loadDetailedCoverage?.(task.run, report, token);
        return (await details) ?? [];
    }
    createRun(name) {
        const runId = this.dto.id;
        const ctrlId = this.dto.controllerId;
        const taskId = generateUuid();
        const guardTestMutation = (fn) => (test, ...args) => {
            if (ended) {
                this.logService.warn(`Setting the state of test "${test.id}" is a no-op after the run ends.`);
                return;
            }
            this.ensureTestIsKnown(test);
            fn(test, ...args);
        };
        const appendMessages = (test, messages) => {
            const converted = messages instanceof Array
                ? ( messages.map(TestMessage.from))
                : [TestMessage.from(messages)];
            if (test.uri && test.range) {
                const defaultLocation = { range: Range.from(test.range), uri: test.uri };
                for (const message of converted) {
                    message.location = message.location || defaultLocation;
                }
            }
            this.proxy.$appendTestMessagesInRun(runId, taskId, ( TestId.fromExtHostTestItem(test, ctrlId).toString()), converted);
        };
        let ended = false;
        const run = {
            isPersisted: this.dto.isPersisted,
            token: this.cts.token,
            name,
            onDidDispose: this.onDidDispose,
            addCoverage: (coverage) => {
                if (ended) {
                    return;
                }
                const fromTests = coverage instanceof FileCoverage ? coverage.fromTests : [];
                if (fromTests.length) {
                    checkProposedApiEnabled(this.extension, 'attributableCoverage');
                    for (const test of fromTests) {
                        this.ensureTestIsKnown(test);
                    }
                }
                const uriStr = ( coverage.uri.toString());
                const id = ( ( new TestId([runId, taskId, uriStr])).toString());
                this.publishedCoverage.set(id, { report: coverage, extIds: ( fromTests.map(t => ( TestId.fromExtHostTestItem(t, ctrlId).toString()))) });
                this.proxy.$appendCoverage(runId, taskId, TestCoverage.fromFile(ctrlId, id, coverage));
            },
            enqueued: guardTestMutation(test => {
                this.proxy.$updateTestStateInRun(runId, taskId, ( TestId.fromExtHostTestItem(test, ctrlId).toString()), TestResultState.Queued);
            }),
            skipped: guardTestMutation(test => {
                this.proxy.$updateTestStateInRun(runId, taskId, ( TestId.fromExtHostTestItem(test, ctrlId).toString()), TestResultState.Skipped);
            }),
            started: guardTestMutation(test => {
                this.proxy.$updateTestStateInRun(runId, taskId, ( TestId.fromExtHostTestItem(test, ctrlId).toString()), TestResultState.Running);
            }),
            errored: guardTestMutation((test, messages, duration) => {
                appendMessages(test, messages);
                this.proxy.$updateTestStateInRun(runId, taskId, ( TestId.fromExtHostTestItem(test, ctrlId).toString()), TestResultState.Errored, duration);
            }),
            failed: guardTestMutation((test, messages, duration) => {
                appendMessages(test, messages);
                this.proxy.$updateTestStateInRun(runId, taskId, ( TestId.fromExtHostTestItem(test, ctrlId).toString()), TestResultState.Failed, duration);
            }),
            passed: guardTestMutation((test, duration) => {
                this.proxy.$updateTestStateInRun(runId, taskId, ( TestId.fromExtHostTestItem(test, this.dto.controllerId).toString()), TestResultState.Passed, duration);
            }),
            appendOutput: (output, location$1, test) => {
                if (ended) {
                    return;
                }
                if (test) {
                    this.ensureTestIsKnown(test);
                }
                this.proxy.$appendOutputToRun(runId, taskId, VSBuffer.fromString(output), location$1 && location.from(location$1), test && ( TestId.fromExtHostTestItem(test, ctrlId).toString()));
            },
            end: () => {
                if (ended) {
                    return;
                }
                ended = true;
                this.proxy.$finishedTestRunTask(runId, taskId);
                if (!--this.running) {
                    this.markEnded();
                }
            }
        };
        this.running++;
        this.tasks.set(taskId, { run });
        this.proxy.$startedTestRunTask(runId, { id: taskId, name, running: true });
        return run;
    }
    forciblyEndTasks() {
        for (const { run } of ( this.tasks.values())) {
            run.end();
        }
    }
    markEnded() {
        if (this.state !== TestRunTrackerState.Ended) {
            this.state = TestRunTrackerState.Ended;
            this.endEmitter.fire();
        }
    }
    ensureTestIsKnown(test) {
        if (!(test instanceof TestItemImpl)) {
            throw ( new InvalidTestItemError(test.id));
        }
        if (( this.sharedTestIds.has(( TestId.fromExtHostTestItem(test, this.dto.controllerId).toString())))) {
            return;
        }
        const chain = [];
        const root = this.dto.colllection.root;
        while (true) {
            const converted = TestItem.from(test);
            chain.unshift(converted);
            if (( this.sharedTestIds.has(converted.extId))) {
                break;
            }
            this.sharedTestIds.add(converted.extId);
            if (test === root) {
                break;
            }
            test = test.parent || root;
        }
        this.proxy.$addTestsToRun(this.dto.controllerId, this.dto.id, chain);
    }
    dispose() {
        this.markEnded();
        super.dispose();
    }
}
class TestRunCoordinator {
    get trackers() {
        return ( this.tracked.values());
    }
    constructor(proxy, logService) {
        this.proxy = proxy;
        this.logService = logService;
        this.tracked = ( new Map());
        this.trackedById = ( new Map());
    }
    getCoverageDetails(id, testId, token) {
        const runId = TestId.root(id);
        return this.trackedById.get(runId)?.getCoverageDetails(id, testId, token) || [];
    }
    disposeTestRun(runId) {
        this.trackedById.get(runId)?.dispose();
        this.trackedById.delete(runId);
        for (const [req, { id }] of this.tracked) {
            if (id === runId) {
                this.tracked.delete(req);
            }
        }
    }
    prepareForMainThreadTestRun(extension, req, dto, profile, token) {
        return this.getTracker(req, dto, profile, extension, token);
    }
    cancelRunById(runId) {
        this.trackedById.get(runId)?.cancel();
    }
    cancelAllRuns() {
        for (const tracker of ( this.tracked.values())) {
            tracker.cancel();
        }
    }
    createTestRun(extension, controllerId, collection, request, name, persist) {
        const existing = this.tracked.get(request);
        if (existing) {
            return existing.createRun(name);
        }
        const dto = TestRunDto.fromPublic(controllerId, collection, request, persist);
        const profile = tryGetProfileFromTestRunReq(request);
        this.proxy.$startedExtensionTestRun({
            controllerId,
            continuous: !!request.continuous,
            profile: profile && { group: profileGroupToBitset[profile.kind], id: profile.profileId },
            exclude: request.exclude?.map(t => ( TestId.fromExtHostTestItem(t, collection.root.id).toString())) ?? [],
            id: dto.id,
            include: request.include?.map(t => ( TestId.fromExtHostTestItem(t, collection.root.id).toString())) ?? [collection.root.id],
            preserveFocus: request.preserveFocus ?? true,
            persist
        });
        const tracker = this.getTracker(request, dto, request.profile, extension);
        Event.once(tracker.onEnd)(() => {
            this.proxy.$finishedExtensionTestRun(dto.id);
        });
        return tracker.createRun(name);
    }
    getTracker(req, dto, profile, extension, token) {
        const tracker = ( new TestRunTracker(dto, this.proxy, this.logService, profile, extension, token));
        this.tracked.set(req, tracker);
        this.trackedById.set(tracker.id, tracker);
        return tracker;
    }
}
const tryGetProfileFromTestRunReq = (request) => {
    if (!request.profile) {
        return undefined;
    }
    if (!(request.profile instanceof TestRunProfileImpl)) {
        throw ( new Error(
            `TestRunRequest.profile is not an instance created from TestController.createRunProfile`
        ));
    }
    return request.profile;
};
class TestRunDto {
    static fromPublic(controllerId, collection, request, persist) {
        return ( new TestRunDto(controllerId, generateUuid(), persist, collection));
    }
    static fromInternal(request, collection) {
        return ( new TestRunDto(request.controllerId, request.runId, true, collection));
    }
    constructor(controllerId, id, isPersisted, colllection) {
        this.controllerId = controllerId;
        this.id = id;
        this.isPersisted = isPersisted;
        this.colllection = colllection;
    }
}
class MirroredChangeCollector {
    get isEmpty() {
        return this.added.size === 0 && this.removed.size === 0 && this.updated.size === 0;
    }
    constructor(emitter) {
        this.emitter = emitter;
        this.added = ( new Set());
        this.updated = ( new Set());
        this.removed = ( new Set());
        this.alreadyRemoved = ( new Set());
    }
    add(node) {
        this.added.add(node);
    }
    update(node) {
        Object.assign(node.revived, TestItem.toPlain(node.item));
        if (!( this.added.has(node))) {
            this.updated.add(node);
        }
    }
    remove(node) {
        if (( this.added.has(node))) {
            this.added.delete(node);
            return;
        }
        this.updated.delete(node);
        const parentId = TestId.parentId(node.item.extId);
        if (parentId && ( this.alreadyRemoved.has(( parentId.toString())))) {
            this.alreadyRemoved.add(node.item.extId);
            return;
        }
        this.removed.add(node);
    }
    getChangeEvent() {
        const { added, updated, removed } = this;
        return {
            get added() { return ( [...added].map(n => n.revived)); },
            get updated() { return ( [...updated].map(n => n.revived)); },
            get removed() { return ( [...removed].map(n => n.revived)); },
        };
    }
    complete() {
        if (!this.isEmpty) {
            this.emitter.fire(this.getChangeEvent());
        }
    }
}
class MirroredTestCollection extends AbstractIncrementalTestCollection {
    constructor() {
        super(...arguments);
        this.changeEmitter = ( new Emitter());
        this.onDidChangeTests = this.changeEmitter.event;
    }
    get rootTests() {
        return this.roots;
    }
    getMirroredTestDataById(itemId) {
        return this.items.get(itemId);
    }
    getMirroredTestDataByReference(item) {
        return this.items.get(item.id);
    }
    createItem(item, parent) {
        return {
            ...item,
            revived: TestItem.toPlain(item.item),
            depth: parent ? parent.depth + 1 : 0,
            children: ( new Set()),
        };
    }
    createChangeCollector() {
        return ( new MirroredChangeCollector(this.changeEmitter));
    }
}
class TestObservers {
    constructor(proxy) {
        this.proxy = proxy;
    }
    checkout() {
        if (!this.current) {
            this.current = this.createObserverData();
        }
        const current = this.current;
        current.observers++;
        return {
            onDidChangeTest: current.tests.onDidChangeTests,
            get tests() { return ( [...current.tests.rootTests].map(t => t.revived)); },
            dispose: createSingleCallFunction(() => {
                if (--current.observers === 0) {
                    this.proxy.$unsubscribeFromDiffs();
                    this.current = undefined;
                }
            }),
        };
    }
    getMirroredTestDataByReference(ref) {
        return this.current?.tests.getMirroredTestDataByReference(ref);
    }
    applyDiff(diff) {
        this.current?.tests.apply(diff);
    }
    createObserverData() {
        const tests = ( new MirroredTestCollection({ asCanonicalUri: u => u }));
        this.proxy.$subscribeToDiffs();
        return { observers: 0, tests, };
    }
}
const updateProfile = (impl, proxy, initial, update) => {
    if (initial) {
        Object.assign(initial, update);
    }
    else {
        proxy.$updateTestRunConfig(impl.controllerId, impl.profileId, update);
    }
};
class TestRunProfileImpl {
    #proxy;
    #activeProfiles;
    #onDidChangeDefaultProfiles;
    #initialPublish;
    #profiles;
    get label() {
        return this._label;
    }
    set label(label) {
        if (label !== this._label) {
            this._label = label;
            updateProfile(this, this.#proxy, this.#initialPublish, { label });
        }
    }
    get supportsContinuousRun() {
        return this._supportsContinuousRun;
    }
    set supportsContinuousRun(supports) {
        if (supports !== this._supportsContinuousRun) {
            this._supportsContinuousRun = supports;
            updateProfile(this, this.#proxy, this.#initialPublish, { supportsContinuousRun: supports });
        }
    }
    get isDefault() {
        return ( this.#activeProfiles.has(this.profileId));
    }
    set isDefault(isDefault) {
        if (isDefault !== this.isDefault) {
            if (isDefault) {
                this.#activeProfiles.add(this.profileId);
            }
            else {
                this.#activeProfiles.delete(this.profileId);
            }
            updateProfile(this, this.#proxy, this.#initialPublish, { isDefault });
        }
    }
    get tag() {
        return this._tag;
    }
    set tag(tag) {
        if (tag?.id !== this._tag?.id) {
            this._tag = tag;
            updateProfile(this, this.#proxy, this.#initialPublish, {
                tag: tag ? TestTag.namespace(this.controllerId, tag.id) : null,
            });
        }
    }
    get configureHandler() {
        return this._configureHandler;
    }
    set configureHandler(handler) {
        if (handler !== this._configureHandler) {
            this._configureHandler = handler;
            updateProfile(this, this.#proxy, this.#initialPublish, { hasConfigurationHandler: !!handler });
        }
    }
    get onDidChangeDefault() {
        return Event.chain(this.#onDidChangeDefaultProfiles, $ => ( $
            .map(ev => ev.get(this.controllerId)?.get(this.profileId)))
            .filter(isDefined));
    }
    constructor(proxy, profiles, activeProfiles, onDidChangeActiveProfiles, controllerId, profileId, _label, kind, runHandler, _isDefault = false, _tag = undefined, _supportsContinuousRun = false) {
        this.controllerId = controllerId;
        this.profileId = profileId;
        this._label = _label;
        this.kind = kind;
        this.runHandler = runHandler;
        this._tag = _tag;
        this._supportsContinuousRun = _supportsContinuousRun;
        this.#proxy = proxy;
        this.#profiles = profiles;
        this.#activeProfiles = activeProfiles;
        this.#onDidChangeDefaultProfiles = onDidChangeActiveProfiles;
        profiles.set(profileId, this);
        const groupBitset = profileGroupToBitset[kind];
        if (typeof groupBitset !== 'number') {
            throw ( new Error(`Unknown TestRunProfile.group ${kind}`));
        }
        if (_isDefault) {
            activeProfiles.add(profileId);
        }
        this.#initialPublish = {
            profileId: profileId,
            controllerId,
            tag: _tag ? TestTag.namespace(this.controllerId, _tag.id) : null,
            label: _label,
            group: groupBitset,
            isDefault: _isDefault,
            hasConfigurationHandler: false,
            supportsContinuousRun: _supportsContinuousRun,
        };
        queueMicrotask(() => {
            if (this.#initialPublish) {
                this.#proxy.$publishTestRunProfile(this.#initialPublish);
                this.#initialPublish = undefined;
            }
        });
    }
    dispose() {
        if (this.#profiles?.delete(this.profileId)) {
            this.#profiles = undefined;
            this.#proxy.$removeTestProfile(this.controllerId, this.profileId);
        }
        this.#initialPublish = undefined;
    }
}
const profileGroupToBitset = {
    [TestRunProfileKind.Coverage]: TestRunProfileBitset.Coverage,
    [TestRunProfileKind.Debug]: TestRunProfileBitset.Debug,
    [TestRunProfileKind.Run]: TestRunProfileBitset.Run,
};
function findTestInResultSnapshot(extId, snapshot) {
    for (let i = 0; i < extId.path.length; i++) {
        const item = snapshot.find(s => s.id === extId.path[i]);
        if (!item) {
            return undefined;
        }
        if (i === extId.path.length - 1) {
            return item;
        }
        snapshot = item.children;
    }
    return undefined;
}
export { ExtHostTesting, IExtHostTesting, TestRunCoordinator, TestRunDto, TestRunProfileImpl };
