"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.packParent = exports.zamboniSegments = exports.zamboniSegmentsMax = void 0;
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const constants_js_1 = require("./constants.js");
const mergeTreeDeltaCallback_js_1 = require("./mergeTreeDeltaCallback.js");
const mergeTreeNodes_js_1 = require("./mergeTreeNodes.js");
const properties_js_1 = require("./properties.js");
const segmentInfos_js_1 = require("./segmentInfos.js");
const opstampUtils = __importStar(require("./stamps.js"));
exports.zamboniSegmentsMax = 2;
function underflow(node) {
    return node.childCount < mergeTreeNodes_js_1.MaxNodesInBlock / 2;
}
// blockUpdatePathLengths requires an OperationStamp but it is unused when passing `newStructure: true`.
const dummyStamp = { seq: constants_js_1.UnassignedSequenceNumber, clientId: -1 };
function zamboniSegments(mergeTree, zamboniSegmentsMaxCount = exports.zamboniSegmentsMax) {
    if (!mergeTree.collabWindow.collaborating) {
        return;
    }
    for (let i = 0; i < zamboniSegmentsMaxCount; i++) {
        let segmentToScour = mergeTree.segmentsToScour.peek()?.value;
        segmentToScour?.segment?.propertyManager?.updateMsn(mergeTree.collabWindow.minSeq);
        if (!segmentToScour || segmentToScour.maxSeq > mergeTree.collabWindow.minSeq) {
            break;
        }
        segmentToScour = mergeTree.segmentsToScour.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++) {
                    (0, mergeTreeNodes_js_1.assignChild)(block, childrenCopy[j], j, false);
                }
                if (underflow(block) && block.parent) {
                    packParent(block.parent, mergeTree);
                }
                else {
                    mergeTree.nodeUpdateOrdinals(block);
                    mergeTree.blockUpdatePathLengths(block, dummyStamp, 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()
        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_js_1.MaxNodesInBlock / 2;
        let childCount = Math.min(mergeTreeNodes_js_1.MaxNodesInBlock - 1, Math.floor(totalNodeCount / halfOfMaxNodeCount));
        if (childCount < 1) {
            childCount = 1;
        }
        const baseNodesInBlockCount = Math.floor(totalNodeCount / childCount);
        let remainderCount = totalNodeCount % childCount;
        const packedBlocks = Array.from({ length: mergeTreeNodes_js_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++];
                (0, mergeTreeNodes_js_1.assignChild)(packedBlock, nodeToPack, packedNodeIndex, false);
            }
            packedBlock.parent = parent;
            packedBlocks[nodeIndex] = packedBlock;
            mergeTree.nodeUpdateLengthNewStructure(packedBlock);
        }
        parent.children = packedBlocks;
        for (let j = 0; j < childCount; j++) {
            (0, mergeTreeNodes_js_1.assignChild)(parent, 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, dummyStamp, true);
    }
}
exports.packParent = packParent;
function scourNode(node, holdNodes, mergeTree) {
    // The previous segment is tracked while scouring for the purposes of merging adjacent segments
    // when possible.
    let prevSegment;
    for (let k = 0; k < node.childCount; k++) {
        // TODO Non null asserting, why is this not null?
        const childNode = node.children[k];
        if (!childNode.isLeaf() || childNode.segmentGroups?.empty === false) {
            holdNodes.push(childNode);
            prevSegment = undefined;
            continue;
        }
        const segment = childNode;
        const removalInfo = (0, segmentInfos_js_1.toRemovalInfo)(segment);
        const minSeqStamp = (0, mergeTreeNodes_js_1.getMinSeqStamp)(mergeTree.collabWindow);
        if (removalInfo === undefined) {
            if (opstampUtils.lte(segment.insert, minSeqStamp)) {
                const segmentHasPositiveLength = (mergeTree.leafLength(segment) ?? 0) > 0;
                const canAppend = prevSegment?.canAppend(segment) &&
                    (0, properties_js_1.matchProperties)(prevSegment.properties, segment.properties) &&
                    prevSegment.trackingCollection.matches(segment.trackingCollection) &&
                    segmentHasPositiveLength;
                if (canAppend) {
                    prevSegment.append(segment);
                    mergeTree.mergeTreeMaintenanceCallback?.({
                        operation: mergeTreeDeltaCallback_js_1.MergeTreeMaintenanceType.APPEND,
                        deltaSegments: [{ segment: prevSegment }, { segment }],
                    }, undefined);
                    for (const tg of segment.trackingCollection.trackingGroups)
                        tg.unlink(segment);
                    (0, segmentInfos_js_1.removeMergeNodeInfo)(segment);
                }
                else {
                    holdNodes.push(segment);
                    prevSegment = segmentHasPositiveLength ? segment : undefined;
                }
            }
            else {
                holdNodes.push(segment);
                prevSegment = undefined;
            }
        }
        else {
            const firstRemove = removalInfo.removes[0];
            // If the segment's removal is below the MSN and it's not being held onto by a tracking group,
            // it can be unlinked (i.e. removed from the merge-tree)
            if (!!firstRemove &&
                opstampUtils.lte(firstRemove, minSeqStamp) &&
                segment.trackingCollection.empty) {
                mergeTree.mergeTreeMaintenanceCallback?.({
                    operation: mergeTreeDeltaCallback_js_1.MergeTreeMaintenanceType.UNLINK,
                    deltaSegments: [{ segment }],
                }, undefined);
                if (mergeTreeNodes_js_1.Marker.is(segment)) {
                    mergeTree.unlinkMarker(segment);
                }
                (0, segmentInfos_js_1.removeMergeNodeInfo)(segment);
            }
            else {
                holdNodes.push(segment);
            }
            prevSegment = undefined;
        }
    }
}
//# sourceMappingURL=zamboni.js.map