
import { isFalsyOrEmpty, isNonEmptyArray } from '../../../base/common/arrays.js';
import { MicrotaskEmitter } from '../../../base/common/event.js';
import { Iterable } from '../../../base/common/iterator.js';
import { toDisposable } from '../../../base/common/lifecycle.js';
import { ResourceMap, ResourceSet } from '../../../base/common/map.js';
import { Schemas } from '../../../base/common/network.js';
import { URI } from '../../../base/common/uri.js';
import { localize } from '../../../nls.js';
import { MarkerSeverity } from './markers.js';

const unsupportedSchemas = ( new Set([
    Schemas.inMemory,
    Schemas.vscodeSourceControl,
    Schemas.walkThrough,
    Schemas.walkThroughSnippet,
    Schemas.vscodeChatCodeBlock,
    Schemas.vscodeTerminal
]));
class DoubleResourceMap {
    constructor() {
        this._byResource = ( new ResourceMap());
        this._byOwner = ( new Map());
    }
    set(resource, owner, value) {
        let ownerMap = this._byResource.get(resource);
        if (!ownerMap) {
            ownerMap = ( new Map());
            this._byResource.set(resource, ownerMap);
        }
        ownerMap.set(owner, value);
        let resourceMap = this._byOwner.get(owner);
        if (!resourceMap) {
            resourceMap = ( new ResourceMap());
            this._byOwner.set(owner, resourceMap);
        }
        resourceMap.set(resource, value);
    }
    get(resource, owner) {
        const ownerMap = this._byResource.get(resource);
        return ownerMap?.get(owner);
    }
    delete(resource, owner) {
        let removedA = false;
        let removedB = false;
        const ownerMap = this._byResource.get(resource);
        if (ownerMap) {
            removedA = ownerMap.delete(owner);
        }
        const resourceMap = this._byOwner.get(owner);
        if (resourceMap) {
            removedB = resourceMap.delete(resource);
        }
        if (removedA !== removedB) {
            throw ( new Error("illegal state"));
        }
        return removedA && removedB;
    }
    values(key) {
        if (typeof key === "string") {
            return this._byOwner.get(key)?.values() ?? Iterable.empty();
        }
        if (URI.isUri(key)) {
            return this._byResource.get(key)?.values() ?? Iterable.empty();
        }
        return ( Iterable.map(Iterable.concat(...( this._byOwner.values())), map => map[1]));
    }
}
class MarkerStats {
    constructor(service) {
        this.errors = 0;
        this.infos = 0;
        this.warnings = 0;
        this.unknowns = 0;
        this._data = ( new ResourceMap());
        this._service = service;
        this._subscription = service.onMarkerChanged(this._update, this);
    }
    dispose() {
        this._subscription.dispose();
    }
    _update(resources) {
        for (const resource of resources) {
            const oldStats = this._data.get(resource);
            if (oldStats) {
                this._substract(oldStats);
            }
            const newStats = this._resourceStats(resource);
            this._add(newStats);
            this._data.set(resource, newStats);
        }
    }
    _resourceStats(resource) {
        const result = {
            errors: 0,
            warnings: 0,
            infos: 0,
            unknowns: 0
        };
        if (( unsupportedSchemas.has(resource.scheme))) {
            return result;
        }
        for (const {
            severity
        } of this._service.read({
            resource
        })) {
            if (severity === MarkerSeverity.Error) {
                result.errors += 1;
            } else if (severity === MarkerSeverity.Warning) {
                result.warnings += 1;
            } else if (severity === MarkerSeverity.Info) {
                result.infos += 1;
            } else {
                result.unknowns += 1;
            }
        }
        return result;
    }
    _substract(op) {
        this.errors -= op.errors;
        this.warnings -= op.warnings;
        this.infos -= op.infos;
        this.unknowns -= op.unknowns;
    }
    _add(op) {
        this.errors += op.errors;
        this.warnings += op.warnings;
        this.infos += op.infos;
        this.unknowns += op.unknowns;
    }
}
class MarkerService {
    constructor() {
        this._onMarkerChanged = ( new MicrotaskEmitter({
            merge: MarkerService._merge
        }));
        this.onMarkerChanged = this._onMarkerChanged.event;
        this._data = ( new DoubleResourceMap());
        this._stats = ( new MarkerStats(this));
        this._filteredResources = ( new ResourceMap());
    }
    dispose() {
        this._stats.dispose();
        this._onMarkerChanged.dispose();
    }
    getStatistics() {
        return this._stats;
    }
    remove(owner, resources) {
        for (const resource of resources || []) {
            this.changeOne(owner, resource, []);
        }
    }
    changeOne(owner, resource, markerData) {
        if (isFalsyOrEmpty(markerData)) {
            const removed = this._data.delete(resource, owner);
            if (removed) {
                this._onMarkerChanged.fire([resource]);
            }
        } else {
            const markers = [];
            for (const data of markerData) {
                const marker = MarkerService._toMarker(owner, resource, data);
                if (marker) {
                    markers.push(marker);
                }
            }
            this._data.set(resource, owner, markers);
            this._onMarkerChanged.fire([resource]);
        }
    }
    installResourceFilter(resource, reason) {
        let reasons = this._filteredResources.get(resource);
        if (!reasons) {
            reasons = [];
            this._filteredResources.set(resource, reasons);
        }
        reasons.push(reason);
        this._onMarkerChanged.fire([resource]);
        return toDisposable(() => {
            const reasons = this._filteredResources.get(resource);
            if (!reasons) {
                return;
            }
            const reasonIndex = reasons.indexOf(reason);
            if (reasonIndex !== -1) {
                reasons.splice(reasonIndex, 1);
                if (reasons.length === 0) {
                    this._filteredResources.delete(resource);
                }
                this._onMarkerChanged.fire([resource]);
            }
        });
    }
    static _toMarker(owner, resource, data) {
        let {
            code,
            severity,
            message,
            source,
            startLineNumber,
            startColumn,
            endLineNumber,
            endColumn,
            relatedInformation,
            modelVersionId,
            tags,
            origin
        } = data;
        if (!message) {
            return undefined;
        }
        startLineNumber = startLineNumber > 0 ? startLineNumber : 1;
        startColumn = startColumn > 0 ? startColumn : 1;
        endLineNumber = endLineNumber >= startLineNumber ? endLineNumber : startLineNumber;
        endColumn = endColumn > 0 ? endColumn : startColumn;
        return {
            resource,
            owner,
            code,
            severity,
            message,
            source,
            startLineNumber,
            startColumn,
            endLineNumber,
            endColumn,
            relatedInformation,
            modelVersionId,
            tags,
            origin
        };
    }
    changeAll(owner, data) {
        const changes = [];
        const existing = ( this._data.values(owner));
        if (existing) {
            for (const data of existing) {
                const first = Iterable.first(data);
                if (first) {
                    changes.push(first.resource);
                    this._data.delete(first.resource, owner);
                }
            }
        }
        if (isNonEmptyArray(data)) {
            const groups = ( new ResourceMap());
            for (const {
                resource,
                marker: markerData
            } of data) {
                const marker = MarkerService._toMarker(owner, resource, markerData);
                if (!marker) {
                    continue;
                }
                const array = groups.get(resource);
                if (!array) {
                    groups.set(resource, [marker]);
                    changes.push(resource);
                } else {
                    array.push(marker);
                }
            }
            for (const [resource, value] of groups) {
                this._data.set(resource, owner, value);
            }
        }
        if (changes.length > 0) {
            this._onMarkerChanged.fire(changes);
        }
    }
    _createFilteredMarker(resource, reasons) {
        const message = reasons.length === 1 ? ( localize(2032, "Problems are paused because: \"{0}\"", reasons[0])) : ( localize(
            2033,
            "Problems are paused because: \"{0}\" and {1} more",
            reasons[0],
            reasons.length - 1
        ));
        return {
            owner: "markersFilter",
            resource,
            severity: MarkerSeverity.Info,
            message,
            startLineNumber: 1,
            startColumn: 1,
            endLineNumber: 1,
            endColumn: 1
        };
    }
    read(filter = Object.create(null)) {
        let {
            owner,
            resource,
            severities,
            take
        } = filter;
        if (!take || take < 0) {
            take = -1;
        }
        if (owner && resource) {
            const reasons = !filter.ignoreResourceFilters ? this._filteredResources.get(resource) : undefined;
            if (reasons?.length) {
                const infoMarker = this._createFilteredMarker(resource, reasons);
                return [infoMarker];
            }
            const data = this._data.get(resource, owner);
            if (!data) {
                return [];
            }
            const result = [];
            for (const marker of data) {
                if (take > 0 && result.length === take) {
                    break;
                }
                const reasons = !filter.ignoreResourceFilters ? this._filteredResources.get(resource) : undefined;
                if (reasons?.length) {
                    result.push(this._createFilteredMarker(resource, reasons));
                } else if (MarkerService._accept(marker, severities)) {
                    result.push(marker);
                }
            }
            return result;
        } else {
            const iterable = !owner && !resource ? ( this._data.values()) : ( this._data.values(resource ?? owner));
            const result = [];
            const filtered = ( new ResourceSet());
            for (const markers of iterable) {
                for (const data of markers) {
                    if (( filtered.has(data.resource))) {
                        continue;
                    }
                    if (take > 0 && result.length === take) {
                        break;
                    }
                    const reasons = !filter.ignoreResourceFilters ? this._filteredResources.get(data.resource) : undefined;
                    if (reasons?.length) {
                        result.push(this._createFilteredMarker(data.resource, reasons));
                        filtered.add(data.resource);
                    } else if (MarkerService._accept(data, severities)) {
                        result.push(data);
                    }
                }
            }
            return result;
        }
    }
    static _accept(marker, severities) {
        return severities === undefined || (severities & marker.severity) === marker.severity;
    }
    static _merge(all) {
        const set = ( new ResourceMap());
        for (const array of all) {
            for (const item of array) {
                set.set(item, true);
            }
        }
        return Array.from(( set.keys()));
    }
}

export { MarkerService, unsupportedSchemas };
