import { CancellationToken } from '../../../../base/common/cancellation.js';
import { ResourceMap } from '../../../../base/common/map.js';
import { deepClone } from '../../../../base/common/objects.js';
import '../../../../base/common/observableInternal/derived.js';
import '../../../../base/common/observableInternal/autorun.js';
import { observableSignal } from '../../../../base/common/observableInternal/utils.js';
import { WellDefinedPrefixTree } from '../../../../base/common/prefixTree.js';
import { URI } from '../../../../base/common/uri.js';
let incId = 0;
class TestCoverage {
    constructor(fromTaskId, uriIdentityService, accessor) {
        this.fromTaskId = fromTaskId;
        this.uriIdentityService = uriIdentityService;
        this.accessor = accessor;
        this.fileCoverage = ( new ResourceMap());
        this.didAddCoverage = observableSignal(this);
        this.tree = ( new WellDefinedPrefixTree());
        this.associatedData = ( new Map());
    }
    append(rawCoverage, tx) {
        const coverage = ( new FileCoverage(rawCoverage, this.accessor));
        const previous = this.getComputedForUri(coverage.uri);
        const applyDelta = (kind, node) => {
            if (!node[kind]) {
                if (coverage[kind]) {
                    node[kind] = { ...coverage[kind] };
                }
            }
            else {
                node[kind].covered += (coverage[kind]?.covered || 0) - (previous?.[kind]?.covered || 0);
                node[kind].total += (coverage[kind]?.total || 0) - (previous?.[kind]?.total || 0);
            }
        };
        const canonical = [...this.treePathForUri(coverage.uri,  true)];
        const chain = [];
        this.tree.insert(this.treePathForUri(coverage.uri,  false), coverage, node => {
            chain.push(node);
            if (chain.length === canonical.length) {
                node.value = coverage;
            }
            else if (!node.value) {
                const intermediate = deepClone(rawCoverage);
                intermediate.id = String(incId++);
                intermediate.uri = this.treePathToUri(canonical.slice(0, chain.length));
                node.value = ( new ComputedFileCoverage(intermediate));
            }
            else {
                applyDelta('statement', node.value);
                applyDelta('branch', node.value);
                applyDelta('declaration', node.value);
                node.value.didChange.trigger(tx);
            }
        });
        this.fileCoverage.set(coverage.uri, coverage);
        if (chain) {
            this.didAddCoverage.trigger(tx, chain);
        }
    }
    getAllFiles() {
        return this.fileCoverage;
    }
    getUri(uri) {
        return this.fileCoverage.get(uri);
    }
    getComputedForUri(uri) {
        return this.tree.find(this.treePathForUri(uri,  false));
    }
    *treePathForUri(uri, canconicalPath) {
        yield uri.scheme;
        yield uri.authority;
        const path = !canconicalPath && this.uriIdentityService.extUri.ignorePathCasing(uri) ? uri.path.toLowerCase() : uri.path;
        yield* path.split('/');
    }
    treePathToUri(path) {
        return ( URI.from({ scheme: path[0], authority: path[1], path: path.slice(2).join('/') }));
    }
}
const getTotalCoveragePercent = (statement, branch, function_) => {
    let numerator = statement.covered;
    let denominator = statement.total;
    if (branch) {
        numerator += branch.covered;
        denominator += branch.total;
    }
    if (function_) {
        numerator += function_.covered;
        denominator += function_.total;
    }
    return denominator === 0 ? 1 : numerator / denominator;
};
class AbstractFileCoverage {
    get tpc() {
        return getTotalCoveragePercent(this.statement, this.branch, this.declaration);
    }
    constructor(coverage) {
        this.didChange = observableSignal(this);
        this.id = coverage.id;
        this.uri = coverage.uri;
        this.statement = coverage.statement;
        this.branch = coverage.branch;
        this.declaration = coverage.declaration;
    }
}
class ComputedFileCoverage extends AbstractFileCoverage {
}
class FileCoverage extends AbstractFileCoverage {
    get hasSynchronousDetails() {
        return this._details instanceof Array || this.resolved;
    }
    constructor(coverage, accessor) {
        super(coverage);
        this.accessor = accessor;
    }
    async details(token = CancellationToken.None) {
        this._details ??= this.accessor.getCoverageDetails(this.id, token);
        try {
            const d = await this._details;
            this.resolved = true;
            return d;
        }
        catch (e) {
            this._details = undefined;
            throw e;
        }
    }
}
export { AbstractFileCoverage, ComputedFileCoverage, FileCoverage, TestCoverage, getTotalCoveragePercent };
