"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2018, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
var algorithm_1 = require("@phosphor/algorithm");
/**
 * A generic B+ tree.
 *
 * #### Notes
 * Most operations have `O(log32 n)` or better complexity.
 */
var BPlusTree = /** @class */ (function () {
    /**
     * Construct a new B+ tree.
     *
     * @param cmp - The item comparison function for the tree.
     */
    function BPlusTree(cmp) {
        this._root = new Private.LeafNode();
        this.cmp = cmp;
    }
    Object.defineProperty(BPlusTree.prototype, "isEmpty", {
        /**
         * Whether the tree is empty.
         *
         * #### Complexity
         * `O(1)`
         */
        get: function () {
            return this._root.size === 0;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(BPlusTree.prototype, "size", {
        /**
         * The size of the tree.
         *
         * #### Complexity
         * `O(1)`
         */
        get: function () {
            return this._root.size;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(BPlusTree.prototype, "first", {
        /**
         * The first item in the tree.
         *
         * This is `undefined` if the tree is empty.
         *
         * #### Complexity
         * `O(log32 n)`
         */
        get: function () {
            var node = Private.firstLeaf(this._root);
            return node.size > 0 ? node.items[0] : undefined;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(BPlusTree.prototype, "last", {
        /**
         * The last item in the tree.
         *
         * This is `undefined` if the tree is empty.
         *
         * #### Complexity
         * `O(log32 n)`
         */
        get: function () {
            var node = Private.lastLeaf(this._root);
            return node.size > 0 ? node.items[node.size - 1] : undefined;
        },
        enumerable: true,
        configurable: true
    });
    /**
     * Create an iterator over the items in the tree.
     *
     * @returns A new iterator starting with the first item.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    BPlusTree.prototype.iter = function () {
        return Private.iterItems(this._root);
    };
    /**
     * Create a reverse iterator over the items in the tree.
     *
     * @returns A new iterator starting with the last item.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    BPlusTree.prototype.retro = function () {
        return Private.retroItems(this._root);
    };
    /**
     * Create an iterator for a slice of items in the tree.
     *
     * @param start - The index of the first item, inclusive. This
     *   should be `< stop`. Negative values are taken as an offset
     *   from the end of the tree. The default is `0`.
     *
     * @param stop - The index of the last item, exclusive. This
     *   should be `> start`. Negative values are taken as an offset
     *   from the end of the tree. The default is `size`.
     *
     * @returns A new iterator starting with the specified item.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    BPlusTree.prototype.slice = function (start, stop) {
        return Private.sliceItems(this._root, start, stop);
    };
    /**
     * Create a reverse iterator for a slice of items in the tree.
     *
     * @param start - The index of the first item, inclusive. This
     *   should be `> stop`. Negative values are taken as an offset
     *   from the end of the tree. The default is `size - 1`.
     *
     * @param stop - The index of the last item, exclusive. This
     *   should be `< start`. Negative values are taken as an offset
     *   from the end of the tree. The default is `-size - 1`.
     *
     * @returns A new reverse iterator starting with the specified item.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    BPlusTree.prototype.retroSlice = function (start, stop) {
        return Private.retroSliceItems(this._root, start, stop);
    };
    /**
     * Get the item at a particular index.
     *
     * @param index - The index of the item of interest. Negative
     *   values are taken as an offset from the end of the tree.
     *
     * @returns The item at the specified index, or `undefined` if
     *   the index is out of range.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    BPlusTree.prototype.at = function (index) {
        return Private.itemAt(this._root, index);
    };
    /**
     * Test whether the tree has an item which matches a key.
     *
     * @param key - The key of interest.
     *
     * @param cmp - A function which compares an item against the key.
     *
     * @returns `true` if the tree has an item which matches the given
     *   key, `false` otherwise.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    BPlusTree.prototype.has = function (key, cmp) {
        return Private.hasItem(this._root, key, cmp);
    };
    /**
     * Get the index of an item which matches a key.
     *
     * @param key - The key of interest.
     *
     * @param cmp - A function which compares an item against the key.
     *
     * @returns The index of the item which matches the given key. A
     *   negative value means that a matching item does not exist in
     *   the tree, but if one did it would reside at `-index - 1`.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    BPlusTree.prototype.indexOf = function (key, cmp) {
        return Private.indexOf(this._root, key, cmp);
    };
    /**
     * Get the item which matches a key.
     *
     * @param item - The key of interest.
     *
     * @param cmp - A function which compares an item against the key.
     *
     * @returns The item which matches the given key, or `undefined` if
     *   the tree does not have a matching item.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    BPlusTree.prototype.get = function (key, cmp) {
        return Private.getItem(this._root, key, cmp);
    };
    /**
     * Assign new items to the tree, replacing all current items.
     *
     * @param items - The items to assign to the tree.
     *
     * #### Complexity
     * `O(n log32 n)`
     */
    BPlusTree.prototype.assign = function (items) {
        this.clear();
        this.update(items);
    };
    /**
     * Insert an item into the tree.
     *
     * @param item - The item of interest.
     *
     * @returns If the given item matches an existing item in the tree,
     *   the given item will replace it, and the existing item will be
     *   returned. Otherwise, this method returns `undefined`.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    BPlusTree.prototype.insert = function (item) {
        var existing = Private.insertItem(this._root, item, this.cmp);
        this._root = Private.maybeSplitRoot(this._root);
        return existing;
    };
    /**
     * Update the tree with multiple items.
     *
     * @param items - The items to insert into the tree.
     *
     * #### Complexity
     * `O(k log32 n)`
     */
    BPlusTree.prototype.update = function (items) {
        var _this = this;
        algorithm_1.each(items, function (item) { _this.insert(item); });
    };
    /**
     * Delete an item which matches a particular key.
     *
     * @param key - The key of interest.
     *
     * @param cmp - A function which compares an item against the key.
     *
     * @returns The item removed from the tree, or `undefined` if no
     *   item matched the given key.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    BPlusTree.prototype.delete = function (key, cmp) {
        var item = Private.deleteItem(this._root, key, cmp);
        this._root = Private.maybeExtractRoot(this._root);
        return item;
    };
    /**
     * Remove an item at a particular index.
     *
     * @param index - The index of the item to remove. Negative
     *   values are taken as an offset from the end of the tree.
     *
     * @returns The item removed from the tree, or `undefined` if
     *   the given index is out of range.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    BPlusTree.prototype.remove = function (index) {
        var item = Private.removeItem(this._root, index);
        this._root = Private.maybeExtractRoot(this._root);
        return item;
    };
    /**
     * Clear the contents of the tree.
     *
     * #### Complexity
     * `O(n)`
     */
    BPlusTree.prototype.clear = function () {
        Private.clear(this._root);
        this._root = new Private.LeafNode();
    };
    return BPlusTree;
}());
exports.BPlusTree = BPlusTree;
/**
 * The namespace for the `BPlusTree` class statics.
 */
(function (BPlusTree) {
    /**
     * Create a new B+ tree populated with the given items.
     *
     * @param items - The items to add to the tree.
     *
     * @param cmp - The item comparison function for the tree.
     *
     * @returns A new B+ tree populated with the given items.
     *
     * #### Complexity
     * `O(n log32 n)`
     */
    function from(items, cmp) {
        var tree = new BPlusTree(cmp);
        tree.assign(items);
        return tree;
    }
    BPlusTree.from = from;
})(BPlusTree = exports.BPlusTree || (exports.BPlusTree = {}));
exports.BPlusTree = BPlusTree;
/**
 * The namespace for the module implementation details.
 */
var Private;
(function (Private) {
    /**
     * A branch node in a B+ tree.
     */
    var BranchNode = /** @class */ (function () {
        function BranchNode() {
            /**
             * The left-most item of each child subtree.
             */
            this.items = [];
            /**
             * The cumulative sizes of each child subtree.
             */
            this.sizes = [];
            /**
             * The child nodes of this branch node.
             */
            this.children = [];
        }
        Object.defineProperty(BranchNode.prototype, "type", {
            /**
             * The discriminated type of the node.
             */
            get: function () {
                return 0 /* Branch */;
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(BranchNode.prototype, "size", {
            /**
             * The total number of items in the subtree.
             */
            get: function () {
                return this.sizes[this.sizes.length - 1];
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(BranchNode.prototype, "width", {
            /**
             * The tree width of the node.
             */
            get: function () {
                return this.children.length;
            },
            enumerable: true,
            configurable: true
        });
        return BranchNode;
    }());
    Private.BranchNode = BranchNode;
    /**
     * A leaf node in a B+ tree.
     */
    var LeafNode = /** @class */ (function () {
        function LeafNode() {
            /**
             * The next sibling leaf node of this leaf node.
             */
            this.next = null;
            /**
             * The previous sibling leaf node of this leaf node.
             */
            this.prev = null;
            /**
             * The items of the leaf.
             */
            this.items = [];
        }
        Object.defineProperty(LeafNode.prototype, "type", {
            /**
             * The discriminated type of the node.
             */
            get: function () {
                return 1 /* Leaf */;
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(LeafNode.prototype, "size", {
            /**
             * The total number of items in the leaf.
             */
            get: function () {
                return this.items.length;
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(LeafNode.prototype, "width", {
            /**
             * The tree width of the node.
             */
            get: function () {
                return this.items.length;
            },
            enumerable: true,
            configurable: true
        });
        return LeafNode;
    }());
    Private.LeafNode = LeafNode;
    /**
     * Get the first leaf node in the tree.
     *
     * @param node - The root node of interest.
     *
     * @returns The first leaf node in the tree.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    function firstLeaf(node) {
        while (node.type === 0 /* Branch */) {
            node = node.children[0];
        }
        return node;
    }
    Private.firstLeaf = firstLeaf;
    /**
     * Get the last leaf node in the tree.
     *
     * @param node - The root node of interest.
     *
     * @returns The last leaf node in the tree.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    function lastLeaf(node) {
        while (node.type === 0 /* Branch */) {
            node = node.children[node.children.length - 1];
        }
        return node;
    }
    Private.lastLeaf = lastLeaf;
    /**
     * Create a forward iterator for the items in the tree.
     *
     * @param node - The root node of interest.
     *
     * @returns A new forward iterator starting with the first item.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    function iterItems(node) {
        var leaf = firstLeaf(node);
        return new ForwardIterator(leaf, 0, -1);
    }
    Private.iterItems = iterItems;
    /**
     * Create a reverse iterator for the items in the tree.
     *
     * @param node - The root node of interest.
     *
     * @returns A new reverse iterator starting with the last item.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    function retroItems(node) {
        var leaf = lastLeaf(node);
        return new RetroIterator(leaf, leaf.size - 1, -1);
    }
    Private.retroItems = retroItems;
    /**
     * Create an iterator for a slice of items in the tree.
     *
     * @param node - The root node of interest.
     *
     * @param start - The index of the first item, inclusive. This
     *   should be `< stop`. Negative values are taken as an offset
     *   from the end of the tree. The default is `0`.
     *
     * @param stop - The index of the last item, exclusive. This
     *   should be `> start`. Negative values are taken as an offset
     *   from the end of the tree. The default is `size`.
     *
     * @returns A new iterator starting with the specified item.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    function sliceItems(node, start, stop) {
        // Normalize the start index.
        if (start === undefined) {
            start = 0;
        }
        else if (start < 0) {
            start = Math.max(0, start + node.size);
        }
        else {
            start = Math.min(start, node.size);
        }
        // Normalize the stop index.
        if (stop === undefined) {
            stop = node.size;
        }
        else if (stop < 0) {
            stop = Math.max(0, stop + node.size);
        }
        else {
            stop = Math.min(stop, node.size);
        }
        // Compute effective count.
        var count = Math.max(0, stop - start);
        // Bail early if there is nothing to iterate.
        if (count === 0) {
            return algorithm_1.empty();
        }
        // Find the starting leaf node and local index.
        while (node.type === 0 /* Branch */) {
            var i = findPivotIndexByIndex(node.sizes, start);
            if (i > 0)
                start -= node.sizes[i - 1];
            node = node.children[i];
        }
        // Return the forward iterator for the range.
        return new ForwardIterator(node, start, count);
    }
    Private.sliceItems = sliceItems;
    /**
     * Create a reverse iterator for a slice of items in the tree.
     *
     * @param node - The root node of interest.
     *
     * @param start - The index of the first item, inclusive. This
     *   should be `> stop`. Negative values are taken as an offset
     *   from the end of the tree. The default is `size - 1`.
     *
     * @param stop - The index of the last item, exclusive. This
     *   should be `< start`. Negative values are taken as an offset
     *   from the end of the tree. The default is `-size - 1`.
     *
     * @returns A new reverse iterator starting with the specified item.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    function retroSliceItems(node, start, stop) {
        // Normalize the start index.
        if (start === undefined) {
            start = node.size - 1;
        }
        else if (start < 0) {
            start = Math.max(-1, start + node.size);
        }
        else {
            start = Math.min(start, node.size - 1);
        }
        // Normalize the stop index.
        if (stop === undefined) {
            stop = -1;
        }
        else if (stop < 0) {
            stop = Math.max(-1, stop + node.size);
        }
        else {
            stop = Math.min(stop, node.size - 1);
        }
        // Compute the effective count.
        var count = Math.max(0, start - stop);
        // Bail early if there is nothing to iterate.
        if (count === 0) {
            return algorithm_1.empty();
        }
        // Find the starting leaf node and local index.
        while (node.type === 0 /* Branch */) {
            var i = findPivotIndexByIndex(node.sizes, start);
            if (i > 0)
                start -= node.sizes[i - 1];
            node = node.children[i];
        }
        // Return the retro iterator for the range.
        return new RetroIterator(node, start, count);
    }
    Private.retroSliceItems = retroSliceItems;
    /**
     * Get the item at the specified index.
     *
     * @param node - The root node of interest.
     *
     * @param index - The index of the item of interest. Negative
     *   values are taken as an offset from the end of the tree.
     *
     * @returns The item at the specified index, or `undefined` if
     *   the index is out of range.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    function itemAt(node, index) {
        // Wrap negative indices.
        if (index < 0) {
            index += node.size;
        }
        // Bail early if the index is out of range.
        if (index < 0 || index >= node.size) {
            return undefined;
        }
        // Find the containing leaf node and local index.
        while (node.type === 0 /* Branch */) {
            var i = findPivotIndexByIndex(node.sizes, index);
            if (i > 0)
                index -= node.sizes[i - 1];
            node = node.children[i];
        }
        // Return the item at the specified index.
        return node.items[index];
    }
    Private.itemAt = itemAt;
    /**
     * Test whether the tree contains an item which matches a key.
     *
     * @param node - The root node of interest.
     *
     * @param key - The key of interest.
     *
     * @param cmp - The key comparison function.
     *
     * @returns Whether the tree contains a matching item.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    function hasItem(node, key, cmp) {
        // Find the containing leaf node.
        while (node.type === 0 /* Branch */) {
            var i_1 = findPivotIndexByKey(node.items, key, cmp);
            node = node.children[i_1];
        }
        // Find the key index.
        var i = findKeyIndex(node.items, key, cmp);
        // Return whether or not the node contains a matching item.
        return i >= 0;
    }
    Private.hasItem = hasItem;
    /**
     * Get the index of the item which matches a key.
     *
     * @param node - The node of interest.
     *
     * @param key - The key of interest.
     *
     * @param cmp - The key comparison function.
     *
     * @returns The index of the item which matches the given key. A
     *   negative value means that a matching item does not exist in
     *   the tree, but if one did it would reside at `-index - 1`.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    function indexOf(node, key, cmp) {
        // Set up the global index.
        var index = 0;
        // Find the containing leaf node and global index.
        while (node.type === 0 /* Branch */) {
            var i_2 = findPivotIndexByKey(node.items, key, cmp);
            if (i_2 > 0)
                index += node.sizes[i_2 - 1];
            node = node.children[i_2];
        }
        // Find the key index.
        var i = findKeyIndex(node.items, key, cmp);
        // Return the final computed index.
        return i >= 0 ? index + i : -index + i;
    }
    Private.indexOf = indexOf;
    /**
     * Get the item for a particular key.
     *
     * @param node - The node of interest.
     *
     * @param key - The key of interest.
     *
     * @param cmp - The key comparison function.
     *
     * @returns The item for the specified key, or `undefined` if
     *   the tree does not have a matching item for the key.
     *
     * #### Complexity
     * `O(log32 n)`
     */
    function getItem(node, key, cmp) {
        // Find the containing leaf node.
        while (node.type === 0 /* Branch */) {
            var i_3 = findPivotIndexByKey(node.items, key, cmp);
            node = node.children[i_3];
        }
        // Find the key index.
        var i = findKeyIndex(node.items, key, cmp);
        // Return the item for the given key.
        return i >= 0 ? node.items[i] : undefined;
    }
    Private.getItem = getItem;
    /**
     * Insert an item into the tree.
     *
     * @param node - The root node of interest.
     *
     * @param item - The item of interest.
     *
     * @param cmp - The item comparison function.
     *
     * @returns If the given item matches an existing item in the tree,
     *   the given item will replace it, and the existing item will be
     *   returned. Otherwise, this function returns `undefined`.
     *
     * #### Complexity
     * `O(log32 n)`
     *
     * #### Notes
     * The root may be overfull after calling this function.
     */
    function insertItem(node, item, cmp) {
        // Handle leaf nodes first.
        if (node.type === 1 /* Leaf */) {
            // Find the index for the given item.
            var i_4 = findKeyIndex(node.items, item, cmp);
            // Fetch the existing item and insert the new item.
            var existing_1;
            if (i_4 >= 0) {
                existing_1 = node.items[i_4];
                node.items[i_4] = item;
            }
            else {
                existing_1 = undefined;
                algorithm_1.ArrayExt.insert(node.items, -i_4 - 1, item);
            }
            // Return the existing item.
            return existing_1;
        }
        // Find the pivot index for the insert.
        var i = findPivotIndexByKey(node.items, item, cmp);
        // Fetch the pivot child.
        var child = node.children[i];
        // Fetch the current size of the child.
        var prevSize = child.size;
        // Recursively insert the item into the child.
        var existing = insertItem(child, item, cmp);
        // Fetch the updated size of the child.
        var currSize = child.size;
        // Update the item state of the branch.
        node.items[i] = child.items[0];
        // Bail early if the child size did not change.
        if (prevSize === currSize) {
            return existing;
        }
        // Split the child if it's overfull.
        if (child.width > MAX_NODE_WIDTH) {
            var next = splitNode(child);
            algorithm_1.ArrayExt.insert(node.children, i + 1, next);
            algorithm_1.ArrayExt.insert(node.items, i + 1, next.items[0]);
        }
        // Update the dirty sizes of the branch.
        updateSizes(node, i);
        // Return the existing item.
        return existing;
    }
    Private.insertItem = insertItem;
    /**
     * Delete an item in the tree.
     *
     * @param node - The node of interest.
     *
     * @param key - The key of interest.
     *
     * @param cmp - The key comparison function.
     *
     * @returns The deleted item or `undefined`.
     *
     * #### Complexity
     * `O(log32 n)`
     *
     * #### Notes
     * The root may be underfull after calling this function.
     */
    function deleteItem(node, key, cmp) {
        // Handle leaf nodes first.
        if (node.type === 1 /* Leaf */) {
            // Find the index for the given key.
            var i_5 = findKeyIndex(node.items, key, cmp);
            // Bail early if the item does not exist.
            if (i_5 < 0) {
                return undefined;
            }
            // Remove the item at the computed index.
            return algorithm_1.ArrayExt.removeAt(node.items, i_5);
        }
        // Find the pivot index for the delete.
        var i = findPivotIndexByKey(node.items, key, cmp);
        // Fetch the pivot child.
        var child = node.children[i];
        // Fetch the current size of the child.
        var prevSize = child.size;
        // Recursively remove the item from the child.
        var item = deleteItem(child, key, cmp);
        // Fetch the updated size of the child.
        var currSize = child.size;
        // Bail early if the child size did not change.
        if (prevSize === currSize) {
            return item;
        }
        // Update the item state of the branch.
        node.items[i] = child.items[0];
        // Join the child if it's underfull.
        if (child.width < MIN_NODE_WIDTH) {
            i = joinChild(node, i);
        }
        // Update the dirty sizes of the branch.
        updateSizes(node, i);
        // Return the deleted item.
        return item;
    }
    Private.deleteItem = deleteItem;
    /**
     * Remove an item from the tree.
     *
     * @param node - The node of interest.
     *
     * @param index - The index of interest.
     *
     * @returns The removed item or `undefined`.
     *
     * #### Complexity
     * `O(log32 n)`
     *
     * #### Notes
     * The root may be underfull after calling this function.
     */
    function removeItem(node, index) {
        // Wrap negative indices.
        if (index < 0) {
            index += node.size;
        }
        // Bail early if the index is out of range.
        if (index < 0 || index >= node.size) {
            return undefined;
        }
        // Handle leaf nodes first.
        if (node.type === 1 /* Leaf */) {
            return algorithm_1.ArrayExt.removeAt(node.items, index);
        }
        // Find the pivot index for the remove.
        var i = findPivotIndexByIndex(node.sizes, index);
        if (i > 0)
            index -= node.sizes[i];
        // Fetch the pivot child.
        var child = node.children[i];
        // Recursively remove the item from the child.
        var item = removeItem(child, index);
        // Update the item state of the branch.
        node.items[i] = child.items[0];
        // Join the child if it's underfull.
        if (child.width < MIN_NODE_WIDTH) {
            i = joinChild(node, i);
        }
        // Update the dirty sizes of the branch.
        updateSizes(node, i);
        // Return the removed item.
        return item;
    }
    Private.removeItem = removeItem;
    /**
     * Recursively clear the contents of a node.
     *
     * @param node - The node of interest.
     *
     * #### Complexity
     * `O(n)`
     */
    function clear(node) {
        if (node.type === 0 /* Branch */) {
            algorithm_1.each(node.children, clear);
            node.children.length = 0;
            node.sizes.length = 0;
            node.items.length = 0;
        }
        else {
            node.items.length = 0;
            node.next = null;
            node.prev = null;
        }
    }
    Private.clear = clear;
    /**
     * Split a root node and create a new root, if needed.
     *
     * @param node - The root node of interest.
     *
     * @returns The new root node.
     */
    function maybeSplitRoot(node) {
        // Bail early if the current root is not overfull.
        if (node.width <= MAX_NODE_WIDTH) {
            return node;
        }
        // Create a new root branch node.
        var root = new BranchNode();
        // Split the node to the right and create a new sibling.
        var next = splitNode(node);
        // Add the sizes to the root.
        root.sizes[0] = node.size;
        root.sizes[1] = node.size + next.size;
        // Add the children to the root.
        root.children[0] = node;
        root.children[1] = next;
        // Add the items to the root.
        root.items[0] = node.items[0];
        root.items[1] = next.items[0];
        // Return the new root node.
        return root;
    }
    Private.maybeSplitRoot = maybeSplitRoot;
    /**
     * Extract a single node child as a new root, if needed.
     *
     * @param node - The root node of interest.
     *
     * @returns The new root node.
     */
    function maybeExtractRoot(node) {
        // Bail early if the node it already a leaf.
        if (node.type === 1 /* Leaf */) {
            return node;
        }
        // Bail early if the branch has more than one child.
        if (node.children.length > 1) {
            return node;
        }
        // Extract the sole remaining child as the new root.
        var root = node.children.pop();
        // Clear the rest of the node state.
        clear(node);
        // Return the new root.
        return root;
    }
    Private.maybeExtractRoot = maybeExtractRoot;
    /**
     * The maximum width for a node in the tree.
     */
    var MAX_NODE_WIDTH = 32;
    /**
     * The minimum width for a node in the tree.
     */
    var MIN_NODE_WIDTH = MAX_NODE_WIDTH >> 1;
    /**
     * A forward iterator for a B+ tree.
     */
    var ForwardIterator = /** @class */ (function () {
        /**
         * Construct a new forward iterator.
         *
         * @param node - The first leaf node in the chain.
         *
         * @param index - The local index of the first item.
         *
         * @param count - The number of items to iterate. A value `< 0`
         *   will iterate all available items.
         */
        function ForwardIterator(node, index, count) {
            this._node = node;
            this._index = index;
            this._count = count;
        }
        /**
         * Get an iterator over the object's values.
         *
         * @returns An iterator which yields the object's values.
         */
        ForwardIterator.prototype.iter = function () {
            return this;
        };
        /**
         * Create an independent clone of the iterator.
         *
         * @returns A new independent clone of the iterator.
         */
        ForwardIterator.prototype.clone = function () {
            return new ForwardIterator(this._node, this._index, this._count);
        };
        /**
         * Get the next value from the iterator.
         *
         * @returns The next value from the iterator, or `undefined`.
         */
        ForwardIterator.prototype.next = function () {
            if (this._node === null || this._count === 0) {
                return undefined;
            }
            if (this._index >= this._node.size) {
                this._node = this._node.next;
                this._index = 0;
                return this.next();
            }
            if (this._count > 0) {
                this._count--;
            }
            return this._node.items[this._index++];
        };
        return ForwardIterator;
    }());
    /**
     * A reverse iterator for a B+ tree.
     */
    var RetroIterator = /** @class */ (function () {
        /**
         * Construct a new retro iterator.
         *
         * @param node - The last leaf node in the chain.
         *
         * @param index - The local index of the last item.
         *
         * @param count - The number of items to iterate. A value `< 0`
         *   will iterate all available items.
         */
        function RetroIterator(node, index, count) {
            this._node = node;
            this._index = index;
            this._count = count;
        }
        /**
         * Get an iterator over the object's values.
         *
         * @returns An iterator which yields the object's values.
         */
        RetroIterator.prototype.iter = function () {
            return this;
        };
        /**
         * Create an independent clone of the iterator.
         *
         * @returns A new independent clone of the iterator.
         */
        RetroIterator.prototype.clone = function () {
            return new RetroIterator(this._node, this._index, this._count);
        };
        /**
         * Get the next value from the iterator.
         *
         * @returns The next value from the iterator, or `undefined`.
         */
        RetroIterator.prototype.next = function () {
            if (this._node === null || this._count === 0) {
                return undefined;
            }
            if (this._index >= this._node.size) {
                this._index = this._node.size - 1;
            }
            if (this._index < 0) {
                this._node = this._node.prev;
                this._index = this._node ? this._node.size - 1 : -1;
                return this.next();
            }
            if (this._count > 0) {
                this._count--;
            }
            return this._node.items[this._index--];
        };
        return RetroIterator;
    }());
    /**
     * Find the pivot index for a particular local index.
     */
    function findPivotIndexByIndex(sizes, index) {
        var n = sizes.length;
        for (var i = 0; i < n; ++i) {
            if (sizes[i] > index) {
                return i;
            }
        }
        return n - 1;
    }
    /**
     * Find the pivot index for a particular key.
     */
    function findPivotIndexByKey(items, key, cmp) {
        var n = items.length;
        for (var i = 1; i < n; ++i) {
            if (cmp(items[i], key) > 0) {
                return i - 1;
            }
        }
        return n - 1;
    }
    /**
     * Find the key index for a particular key.
     */
    function findKeyIndex(items, key, cmp) {
        var n = items.length;
        for (var i = 0; i < n; ++i) {
            var c = cmp(items[i], key);
            if (c === 0) {
                return i;
            }
            if (c > 0) {
                return -i - 1;
            }
        }
        return -n - 1;
    }
    /**
     * Update the sizes of a branch node starting at the given index.
     */
    function updateSizes(node, i) {
        var sizes = node.sizes, children = node.children;
        var last = i > 0 ? sizes[i - 1] : 0;
        for (var n = children.length; i < n; ++i) {
            last = sizes[i] = last + children[i].size;
        }
        sizes.length = children.length;
    }
    /**
     * Split a node and return its new next sibling.
     *
     * @param node - The node of interest.
     *
     * @returns The new next sibling node.
     */
    function splitNode(node) {
        // Handle leaf nodes first.
        if (node.type === 1 /* Leaf */) {
            // Create the new sibling leaf node.
            var next_1 = new LeafNode();
            // Move the items to the new sibling.
            var v1_1 = node.items;
            var v2_1 = next_1.items;
            for (var i = MIN_NODE_WIDTH, n = v1_1.length; i < n; ++i) {
                v2_1.push(v1_1[i]);
            }
            v1_1.length = MIN_NODE_WIDTH;
            // Patch up the sibling links.
            if (node.next)
                node.next.prev = next_1;
            next_1.next = node.next;
            next_1.prev = node;
            node.next = next_1;
            // Return the new next sibling.
            return next_1;
        }
        // Create the new sibling branch node.
        var next = new BranchNode();
        // Move the children to the new sibling.
        var c1 = node.children;
        var c2 = next.children;
        for (var i = MIN_NODE_WIDTH, n = c1.length; i < n; ++i) {
            c2.push(c1[i]);
        }
        c1.length = MIN_NODE_WIDTH;
        // Move the items to the new sibling.
        var v1 = node.items;
        var v2 = next.items;
        for (var i = MIN_NODE_WIDTH, n = v1.length; i < n; ++i) {
            v2.push(v1[i]);
        }
        v1.length = MIN_NODE_WIDTH;
        // Update the dirty sizes of the nodes.
        updateSizes(node, MIN_NODE_WIDTH);
        updateSizes(next, 0);
        // Return the new next sibling.
        return next;
    }
    /**
     * Join a child node of a branch with one of its siblings.
     *
     * @param node - The branch node of interest.
     *
     * @param i - The index of the child node of interest.
     *
     * @returns The first modified index.
     *
     * #### Notes
     * This may cause the branch to become underfull.
     */
    function joinChild(node, i) {
        var _a, _b, _c, _d, _e, _f;
        // Fetch the child to be joined.
        var child = node.children[i];
        // Fetch the relevant sibling.
        var sibling = i === 0 ? node.children[i + 1] : node.children[i - 1];
        // Compute the flags which control the join behavior.
        var hasNext = i === 0;
        var isLeaf = child.type === 1 /* Leaf */;
        var hasExtra = sibling.width > MIN_NODE_WIDTH;
        // Join case #1: steal from next sibling leaf
        if (isLeaf && hasExtra && hasNext) {
            // Cast the children as leaves.
            var c = child;
            var s = sibling;
            // Steal an item.
            c.items.push(s.items.shift());
            // Update the branch items.
            node.items[i + 1] = s.items[0];
            // Return the first modified index.
            return i;
        }
        // Join case #2: steal from previous sibling leaf
        if (isLeaf && hasExtra && !hasNext) {
            // Cast the children as leaves.
            var c = child;
            var s = sibling;
            // Steal an item.
            c.items.unshift(s.items.pop());
            // Update the branch items.
            node.items[i] = c.items[0];
            // Return the first modified index.
            return i - 1;
        }
        // Join case #3: merge with next sibling leaf
        if (isLeaf && !hasExtra && hasNext) {
            // Cast the children as leaves.
            var c = child;
            var s = sibling;
            // Merge items.
            (_a = s.items).unshift.apply(_a, c.items);
            // Remove the old branch child.
            algorithm_1.ArrayExt.removeAt(node.children, i);
            // Remove the stale branch item.
            algorithm_1.ArrayExt.removeAt(node.items, i + 1);
            // Patch up the sibling links.
            if (c.prev)
                c.prev.next = s;
            s.prev = c.prev;
            // Clear the original child.
            clear(c);
            // Return the first modified index.
            return i;
        }
        // Join case #4: merge with previous sibling leaf
        if (isLeaf && !hasExtra && !hasNext) {
            // Cast the children as leaves.
            var c = child;
            var s = sibling;
            // Merge items.
            (_b = s.items).push.apply(_b, c.items);
            // Remove the old branch child.
            algorithm_1.ArrayExt.removeAt(node.children, i);
            // Remove the stale branch item.
            algorithm_1.ArrayExt.removeAt(node.items, i);
            // Patch up the sibling links.
            if (c.next)
                c.next.prev = s;
            s.next = c.next;
            // Clear the original child.
            clear(c);
            // Return the first modified index.
            return i - 1;
        }
        // Join case #5: steal from next sibling branch
        if (!isLeaf && hasExtra && hasNext) {
            // Cast the children to branches.
            var c = child;
            var s = sibling;
            // Steal a child from the next sibling.
            c.children.push(s.children.shift());
            // Steal an item from the next sibling.
            c.items.push(s.items.shift());
            // Update the branch items.
            node.items[i + 1] = s.items[0];
            // Update the sibling sizes.
            updateSizes(c, c.width - 1);
            updateSizes(s, 0);
            // Return the first modified index.
            return i;
        }
        // Join case #6: steal from previous sibling branch
        if (!isLeaf && hasExtra && !hasNext) {
            // Cast the children to branches.
            var c = child;
            var s = sibling;
            // Steal a child from the previous sibling.
            c.children.unshift(s.children.pop());
            // Steal an item from the previous sibling.
            c.items.unshift(s.items.pop());
            // Update the branch items.
            node.items[i] = c.items[0];
            // Update the sibling sizes.
            updateSizes(c, 0);
            updateSizes(s, s.width - 1);
            // Return the first modified index.
            return i - 1;
        }
        // Join case #7: merge with next sibling branch
        if (!isLeaf && !hasExtra && hasNext) {
            // Cast the children to branches.
            var c = child;
            var s = sibling;
            // Merge the children with the next sibling.
            (_c = s.children).unshift.apply(_c, c.children);
            // Merge the items with the next sibling.
            (_d = s.items).unshift.apply(_d, c.items);
            // Remove the old branch child.
            algorithm_1.ArrayExt.removeAt(node.children, i);
            // Remove the stale branch item.
            algorithm_1.ArrayExt.removeAt(node.items, i + 1);
            // Update the sibling sizes.
            updateSizes(s, 0);
            // Clear the original child but, not its children.
            c.children.length = 0;
            clear(c);
            // Return the first modified index.
            return i;
        }
        // Join case #8: merge with previous sibling branch
        if (!isLeaf && !hasExtra && !hasNext) {
            // Cast the children to branches.
            var c = child;
            var s = sibling;
            // Merge the children with the previous sibling.
            (_e = s.children).push.apply(_e, c.children);
            // Merge the items with the previous sibling.
            (_f = s.items).push.apply(_f, c.items);
            // Remove the old branch child.
            algorithm_1.ArrayExt.removeAt(node.children, i);
            // Remove the stale branch item.
            algorithm_1.ArrayExt.removeAt(node.items, i);
            // Update the sibling sizes.
            updateSizes(s, 0);
            // Clear the original child, but not its children.
            c.children.length = 0;
            clear(c);
            // Return the first modified index.
            return i - 1;
        }
        // One of the above cases must match.
        throw 'unreachable';
    }
})(Private || (Private = {}));
