"use strict";
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Red Hat, Inc. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.aliasDepth = void 0;
exports.convertAST = convertAST;
exports.toOffsetLength = toOffsetLength;
const yaml_1 = require("yaml");
const customTags_1 = require("../utils/customTags");
const jsonDocument_1 = require("./jsonDocument");
// Exported for tests
exports.aliasDepth = {
    maxRefCount: 1000,
    currentRefDepth: 0,
    aliasResolutionCache: new Map(),
};
function convertAST(parent, node, doc, lineCounter) {
    if (!parent) {
        // first invocation
        exports.aliasDepth.currentRefDepth = 0;
        exports.aliasDepth.aliasResolutionCache = new Map();
    }
    if (!node) {
        return null;
    }
    let result;
    if ((0, yaml_1.isMap)(node)) {
        result = convertMap(node, parent, doc, lineCounter);
    }
    else if ((0, yaml_1.isPair)(node)) {
        result = convertPair(node, parent, doc, lineCounter);
    }
    else if ((0, yaml_1.isSeq)(node)) {
        result = convertSeq(node, parent, doc, lineCounter);
    }
    else if ((0, yaml_1.isScalar)(node)) {
        result = convertScalar(node, parent);
    }
    else if ((0, yaml_1.isAlias)(node) && exports.aliasDepth.currentRefDepth < exports.aliasDepth.maxRefCount) {
        result = convertAlias(node, parent, doc, lineCounter);
    }
    if (result && (0, yaml_1.isNode)(node)) {
        result.customTagReturnType = (0, customTags_1.getCustomTagReturnType)(node);
    }
    return result;
}
function convertMap(node, parent, doc, lineCounter) {
    let range;
    if (node.flow && !node.range) {
        range = collectFlowMapRange(node);
    }
    else {
        range = node.range;
    }
    const result = new jsonDocument_1.ObjectASTNodeImpl(parent, node, ...toFixedOffsetLength(range, lineCounter));
    for (const it of node.items) {
        if ((0, yaml_1.isPair)(it)) {
            result.properties.push(convertAST(result, it, doc, lineCounter));
        }
    }
    return result;
}
function convertPair(node, parent, doc, lineCounter) {
    const keyNode = node.key;
    const valueNode = node.value;
    const rangeStart = keyNode.range[0];
    let rangeEnd = keyNode.range[1];
    let nodeEnd = keyNode.range[2];
    if (valueNode) {
        rangeEnd = valueNode.range[1];
        nodeEnd = valueNode.range[2];
    }
    // Pair does not return a range using the key/value ranges to fake one.
    const result = new jsonDocument_1.PropertyASTNodeImpl(parent, node, ...toFixedOffsetLength([rangeStart, rangeEnd, nodeEnd], lineCounter));
    if ((0, yaml_1.isAlias)(keyNode)) {
        const keyAlias = new jsonDocument_1.StringASTNodeImpl(parent, keyNode, ...toOffsetLength(keyNode.range));
        keyAlias.value = keyNode.source;
        result.keyNode = keyAlias;
    }
    else {
        result.keyNode = convertAST(result, keyNode, doc, lineCounter);
    }
    result.valueNode = convertAST(result, valueNode, doc, lineCounter);
    return result;
}
function convertSeq(node, parent, doc, lineCounter) {
    const result = new jsonDocument_1.ArrayASTNodeImpl(parent, node, ...toOffsetLength(node.range));
    for (const it of node.items) {
        if ((0, yaml_1.isNode)(it)) {
            const convertedNode = convertAST(result, it, doc, lineCounter);
            // due to recursion protection, convertAST may return undefined
            if (convertedNode) {
                result.children.push(convertedNode);
            }
        }
    }
    return result;
}
function convertScalar(node, parent) {
    if (node.value === null) {
        return new jsonDocument_1.NullASTNodeImpl(parent, node, ...toOffsetLength(node.range));
    }
    switch (typeof node.value) {
        case 'string': {
            const result = new jsonDocument_1.StringASTNodeImpl(parent, node, ...toOffsetLength(node.range));
            result.value = node.value;
            return result;
        }
        case 'boolean':
            return new jsonDocument_1.BooleanASTNodeImpl(parent, node, node.value, node.source, ...toOffsetLength(node.range));
        case 'number': {
            const result = new jsonDocument_1.NumberASTNodeImpl(parent, node, ...toOffsetLength(node.range));
            result.value = node.value;
            result.isInteger = Number.isInteger(result.value);
            return result;
        }
        default: {
            // fail safe converting, we need to return some node anyway
            const result = new jsonDocument_1.StringASTNodeImpl(parent, node, ...toOffsetLength(node.range));
            result.value = node.source;
            return result;
        }
    }
}
function convertAlias(node, parent, doc, lineCounter) {
    if (exports.aliasDepth.aliasResolutionCache.has(node)) {
        return exports.aliasDepth.aliasResolutionCache.get(node);
    }
    exports.aliasDepth.currentRefDepth++;
    const resolvedNode = node.resolve(doc);
    let ans;
    if (resolvedNode) {
        ans = convertAST(parent, resolvedNode, doc, lineCounter);
    }
    else {
        const resultNode = new jsonDocument_1.StringASTNodeImpl(parent, node, ...toOffsetLength(node.range));
        resultNode.value = node.source;
        ans = resultNode;
    }
    exports.aliasDepth.currentRefDepth--;
    exports.aliasDepth.aliasResolutionCache.set(node, ans);
    return ans;
}
function toOffsetLength(range) {
    return [range[0], range[1] - range[0]];
}
/**
 * Convert offsets to offset+length with fix length to not include '\n' character in some cases
 * @param range the yaml ast range
 * @param lineCounter the line counter
 * @returns the offset and length
 */
function toFixedOffsetLength(range, lineCounter) {
    const start = lineCounter.linePos(range[0]);
    const end = lineCounter.linePos(range[1]);
    const result = [range[0], range[1] - range[0]];
    // -1 as range may include '\n'
    if (start.line !== end.line && (lineCounter.lineStarts.length !== end.line || end.col === 1)) {
        result[1]--;
    }
    return result;
}
function collectFlowMapRange(node) {
    let start = Number.MAX_SAFE_INTEGER;
    let end = 0;
    for (const it of node.items) {
        if ((0, yaml_1.isPair)(it)) {
            if ((0, yaml_1.isNode)(it.key)) {
                if (it.key.range && it.key.range[0] <= start) {
                    start = it.key.range[0];
                }
            }
            if ((0, yaml_1.isNode)(it.value)) {
                if (it.value.range && it.value.range[2] >= end) {
                    end = it.value.range[2];
                }
            }
        }
    }
    return [start, end, end];
}
//# sourceMappingURL=ast-converter.js.map