/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { AttributionKey } from "@fluidframework/runtime-definitions";
import { IAttributionCollection } from "./attributionCollection";
import { LocalReferenceCollection } from "./localReference";
import { ISegmentLeaf } from "./mergeTree";
import { IMergeTreeDeltaOpArgs } from "./mergeTreeDeltaCallback";
import { TrackingGroupCollection } from "./mergeTreeTracking";
import { IJSONSegment, IMarkerDef, ReferenceType } from "./ops";
import { PartialSequenceLengths } from "./partialLengths";
import { MapLike, PropertySet } from "./properties";
import { ReferencePosition } from "./referencePositions";
import { SegmentGroupCollection } from "./segmentGroupCollection";
import { PropertiesManager, PropertiesRollback } from "./segmentPropertiesManager";
/**
 * Common properties for a node in a merge tree.
 * @alpha
 */
export interface IMergeNodeCommon {
    /**
     * The index of this node in its parent's list of children.
     */
    index: number;
    /**
     * A string that can be used for comparing the location of this node to other `MergeNode`s in the same tree.
     * `a.ordinal < b.ordinal` if and only if `a` comes before `b` in a pre-order traversal of the tree.
     */
    ordinal: string;
    isLeaf(): this is ISegment;
}
export type IMergeLeaf = ISegment & {
    parent?: IMergeBlock;
};
export type IMergeNode = IMergeBlock | IMergeLeaf;
/**
 * Internal (i.e. non-leaf) node in a merge tree.
 * @internal
 */
export interface IMergeBlock extends IMergeNodeCommon {
    parent?: IMergeBlock;
    needsScour?: boolean;
    /**
     * Number of direct children of this node
     */
    childCount: number;
    /**
     * Array of child nodes.
     *
     * @remarks To avoid reallocation, this is always initialized to have maximum length as deemed by
     * the merge tree's branching factor. Use `childCount` to determine how many children this node actually has.
     */
    children: IMergeNode[];
    /**
     * Supports querying the total length of all descendants of this IMergeBlock from the perspective of any
     * (clientId, seq) within the collab window.
     *
     * @remarks This is only optional for implementation reasons (internal nodes can be created/moved without
     * immediately initializing the partial lengths). Aside from mid-update on tree operations, these lengths
     * objects are always defined.
     */
    partialLengths?: PartialSequenceLengths;
    /**
     * The length of the contents of the node.
     */
    cachedLength: number | undefined;
    hierBlock(): IHierBlock | undefined;
    assignChild(child: IMergeNode, index: number, updateOrdinal?: boolean): void;
    setOrdinal(child: IMergeNode, index: number): void;
}
/**
 * @internal
 */
export interface IHierBlock extends IMergeBlock {
    rightmostTiles: MapLike<ReferencePosition>;
    leftmostTiles: MapLike<ReferencePosition>;
}
/**
 * Contains removal information associated to an {@link ISegment}.
 * @alpha
 */
export interface IRemovalInfo {
    /**
     * Local seq at which this segment was removed, if the removal is yet-to-be acked.
     */
    localRemovedSeq?: number;
    /**
     * Seq at which this segment was removed.
     */
    removedSeq: number;
    /**
     * List of client IDs that have removed this segment.
     * The client that actually removed the segment (i.e. whose removal op was sequenced first) is stored as the first
     * client in this list. Other clients in the list have all issued concurrent ops to remove the segment.
     * @remarks When this list has length \> 1, this is referred to as the "overlapping remove" case.
     */
    removedClientIds: number[];
}
/**
 * @internal
 */
export declare function toRemovalInfo(maybe: Partial<IRemovalInfo> | undefined): IRemovalInfo | undefined;
/**
 * Tracks information about when and where this segment was moved to.
 *
 * Note that merge-tree does not currently support moving and only supports
 * obliterate. The fields below include "move" in their names to avoid renaming
 * in the future, when moves _are_ supported.
 * @alpha
 */
export interface IMoveInfo {
    /**
     * Local seq at which this segment was moved if the move is yet-to-be
     * acked.
     */
    localMovedSeq?: number;
    /**
     * The first seq at which this segment was moved.
     */
    movedSeq: number;
    /**
     * All seqs at which this segment was moved. In the case of overlapping,
     * concurrent moves this array will contain multiple seqs.
     *
     * The seq at  `movedSeqs[i]` corresponds to the client id at `movedClientIds[i]`.
     *
     * The first element corresponds to the seq of the first move
     */
    movedSeqs: number[];
    /**
     * A reference to the inserted destination segment corresponding to this
     * segment's move.
     *
     * If undefined, the move was an obliterate.
     *
     * Currently this field is unused, as we only support obliterate operations
     */
    moveDst?: ReferencePosition;
    /**
     * List of client IDs that have moved this segment.
     *
     * The client that actually moved the segment (i.e. whose move op was sequenced
     * first) is stored as the first client in this list. Other clients in the
     * list have all issued concurrent ops to move the segment.
     */
    movedClientIds: number[];
    /**
     * If this segment was inserted into a concurrently moved range and
     * the move op was sequenced before the insertion op. In this case,
     * the segment is visible only to the inserting client
     *
     * `wasMovedOnInsert` only applies for acked obliterates. That is, if
     * a segment inserted by a remote client is moved on insertion by a local
     * and unacked obliterate, we do not consider it as having been moved
     * on insert
     *
     * If a segment is moved on insertion, its length is only ever visible to
     * the client that inserted the segment. This is relevant in partial length
     * calculations
     */
    wasMovedOnInsert: boolean;
}
export declare function toMoveInfo(maybe: Partial<IMoveInfo> | undefined): IMoveInfo | undefined;
/**
 * A segment representing a portion of the merge tree.
 * Segments are leaf nodes of the merge tree and contain data.
 * @alpha
 */
export interface ISegment extends IMergeNodeCommon, Partial<IRemovalInfo>, Partial<IMoveInfo> {
    readonly type: string;
    readonly segmentGroups: SegmentGroupCollection;
    readonly trackingCollection: TrackingGroupCollection;
    /**
     * Whether or not this segment is a special segment denoting the start or
     * end of the tree
     *
     * Endpoint segments are imaginary segments positioned immediately before or
     * after the tree. These segments cannot be referenced by regular operations
     * and exist primarily as a bucket for local references to slide onto during
     * deletion of regular segments.
     */
    readonly endpointType?: "start" | "end";
    /**
     * The length of the contents of the node.
     */
    cachedLength: number;
    /**
     * Stores attribution keys associated with offsets of this segment.
     * This data is only persisted if MergeTree's `attributions.track` flag is set to true.
     * Pending segments (i.e. ones that only exist locally and haven't been acked by the server) also have
     * `attribution === undefined` until ack.
     *
     * Keys can be used opaquely with an IAttributor or a container runtime that provides attribution.
     * @remarks There are plans to make the shape of the data stored extensible in a couple ways:
     *
     * 1. Injection of custom attribution information associated with the segment (ex: copy-paste of
     * content but keeping the old attribution information).
     *
     * 2. Storage of multiple "channels" of information (ex: track property changes separately from insertion,
     * or only attribute certain property modifications, etc.)
     */
    attribution?: IAttributionCollection<AttributionKey>;
    /**
     * Manages pending local state for properties on this segment.
     */
    propertyManager?: PropertiesManager;
    /**
     * Local seq at which this segment was inserted.
     * This is defined if and only if the insertion of the segment is pending ack, i.e. `seq` is UnassignedSequenceNumber.
     * Once the segment is acked, this field is cleared.
     *
     * See {@link CollaborationWindow.localSeq} for more information on the semantics of localSeq.
     */
    localSeq?: number;
    /**
     * Local seq at which this segment was removed. If this is defined, `removedSeq` will initially be set to
     * UnassignedSequenceNumber. However, if another client concurrently removes the same segment, `removedSeq`
     * will be updated to the seq at which that client removed this segment.
     *
     * Like {@link ISegment.localSeq}, this field is cleared once the local removal of the segment is acked.
     * See {@link CollaborationWindow.localSeq} for more information on the semantics of localSeq.
     */
    localRemovedSeq?: number;
    /**
     * Seq at which this segment was inserted.
     * If undefined, it is assumed the segment was inserted prior to the collab window's minimum sequence number.
     */
    seq?: number;
    /**
     * Short clientId for the client that inserted this segment.
     */
    clientId: number;
    /**
     * Local references added to this segment.
     */
    localRefs?: LocalReferenceCollection;
    /**
     * Properties that have been added to this segment via annotation.
     */
    properties?: PropertySet;
    /**
     * Add properties to this segment via annotation.
     *
     * @remarks This function should not be called directly. Properties should
     * be added through the `annotateRange` functions.
     */
    addProperties(newProps: PropertySet, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback): PropertySet;
    clone(): ISegment;
    canAppend(segment: ISegment): boolean;
    append(segment: ISegment): void;
    splitAt(pos: number): ISegment | undefined;
    toJSONObject(): any;
    /**
     * Acks the current segment against the segment group, op, and merge tree.
     *
     * @param segmentGroup - Pending segment group associated with this op.
     * @param opArgs - Information about the op that was acked
     * @returns `true` if the op modifies the segment, otherwise `false`.
     * The only current false case is overlapping remove, where a segment is removed
     * by a previously sequenced operation before the current operation is acked.
     * @throws - error if the segment state doesn't match segment group or op.
     * E.g. if the segment group is not first in the pending queue, or
     * an inserted segment does not have unassigned sequence number.
     */
    ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean;
}
/**
 * @internal
 */
export interface IMarkerModifiedAction {
    (marker: Marker): void;
}
/**
 * @alpha
 */
export interface ISegmentAction<TClientData> {
    (segment: ISegment, pos: number, refSeq: number, clientId: number, start: number, end: number, accum: TClientData): boolean;
}
/**
 * @internal
 */
export interface ISegmentChanges {
    next?: ISegment;
    replaceCurrent?: ISegment;
}
/**
 * @internal
 */
export interface BlockAction<TClientData> {
    (block: IMergeBlock, pos: number, refSeq: number, clientId: number, start: number | undefined, end: number | undefined, accum: TClientData): boolean;
}
/**
 * @internal
 */
export interface NodeAction<TClientData> {
    (node: IMergeNode, pos: number, refSeq: number, clientId: number, start: number | undefined, end: number | undefined, clientData: TClientData): boolean;
}
/**
 * @internal
 */
export interface InsertContext {
    candidateSegment?: ISegment;
    leaf: (segment: ISegment | undefined, pos: number, ic: InsertContext) => ISegmentChanges;
    continuePredicate?: (continueFromBlock: IMergeBlock) => boolean;
}
/**
 * @internal
 */
export interface SegmentActions<TClientData> {
    leaf?: ISegmentAction<TClientData>;
    shift?: NodeAction<TClientData>;
    contains?: NodeAction<TClientData>;
    pre?: BlockAction<TClientData>;
    post?: BlockAction<TClientData>;
}
/**
 * @deprecated This functionality was not meant to be exported and will be removed in a future release
 * @alpha
 */
export interface SegmentGroup {
    segments: ISegmentLeaf[];
    previousProps?: PropertySet[];
    localSeq?: number;
    refSeq: number;
}
/**
 * @alpha
 */
export declare class MergeNode implements IMergeNodeCommon {
    index: number;
    ordinal: string;
    cachedLength: number;
    isLeaf(): this is ISegment;
}
/**
 * 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.)
 * @internal
 */
export declare const MaxNodesInBlock = 8;
/**
 * @internal
 */
export declare class MergeBlock extends MergeNode implements IMergeBlock {
    childCount: number;
    parent?: IMergeBlock;
    children: IMergeNode[];
    constructor(childCount: number);
    hierBlock(): IHierBlock | undefined;
    setOrdinal(child: IMergeNode, index: number): void;
    assignChild(child: IMergeNode, index: number, updateOrdinal?: boolean): void;
}
export declare function seqLTE(seq: number, minOrRefSeq: number): boolean;
/**
 * @alpha
 */
export declare abstract class BaseSegment extends MergeNode implements ISegment {
    clientId: number;
    seq: number;
    removedSeq?: number;
    removedClientIds?: number[];
    movedSeq?: number;
    movedSeqs?: number[];
    movedClientIds?: number[];
    wasMovedOnInsert?: boolean | undefined;
    readonly segmentGroups: SegmentGroupCollection;
    readonly trackingCollection: TrackingGroupCollection;
    /***/
    attribution?: IAttributionCollection<AttributionKey>;
    propertyManager?: PropertiesManager;
    properties?: PropertySet;
    localRefs?: LocalReferenceCollection;
    abstract readonly type: string;
    localSeq?: number;
    localRemovedSeq?: number;
    localMovedSeq?: number;
    addProperties(newProps: PropertySet, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback): PropertySet;
    hasProperty(key: string): boolean;
    isLeaf(): this is ISegment;
    protected cloneInto(b: ISegment): void;
    canAppend(segment: ISegment): boolean;
    protected addSerializedProps(jseg: IJSONSegment): void;
    abstract toJSONObject(): any;
    /***/
    ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean;
    splitAt(pos: number): ISegment | undefined;
    private copyPropertiesTo;
    abstract clone(): ISegment;
    append(other: ISegment): void;
    protected abstract createSplitSegmentAt(pos: number): BaseSegment | undefined;
}
/**
 * The special-cased property key that tracks the id of a {@link Marker}.
 *
 * @remarks In general, marker ids should be accessed using the inherent method
 * {@link Marker.getId}. Marker ids should not be updated after creation.
 * @internal
 */
export declare const reservedMarkerIdKey = "markerId";
/**
 * @internal
 */
export declare const reservedMarkerSimpleTypeKey = "markerSimpleType";
/**
 * @alpha
 */
export interface IJSONMarkerSegment extends IJSONSegment {
    marker: IMarkerDef;
}
/**
 * Markers are a special kind of segment that do not hold any content.
 *
 * Markers with a reference type of {@link ReferenceType.Tile} support spatially
 * accelerated queries for finding the next marker to the left or right of it in
 * sub-linear time. This is useful, for example, in the case of jumping from the
 * start of a paragraph to the end, assuming a paragraph is bound by markers at
 * the start and end.
 *
 * @alpha
 */
export declare class Marker extends BaseSegment implements ReferencePosition, ISegment {
    refType: ReferenceType;
    static readonly type = "Marker";
    static is(segment: ISegment): segment is Marker;
    readonly type = "Marker";
    static make(refType: ReferenceType, props?: PropertySet): Marker;
    constructor(refType: ReferenceType);
    toJSONObject(): IJSONMarkerSegment;
    static fromJSONObject(spec: any): Marker | undefined;
    clone(): Marker;
    getSegment(): this;
    getOffset(): number;
    getProperties(): PropertySet | undefined;
    getId(): string | undefined;
    toString(): string;
    protected createSplitSegmentAt(pos: number): undefined;
    canAppend(segment: ISegment): boolean;
    append(): void;
}
/**
 * @deprecated This functionality was not meant to be exported and will be removed in a future release
 * @alpha
 */
export declare class CollaborationWindow {
    clientId: number;
    collaborating: boolean;
    /**
     * Lowest-numbered segment in window; no client can reference a state before this one
     */
    minSeq: number;
    /**
     * Highest-numbered segment in window and current reference sequence number for this client.
     */
    currentSeq: number;
    /**
     * Highest-numbered localSeq used for a pending segment.
     * Semantically, `localSeq`s provide an ordering on in-flight merge-tree operations:
     * for operations stamped with localSeqs `a` and `b`, `a < b` if and only if `a` was submitted before `b`.
     *
     * @remarks - This field is analogous to the `clientSequenceNumber` field on ops, but it's accessible to merge-tree
     * at op submission time rather than only at ack time. This enables more natural state tracking for in-flight ops.
     *
     * It's useful to stamp ops with such an incrementing counter because it enables reasoning about which segments existed from
     * the perspective of the local client at a given point in 'un-acked' time, which is necessary to support the reconnect flow.
     *
     * For example, imagine a client with initial state "123456" submits some ops to create the text "123456ABC".
     * If they insert the "C" first, then "B", then "A", their local segment state might look like this:
     * ```js
     * [
     *     { seq: 0, text: "1234" },
     *     { seq: 5, text: "56" },
     *     { localSeq: 3, seq: UnassignedSequenceNumber, text: "A" },
     *     { localSeq: 2, seq: UnassignedSequenceNumber, text: "B" },
     *     { localSeq: 1, seq: UnassignedSequenceNumber, text: "C" },
     * ]
     * ```
     * (note that {@link ISegment.localSeq} tracks the localSeq at which a segment was inserted)
     *
     * Suppose the client then disconnects and reconnects before any of its insertions are acked. The reconnect flow will necessitate
     * that the client regenerates and resubmits ops based on its current segment state as well as the original op that was sent.
     *
     * It will generate the ops
     * 1. \{ pos: 6, text: "C" \}
     * 2. \{ pos: 6, text: "B" \}
     * 3. \{ pos: 6, text: "A" \}
     *
     * since when submitting the first op, remote clients don't know that this client is about to submit the "A" and "B".
     *
     * On the other hand, imagine if the client had originally submitted the ops in the order "A", "B", "C"
     * such that the segments' local state was instead:
     *
     * ```js
     * [
     *     { seq: 0, text: "1234" },
     *     { seq: 5, text: "56" },
     *     { localSeq: 1, seq: UnassignedSequenceNumber, text: "A" },
     *     { localSeq: 2, seq: UnassignedSequenceNumber, text: "B" },
     *     { localSeq: 3, seq: UnassignedSequenceNumber, text: "C" },
     * ]
     * ```
     *
     * The resubmitted ops should instead be:
     * 1. \{ pos: 6, text: "A" \}
     * 2. \{ pos: 7, text: "B" \}
     * 3. \{ pos: 8, text: "C" \}
     *
     * since remote clients will have seen the "A" when processing the "B" as well as both the "A" and "B" when processing the "C".
     * As can be seen, the list of resubmitted ops is different in the two cases despite the merge-tree's segment state only differing
     * in `localSeq`.
     *
     * This example is a bit simplified from the general scenario: since no remote clients modified the merge-tree while the client
     * was disconnected, the resubmitted ops end up matching the original ops exactly.
     * However, this is not generally true: the production reconnect code takes into account visibility of segments based on both acked
     * and local information as appropriate.
     * Nonetheless, this simple scenario is enough to understand why it's useful to be able to determine if a segment should be visible
     * from a given (seq, localSeq) perspective.
     */
    localSeq: number;
    loadFrom(a: CollaborationWindow): void;
}
/**
 * @internal
 */
export declare const compareNumbers: (a: number, b: number) => number;
/**
 * @internal
 */
export declare const compareStrings: (a: string, b: string) => number;
/**
 * Get a human-readable string for a given {@link Marker}.
 *
 * @remarks This function is intended for debugging only. The exact format of
 * this string should not be relied upon between versions.
 * @internal
 */
export declare function debugMarkerToString(marker: Marker): string;
//# sourceMappingURL=mergeTreeNodes.d.ts.map