/******************************************************************************
 * 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.
 ******************************************************************************/
import { Position } from 'vscode-languageserver-types';
import { isCompositeCstNode } from '../syntax-tree.js';
import { tokenToRange } from '../utils/cst-utils.js';
export class CstNodeBuilder {
    constructor() {
        this.nodeStack = [];
    }
    get current() {
        return this.nodeStack[this.nodeStack.length - 1];
    }
    buildRootNode(input) {
        this.rootNode = new RootCstNodeImpl(input);
        this.rootNode.root = this.rootNode;
        this.nodeStack = [this.rootNode];
        return this.rootNode;
    }
    buildCompositeNode(feature) {
        const compositeNode = new CompositeCstNodeImpl();
        compositeNode.grammarSource = feature;
        compositeNode.root = this.rootNode;
        this.current.content.push(compositeNode);
        this.nodeStack.push(compositeNode);
        return compositeNode;
    }
    buildLeafNode(token, feature) {
        const leafNode = new LeafCstNodeImpl(token.startOffset, token.image.length, tokenToRange(token), token.tokenType, false);
        leafNode.grammarSource = feature;
        leafNode.root = this.rootNode;
        this.current.content.push(leafNode);
        return leafNode;
    }
    removeNode(node) {
        const parent = node.container;
        if (parent) {
            const index = parent.content.indexOf(node);
            if (index >= 0) {
                parent.content.splice(index, 1);
            }
        }
    }
    construct(item) {
        const current = this.current;
        // The specified item could be a datatype ($type is symbol) or a fragment ($type is undefined)
        // Only if the $type is a string, we actually assign the element
        if (typeof item.$type === 'string') {
            this.current.astNode = item;
        }
        item.$cstNode = current;
        const node = this.nodeStack.pop();
        // Empty composite nodes are not valid
        // Simply remove the node from the tree
        if ((node === null || node === void 0 ? void 0 : node.content.length) === 0) {
            this.removeNode(node);
        }
    }
    addHiddenTokens(hiddenTokens) {
        for (const token of hiddenTokens) {
            const hiddenNode = new LeafCstNodeImpl(token.startOffset, token.image.length, tokenToRange(token), token.tokenType, true);
            hiddenNode.root = this.rootNode;
            this.addHiddenToken(this.rootNode, hiddenNode);
        }
    }
    addHiddenToken(node, token) {
        const { offset: tokenStart, end: tokenEnd } = token;
        for (let i = 0; i < node.content.length; i++) {
            const child = node.content[i];
            const { offset: childStart, end: childEnd } = child;
            if (isCompositeCstNode(child) && tokenStart > childStart && tokenEnd < childEnd) {
                this.addHiddenToken(child, token);
                return;
            }
            else if (tokenEnd <= childStart) {
                node.content.splice(i, 0, token);
                return;
            }
        }
        // We know that we haven't found a suited position for the token
        // So we simply add it to the end of the current node
        node.content.push(token);
    }
}
export class AbstractCstNode {
    /** @deprecated use `container` instead. */
    get parent() {
        return this.container;
    }
    /** @deprecated use `grammarSource` instead. */
    get feature() {
        return this.grammarSource;
    }
    get hidden() {
        return false;
    }
    get astNode() {
        var _a, _b;
        const node = typeof ((_a = this._astNode) === null || _a === void 0 ? void 0 : _a.$type) === 'string' ? this._astNode : (_b = this.container) === null || _b === void 0 ? void 0 : _b.astNode;
        if (!node) {
            throw new Error('This node has no associated AST element');
        }
        return node;
    }
    set astNode(value) {
        this._astNode = value;
    }
    /** @deprecated use `astNode` instead. */
    get element() {
        return this.astNode;
    }
    get text() {
        return this.root.fullText.substring(this.offset, this.end);
    }
}
export class LeafCstNodeImpl extends AbstractCstNode {
    get offset() {
        return this._offset;
    }
    get length() {
        return this._length;
    }
    get end() {
        return this._offset + this._length;
    }
    get hidden() {
        return this._hidden;
    }
    get tokenType() {
        return this._tokenType;
    }
    get range() {
        return this._range;
    }
    constructor(offset, length, range, tokenType, hidden = false) {
        super();
        this._hidden = hidden;
        this._offset = offset;
        this._tokenType = tokenType;
        this._length = length;
        this._range = range;
    }
}
export class CompositeCstNodeImpl extends AbstractCstNode {
    constructor() {
        super(...arguments);
        this.content = new CstNodeContainer(this);
    }
    /** @deprecated use `content` instead. */
    get children() {
        return this.content;
    }
    get offset() {
        var _a, _b;
        return (_b = (_a = this.firstNonHiddenNode) === null || _a === void 0 ? void 0 : _a.offset) !== null && _b !== void 0 ? _b : 0;
    }
    get length() {
        return this.end - this.offset;
    }
    get end() {
        var _a, _b;
        return (_b = (_a = this.lastNonHiddenNode) === null || _a === void 0 ? void 0 : _a.end) !== null && _b !== void 0 ? _b : 0;
    }
    get range() {
        const firstNode = this.firstNonHiddenNode;
        const lastNode = this.lastNonHiddenNode;
        if (firstNode && lastNode) {
            if (this._rangeCache === undefined) {
                const { range: firstRange } = firstNode;
                const { range: lastRange } = lastNode;
                this._rangeCache = { start: firstRange.start, end: lastRange.end.line < firstRange.start.line ? firstRange.start : lastRange.end };
            }
            return this._rangeCache;
        }
        else {
            return { start: Position.create(0, 0), end: Position.create(0, 0) };
        }
    }
    get firstNonHiddenNode() {
        for (const child of this.content) {
            if (!child.hidden) {
                return child;
            }
        }
        return this.content[0];
    }
    get lastNonHiddenNode() {
        for (let i = this.content.length - 1; i >= 0; i--) {
            const child = this.content[i];
            if (!child.hidden) {
                return child;
            }
        }
        return this.content[this.content.length - 1];
    }
}
class CstNodeContainer extends Array {
    constructor(parent) {
        super();
        this.parent = parent;
        Object.setPrototypeOf(this, CstNodeContainer.prototype);
    }
    push(...items) {
        this.addParents(items);
        return super.push(...items);
    }
    unshift(...items) {
        this.addParents(items);
        return super.unshift(...items);
    }
    splice(start, count, ...items) {
        this.addParents(items);
        return super.splice(start, count, ...items);
    }
    addParents(items) {
        for (const item of items) {
            item.container = this.parent;
        }
    }
}
export class RootCstNodeImpl extends CompositeCstNodeImpl {
    get text() {
        return this._text.substring(this.offset, this.end);
    }
    get fullText() {
        return this._text;
    }
    constructor(input) {
        super();
        this._text = '';
        this._text = input !== null && input !== void 0 ? input : '';
    }
}
//# sourceMappingURL=cst-node-builder.js.map