/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert } from "@fluidframework/core-utils/internal";
import { LocalClientId } from "./constants.js";
import { NodeAction, depthFirstNodeWalk } from "./mergeTreeNodeWalk.js";
/**
 * This is a special segment that is not bound or known by the merge tree itself,
 * but the segment itself pretends to be a removed segment at an endpoint of the
 * tree. It is removed so it appears as a undefined/0 length segment. This segment
 * adds the capability to hold local references that have been detached from the
 * real merge tree, and give the appearance that they exist at an endpoint of the
 * tree.
 *
 * This is useful today in 2 cases: detached references and interval stickiness.
 *
 * In general, local references only become detached when the tree becomes empty
 * and the EndOfTreeSegment allows us to gracefully handle that case by giving
 * those references a place to live.
 *
 * In the case of interval stickiness, it is desirable to be able to refer to
 * the position immediately after or before a segment, in order for the endpoint
 * of an interval to be exclusive. This means that in order to support intervals
 * that are exclusive and also include the first or last segment of the tree, it
 * must be possible in some way to refer to a position before or after the tree
 * respectively. The endpoint segments allow us to support such behavior.
 */
class BaseEndpointSegment {
    constructor(mergeTree) {
        this.mergeTree = mergeTree;
        /*
         * segments must be of at least length one, but
         * removed segments will have a calculated length
         * of undefined/0. we leverage this to create
         * a 0 length segment for an endpoint of the tree
         */
        this.removedSeq = 0;
        this.removedClientIds = [LocalClientId];
        this.seq = 0;
        this.clientId = LocalClientId;
        this.cachedLength = 1;
        this.addProperties = notSupported;
        this.clone = notSupported;
        this.canAppend = notSupported;
        this.append = notSupported;
        this.splitAt = notSupported;
        this.toJSONObject = notSupported;
        this.ack = notSupported;
    }
    isLeaf() {
        return true;
    }
    get parent() {
        return this.endpointSegmentProps().parent;
    }
    get index() {
        return this.endpointSegmentProps().index;
    }
    /*
     * since this segment isn't real, throw on any segment
     * operation that isn't expected
     */
    get segmentGroups() {
        return notSupported();
    }
    get trackingCollection() {
        return notSupported();
    }
}
const notSupported = () => {
    assert(false, 0x3ed /* operation not supported */);
};
/**
 * The position immediately prior to the start of the tree
 */
export class StartOfTreeSegment extends BaseEndpointSegment {
    constructor() {
        super(...arguments);
        this.type = "StartOfTreeSegment";
        this.endpointType = "start";
    }
    /**
     * this segment pretends to be a sibling of the first real segment.
     * so compute the necessary properties to pretend to be that segment.
     */
    endpointSegmentProps() {
        let firstSegment;
        let depth = 1;
        const root = this.mergeTree.root;
        depthFirstNodeWalk(root, root.children[0], (node) => {
            depth++;
            if (node?.isLeaf()) {
                firstSegment = node;
                return NodeAction.Exit;
            }
        }, undefined, undefined, false);
        const parent = firstSegment?.parent ?? root;
        const index = 0;
        return {
            parent,
            index,
            depth,
        };
    }
    get ordinal() {
        // Ordinals exist purely for lexicographical sort order and use a small set of valid bytes for each string character.
        // The extra handling fromCodePoint has for things like surrogate pairs is therefore unnecessary.
        // eslint-disable-next-line unicorn/prefer-code-point
        return String.fromCharCode(0x00);
    }
}
/**
 * The position immediately after the end of the tree
 */
export class EndOfTreeSegment extends BaseEndpointSegment {
    constructor() {
        super(...arguments);
        this.type = "EndOfTreeSegment";
        this.endpointType = "end";
    }
    /**
     * this segment pretends to be a sibling of the last real segment.
     * so compute the necessary properties to pretend to be that segment.
     */
    endpointSegmentProps() {
        let lastSegment;
        let depth = 1;
        const root = this.mergeTree.root;
        depthFirstNodeWalk(root, root.children[root.childCount - 1], (node) => {
            depth++;
            if (node?.isLeaf()) {
                lastSegment = node;
                return NodeAction.Exit;
            }
        }, undefined, undefined, false);
        const parent = lastSegment?.parent ?? root;
        const index = parent.childCount;
        return {
            parent,
            index,
            depth,
        };
    }
    get ordinal() {
        // just compute an arbitrarily big ordinal
        // we base it on the depth of the tree
        // to ensure it is bigger than all ordinals in
        // the tree, as each layer appends to the previous
        return String.fromCodePoint(0xffff).repeat(this.endpointSegmentProps().depth);
    }
}
//# sourceMappingURL=endOfTreeSegment.js.map