'use strict';

var astUtils = require('../utils/ast-utils.js');
var caching = require('../utils/caching.js');
var stream = require('../utils/stream.js');
var uriUtils = require('../utils/uri-utils.js');
var cancellation = require('../../../../../../../_virtual/cancellation.js');

/******************************************************************************
 * Copyright 2021 TypeFox GmbH
 * This program and the accompanying materials are made available under the
 * terms of the MIT License, which is available in the project root.
 ******************************************************************************/
class DefaultIndexManager {
    constructor(services) {
        /**
         * The symbol index stores all `AstNodeDescription` items exported by a document.
         * The key used in this map is the string representation of the specific document URI.
         */
        this.symbolIndex = new Map();
        /**
         * This is a cache for the `allElements()` method.
         * It caches the descriptions from `symbolIndex` grouped by types.
         */
        this.symbolByTypeIndex = new caching.ContextCache();
        /**
         * This index keeps track of all `ReferenceDescription` items exported by a document.
         * This is used to compute which elements are affected by a document change
         * and for finding references to an AST node.
         */
        this.referenceIndex = new Map();
        this.documents = services.workspace.LangiumDocuments;
        this.serviceRegistry = services.ServiceRegistry;
        this.astReflection = services.AstReflection;
    }
    findAllReferences(targetNode, astNodePath) {
        const targetDocUri = astUtils.getDocument(targetNode).uri;
        const result = [];
        this.referenceIndex.forEach(docRefs => {
            docRefs.forEach(refDescr => {
                if (uriUtils.UriUtils.equals(refDescr.targetUri, targetDocUri) && refDescr.targetPath === astNodePath) {
                    result.push(refDescr);
                }
            });
        });
        return stream.stream(result);
    }
    allElements(nodeType, uris) {
        let documentUris = stream.stream(this.symbolIndex.keys());
        if (uris) {
            documentUris = documentUris.filter(uri => !uris || uris.has(uri));
        }
        return documentUris
            .map(uri => this.getFileDescriptions(uri, nodeType))
            .flat();
    }
    getFileDescriptions(uri, nodeType) {
        var _a;
        if (!nodeType) {
            return (_a = this.symbolIndex.get(uri)) !== null && _a !== void 0 ? _a : [];
        }
        const descriptions = this.symbolByTypeIndex.get(uri, nodeType, () => {
            var _a;
            const allFileDescriptions = (_a = this.symbolIndex.get(uri)) !== null && _a !== void 0 ? _a : [];
            return allFileDescriptions.filter(e => this.astReflection.isSubtype(e.type, nodeType));
        });
        return descriptions;
    }
    remove(uri) {
        const uriString = uri.toString();
        this.symbolIndex.delete(uriString);
        this.symbolByTypeIndex.clear(uriString);
        this.referenceIndex.delete(uriString);
    }
    async updateContent(document, cancelToken = cancellation.cancellationExports.CancellationToken.None) {
        const services = this.serviceRegistry.getServices(document.uri);
        const exports = await services.references.ScopeComputation.computeExports(document, cancelToken);
        const uri = document.uri.toString();
        this.symbolIndex.set(uri, exports);
        this.symbolByTypeIndex.clear(uri);
    }
    async updateReferences(document, cancelToken = cancellation.cancellationExports.CancellationToken.None) {
        const services = this.serviceRegistry.getServices(document.uri);
        const indexData = await services.workspace.ReferenceDescriptionProvider.createDescriptions(document, cancelToken);
        this.referenceIndex.set(document.uri.toString(), indexData);
    }
    isAffected(document, changedUris) {
        const references = this.referenceIndex.get(document.uri.toString());
        if (!references) {
            return false;
        }
        return references.some(ref => !ref.local && changedUris.has(ref.targetUri.toString()));
    }
}

exports.DefaultIndexManager = DefaultIndexManager;
