"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports._getValueType = exports.OBJECT_TREE = exports.REGEX_TREE = exports.ANY_TREE = exports.NUMBER_TREE = exports.BOOLEAN_TREE = exports.STRING_TREE = exports.NULL_TREE = exports._extractGenerics = exports.getValueTree = exports._getTypeTree = exports.getTree = exports.isValidKeyStr = exports.isValidIdentifierStr = exports.isValidStringStr = exports.isValidNumberStr = exports.isValidBooleanStr = exports.isKey = exports._isKey = exports.isValidObjKey = exports._isValidObjKeyType = exports.isValidObjValue = exports.isValidValue = exports._isValidType = exports._isValidValue = exports.isValidTree = exports._isValidTree = exports.isTypeMatch = exports._isTypeMatch = exports.checkClassInheritance = exports.getLiteralType = exports.isLiteralType = exports.getTreeNodeType = exports.isValidType = exports.isValueOfType = exports._isValueOfType = exports._hasGeneric = exports._isGeneric = exports.hasGeneric = exports.isGeneric = exports.isCompositeType = exports.printTree = exports.ARRAY_CLOSE = exports.ARRAY_OPEN = exports.OBJECT_CLOSE = exports.OBJECT_OPEN = exports.SINGLE_QUOTE = exports.DOUBLE_QUOTE = exports.COMMA = exports.TreeNodeType = void 0;
exports.matchAllExcTypes = exports._matchAllExcTypes = exports.removeNodeAt = exports._removeNodeAt = exports.EMPTY_TREE = exports.updateNodeAt = exports._updateNodeAt = exports.insertNodeAt = exports._insertNodeAt = exports._getParent = exports.getLastLeaf = exports.getLastLeafPath = exports._getLastLeafPath = exports._getLastLeaf = exports.getParent = exports.getParentPath = exports.getNextSiblingPath = exports._getNextSiblingPath = exports.getNextLeafPath = exports._getNextLeafPath = exports.getNextNodePath = exports._getNextNodePath = exports.getNextNode = exports._getNextNode = exports.getNodeAtPath = exports._getNodeAtPath = exports.applyGenerics = exports._applyGenerics = exports.extractGenerics = exports._findGenerics = exports.findGenerics = exports.filterEmptyNodes = exports._filterEmptyNodes = exports._stringify = exports.getValueType = void 0;
const MethodNotImplementedError_1 = require("../exception/MethodNotImplementedError");
const interface_1 = require("../interface");
const f_1 = require("../system/f/object/Keys/f");
const array_1 = require("../util/array");
const object_1 = require("../util/object");
const string_1 = require("../util/string");
const weakMerge_1 = require("../weakMerge");
const idFromUnitValue_1 = require("./idFromUnitValue");
const BOOLEAN_LITERAL_1 = require("./regex/BOOLEAN_LITERAL");
const IDENTIFIER_1 = require("./regex/IDENTIFIER");
const NUMBER_LITERAL_1 = require("./regex/NUMBER_LITERAL");
const STRING_LITERAL_1 = require("./regex/STRING_LITERAL");
var TreeNodeType;
(function (TreeNodeType) {
    TreeNodeType["Invalid"] = "invalid";
    TreeNodeType["Null"] = "null";
    TreeNodeType["Generic"] = "generic";
    TreeNodeType["Identifier"] = "identifier";
    TreeNodeType["Expression"] = "expression";
    TreeNodeType["ArrayExpression"] = "array expression";
    TreeNodeType["ObjectExpression"] = "object expression";
    TreeNodeType["Or"] = "or";
    TreeNodeType["And"] = "and";
    TreeNodeType["Object"] = "object";
    TreeNodeType["String"] = "string";
    TreeNodeType["Number"] = "number";
    TreeNodeType["Boolean"] = "boolean";
    TreeNodeType["Class"] = "class";
    TreeNodeType["StringLiteral"] = "string literal";
    TreeNodeType["BooleanLiteral"] = "boolean literal";
    TreeNodeType["NumberLiteral"] = "number literal";
    TreeNodeType["ObjectLiteral"] = "object literal";
    TreeNodeType["ArrayLiteral"] = "array literal";
    TreeNodeType["ClassLiteral"] = "class literal";
    TreeNodeType["KeyValue"] = "key value";
    TreeNodeType["Regex"] = "regex";
    TreeNodeType["RegexLiteral"] = "regex literal";
    TreeNodeType["Date"] = "date";
    TreeNodeType["DateLiteral"] = "date literal";
    TreeNodeType["Time"] = "time";
    TreeNodeType["Unit"] = "unit";
    TreeNodeType["Any"] = "any";
    TreeNodeType["PropExpression"] = "prop expression";
    TreeNodeType["ArithmeticExpression"] = "arithmetic expression";
    TreeNodeType["Url"] = "url";
})(TreeNodeType || (exports.TreeNodeType = TreeNodeType = {}));
exports.COMMA = ',';
exports.DOUBLE_QUOTE = '"';
exports.SINGLE_QUOTE = "'";
exports.OBJECT_OPEN = '{';
exports.OBJECT_CLOSE = '}';
exports.ARRAY_OPEN = '[';
exports.ARRAY_CLOSE = ']';
function trimSides(str) {
    return str.substring(1, str.length - 1);
}
function _traverse(root, callback, path = []) {
    callback(root, path);
    root.children.forEach((child, index) => _traverse(child, callback, [...path, index]));
}
function _printTree(tree) {
    // console.log(JSON.stringify(tree, null, 2))
    _traverse(tree, (node, path) => 
    // eslint-disable-next-line no-console
    console.log(path, node.value));
}
function printTree(data) {
    const tree = getTree(data);
    _printTree(tree);
}
exports.printTree = printTree;
function isCompositeType(type) {
    return ([
        TreeNodeType.Or,
        TreeNodeType.And,
        TreeNodeType.ArrayExpression,
        TreeNodeType.Expression,
        TreeNodeType.ObjectLiteral,
        TreeNodeType.ArrayLiteral,
        TreeNodeType.KeyValue,
        TreeNodeType.Class,
    ].indexOf(type) > -1);
}
exports.isCompositeType = isCompositeType;
function isGeneric(type) {
    return hasGeneric(type);
}
exports.isGeneric = isGeneric;
function hasGeneric(type) {
    const tree = getTree(type);
    return _hasGeneric(tree);
}
exports.hasGeneric = hasGeneric;
function _isGeneric(tree) {
    return _hasGeneric(tree);
}
exports._isGeneric = _isGeneric;
function _hasGeneric(tree) {
    return (tree.type === TreeNodeType.Generic ||
        (isCompositeType(tree.type) && tree.children.some(_hasGeneric)));
}
exports._hasGeneric = _hasGeneric;
function getObjLiteralKeyValueMap(tree) {
    const keyValueMap = {};
    for (const keyValue of tree.children) {
        if (keyValue.type === TreeNodeType.KeyValue) {
            const keyTree = keyValue.children[0];
            let key;
            const { value, type } = keyTree;
            if (type === TreeNodeType.Identifier) {
                key = value;
            }
            else {
                key = value.substr(1, value.length - 2);
            }
            keyValueMap[key] = keyValue.children[1];
        }
        else {
            keyValueMap[keyValue.value] = keyValue.value;
        }
    }
    return keyValueMap;
}
function _isValueOfType(value, type) {
    switch (type.type) {
        case TreeNodeType.Generic:
            return _isValidValue(value);
        case TreeNodeType.Identifier:
        case TreeNodeType.StringLiteral:
        case TreeNodeType.BooleanLiteral:
        case TreeNodeType.NumberLiteral:
            return value.type === type.type && value.value === type.value;
        case TreeNodeType.ObjectLiteral: {
            const typeKeyValueMap = getObjLiteralKeyValueMap(type);
            const valueKeyValueMap = getObjLiteralKeyValueMap(value);
            for (const key in typeKeyValueMap) {
                if (!valueKeyValueMap[key] ||
                    !_isValueOfType(valueKeyValueMap[key], typeKeyValueMap[key])) {
                    return false;
                }
            }
            return value.type === TreeNodeType.ObjectLiteral;
        }
        case TreeNodeType.ArrayLiteral:
            return (value.type === TreeNodeType.ArrayLiteral &&
                !value.children.some((child, index) => !_isValueOfType(child, type.children[index])));
        case TreeNodeType.Object:
            return value.type === TreeNodeType.ObjectLiteral;
        case TreeNodeType.String:
            return value.type === TreeNodeType.StringLiteral;
        case TreeNodeType.Number:
            return value.type === TreeNodeType.NumberLiteral;
        case TreeNodeType.Boolean:
            return value.type === TreeNodeType.BooleanLiteral;
        case TreeNodeType.Or:
            return type.children.some((child) => _isValueOfType(value, child));
        case TreeNodeType.And:
            return !type.children.some((child) => !_isValueOfType(value, child));
        case TreeNodeType.ArrayExpression:
            return (value.type === TreeNodeType.ArrayLiteral &&
                !value.children.some((child) => !_isValueOfType(child, type.children[0])));
        case TreeNodeType.ObjectExpression:
            return (value.type === TreeNodeType.ObjectLiteral &&
                !value.children.some((child) => !_isValueOfType(child, type.children[0])));
        case TreeNodeType.Expression:
            return _isValueOfType(value, type.children[0]);
        case TreeNodeType.KeyValue:
            return (_isValueOfType(value.children[0], type.children[0]) &&
                _isValueOfType(value.children[1], type.children[1]));
        default:
            throw new Error('not a valid TreeNodeType');
    }
}
exports._isValueOfType = _isValueOfType;
function isValueOfType(value, type) {
    const valueTree = getTree(value);
    const typeTree = getTree(type);
    if (!_isValidValue(valueTree)) {
        return false;
    }
    if (!_isValidType(typeTree)) {
        return false;
    }
    return _isValueOfType(valueTree, typeTree);
}
exports.isValueOfType = isValueOfType;
function isValidType(type) {
    const tree = getTree(type);
    return _isValidType(tree);
}
exports.isValidType = isValidType;
function getTreeNodeType(value) {
    const tree = getTree(value);
    return tree.type;
}
exports.getTreeNodeType = getTreeNodeType;
function isLiteralType(type) {
    return !!LITERAL_TO_TYPE[type];
}
exports.isLiteralType = isLiteralType;
const LITERAL_TO_TYPE = {
    [TreeNodeType.NumberLiteral]: TreeNodeType.Number,
    [TreeNodeType.StringLiteral]: TreeNodeType.String,
    [TreeNodeType.BooleanLiteral]: TreeNodeType.Boolean,
    [TreeNodeType.RegexLiteral]: TreeNodeType.Regex,
    [TreeNodeType.DateLiteral]: TreeNodeType.Date,
};
const TYPE_TO_LITERAL = {
    [TreeNodeType.Number]: TreeNodeType.NumberLiteral,
    [TreeNodeType.String]: TreeNodeType.StringLiteral,
    [TreeNodeType.Boolean]: TreeNodeType.BooleanLiteral,
    [TreeNodeType.Object]: TreeNodeType.ObjectLiteral,
    [TreeNodeType.ArrayExpression]: TreeNodeType.ArrayLiteral,
    [TreeNodeType.Regex]: TreeNodeType.RegexLiteral,
    [TreeNodeType.Date]: TreeNodeType.DateLiteral,
};
function getLiteralType(type) {
    return TYPE_TO_LITERAL[type];
}
exports.getLiteralType = getLiteralType;
const checkClassInheritance = (sourceClass, targetClass) => {
    if (sourceClass === targetClass) {
        return true;
    }
    const parentClasses = interface_1.INHERITANCE[sourceClass];
    if (parentClasses) {
        return parentClasses.some((parentClass) => (0, exports.checkClassInheritance)(parentClass, targetClass));
    }
    return false;
};
exports.checkClassInheritance = checkClassInheritance;
function _isTypeMatch(specs, source, target) {
    var _a;
    if (source.type === TreeNodeType.Invalid) {
        return false;
    }
    if (source.type === TreeNodeType.Identifier) {
        return false;
    }
    if (source.type === TreeNodeType.Any) {
        return true;
    }
    if (source.type === TreeNodeType.Generic) {
        return true;
    }
    if (source.type === TreeNodeType.Or) {
        return !source.children.some((sourceChild) => {
            return !_isTypeMatch(specs, sourceChild, target);
        });
    }
    if (source.type === TreeNodeType.And) {
        if (target.type === TreeNodeType.And) {
            return target.children.every((targetChild) => {
                return source.children.some((sourceChild) => {
                    const typeMatch = _isTypeMatch(specs, sourceChild, targetChild);
                    return typeMatch;
                });
            });
        }
        else {
            return source.children.some((sourceChild) => {
                return _isTypeMatch(specs, sourceChild, target);
            });
        }
    }
    if (source.type === TreeNodeType.Expression) {
        return _isTypeMatch(specs, source.children[0], target);
    }
    switch (target.type) {
        case TreeNodeType.Invalid:
            return false;
        case TreeNodeType.Identifier:
            return false;
        case TreeNodeType.Any:
            return true;
        case TreeNodeType.Generic:
            return true;
        case TreeNodeType.String:
            return (source.type === TreeNodeType.String ||
                source.type === TreeNodeType.StringLiteral);
        case TreeNodeType.Number:
            return (source.type === TreeNodeType.Number ||
                source.type === TreeNodeType.NumberLiteral);
        case TreeNodeType.Boolean:
            return (source.type === TreeNodeType.Boolean ||
                source.type === TreeNodeType.BooleanLiteral);
        case TreeNodeType.Object:
            return (source.type === TreeNodeType.Object ||
                (source.type === TreeNodeType.ObjectLiteral && _isValidTree(source)));
        case TreeNodeType.Regex:
            return (source.type === TreeNodeType.Regex ||
                source.type === TreeNodeType.RegexLiteral);
        case TreeNodeType.Class:
            if (source.type === TreeNodeType.Class) {
                const sourceClass = source.value.slice(1, -1);
                const targetClass = target.value.slice(1, -1);
                const match = (0, exports.checkClassInheritance)(sourceClass, targetClass);
                return match;
            }
            else if (source.type === TreeNodeType.Unit) {
                const bundle = (0, idFromUnitValue_1.evaluateBundleStr)(source.value, specs, {});
                const specId = bundle.unit.id;
                const _specs = (0, weakMerge_1.weakMerge)(specs, (_a = bundle.specs) !== null && _a !== void 0 ? _a : {});
                const spec = _specs[specId];
                const { type = '`U`&`C`&`G`' } = spec;
                const typeTree = getTree(type);
                return _isTypeMatch(specs, typeTree, target);
            }
            else {
                return false;
            }
        case TreeNodeType.Null:
            return source.type === TreeNodeType.Null;
        case TreeNodeType.Unit:
            return source.type === TreeNodeType.Unit;
        case TreeNodeType.ArithmeticExpression:
            return source.type === TreeNodeType.ArithmeticExpression;
        case TreeNodeType.BooleanLiteral:
        case TreeNodeType.NumberLiteral:
        case TreeNodeType.StringLiteral:
            return (source.type === target.type &&
                source.value.substr(1, source.value.length - 2) ===
                    target.value.substr(1, target.value.length - 2));
        case TreeNodeType.ObjectLiteral:
            if (source.type === TreeNodeType.Object) {
                return true;
            }
            else if (source.type === TreeNodeType.ObjectLiteral) {
                const sourceDict = getObjLiteralKeyValueMap(source);
                const targetDict = getObjLiteralKeyValueMap(target);
                for (const targetKey in targetDict) {
                    const sourceValue = sourceDict[targetKey];
                    const targetValue = targetDict[targetKey];
                    if (targetKey.endsWith('?')) {
                        if (sourceValue !== undefined &&
                            !_isTypeMatch(specs, sourceValue, targetValue)) {
                            return false;
                        }
                    }
                    else if (sourceValue === undefined ||
                        !_isTypeMatch(specs, sourceValue, targetValue)) {
                        return false;
                    }
                }
                return true;
            }
            else {
                return false;
            }
        case TreeNodeType.ArrayLiteral:
            if (source.type !== TreeNodeType.ArrayLiteral) {
                return false;
            }
            for (let i = 0; i < target.children.length; i++) {
                const targetChild = target.children[i];
                const sourceChild = source.children[i];
                if (!sourceChild || !_isTypeMatch(specs, sourceChild, targetChild)) {
                    return false;
                }
            }
            return true;
        case TreeNodeType.Or:
            return target.children.some((targetChild) => _isTypeMatch(specs, source, targetChild));
        case TreeNodeType.And:
            return !target.children.some((targetChild) => !_isTypeMatch(specs, source, targetChild));
        case TreeNodeType.Expression:
            return _isTypeMatch(specs, source, target.children[0]);
        case TreeNodeType.ArrayExpression:
            return ((source.type === TreeNodeType.ArrayExpression &&
                _isTypeMatch(specs, source.children[0], target.children[0])) ||
                (source.type === TreeNodeType.ArrayLiteral &&
                    (source.children.length === 0 ||
                        !source.children.some((c) => !_isTypeMatch(specs, c, target.children[0])))));
        case TreeNodeType.ObjectExpression:
            return ((source.type === TreeNodeType.ObjectExpression &&
                _isTypeMatch(specs, source.children[0], target.children[0])) ||
                (source.type === TreeNodeType.ObjectLiteral &&
                    (source.children.length === 0 ||
                        !source.children.some((keyValue) => !_isTypeMatch(specs, keyValue.children[1], target.children[0])))));
    }
    return false;
}
exports._isTypeMatch = _isTypeMatch;
function isTypeMatch(specs, source, target) {
    const sourceTree = getTree(source);
    const targetTree = getTree(target);
    return _isTypeMatch(specs, sourceTree, targetTree);
}
exports.isTypeMatch = isTypeMatch;
function _isValidTree(value) {
    return _isValidType(value) || _isValidValue(value);
}
exports._isValidTree = _isValidTree;
function isValidTree(value) {
    const tree = getTree(value);
    return _isValidType(tree) || _isValidValue(tree);
}
exports.isValidTree = isValidTree;
function _isValidValue(tree) {
    switch (tree.type) {
        case TreeNodeType.Invalid:
        case TreeNodeType.Generic:
        case TreeNodeType.Identifier:
        case TreeNodeType.String:
        case TreeNodeType.Number:
        case TreeNodeType.Boolean:
        case TreeNodeType.Object:
        case TreeNodeType.Or:
        case TreeNodeType.And:
        case TreeNodeType.ArrayExpression:
        case TreeNodeType.ObjectExpression:
        case TreeNodeType.Expression:
        case TreeNodeType.KeyValue:
            return false;
        case TreeNodeType.Null:
        case TreeNodeType.Unit:
        case TreeNodeType.StringLiteral:
        case TreeNodeType.BooleanLiteral:
        case TreeNodeType.NumberLiteral:
        case TreeNodeType.RegexLiteral:
        case TreeNodeType.DateLiteral:
        case TreeNodeType.ClassLiteral:
        case TreeNodeType.ArithmeticExpression:
            return true;
        case TreeNodeType.ObjectLiteral:
            return !tree.children.some((element, index) => {
                if (element.type === TreeNodeType.KeyValue) {
                    const { children: [key_tree, value_tree], } = element;
                    if (!key_tree) {
                        return true;
                    }
                    if (key_tree.value === '') {
                        return true;
                    }
                    if (!_isValidObjKeyType(key_tree)) {
                        return true;
                    }
                    if (value_tree.value !== '' && !_isValidValue(value_tree)) {
                        return true;
                    }
                    return false;
                }
                else if (_isValidObjKeyType(element)) {
                    return false;
                }
                else if (element.value === '' && index === tree.children.length - 1) {
                    return false;
                }
                return true;
            });
        case TreeNodeType.ArrayLiteral:
            return !tree.children.some((element, index) => {
                if (!_isValidValue(element) &&
                    (element.value !== '' || index !== tree.children.length - 1)) {
                    return true;
                }
                return false;
            });
        default:
            return false;
    }
}
exports._isValidValue = _isValidValue;
function _isValidType(tree) {
    switch (tree.type) {
        case TreeNodeType.Invalid:
        case TreeNodeType.Identifier:
        case TreeNodeType.KeyValue:
            return false;
        case TreeNodeType.Generic:
        case TreeNodeType.String:
        case TreeNodeType.Number:
        case TreeNodeType.Regex:
        case TreeNodeType.Date:
        case TreeNodeType.Boolean:
        case TreeNodeType.Object:
        case TreeNodeType.Null:
        case TreeNodeType.Class:
        case TreeNodeType.Any:
        case TreeNodeType.Unit:
        case TreeNodeType.StringLiteral:
        case TreeNodeType.BooleanLiteral:
        case TreeNodeType.NumberLiteral:
        case TreeNodeType.RegexLiteral:
        case TreeNodeType.DateLiteral:
        case TreeNodeType.ClassLiteral:
        case TreeNodeType.ArithmeticExpression:
            return true;
        case TreeNodeType.ArrayExpression:
        case TreeNodeType.ObjectExpression:
        case TreeNodeType.PropExpression:
        case TreeNodeType.Expression:
        case TreeNodeType.Or:
        case TreeNodeType.And:
            return !tree.children.some((child) => !_isValidType(child));
        case TreeNodeType.ObjectLiteral:
            return !tree.children.some((element, index) => {
                if (element.type === TreeNodeType.KeyValue) {
                    const { children: [key, value], } = element;
                    if (key.value === '') {
                        return true;
                    }
                    return ((!key ||
                        !_isValidObjKeyType(key) ||
                        !value ||
                        !_isValidType(value)) &&
                        (element.value !== '' || index !== tree.children.length - 1));
                }
                else if (_isValidObjKeyType(element) ||
                    (element.value === '' && index === tree.children.length - 1)) {
                    return false;
                }
                return true;
            });
        case TreeNodeType.ArrayLiteral:
            return !tree.children.some((element, index) => !_isValidType(element) &&
                (element.value !== '' || index !== tree.children.length - 1));
        default:
            return false;
    }
}
exports._isValidType = _isValidType;
function isValidValue(value) {
    const tree = getTree(value);
    return _isValidValue(tree);
}
exports.isValidValue = isValidValue;
function _isValidObjValueType(type) {
    return type !== TreeNodeType.Identifier;
}
function isValidObjValue(type) {
    const tree = getTree(type);
    return _isValidObjValueType(tree.type);
}
exports.isValidObjValue = isValidObjValue;
function _isValidObjKeyType(tree) {
    return (tree.type === TreeNodeType.Generic ||
        tree.type === TreeNodeType.Identifier ||
        tree.type === TreeNodeType.StringLiteral ||
        tree.type === TreeNodeType.BooleanLiteral ||
        tree.type === TreeNodeType.NumberLiteral);
}
exports._isValidObjKeyType = _isValidObjKeyType;
function isValidObjKey(key) {
    const tree = getTree(key);
    return _isValidObjKeyType(tree);
}
exports.isValidObjKey = isValidObjKey;
function _isKey(root, path) {
    const parent = _getParent(root, path);
    return !!(parent &&
        parent.type === TreeNodeType.KeyValue &&
        path[path.length - 1] === 0);
}
exports._isKey = _isKey;
function isKey(root, path) {
    const tree = getTree(root);
    return _isKey(tree, path);
}
exports.isKey = isKey;
function isValidBooleanStr(str) {
    return BOOLEAN_LITERAL_1.BOOLEAN_LITERAL_REGEX.test(str);
}
exports.isValidBooleanStr = isValidBooleanStr;
function isValidNumberStr(str) {
    return NUMBER_LITERAL_1.NUMBER_LITERAL_REGEX.test(str);
}
exports.isValidNumberStr = isValidNumberStr;
function isValidStringStr(str) {
    return STRING_LITERAL_1.STRING_LITERAL_REGEX.test(str);
}
exports.isValidStringStr = isValidStringStr;
function isValidIdentifierStr(str) {
    return IDENTIFIER_1.IDENTIFIER_REGEX.test(str);
}
exports.isValidIdentifierStr = isValidIdentifierStr;
function isValidKeyStr(str) {
    return (isValidBooleanStr(str) ||
        isValidNumberStr(str) ||
        isValidStringStr(str) ||
        isValidIdentifierStr(str));
}
exports.isValidKeyStr = isValidKeyStr;
function getTree(value, keyValue = false, ignoreKeyword = false) {
    return (_getValueTree(value, keyValue, ignoreKeyword, getTree) ||
        _getTypeTree(value, keyValue, ignoreKeyword) || {
        value,
        type: TreeNodeType.Invalid,
        children: [],
    });
}
exports.getTree = getTree;
function _getTypeTree(value, keyValue = false, ignoreKeyword = false) {
    value = value.trim();
    if (!ignoreKeyword) {
        const classLiteralTest = /^\((.*)\)=>\((.*)\)$/.exec(value);
        if (classLiteralTest) {
            return {
                value,
                type: TreeNodeType.ClassLiteral,
                children: [],
            };
        }
    }
    const expressionTest = /^\((.+)\)$/.exec(value);
    if (expressionTest) {
        const children = [getTree(expressionTest[1])];
        return {
            value,
            type: TreeNodeType.Expression,
            children,
        };
    }
    const genericTest = /^<[^]*>$/.exec(value);
    if (genericTest) {
        return {
            value,
            type: TreeNodeType.Generic,
            children: [],
        };
    }
    if (!ignoreKeyword) {
        const regexTest = /^regex$/.exec(value);
        if (regexTest) {
            return exports.REGEX_TREE;
        }
        const stringTest = /^string$/.exec(value);
        if (stringTest) {
            return exports.STRING_TREE;
        }
        const numberTest = /^number$/.exec(value);
        if (numberTest) {
            return exports.NUMBER_TREE;
        }
        const booleanTest = /^boolean$/.exec(value);
        if (booleanTest) {
            return exports.BOOLEAN_TREE;
        }
        const objectTest = /^object$/.exec(value);
        if (objectTest) {
            return exports.OBJECT_TREE;
        }
        const anyTest = /^any$/.exec(value);
        if (anyTest) {
            return exports.ANY_TREE;
        }
    }
    const propExpressionTest = /^([^ [\]]+)(\[[^ [\]]+\]+)$/.exec(value);
    if (propExpressionTest) {
        const _ = getTree(propExpressionTest[1]);
        const __ = getTree(propExpressionTest[2]);
        const children = [_, __];
        return {
            value,
            type: TreeNodeType.PropExpression,
            children,
        };
    }
    const arrayExpressionTest = /^([^ ]+)\[\]$/.exec(value);
    if (arrayExpressionTest) {
        const child = getTree(arrayExpressionTest[1]);
        if (child.type !== TreeNodeType.Invalid) {
            const children = [child];
            return {
                value,
                type: TreeNodeType.ArrayExpression,
                children,
            };
        }
    }
    const objectExpressionTest = /^([^|{}]+)\{\}$/.exec(value);
    if (objectExpressionTest) {
        const children = [getTree(objectExpressionTest[1])];
        return {
            value,
            type: TreeNodeType.ObjectExpression,
            children,
        };
    }
    const orTest = /^.+(\|.+)+$/.exec(value);
    if (orTest) {
        const children = _getDelimiterSeparated(value, false, false, '|', getTree);
        return {
            value,
            type: TreeNodeType.Or,
            children,
        };
    }
    const andTest = /^(.+)&(.+)$/.exec(value);
    if (andTest) {
        const children = _getDelimiterSeparated(value, false, false, '&', getTree);
        return {
            value,
            type: TreeNodeType.And,
            children,
        };
    }
    const classTest = /^`([A-Z]+)(.*)?`$/i.exec(value);
    if (classTest) {
        const childStr = classTest[2];
        return {
            value,
            type: TreeNodeType.Class,
            children: childStr ? [getTree(childStr)] : [],
        };
    }
    return undefined;
}
exports._getTypeTree = _getTypeTree;
function getValueTree(value, keyValue = false, ignoreKeyword = false, _getTree = _getValueTree) {
    return (_getValueTree(value, keyValue, ignoreKeyword, _getTree) || {
        value,
        type: TreeNodeType.Invalid,
        children: [],
    });
}
exports.getValueTree = getValueTree;
function execComposed(str, open_delimiter, close_delimiter) {
    const l = str.length;
    if (str[0] === open_delimiter && str[l - 1] === close_delimiter) {
        let sq_open = false;
        let dq_open = false;
        let open = 0;
        for (let i = 1; i < l - 1; i++) {
            const c = str[i];
            const pc = str[i - 1];
            if (c === "'" && pc !== '\\') {
                if (!dq_open) {
                    sq_open = !sq_open;
                }
            }
            else if (c === '"' && pc !== '\\') {
                if (!sq_open) {
                    dq_open = !dq_open;
                }
            }
            else {
                if (!sq_open && !dq_open) {
                    if (c === open_delimiter) {
                        open++;
                    }
                    else if (c === close_delimiter) {
                        open--;
                        if (open === -1) {
                            return null;
                        }
                    }
                }
            }
        }
        if (open > 0) {
            return null;
        }
        return [str, str.substr(1, l - 2)];
    }
    else {
        return null;
    }
}
function execObject(str) {
    return execComposed(str, exports.OBJECT_OPEN, exports.OBJECT_CLOSE);
}
function execArray(str) {
    return execComposed(str, exports.ARRAY_OPEN, exports.ARRAY_CLOSE);
}
function _getValueTree(value, keyValue = false, ignoreKeyword = false, _getTree = _getValueTree) {
    value = value.trim();
    if (!ignoreKeyword) {
        const nullTest = /^null$/.exec(value);
        if (nullTest) {
            return exports.NULL_TREE;
        }
        const infinityTest = /^[+-]?Infinity$/.exec(value);
        if (infinityTest) {
            return {
                value,
                type: TreeNodeType.NumberLiteral,
                children: [],
            };
        }
    }
    const booleanLiteralTest = BOOLEAN_LITERAL_1.BOOLEAN_LITERAL_REGEX.exec(value);
    if (booleanLiteralTest) {
        return {
            value,
            type: TreeNodeType.BooleanLiteral,
            children: [],
        };
    }
    // const stringLiteralTest = STRING_LITERAL_REGEX.exec(value)
    // if (stringLiteralTest) {
    if ((value.startsWith('"') && value.endsWith('"')) ||
        (value.startsWith("'") && value.endsWith("'"))) {
        const startChar = value[0];
        let valid = true;
        for (let i = 1; i < value.length - 1; i++) {
            if (value[i] === startChar && value[i - 1] !== '\\') {
                valid = false;
                break;
            }
        }
        if (valid) {
            return {
                value,
                type: TreeNodeType.StringLiteral,
                children: [],
            };
        }
    }
    const numberLiteralTest = NUMBER_LITERAL_1.NUMBER_LITERAL_REGEX.exec(value);
    if (numberLiteralTest) {
        return {
            value,
            type: TreeNodeType.NumberLiteral,
            children: [],
        };
    }
    const arithmeticExpressionTest = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?(?:[\s]*[+\-*/][\s]*-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)+$/g.exec(value);
    if (arithmeticExpressionTest) {
        return {
            value,
            type: TreeNodeType.ArithmeticExpression,
            children: [],
        };
    }
    const unitTest = /^\$\{(.*)\}$/is.exec(value);
    if (unitTest) {
        return {
            value,
            type: TreeNodeType.Unit,
            children: [],
        };
    }
    const regexLiteralTest = /^\/.+\/$/.exec(value);
    if (regexLiteralTest) {
        return {
            value,
            type: TreeNodeType.RegexLiteral,
            children: [],
        };
    }
    // this check must come after keywords (null, string, etc.)
    if (ignoreKeyword) {
        const identifierTest = IDENTIFIER_1.IDENTIFIER_REGEX.test(value);
        if (identifierTest) {
            return {
                value,
                type: TreeNodeType.Identifier,
                children: [],
            };
        }
    }
    if (keyValue) {
        const keyValueTest = /^("[^"]*"|'[^']*'|[^:{]*):([^]*)$/.exec(value);
        if (keyValueTest) {
            let k = _getTree(keyValueTest[1], false, true);
            if (!_isValidObjKeyType(k)) {
                return {
                    value,
                    type: TreeNodeType.Invalid,
                    children: [],
                };
            }
            const value_value = keyValueTest[2];
            const v = _getTree(value_value) || {
                value: value_value,
                type: TreeNodeType.Invalid,
                children: [],
            };
            const children = [k, v];
            return {
                value,
                type: TreeNodeType.KeyValue,
                children,
            };
        }
        else {
            return {
                value,
                type: TreeNodeType.Invalid,
                children: [],
            };
        }
    }
    const objectLiteralTest = execObject(value);
    if (objectLiteralTest) {
        const commaSeparated = objectLiteralTest[1];
        const children = _getCommaSeparated(commaSeparated, true, true, _getTree);
        return {
            value,
            type: TreeNodeType.ObjectLiteral,
            children,
        };
    }
    const arrayLiteralTest = execArray(value);
    if (arrayLiteralTest) {
        const commaSeparated = arrayLiteralTest[1];
        const children = _getCommaSeparated(commaSeparated, false, false, _getTree);
        return {
            value,
            type: TreeNodeType.ArrayLiteral,
            children,
        };
    }
    const urlTest = /^unit:\/\/.+$/i.exec(value);
    if (urlTest) {
        return {
            value,
            type: TreeNodeType.Url,
            children: [],
        };
    }
    return undefined;
}
function _extractGenerics(specs, value, type) {
    var _a, _b;
    let generics = {};
    switch (type.type) {
        case TreeNodeType.Generic: {
            const valueTypeTree = _getValueType(specs, value);
            const valueTypeTreeStr = _stringify(valueTypeTree);
            return { [type.value]: valueTypeTreeStr };
        }
        case TreeNodeType.Identifier:
        case TreeNodeType.StringLiteral:
        case TreeNodeType.BooleanLiteral:
        case TreeNodeType.NumberLiteral:
        case TreeNodeType.Object:
        case TreeNodeType.String:
        case TreeNodeType.Number:
        case TreeNodeType.Boolean:
        case TreeNodeType.Any:
        case TreeNodeType.ArithmeticExpression:
            break;
        case TreeNodeType.ArrayLiteral:
        case TreeNodeType.ObjectLiteral: {
            const typeKeyValueMap = getObjLiteralKeyValueMap(type);
            const valueKeyValueMap = getObjLiteralKeyValueMap(value);
            for (const key in typeKeyValueMap) {
                const _key = key.endsWith('?') ? key.slice(0, -1) : key;
                const childGenerics = _extractGenerics(specs, (_a = valueKeyValueMap[_key]) !== null && _a !== void 0 ? _a : valueKeyValueMap[key], (_b = typeKeyValueMap[_key]) !== null && _b !== void 0 ? _b : typeKeyValueMap[key]);
                for (const name in childGenerics) {
                    const childGeneric = childGenerics[name];
                    if (!generics[name]) {
                        generics[name] = childGeneric;
                    }
                    else if (generics[name] !== childGeneric) {
                        throw `Found two possible types for generic ${name}: ${generics[name]} and ${childGeneric}`;
                    }
                }
            }
            break;
        }
        case TreeNodeType.Or:
        case TreeNodeType.And:
            for (const child of type.children) {
                if (_hasGeneric(child)) {
                    generics = Object.assign(Object.assign({}, generics), _extractGenerics(specs, value, child));
                }
            }
            break;
        case TreeNodeType.ArrayExpression:
        case TreeNodeType.ObjectExpression:
            return _extractGenerics(specs, value.children[0] || exports.ANY_TREE, type.children[0]);
        case TreeNodeType.Expression:
            // TODO
            break;
        case TreeNodeType.KeyValue:
            return _extractGenerics(specs, value.children[1], type.children[1]);
        case TreeNodeType.Class:
            if (value.type === TreeNodeType.Class ||
                value.type === TreeNodeType.ClassLiteral) {
                const valueChild = value.children[0];
                const _valueTree = valueChild
                    ? getTree(valueChild.value.substring(1, valueChild.value.length - 1))
                    : exports.ANY_TREE;
                return _extractGenerics(specs, _valueTree, type.children[0] || exports.ANY_TREE);
            }
            else {
                break;
            }
    }
    return generics;
}
exports._extractGenerics = _extractGenerics;
exports.NULL_TREE = {
    value: 'null',
    type: TreeNodeType.Null,
    children: [],
};
exports.STRING_TREE = {
    value: 'string',
    type: TreeNodeType.String,
    children: [],
};
exports.BOOLEAN_TREE = {
    value: 'boolean',
    type: TreeNodeType.Boolean,
    children: [],
};
exports.NUMBER_TREE = {
    value: 'number',
    type: TreeNodeType.Number,
    children: [],
};
exports.ANY_TREE = {
    value: 'any',
    type: TreeNodeType.Any,
    children: [],
};
exports.REGEX_TREE = {
    value: 'regex',
    type: TreeNodeType.Regex,
    children: [],
};
exports.OBJECT_TREE = {
    value: 'object',
    type: TreeNodeType.Object,
    children: [],
};
function basePinsSpecToParams(pins) {
    return (0, f_1.keys)(pins)
        .map((pinId) => {
        const { type } = pins[pinId];
        return `${(0, string_1.removeWhiteSpace)(pinId)}:${type}`;
    })
        .join(',');
}
function _getValueType(specs, tree) {
    let children = [];
    switch (tree.type) {
        case TreeNodeType.Invalid:
        case TreeNodeType.Null:
        case TreeNodeType.Identifier:
            return tree;
        case TreeNodeType.StringLiteral:
            return exports.STRING_TREE;
        case TreeNodeType.BooleanLiteral:
            return exports.BOOLEAN_TREE;
        case TreeNodeType.NumberLiteral:
            return exports.NUMBER_TREE;
        case TreeNodeType.RegexLiteral:
            return exports.REGEX_TREE;
        case TreeNodeType.ClassLiteral: {
            const spec = specs[tree.value];
            return {
                value: `(${basePinsSpecToParams(spec.inputs)})=>(${basePinsSpecToParams(spec.outputs)})`,
                type: TreeNodeType.ClassLiteral,
                children: [],
            };
        }
        case TreeNodeType.ArrayLiteral: {
            const type = tree.children.length > 0
                ? _getValueType(specs, tree.children[0]) // AD HOC should consider all children
                : getTree('<T>');
            // : getTree('any')
            return {
                value: `${type.value}[]`,
                type: TreeNodeType.ArrayExpression,
                children: [type],
            };
        }
        case TreeNodeType.ObjectLiteral:
            children = tree.children.map((c) => _getValueType(specs, c));
            return {
                value: `{${children.map((c) => c.value).join(',')}}`,
                type: TreeNodeType.ObjectLiteral,
                children,
            };
        case TreeNodeType.KeyValue: {
            const key = tree.children[0];
            const value = _getValueType(specs, tree.children[1]);
            children = [key, value];
            return {
                value: `${key.value}:${value.value}`,
                type: TreeNodeType.KeyValue,
                children,
            };
        }
        case TreeNodeType.Unit: {
            const { value } = tree;
            const bundle = (0, idFromUnitValue_1.evaluateBundleStr)(value, specs, {}); // TODO
            const specId = (0, idFromUnitValue_1.idFromUnitValue)(value, specs, {}); // TODO
            const spec = specs[specId] || bundle.specs[specId];
            if (!spec) {
                throw new Error('Spec not found: ' + specId);
            }
            const { type = '`U`&`C`&`G`' } = spec;
            return getTree(type);
        }
        default:
            return tree;
    }
}
exports._getValueType = _getValueType;
function getValueType(specs, value) {
    const valueTree = getTree(value);
    const typeTree = _getValueType(specs, valueTree);
    return typeTree.value;
}
exports.getValueType = getValueType;
function _stringify(tree) {
    switch (tree.type) {
        case TreeNodeType.Invalid:
        case TreeNodeType.Null:
        case TreeNodeType.Generic:
        case TreeNodeType.Identifier:
        case TreeNodeType.StringLiteral:
        case TreeNodeType.BooleanLiteral:
        case TreeNodeType.NumberLiteral:
        case TreeNodeType.Object:
        case TreeNodeType.String:
        case TreeNodeType.Number:
        case TreeNodeType.Any:
        case TreeNodeType.Boolean:
            return tree.value;
        case TreeNodeType.ArrayLiteral: {
            const children = tree.children;
            return `[${children.map(_stringify).join(',')}]`;
        }
        case TreeNodeType.ObjectLiteral:
            return `{${tree.children.map(_stringify).join(',')}}`;
        case TreeNodeType.Or:
        case TreeNodeType.And:
            return tree.value;
        case TreeNodeType.ArrayExpression:
            return `${tree.children[0].value}[]`;
        case TreeNodeType.ObjectExpression:
            return `${tree.children[0].value}{}`;
        case TreeNodeType.Expression:
            // TODO
            return '';
        case TreeNodeType.KeyValue:
            return `${_stringify(tree.children[0])}${(!!tree.children[1] && `:${_stringify(tree.children[1])}`) || ''}`;
        default:
            return tree.value;
    }
}
exports._stringify = _stringify;
function _filterEmptyNodes(tree) {
    if (isCompositeType(tree.type)) {
        const children = tree.children.map(_filterEmptyNodes);
        const filteredTree = getTree(_stringify(Object.assign(Object.assign({}, tree), { children: children.filter((c) => !!c.value) })));
        return filteredTree;
    }
    else {
        return tree;
    }
}
exports._filterEmptyNodes = _filterEmptyNodes;
function filterEmptyNodes(value) {
    const tree = getTree(value);
    return _filterEmptyNodes(tree);
}
exports.filterEmptyNodes = filterEmptyNodes;
function findGenerics(value) {
    const tree = getTree(value);
    return _findGenerics(tree);
}
exports.findGenerics = findGenerics;
function _findGenerics(tree) {
    if (tree.type === TreeNodeType.Generic) {
        return new Set([tree.value]);
    }
    else {
        return tree.children.reduce((acc, c) => new Set([...acc, ..._findGenerics(c)]), new Set([]));
    }
}
exports._findGenerics = _findGenerics;
function extractGenerics(specs, value, type) {
    const valueTree = getTree(value);
    const typeTree = getTree(type);
    return _extractGenerics(specs, valueTree, typeTree);
}
exports.extractGenerics = extractGenerics;
function _applyGenerics(tree, map) {
    switch (tree.type) {
        case TreeNodeType.Generic: {
            const replacement = map[tree.value];
            if (replacement) {
                const replacementTree = getTree(replacement);
                return {
                    value: replacement,
                    type: replacementTree.type,
                    children: replacementTree.children,
                };
            }
            else {
                return tree;
            }
        }
        case TreeNodeType.Invalid:
        case TreeNodeType.Identifier:
        case TreeNodeType.StringLiteral:
        case TreeNodeType.BooleanLiteral:
        case TreeNodeType.NumberLiteral:
        case TreeNodeType.Null:
        case TreeNodeType.Object:
        case TreeNodeType.Any:
        case TreeNodeType.String:
        case TreeNodeType.Unit:
        case TreeNodeType.Number:
        case TreeNodeType.Boolean:
            return tree;
        case TreeNodeType.ObjectLiteral:
        case TreeNodeType.ArrayLiteral:
        case TreeNodeType.Or:
        case TreeNodeType.And:
        case TreeNodeType.ArrayExpression:
        case TreeNodeType.ObjectExpression:
        case TreeNodeType.Expression:
        case TreeNodeType.KeyValue:
        case TreeNodeType.ClassLiteral:
        case TreeNodeType.PropExpression: {
            const children = tree.children.map((child) => _applyGenerics(child, map));
            const value = tree.children.reduce((acc, child, i) => {
                return acc.replace(child.value, children[i].value);
            }, tree.value);
            return Object.assign(Object.assign({}, tree), { value,
                children });
        }
        case TreeNodeType.Class: {
            const children = tree.children.map((child) => _applyGenerics(child, map));
            const value = tree.children.reduce((acc, child, i) => {
                const _child = children[i];
                return acc.replace(trimSides(child.value), _child.type === TreeNodeType.Generic
                    ? trimSides(_child.value)
                    : _child.value);
            }, tree.value);
            return Object.assign(Object.assign({}, tree), { value,
                children });
        }
        default:
            throw new MethodNotImplementedError_1.MethodNotImplementedError();
    }
}
exports._applyGenerics = _applyGenerics;
function applyGenerics(type, map) {
    const typeTree = getTree(type);
    const valueTree = _applyGenerics(typeTree, map);
    const { value } = valueTree;
    return value;
}
exports.applyGenerics = applyGenerics;
function _isEmtpyString(str) {
    return !!/^ *$/.exec(str);
}
function _getDelimiterSeparated(value, keyValue = false, ignoreKeyword = false, delimiter, _getTree) {
    value = value.trim();
    let objectOpenCount = 0;
    let arrayOpenCount = 0;
    let singleQuoteOpen = false;
    let doubleQuoteOpen = false;
    let pos = 0;
    let lastStop = 0;
    let pushNext = false;
    const children = [];
    let prevChar = undefined;
    while (pos < value.length) {
        const char = value[pos];
        if (!doubleQuoteOpen && !singleQuoteOpen) {
            if (char === exports.OBJECT_OPEN) {
                objectOpenCount++;
            }
            else if (char === exports.OBJECT_CLOSE) {
                objectOpenCount--;
            }
            if (char === exports.ARRAY_OPEN) {
                arrayOpenCount++;
            }
            else if (char === exports.ARRAY_CLOSE) {
                arrayOpenCount--;
            }
        }
        if (!singleQuoteOpen) {
            if (char === exports.DOUBLE_QUOTE && prevChar !== '\\') {
                doubleQuoteOpen = !doubleQuoteOpen;
            }
        }
        if (!doubleQuoteOpen) {
            if (char === exports.SINGLE_QUOTE && prevChar !== '\\') {
                singleQuoteOpen = !singleQuoteOpen;
            }
        }
        if (char === delimiter &&
            objectOpenCount === 0 &&
            arrayOpenCount === 0 &&
            !singleQuoteOpen &&
            !doubleQuoteOpen) {
            const childString = value.substring(lastStop, pos);
            // if (_isEmtpyString(childString)) {
            //   break
            // }
            children.push(_getTree(childString, keyValue, ignoreKeyword));
            lastStop = pos + 1;
            pushNext = true;
        }
        prevChar = char;
        pos++;
    }
    // push last element
    const lastChildString = value.substring(lastStop, value.length);
    if (!_isEmtpyString(lastChildString) || pushNext) {
        children.push(_getTree(lastChildString, keyValue, ignoreKeyword));
    }
    return children;
}
function _getCommaSeparated(value, keyValue = false, ignoreKeyword = false, _getTree) {
    return _getDelimiterSeparated(value, keyValue, ignoreKeyword, exports.COMMA, _getTree);
}
function _getNodeAtPath(root, path) {
    let cursor = root;
    for (const key of path) {
        if (cursor) {
            cursor = cursor.children[key];
        }
        else {
            return null;
        }
    }
    return cursor;
}
exports._getNodeAtPath = _getNodeAtPath;
function getNodeAtPath(data, path) {
    const tree = getTree(data);
    const node = _getNodeAtPath(tree, path);
    return (node && node.value) || null;
}
exports.getNodeAtPath = getNodeAtPath;
function _getNextNode(node, path, direction) {
    const nextNodePath = _getNextNodePath(node, path, direction);
    return nextNodePath && _getNodeAtPath(node, nextNodePath);
}
exports._getNextNode = _getNextNode;
function getNextNode(data, path, direction) {
    const tree = getTree(data);
    const nextNode = _getNextNode(tree, path, direction);
    return nextNode && nextNode.value;
}
exports.getNextNode = getNextNode;
function _getNodePaths(root) {
    const paths = [];
    _traverse(root, (node, path) => {
        paths.push(path);
    });
    return paths;
}
function _getNodesAndPaths(root) {
    const paths = [];
    _traverse(root, (node, path) => {
        paths.push({ node, path });
    });
    return paths;
}
function _pathEqual(a, b) {
    if (a.length !== b.length) {
        return false;
    }
    for (let i = 0; i < a.length; i++) {
        if (a[i] !== b[i]) {
            return false;
        }
    }
    return true;
}
// TODO
// f([], []) === 0
// f([], [0]) === 1
// f([], [0]) === 1
// f([], [1]) === 2
// f([0], [1]) === 1
// f([1], [1]) === 0
// f([1], [1, 0]) === 1 (?)
// f([1], [1, 0, 0]) === 1 (?)
function pathDistance(a, b) {
    return 0;
}
// Given a path and a direction, return the next path in preorder order traversal
// f("[0, 1, 2]", [1], 1) === [2]
// f("{ a: { b: "c" }, d: "f" }", [], 1) === [0]
// f("{ a: { b: "c" }, d: "f" }", [0, 1, 0, 1], 1) === [1]
function _getNextNodePath(root, path, direction) {
    const paths = _getNodePaths(root);
    for (let i = 0; i < paths.length; i++) {
        const p = paths[i];
        if (_pathEqual(path, p)) {
            return paths[i + direction];
        }
    }
    return null;
}
exports._getNextNodePath = _getNextNodePath;
function getNextNodePath(data, path, direction) {
    const tree = getTree(data);
    return _getNextNodePath(tree, path, direction);
}
exports.getNextNodePath = getNextNodePath;
function _getNextLeafPath(root, path, direction) {
    const nodesAndPaths = _getNodesAndPaths(root);
    for (let i = 0; i < nodesAndPaths.length; i++) {
        const { path: p } = nodesAndPaths[i];
        if (_pathEqual(path, p)) {
            for (let j = i + direction; j >= 0 && j < nodesAndPaths.length; j = j + direction) {
                const { node, path: p } = nodesAndPaths[j];
                if (node.children.length === 0) {
                    return p;
                }
            }
        }
    }
    return null;
}
exports._getNextLeafPath = _getNextLeafPath;
function getNextLeafPath(data, path, direction) {
    const tree = getTree(data);
    return _getNextLeafPath(tree, path, direction);
}
exports.getNextLeafPath = getNextLeafPath;
// TODO
function _getNextSiblingPath(tree, path, direction) {
    return _getNextLeafPath(tree, path, direction);
}
exports._getNextSiblingPath = _getNextSiblingPath;
// Here is an idea for the mechanics:
// 1) Arrow key right or left -> go right or left
// 2) Arrow down -> go lower level
// 3) Arrow up -> go uper level
function getNextSiblingPath(value, path, direction) {
    const tree = getTree(value);
    return _getNextLeafPath(tree, path, direction);
}
exports.getNextSiblingPath = getNextSiblingPath;
function getParentPath(path) {
    if (path.length === 0) {
        return null;
    }
    return path.slice(0, path.length - 1);
}
exports.getParentPath = getParentPath;
function getParent(root, path) {
    const tree = getTree(root);
    const parent = _getParent(tree, path);
    if (!parent) {
        return null;
    }
    return parent.value;
}
exports.getParent = getParent;
function _getLastLeaf(root) {
    let cursor = root;
    while (cursor.children.length > 0) {
        const last_child = cursor.children[cursor.children.length - 1];
        cursor = last_child;
    }
    return cursor;
}
exports._getLastLeaf = _getLastLeaf;
function _getLastLeafPath(root) {
    let path = [];
    let cursor = root;
    let l = cursor.children.length;
    while (l > 0) {
        const last_child_index = l - 1;
        path.push(last_child_index);
        const last_child = cursor.children[last_child_index];
        cursor = last_child;
        l = cursor.children.length;
    }
    return path;
}
exports._getLastLeafPath = _getLastLeafPath;
function getLastLeafPath(root) {
    const tree = getTree(root);
    const path = _getLastLeafPath(tree);
    return path;
}
exports.getLastLeafPath = getLastLeafPath;
function getLastLeaf(root) {
    const tree = getTree(root);
    const last = _getLastLeaf(tree);
    const { value } = last;
    return value;
}
exports.getLastLeaf = getLastLeaf;
function _getParent(root, path) {
    const parentPath = getParentPath(path);
    if (!parentPath) {
        return null;
    }
    return _getNodeAtPath(root, parentPath);
}
exports._getParent = _getParent;
function _insertNodeAt(root, path, node) {
    if (path.length === 0) {
        return node;
    }
    root = (0, object_1.clone)(root);
    const parentPath = getParentPath(path);
    const parentNode = _getNodeAtPath(root, parentPath);
    const last = path[path.length - 1];
    parentNode.children.splice(last, 0, node);
    let i = 0;
    let n = root;
    do {
        n.value = _stringify(n);
        n = n.children[path[i]];
        i++;
    } while (i < path.length - 1);
    // TODO performance
    return getTree(root.value);
}
exports._insertNodeAt = _insertNodeAt;
function insertNodeAt(root, path, node) {
    const rootTree = getTree(root);
    const nodeTree = getTree(node);
    const updatedRoot = _insertNodeAt(rootTree, path, nodeTree);
    return updatedRoot.value;
}
exports.insertNodeAt = insertNodeAt;
function _updateNodeAt(root, path, update) {
    if (path.length === 0) {
        return update;
    }
    root = (0, object_1.clone)(root);
    const parentPath = getParentPath(path);
    const parentNode = _getNodeAtPath(root, parentPath);
    const last = path[path.length - 1];
    parentNode.children[last] = update;
    let i = 0;
    let n = root;
    do {
        n.value = _stringify(n);
        n = n.children[path[i]];
        i++;
    } while (i < path.length - 1);
    // TODO performance
    return getTree(root.value);
}
exports._updateNodeAt = _updateNodeAt;
function updateNodeAt(root, path, update) {
    const rootTree = getTree(root);
    const updateTree = getTree(update);
    const updatedRoot = _updateNodeAt(rootTree, path, updateTree);
    return updatedRoot.value;
}
exports.updateNodeAt = updateNodeAt;
exports.EMPTY_TREE = getTree('');
function _removeNodeAt(root, path) {
    if (path.length === 0) {
        return exports.EMPTY_TREE;
    }
    const parentPath = getParentPath(path);
    const parentNode = _getNodeAtPath(root, parentPath);
    const nodeIndex = path[path.length - 1];
    const updatedParentNode = (0, object_1.clone)(parentNode);
    updatedParentNode.children.splice(nodeIndex, 1);
    return _updateNodeAt(root, parentPath, getTree(_stringify(updatedParentNode)));
}
exports._removeNodeAt = _removeNodeAt;
function removeNodeAt(root, path) {
    const rootTree = getTree(root);
    const updatedRoot = _removeNodeAt(rootTree, path);
    return updatedRoot.value;
}
exports.removeNodeAt = removeNodeAt;
function _matchAllExcTypes(specs, a, b) {
    return (0, array_1.matchAllExc)(a, b, (a, b) => _isTypeMatch(specs, a, b));
}
exports._matchAllExcTypes = _matchAllExcTypes;
function matchAllExcTypes(specs, a, b) {
    return (0, array_1.matchAllExc)(a, b, (a, b) => isTypeMatch(specs, a, b));
}
exports.matchAllExcTypes = matchAllExcTypes;
//# sourceMappingURL=parser.js.map