"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.packParent = exports.zamboniSegments = exports.zamboniSegmentsMax = void 0;
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const constants_1 = require("./constants");
const mergeTreeDeltaCallback_1 = require("./mergeTreeDeltaCallback");
const mergeTreeNodes_1 = require("./mergeTreeNodes");
const properties_1 = require("./properties");
exports.zamboniSegmentsMax = 2;
function underflow(node) {
    return node.childCount < (mergeTreeNodes_1.MaxNodesInBlock / 2);
}
function zamboniSegments(mergeTree, zamboniSegmentsMaxCount = exports.zamboniSegmentsMax) {
    if (!mergeTree.collabWindow.collaborating) {
        return;
    }
    for (let i = 0; i < zamboniSegmentsMaxCount; i++) {
        let segmentToScour = mergeTree.getSegmentsToScour.peek();
        if (!segmentToScour || segmentToScour.maxSeq > mergeTree.collabWindow.minSeq) {
            break;
        }
        segmentToScour = mergeTree.getSegmentsToScour.get();
        // Only skip scouring if needs scour is explicitly false, not true or undefined
        if (segmentToScour.segment.parent && segmentToScour.segment.parent.needsScour !== false) {
            const block = segmentToScour.segment.parent;
            const childrenCopy = [];
            scourNode(block, childrenCopy, mergeTree);
            // This will avoid the cost of re-scouring nodes
            // that have recently been scoured
            block.needsScour = false;
            const newChildCount = childrenCopy.length;
            if (newChildCount < block.childCount) {
                block.childCount = newChildCount;
                block.children = childrenCopy;
                for (let j = 0; j < newChildCount; j++) {
                    block.assignChild(childrenCopy[j], j, false);
                }
                if (underflow(block) && block.parent) {
                    packParent(block.parent, mergeTree);
                }
                else {
                    mergeTree.nodeUpdateOrdinals(block);
                    mergeTree.blockUpdatePathLengths(block, constants_1.UnassignedSequenceNumber, -1, true);
                }
            }
        }
    }
}
exports.zamboniSegments = zamboniSegments;
// Interior node with all node children
function packParent(parent, mergeTree) {
    const children = parent.children;
    let childIndex;
    let childBlock;
    const holdNodes = [];
    for (childIndex = 0; childIndex < parent.childCount; childIndex++) {
        // Debug assert not isLeaf()
        // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
        childBlock = children[childIndex];
        scourNode(childBlock, holdNodes, mergeTree);
        // Will replace this block with a packed block
        childBlock.parent = undefined;
    }
    if (holdNodes.length > 0) {
        const totalNodeCount = holdNodes.length;
        const halfOfMaxNodeCount = mergeTreeNodes_1.MaxNodesInBlock / 2;
        let childCount = Math.min(mergeTreeNodes_1.MaxNodesInBlock - 1, Math.floor(totalNodeCount / halfOfMaxNodeCount));
        if (childCount < 1) {
            childCount = 1;
        }
        const baseNodesInBlockCount = Math.floor(totalNodeCount / childCount);
        let remainderCount = totalNodeCount % childCount;
        const packedBlocks = new Array(mergeTreeNodes_1.MaxNodesInBlock);
        let childrenPackedCount = 0;
        for (let nodeIndex = 0; nodeIndex < childCount; nodeIndex++) {
            let nodeCount = baseNodesInBlockCount;
            if (remainderCount > 0) {
                nodeCount++;
                remainderCount--;
            }
            const packedBlock = mergeTree.makeBlock(nodeCount);
            for (let packedNodeIndex = 0; packedNodeIndex < nodeCount; packedNodeIndex++) {
                const nodeToPack = holdNodes[childrenPackedCount++];
                packedBlock.assignChild(nodeToPack, packedNodeIndex, false);
            }
            packedBlock.parent = parent;
            packedBlocks[nodeIndex] = packedBlock;
            mergeTree.nodeUpdateLengthNewStructure(packedBlock);
        }
        parent.children = packedBlocks;
        for (let j = 0; j < childCount; j++) {
            parent.assignChild(packedBlocks[j], j, false);
        }
        parent.childCount = childCount;
    }
    else {
        parent.children = [];
        parent.childCount = 0;
    }
    if (underflow(parent) && (parent.parent)) {
        packParent(parent.parent, mergeTree);
    }
    else {
        mergeTree.nodeUpdateOrdinals(parent);
        mergeTree.blockUpdatePathLengths(parent, constants_1.UnassignedSequenceNumber, -1, true);
    }
}
exports.packParent = packParent;
function scourNode(node, holdNodes, mergeTree) {
    var _a, _b;
    let prevSegment;
    for (let k = 0; k < node.childCount; k++) {
        const childNode = node.children[k];
        if (childNode.isLeaf()) {
            const segment = childNode;
            if (segment.segmentGroups.empty) {
                if (segment.removedSeq !== undefined) {
                    if (segment.removedSeq > mergeTree.collabWindow.minSeq) {
                        holdNodes.push(segment);
                    }
                    else if (!segment.trackingCollection.empty) {
                        holdNodes.push(segment);
                    }
                    else {
                        // Notify maintenance event observers that the segment is being unlinked from the MergeTree
                        if (mergeTree.mergeTreeMaintenanceCallback) {
                            mergeTree.mergeTreeMaintenanceCallback({
                                operation: mergeTreeDeltaCallback_1.MergeTreeMaintenanceType.UNLINK,
                                deltaSegments: [{ segment }],
                            }, undefined);
                        }
                        segment.parent = undefined;
                    }
                    prevSegment = undefined;
                }
                else {
                    if (segment.seq <= mergeTree.collabWindow.minSeq) {
                        // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
                        const canAppend = prevSegment
                            && prevSegment.canAppend(segment)
                            && (0, properties_1.matchProperties)(prevSegment.properties, segment.properties)
                            && prevSegment.trackingCollection.matches(segment.trackingCollection)
                            && ((_a = mergeTree.localNetLength(segment)) !== null && _a !== void 0 ? _a : 0) > 0;
                        if (canAppend) {
                            prevSegment.append(segment);
                            if (mergeTree.mergeTreeMaintenanceCallback) {
                                mergeTree.mergeTreeMaintenanceCallback({
                                    operation: mergeTreeDeltaCallback_1.MergeTreeMaintenanceType.APPEND,
                                    deltaSegments: [{ segment: prevSegment }, { segment }],
                                }, undefined);
                            }
                            segment.parent = undefined;
                            segment.trackingCollection.trackingGroups.forEach((tg) => tg.unlink(segment));
                        }
                        else {
                            holdNodes.push(segment);
                            prevSegment = ((_b = mergeTree.localNetLength(segment)) !== null && _b !== void 0 ? _b : 0) > 0 ? segment : undefined;
                        }
                    }
                    else {
                        holdNodes.push(segment);
                        prevSegment = undefined;
                    }
                }
            }
            else {
                holdNodes.push(segment);
                prevSegment = undefined;
            }
        }
        else {
            holdNodes.push(childNode);
            prevSegment = undefined;
        }
    }
}
//# sourceMappingURL=zamboni.js.map