/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { MergeTree } from "./mergeTree.js";
import { CollaborationWindow, type MergeBlock } from "./mergeTreeNodes.js";
/**
 * Tracks length information for a part of a MergeTree (block) at a given time (seq).
 * These objects are associated with internal nodes (i.e. blocks).
 */
export interface PartialSequenceLength {
    /**
     * Sequence number
     */
    seq: number;
    /**
     * The length of the associated block.
     */
    len: number;
    /**
     * The delta between the current length of the associated block and its length at the previous seq number.
     */
    seglen: number;
    /**
     * clientId for the client that submitted the op with sequence number `seq`.
     */
    clientId?: number;
}
export interface PartialSequenceLengthsOptions {
    verifier?: (partialLengths: PartialSequenceLengths) => void;
    verifyExpected?: (mergeTree: MergeTree, node: MergeBlock, refSeq: number, clientId: number, localSeq?: number) => void;
    zamboni: boolean;
}
/**
 * Keeps track of partial sums of segment lengths for all sequence numbers in the current collaboration window.
 * Only used during active collaboration.
 *
 * This class is associated with an internal node (block) of a MergeTree. It efficiently answers queries of the form
 * "What is the length of `block` from the perspective of some particular seq and clientId?".
 *
 * It also supports incremental updating of state for newly-sequenced ops that don't affect the structure of the
 * MergeTree (in most cases--see AB#31003 or comments on {@link PartialSequenceLengths.update}).
 *
 * To answer these queries, it pre-builds several lists which track the length of the block at a per-sequence-number
 * level. These lists are:
 *
 * 1. (`partialLengths`): Stores the total length of the block.
 * 2. (`perClientAdjustments[clientId]`): Stores adjustments to the base length which account for all changes submitted by `clientId`. [see footnote]
 *
 * The reason both lists are necessary is that resolving the length of the block from the perspective of
 * (clientId, refSeq) requires including both of the following types of segments:
 * 1. Segments sequenced before `refSeq`
 * 2. Segments submitted by `clientId`
 *
 * This is possible with the above bookkeeping, using:
 *
 * (length of the block at the minimum sequence number)
 * + (partialLengths total length at refSeq)
 * + (clientSeqNumbers total length at most recent op)
 * - (clientSeqNumbers total length at refSeq)
 *
 * where the subtraction avoids double-counting segments submitted by clientId sequenced within the collab window.
 *
 * To enable reconnect, if constructed with `computeLocalPartials === true` it also supports querying for the length of
 * the block from the perspective of the local client at a particular `refSeq` and `localSeq`. This computation is
 * similar to the above:
 *
 * (length of the block at the minimum sequence number)
 * + (partialLengths total length at refSeq)
 * + (unsequenced edits' total length submitted before localSeq)
 * + (adjustments for changes double-counted by happening at or before both localSeq and refSeq)
 *
 * This algorithm scales roughly linearly with number of editing clients and the size of the collab window.
 * (certain unlikely sequences of operations may introduce log factors on those variables)
 *
 * @privateRemarks
 * If you are looking to understand this class in more detail, a suggested order of internalization is:
 *
 * 1. The above description and how it relates to the implementation of `getPartialLength` (which implements the above high-level description
 * 2. `PartialSequenceLengthsSet`, which allows binary searching for overall length deltas at a given sequence number and handles updates.
 * 3. The `fromLeaves` method, which is the base case for the [potential] recursion in `combine`
 * 4. The logic in `combine` to aggregate smaller block entries into larger ones
 * 5. The incremental code path of `update`
 */
export declare class PartialSequenceLengths {
    /**
     * The minimumSequenceNumber as defined by the collab window used in the last call to `update`,
     * or if no such calls have been made, the one used on construction.
     */
    minSeq: number;
    static options: PartialSequenceLengthsOptions;
    /**
     * Length of the block this PartialSequenceLength corresponds to when viewed at `minSeq`.
     */
    private minLength;
    /**
     * Total number of segments in the subtree rooted at the block this PartialSequenceLength corresponds to.
     */
    private segmentCount;
    /**
     * List of PartialSequenceLength objects--ordered by increasing seq--giving length information about
     * the block associated with this PartialSequenceLengths object.
     *
     * `minLength + partialLengths[i].len` gives the length of this block when considering the perspective of an observer
     * client who has received edits up to (and including) sequence number `i`.
     */
    private readonly partialLengths;
    /**
     * perClientAdjustments[clientId] contains a PartialSequenceLengthsSet of adjustments to the observer client's
     * perspective (see {@link PartialSequenceLengths.partialLengths}) necessary to account for changes made by
     * that client.
     *
     * As per doc comment on {@link PartialSequenceLengths}, the overall adjustment performed for the perspective of
     * (clientId, refSeq) is given by the sum of length deltas in `perClientAdjustments[clientId]`
     * for all sequence numbers S such that S \>= refSeq.
     *
     * (since these are ordered by sequence number and we cache cumulative sums, this is implemented using two lookups and a subtraction).
     *
     * The specific adjustments are roughly categorized as follows:
     *
     * - Ops submitted by a given client generally receive a partial lengths entry corresponding to their sequence number.
     * e.g. insert of "ABC" at seq 5 will have a per-client adjustment entry of \{ seq: 5, seglen: 3 \}.
     *
     * - When client A deletes a segment concurrently with client B and loses the race (B's delete is sequenced first),
     * A's per-client adjustments will contain an entry with a negative `seglen` corresponding to the length of the segment
     * and a sequence number corresponding to that of B's delete. It will *not* receive a per-client adjustment for its own delete.
     * This ensures that for perspectives (A, refSeq), the deleted segment will show up as a negative delta for all values of refSeq, since:
     * 1. For refSeq \< B's delete, the per-client adjustment will apply and be added to the total length
     * 2. For refSeq \>= B's delete, B's partial length entry in the overall set will apply, and the per-client adjustment will not apply
     *
     * - When client A attempts to insert a segment into a location that is concurrently obliterated by client B immediately upon insertion,
     * A's per-client adjustments will again not include an entry for its own insert.
     * Instead, the entry which would normally contain `seq` equal to that of A's insert would instead have `seq` equal to that of B's obliterate.
     * This gives the overall correct behavior: for any perspective which isn't client A, there is no adjustment necessary anywhere (it's as if
     * the segment never existed). For client A's perspective, the segment should be considered visible until A has acked B's obliterate.
     * This is accomplished as for the perspective (A, refSeq):
     * 1. For refSeq \< B's obliterate, the segment length will be included as part of the per-client adjustment for A
     * 2. For refSeq \>= B's obliterate, the segment will be omitted from the per-client adjustment for A
     *
     * Note that the special-casing for inserting segments that are immediately obliterated is only necessary for segments that never were visible
     * in the tree. If an insert and obliterate are concurrent but the insert is sequenced first, the normal per-client adjustment is fine.
     *
     * The second case (overlapping removal) applies to any combination of remove / obliterate operations.
     */
    private readonly perClientAdjustments;
    /**
     * Contains information required to answer queries for the length of this segment from the perspective of
     * the local client but not including all local segments (i.e., `localSeq !== collabWindow.localSeq`).
     * This field is only computed if requested in the constructor (i.e. `computeLocalPartials === true`).
     *
     * Note that the usage pattern for this list is a bit different from perClientAdjustments: when dealing with perspectives of remote clients,
     * we generally want to know what their view of the block was accounting for all changes made by that client as well as all \<= some refSeq.
     *
     * However, when dealing with perspectives relevant to the local client, we are still interested in changes made \<= some refSeq, but instead
     * of caring about all changes made by the local client, we additionally want the subset of them that were made \<= some localSeq.
     *
     * The PartialSequenceLengthsSets stored in this field therefore track localSeqs rather than seqs (it's still named seq for ease of implementation).
     * Furthermore, when computing the length of the block at a given refSeq/localSeq perspective,
     * rather than add something like `perClientAdjustments[clientId].latestLeq(latestSeq) - perClientAdjustments[clientId].latestLeq(refSeq)` [to
     * get the tail end of adjustments necessary for a remote client client], we instead add `unsequencedRecords.partialLengths.latestLeq(localSeq)`
     * [to get the head end of adjustments necessary for the local client].
     */
    private unsequencedRecords;
    constructor(
    /**
     * The minimumSequenceNumber as defined by the collab window used in the last call to `update`,
     * or if no such calls have been made, the one used on construction.
     */
    minSeq: number, computeLocalPartials: boolean);
    /**
     * Combine the partial lengths of block's children
     * @param block - an interior node. If `recur` is false, it is assumed that each interior node child of this block
     * has its partials up to date.
     * @param collabWindow - segment window of the segment tree containing `block`.
     * @param recur - whether to recursively compute partial lengths for internal children of `block`.
     * This incurs more work, but gives correct bookkeeping in the case that a descendant in the merge tree has been
     * modified without bubbling up the resulting partial length change to this block's partials.
     * @param computeLocalPartials - whether to compute partial length information about local unsequenced ops.
     * This enables querying for the length of the block at a given localSeq, but incurs extra work.
     * Local partial information doesn't support `update`.
     */
    static combine(block: MergeBlock, collabWindow: CollaborationWindow, recur?: boolean, computeLocalPartials?: boolean): PartialSequenceLengths;
    /**
     * Create a `PartialSequenceLengths` which tracks only changes incurred by direct child leaves of `block`.
     */
    private static fromLeaves;
    /**
     * Assuming this segment was removed on insertion, inserts length information about that operation
     * into the appropriate per-client adjustments (the overall view needs no such adjustment since
     * from an observing client's perspective, the segment never exists).
     */
    private static accountForRemoveOnInsert;
    /**
     * Inserts length information about the insertion of `segment` into
     * `combinedPartialLengths.partialLengths` and the appropriate per-client adjustments.
     */
    private static accountForInsertion;
    /**
     * Inserts length information about the removal or obliteration of `segment` into
     * `combinedPartialLengths.partialLengths` and the appropriate per-client adjustments.
     */
    private static accountForRemoval;
    /**
     * If incremental update of partial lengths fails, this gets set to the seq of the failed update.
     * When higher up blocks attempt to incrementally update, they first check if the seq they are updating for
     * matches this value. If it does, they propagate a full refresh instead.
     */
    private lastIncrementalInvalidationSeq;
    update(node: MergeBlock, seq: number, clientId: number, collabWindow: CollaborationWindow): void;
    /**
     * Returns the length of this block as viewed from the perspective of `clientId` at `refSeq`.
     * This is the total length of all segments sequenced at or before refSeq OR submitted by `clientId`.
     * If `clientId` is the local client, `localSeq` can also be provided. In that case, it is the total
     * length of all segments submitted at or before `refSeq` in addition to any local, unacked segments
     * with `segment.localSeq <= localSeq`.
     *
     * Note: the local case (where `localSeq !== undefined`) is only supported on a PartialSequenceLength object
     * constructed with `computeLocalPartials` set to true and not subsequently updated with `update`.
     */
    getPartialLength(refSeq: number, clientId: number, localSeq?: number): number;
    /**
     * Computes the seglen for the double-counted removed overlap at (refSeq, localSeq).
     *
     * Reconnect happens to only need to compute these lengths for two refSeq values: before and
     * after the rebase. Since these lists potentially scale with O(collab window * number of local edits)
     * and potentially need to be queried for each local op that gets rebased,
     * we cache the results for a given refSeq in `this.unsequencedRecords.cachedOverlappingByRefSeq` so
     * that they can be binary-searched the same way the usual partialLengths lists are.
     */
    private computeOverallRefSeqAdjustment;
    toString(glc?: (id: number) => string, indentCount?: number): string;
    private zamboni;
    private addClientAdjustment;
    private addLocalAdjustment;
    /**
     * Returns the partial lengths associated with the latest change associated with `clientId` at or before `refSeq`.
     * Returns undefined if no such change exists.
     */
    private latestClientEntryLEQ;
    /**
     * Get the partial lengths associated with the most recent change received by `clientId`, or undefined
     * if this client has made no changes in this block within the collab window.
     */
    private latestClientEntry;
}
export declare function verifyExpectedPartialLengths(mergeTree: MergeTree, node: MergeBlock, refSeq: number, clientId: number, localSeq?: number): void;
export declare function verifyPartialLengths(partialSeqLengths: PartialSequenceLengths): void;
//# sourceMappingURL=partialLengths.d.ts.map