"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.internedSpaces = exports.compareStrings = exports.compareNumbers = exports.CollaborationWindow = exports.IncrementalMapState = exports.IncrementalExecOp = exports.Marker = exports.reservedMarkerSimpleTypeKey = exports.reservedMarkerIdKey = exports.BaseSegment = exports.MergeBlock = exports.MaxNodesInBlock = exports.ordinalToArray = exports.MergeNode = exports.toRemovalInfo = void 0;
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/prefer-optional-chain, no-bitwise */
const common_utils_1 = require("@fluidframework/common-utils");
const constants_1 = require("./constants");
const mergeTreeTracking_1 = require("./mergeTreeTracking");
const ops_1 = require("./ops");
const properties_1 = require("./properties");
const referencePositions_1 = require("./referencePositions");
const segmentGroupCollection_1 = require("./segmentGroupCollection");
const segmentPropertiesManager_1 = require("./segmentPropertiesManager");
function toRemovalInfo(maybe) {
    if ((maybe === null || maybe === void 0 ? void 0 : maybe.removedClientIds) !== undefined && (maybe === null || maybe === void 0 ? void 0 : maybe.removedSeq) !== undefined) {
        return maybe;
    }
    (0, common_utils_1.assert)((maybe === null || maybe === void 0 ? void 0 : maybe.removedClientIds) === undefined && (maybe === null || maybe === void 0 ? void 0 : maybe.removedSeq) === undefined, 0x2bf /* "both removedClientIds and removedSeq should be set or not set" */);
}
exports.toRemovalInfo = toRemovalInfo;
class MergeNode {
    constructor() {
        this.index = 0;
        this.ordinal = "";
        this.cachedLength = 0;
    }
    isLeaf() {
        return false;
    }
}
exports.MergeNode = MergeNode;
function ordinalToArray(ord) {
    const a = [];
    if (ord) {
        for (let i = 0, len = ord.length; i < len; i++) {
            a.push(ord.charCodeAt(i));
        }
    }
    return a;
}
exports.ordinalToArray = ordinalToArray;
// Note that the actual branching factor of the MergeTree is `MaxNodesInBlock - 1`.  This is because
// the MergeTree always inserts first, then checks for overflow and splits if the child count equals
// `MaxNodesInBlock`.  (i.e., `MaxNodesInBlock` contains 1 extra slot for temporary storage to
// facilitate splits.)
exports.MaxNodesInBlock = 8;
class MergeBlock extends MergeNode {
    constructor(childCount) {
        super();
        this.childCount = childCount;
        this.children = new Array(exports.MaxNodesInBlock);
    }
    hierBlock() {
        return undefined;
    }
    setOrdinal(child, index) {
        let childCount = this.childCount;
        if (childCount === 8) {
            childCount = 7;
        }
        (0, common_utils_1.assert)((childCount >= 1) && (childCount <= 7), 0x040 /* "Child count is not within [1,7] range!" */);
        let localOrdinal;
        const ordinalWidth = 1 << (exports.MaxNodesInBlock - (childCount + 1));
        if (index === 0) {
            localOrdinal = ordinalWidth - 1;
        }
        else {
            const prevOrd = this.children[index - 1].ordinal;
            const prevOrdCode = prevOrd.charCodeAt(prevOrd.length - 1);
            localOrdinal = prevOrdCode + ordinalWidth;
        }
        child.ordinal = this.ordinal + String.fromCharCode(localOrdinal);
        (0, common_utils_1.assert)(child.ordinal.length === (this.ordinal.length + 1), 0x041 /* "Unexpected child ordinal length!" */);
        if (index > 0) {
            (0, common_utils_1.assert)(child.ordinal > this.children[index - 1].ordinal, 0x042);
        }
    }
    assignChild(child, index, updateOrdinal = true) {
        child.parent = this;
        child.index = index;
        if (updateOrdinal) {
            this.setOrdinal(child, index);
        }
        this.children[index] = child;
    }
}
exports.MergeBlock = MergeBlock;
class BaseSegment extends MergeNode {
    constructor() {
        super(...arguments);
        this.clientId = constants_1.LocalClientId;
        this.seq = constants_1.UniversalSequenceNumber;
        this.segmentGroups = new segmentGroupCollection_1.SegmentGroupCollection(this);
        this.trackingCollection = new mergeTreeTracking_1.TrackingGroupCollection(this);
    }
    addProperties(newProps, op, seq, collabWindow, rollback = segmentPropertiesManager_1.PropertiesRollback.None) {
        if (!this.propertyManager) {
            this.propertyManager = new segmentPropertiesManager_1.PropertiesManager();
        }
        if (!this.properties) {
            this.properties = (0, properties_1.createMap)();
        }
        return this.propertyManager.addProperties(this.properties, newProps, op, seq, collabWindow && collabWindow.collaborating, rollback);
    }
    hasProperty(key) {
        return !!this.properties && (this.properties[key] !== undefined);
    }
    isLeaf() {
        return true;
    }
    cloneInto(b) {
        var _a;
        b.clientId = this.clientId;
        // TODO: deep clone properties
        b.properties = (0, properties_1.clone)(this.properties);
        b.removedClientIds = (_a = this.removedClientIds) === null || _a === void 0 ? void 0 : _a.slice();
        // TODO: copy removed client overlap and branch removal info
        b.removedSeq = this.removedSeq;
        b.seq = this.seq;
    }
    canAppend(segment) {
        return false;
    }
    addSerializedProps(jseg) {
        if (this.properties) {
            jseg.props = this.properties;
        }
    }
    /**
     * @deprecated  for internal use only. public export will be removed.
     * @internal
     */
    ack(segmentGroup, opArgs, mergeTree) {
        const currentSegmentGroup = this.segmentGroups.dequeue();
        (0, common_utils_1.assert)(currentSegmentGroup === segmentGroup, 0x043 /* "On ack, unexpected segmentGroup!" */);
        switch (opArgs.op.type) {
            case ops_1.MergeTreeDeltaType.ANNOTATE:
                (0, common_utils_1.assert)(!!this.propertyManager, 0x044 /* "On annotate ack, missing segment property manager!" */);
                this.propertyManager.ackPendingProperties(opArgs.op);
                return true;
            case ops_1.MergeTreeDeltaType.INSERT:
                (0, common_utils_1.assert)(this.seq === constants_1.UnassignedSequenceNumber, 0x045 /* "On insert, seq number already assigned!" */);
                this.seq = opArgs.sequencedMessage.sequenceNumber;
                this.localSeq = undefined;
                return true;
            case ops_1.MergeTreeDeltaType.REMOVE:
                const removalInfo = toRemovalInfo(this);
                (0, common_utils_1.assert)(removalInfo !== undefined, 0x046 /* "On remove ack, missing removal info!" */);
                this.localRemovedSeq = undefined;
                if (removalInfo.removedSeq === constants_1.UnassignedSequenceNumber) {
                    removalInfo.removedSeq = opArgs.sequencedMessage.sequenceNumber;
                    return true;
                }
                return false;
            default:
                throw new Error(`${opArgs.op.type} is in unrecognized operation type`);
        }
    }
    splitAt(pos) {
        var _a;
        if (pos > 0) {
            const leafSegment = this.createSplitSegmentAt(pos);
            if (leafSegment) {
                this.copyPropertiesTo(leafSegment);
                leafSegment.parent = this.parent;
                // Give the leaf a temporary yet valid ordinal.
                // when this segment is put in the tree, it will get its real ordinal,
                // but this ordinal meets all the necessary invariants for now.
                leafSegment.ordinal = this.ordinal + String.fromCharCode(0);
                leafSegment.removedClientIds = (_a = this.removedClientIds) === null || _a === void 0 ? void 0 : _a.slice();
                leafSegment.removedSeq = this.removedSeq;
                leafSegment.localRemovedSeq = this.localRemovedSeq;
                leafSegment.seq = this.seq;
                leafSegment.localSeq = this.localSeq;
                leafSegment.clientId = this.clientId;
                this.segmentGroups.copyTo(leafSegment);
                this.trackingCollection.copyTo(leafSegment);
                if (this.localRefs) {
                    this.localRefs.split(pos, leafSegment);
                }
            }
            return leafSegment;
        }
    }
    copyPropertiesTo(other) {
        if (this.propertyManager) {
            if (this.properties) {
                other.propertyManager = new segmentPropertiesManager_1.PropertiesManager();
                other.properties = this.propertyManager.copyTo(this.properties, other.properties, other.propertyManager);
            }
        }
    }
}
exports.BaseSegment = BaseSegment;
exports.reservedMarkerIdKey = "markerId";
exports.reservedMarkerSimpleTypeKey = "markerSimpleType";
class Marker extends BaseSegment {
    constructor(refType) {
        super();
        this.refType = refType;
        this.type = Marker.type;
        this.cachedLength = 1;
    }
    static is(segment) {
        return segment.type === Marker.type;
    }
    static make(refType, props) {
        const marker = new Marker(refType);
        if (props) {
            marker.addProperties(props);
        }
        return marker;
    }
    toJSONObject() {
        const obj = { marker: { refType: this.refType } };
        super.addSerializedProps(obj);
        return obj;
    }
    static fromJSONObject(spec) {
        if (spec && typeof spec === "object" && "marker" in spec) {
            return Marker.make(spec.marker.refType, spec.props);
        }
        return undefined;
    }
    clone() {
        const b = Marker.make(this.refType, this.properties);
        this.cloneInto(b);
        return b;
    }
    getSegment() {
        return this;
    }
    getOffset() {
        return 0;
    }
    hasSimpleType(simpleTypeName) {
        return !!this.properties &&
            this.properties[exports.reservedMarkerSimpleTypeKey] === simpleTypeName;
    }
    getProperties() {
        return this.properties;
    }
    getId() {
        if (this.properties && this.properties[exports.reservedMarkerIdKey]) {
            return this.properties[exports.reservedMarkerIdKey];
        }
    }
    toString() {
        let bbuf = "";
        if ((0, referencePositions_1.refTypeIncludesFlag)(this, ops_1.ReferenceType.Tile)) {
            bbuf += "Tile";
        }
        if ((0, referencePositions_1.refTypeIncludesFlag)(this, ops_1.ReferenceType.NestBegin)) {
            if (bbuf.length > 0) {
                bbuf += "; ";
            }
            bbuf += "RangeBegin";
        }
        if ((0, referencePositions_1.refTypeIncludesFlag)(this, ops_1.ReferenceType.NestEnd)) {
            if (bbuf.length > 0) {
                bbuf += "; ";
            }
            bbuf += "RangeEnd";
        }
        let lbuf = "";
        const id = this.getId();
        if (id) {
            bbuf += ` (${id}) `;
        }
        const tileLabels = (0, referencePositions_1.refGetTileLabels)(this);
        if (tileLabels) {
            lbuf += "tile -- ";
            for (let i = 0, len = tileLabels.length; i < len; i++) {
                const tileLabel = tileLabels[i];
                if (i > 0) {
                    lbuf += "; ";
                }
                lbuf += tileLabel;
            }
        }
        const rangeLabels = (0, referencePositions_1.refGetRangeLabels)(this);
        if (rangeLabels) {
            let rangeKind = "begin";
            if ((0, referencePositions_1.refTypeIncludesFlag)(this, ops_1.ReferenceType.NestEnd)) {
                rangeKind = "end";
            }
            if (tileLabels) {
                lbuf += " ";
            }
            lbuf += `range ${rangeKind} -- `;
            const labels = rangeLabels;
            for (let i = 0, len = labels.length; i < len; i++) {
                const rangeLabel = labels[i];
                if (i > 0) {
                    lbuf += "; ";
                }
                lbuf += rangeLabel;
            }
        }
        let pbuf = "";
        if (this.properties) {
            pbuf += JSON.stringify(this.properties, (key, value) => {
                // Avoid circular reference when stringifying makers containing handles.
                // (Substitute a debug string instead.)
                const handle = !!value && value.IFluidHandle;
                // eslint-disable-next-line @typescript-eslint/no-unsafe-return
                return handle
                    ? `#Handle(${handle.routeContext.path}/${handle.path})`
                    : value;
            });
        }
        return `M ${bbuf}: ${lbuf} ${pbuf}`;
    }
    createSplitSegmentAt(pos) {
        return undefined;
    }
    canAppend(segment) {
        return false;
    }
    append() { throw new Error("Can not append to marker"); }
}
exports.Marker = Marker;
Marker.type = "Marker";
var IncrementalExecOp;
(function (IncrementalExecOp) {
    IncrementalExecOp[IncrementalExecOp["Go"] = 0] = "Go";
    IncrementalExecOp[IncrementalExecOp["Stop"] = 1] = "Stop";
    IncrementalExecOp[IncrementalExecOp["Yield"] = 2] = "Yield";
})(IncrementalExecOp = exports.IncrementalExecOp || (exports.IncrementalExecOp = {}));
class IncrementalMapState {
    constructor(block, actions, pos, refSeq, clientId, context, start, end, childIndex = 0) {
        this.block = block;
        this.actions = actions;
        this.pos = pos;
        this.refSeq = refSeq;
        this.clientId = clientId;
        this.context = context;
        this.start = start;
        this.end = end;
        this.childIndex = childIndex;
        this.op = IncrementalExecOp.Go;
    }
}
exports.IncrementalMapState = IncrementalMapState;
class CollaborationWindow {
    constructor() {
        this.clientId = constants_1.LocalClientId;
        this.collaborating = false;
        // Lowest-numbered segment in window; no client can reference a state before this one
        this.minSeq = 0;
        // Highest-numbered segment in window and current
        // reference segment for this client
        this.currentSeq = 0;
        this.localSeq = 0;
    }
    loadFrom(a) {
        this.clientId = a.clientId;
        this.collaborating = a.collaborating;
        this.minSeq = a.minSeq;
        this.currentSeq = a.currentSeq;
    }
}
exports.CollaborationWindow = CollaborationWindow;
const compareNumbers = (a, b) => a - b;
exports.compareNumbers = compareNumbers;
const compareStrings = (a, b) => a.localeCompare(b);
exports.compareStrings = compareStrings;
const indentStrings = ["", " ", "  "];
function internedSpaces(n) {
    if (indentStrings[n] === undefined) {
        indentStrings[n] = "";
        for (let i = 0; i < n; i++) {
            indentStrings[n] += " ";
        }
    }
    return indentStrings[n];
}
exports.internedSpaces = internedSpaces;
//# sourceMappingURL=mergeTreeNodes.js.map