import { __assign } from "tslib";
import { isPathEqual } from './path-fns';
import { getNodeDepth } from './node-fns';
export function clone(from, cloneNode) {
    var stack = from.map(function (n) { return ({
        node: cloneNode(n, null),
        children: n.children,
    }); });
    var trees = stack.map(function (n) { return n.node; });
    var _loop_1 = function () {
        var state = stack.pop();
        if (!state) {
            return "continue";
        }
        var node = state.node, children = state.children;
        children === null || children === void 0 ? void 0 : children.forEach(function (n) {
            var m = cloneNode(n, node);
            stack.push({ node: m, children: n.children });
            node.children.push(m);
        });
    };
    while (stack.length > 0) {
        _loop_1();
    }
    return trees;
}
export function insertPath(trees, path, createNode) {
    path = path.slice();
    var stack = [
        {
            parent: null,
            children: trees,
            node: path.shift(),
        },
    ];
    var _loop_2 = function () {
        var frame = stack.pop();
        if (!frame) {
            return "continue";
        }
        var children = frame.children, node = frame.node;
        if (!node) {
            return "break";
        }
        var nval = node.value;
        var matchedNode = children.find(function (n) { return n.value === nval; });
        if (!matchedNode) {
            matchedNode = createNode(node, frame.parent);
            children.push(matchedNode);
        }
        stack.push({
            parent: matchedNode,
            children: matchedNode.children,
            node: path.shift(),
        });
    };
    while (stack.length > 0) {
        var state_1 = _loop_2();
        if (state_1 === "break")
            break;
    }
    return trees;
}
var Forest = (function () {
    function Forest(from) {
        this.trees = this.build(from);
    }
    Forest.prototype.build = function (from) {
        return clone(from, createNode);
    };
    Forest.prototype.reducePath = function (callback, initialValue) {
        var stack = reverse(this.trees);
        var path = [];
        var acc = initialValue;
        var earlyExit = false;
        var terminate = function () {
            earlyExit = true;
        };
        while (stack.length > 0) {
            var node = stack.pop();
            if (!node) {
                continue;
            }
            var depth = getNodeDepth(node);
            while (depth <= path.length) {
                path.pop();
            }
            path.push(node);
            if (node.children.length > 0) {
                reversePush(stack, node.children);
            }
            else {
                acc = callback(acc, path.slice(), terminate);
                if (earlyExit) {
                    break;
                }
            }
        }
        return acc;
    };
    Forest.prototype.reduceNode = function (callback, initialValue) {
        var stack = reverse(this.trees);
        var acc = initialValue;
        var earlyExit = false;
        var terminate = function () {
            earlyExit = true;
        };
        while (stack.length > 0) {
            var node = stack.pop();
            if (!node) {
                continue;
            }
            acc = callback(acc, node, terminate);
            if (earlyExit) {
                break;
            }
            if (node.children.length > 0) {
                reversePush(stack, node.children);
            }
        }
        return acc;
    };
    Forest.prototype.reduceNodeDfs = function (callback, initialValue) {
        var stack = this.trees.map(function (n) { return ({
            node: n,
            phase: 'recurse',
        }); });
        var acc = initialValue;
        var earlyExit = false;
        var terminate = function () {
            earlyExit = true;
        };
        while (stack.length > 0) {
            var frame = stack.pop();
            if (!frame) {
                continue;
            }
            var node = frame.node, phase = frame.phase;
            if (phase === 'recurse') {
                stack.push({
                    node: node,
                    phase: 'visit',
                });
                node.children.forEach(function (node) {
                    stack.push({
                        node: node,
                        phase: 'recurse',
                    });
                });
            }
            else if (phase === 'visit') {
                acc = callback(acc, node, terminate);
                if (earlyExit) {
                    break;
                }
            }
        }
        return acc;
    };
    Forest.prototype.sort = function (paths) {
        return this.reducePath(function (acc, path) {
            if (paths.some(function (x) { return isPathEqual(x, path); })) {
                acc.push(path);
            }
            return acc;
        }, []);
    };
    Forest.prototype.clone = function () {
        return new Forest(this.trees);
    };
    Forest.prototype.insertPath = function (path) {
        insertPath(this.trees, path, createNode);
        return this;
    };
    Forest.prototype.getTrees = function () {
        return this.trees;
    };
    Forest.prototype.getPathByValue = function (values) {
        return this.reducePath(function (found, path, terminate) {
            if (values.length > path.length || values.length === 0) {
                return found;
            }
            var size = values.length;
            var i;
            for (i = 0; i < size; i++) {
                if (path[i].value !== values[i]) {
                    return found;
                }
            }
            terminate();
            return i === path.length ? path : path.slice(0, size);
        }, []);
    };
    Forest.prototype.getPaths = function (startNode, predicate) {
        var depth = getNodeDepth(startNode);
        var idx = depth - 1;
        var value = startNode.value;
        return this.reducePath(function (acc, path) {
            if (path.length > idx &&
                path[idx].value === value &&
                (!predicate || predicate(path))) {
                acc.push(path);
            }
            return acc;
        }, []);
    };
    return Forest;
}());
export { Forest };
function reverse(arr) {
    var ret = arr.slice();
    ret.reverse();
    return ret;
}
function reversePush(arr, from) {
    for (var i = from.length - 1; i >= 0; i--) {
        arr.push(from[i]);
    }
    return arr;
}
function createNode(node, parent) {
    return __assign(__assign({}, node), { parent: parent, children: [] });
}
