'use strict';

var astUtils = require('../utils/ast-utils.js');
var collections = require('../utils/collections.js');
var promiseUtils = require('../utils/promise-utils.js');
var cancellation = require('../../../../../../../_virtual/cancellation.js');

/******************************************************************************
 * Copyright 2021-2022 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.
 ******************************************************************************/
/**
 * The default scope computation creates and collectes descriptions of the AST nodes to be exported into the
 * _global_ scope from the given document. By default those are the document's root AST node and its directly
 * contained child nodes.
 *
 * Besides, it gathers all AST nodes that have a name (according to the `NameProvider` service) and includes them
 * in the local scope of their particular container nodes. As a result, for every cross-reference in the AST,
 * target elements from the same level (siblings) and further up towards the root (parents and siblings of parents)
 * are visible. Elements being nested inside lower levels (children, children of siblings and parents' siblings)
 * are _invisible_ by default, but that can be changed by customizing this service.
 */
class DefaultScopeComputation {
    constructor(services) {
        this.nameProvider = services.references.NameProvider;
        this.descriptions = services.workspace.AstNodeDescriptionProvider;
    }
    async computeExports(document, cancelToken = cancellation.cancellationExports.CancellationToken.None) {
        return this.computeExportsForNode(document.parseResult.value, document, undefined, cancelToken);
    }
    /**
     * Creates {@link AstNodeDescription AstNodeDescriptions} for the given {@link AstNode parentNode} and its children.
     * The list of children to be considered is determined by the function parameter {@link children}.
     * By default only the direct children of {@link parentNode} are visited, nested nodes are not exported.
     *
     * @param parentNode AST node to be exported, i.e., of which an {@link AstNodeDescription} shall be added to the returned list.
     * @param document The document containing the AST node to be exported.
     * @param children A function called with {@link parentNode} as single argument and returning an {@link Iterable} supplying the children to be visited, which must be directly or transitively contained in {@link parentNode}.
     * @param cancelToken Indicates when to cancel the current operation.
     * @throws `OperationCancelled` if a user action occurs during execution.
     * @returns A list of {@link AstNodeDescription AstNodeDescriptions} to be published to index.
     */
    async computeExportsForNode(parentNode, document, children = astUtils.streamContents, cancelToken = cancellation.cancellationExports.CancellationToken.None) {
        const exports = [];
        this.exportNode(parentNode, exports, document);
        for (const node of children(parentNode)) {
            await promiseUtils.interruptAndCheck(cancelToken);
            this.exportNode(node, exports, document);
        }
        return exports;
    }
    /**
     * Add a single node to the list of exports if it has a name. Override this method to change how
     * symbols are exported, e.g. by modifying their exported name.
     */
    exportNode(node, exports, document) {
        const name = this.nameProvider.getName(node);
        if (name) {
            exports.push(this.descriptions.createDescription(node, name, document));
        }
    }
    async computeLocalScopes(document, cancelToken = cancellation.cancellationExports.CancellationToken.None) {
        const rootNode = document.parseResult.value;
        const scopes = new collections.MultiMap();
        // Here we navigate the full AST - local scopes shall be available in the whole document
        for (const node of astUtils.streamAllContents(rootNode)) {
            await promiseUtils.interruptAndCheck(cancelToken);
            this.processNode(node, document, scopes);
        }
        return scopes;
    }
    /**
     * Process a single node during scopes computation. The default implementation makes the node visible
     * in the subtree of its container (if the node has a name). Override this method to change this,
     * e.g. by increasing the visibility to a higher level in the AST.
     */
    processNode(node, document, scopes) {
        const container = node.$container;
        if (container) {
            const name = this.nameProvider.getName(node);
            if (name) {
                scopes.add(container, this.descriptions.createDescription(node, name, document));
            }
        }
    }
}

exports.DefaultScopeComputation = DefaultScopeComputation;
