"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.AttributionCollection = void 0;
const common_utils_1 = require("@fluidframework/common-utils");
class AttributionCollection {
    constructor(baseEntry, _length) {
        this._length = _length;
        this.offsets = [0];
        this.seqs = [baseEntry];
    }
    getAtOffset(offset) {
        (0, common_utils_1.assert)(offset >= 0 && offset < this._length, 0x443 /* Requested offset should be valid */);
        return { type: "op", seq: this.seqs[this.findIndex(offset)] };
    }
    findIndex(offset) {
        // Note: maximum length here is 256 for text segments. Perf testing shows that linear scan beats binary search
        // for attribution collections with under ~64 entries, and even at maximum size (which would require a maximum
        // length segment with every offset having different attribution), getAtOffset is on the order of 100ns.
        let i = 0;
        while (i < this.offsets.length && offset > this.offsets[i]) {
            i++;
        }
        return this.offsets[i] === offset ? i : i - 1;
    }
    get length() {
        return this._length;
    }
    /**
     * Splits this attribution collection into two with entries for [0, pos) and [pos, length).
     */
    splitAt(pos) {
        const splitIndex = this.findIndex(pos);
        const splitBaseEntry = this.seqs[splitIndex];
        const splitCollection = new AttributionCollection(splitBaseEntry, this.length - pos);
        for (let i = splitIndex + 1; i < this.seqs.length; i++) {
            splitCollection.offsets.push(this.offsets[i] - pos);
            splitCollection.seqs.push(this.seqs[i]);
        }
        const spliceIndex = this.offsets[splitIndex] === pos ? splitIndex : splitIndex + 1;
        this.seqs.splice(spliceIndex);
        this.offsets.splice(spliceIndex);
        this._length = pos;
        return splitCollection;
    }
    append(other) {
        const lastEntry = this.seqs[this.seqs.length - 1];
        for (let i = 0; i < other.seqs.length; i++) {
            if (i !== 0 || lastEntry !== other.seqs[i]) {
                this.offsets.push(other.offsets[i] + this.length);
                this.seqs.push(other.seqs[i]);
            }
        }
        this._length += other.length;
    }
    getAll() {
        const results = new Array(this.seqs.length);
        for (let i = 0; i < this.seqs.length; i++) {
            results[i] = { offset: this.offsets[i], key: { type: "op", seq: this.seqs[i] } };
        }
        return results;
    }
    clone() {
        const copy = new AttributionCollection(0, this.length);
        copy.seqs = this.seqs.slice();
        copy.offsets = this.offsets.slice();
        return copy;
    }
    /**
     * Rehydrates attribution information from its serialized form into the provided iterable of consecutive segments.
     */
    static populateAttributionCollections(segments, summary) {
        const { seqs, posBreakpoints } = summary;
        (0, common_utils_1.assert)(seqs.length === posBreakpoints.length && seqs.length > 0, 0x445 /* Invalid attribution summary blob provided */);
        let curIndex = 0;
        let currentInfo = seqs[curIndex];
        let cumulativeSegPos = 0;
        for (const segment of segments) {
            const attribution = new AttributionCollection(currentInfo, segment.cachedLength);
            while (posBreakpoints[curIndex] < cumulativeSegPos + segment.cachedLength) {
                currentInfo = seqs[curIndex];
                const nextOffset = posBreakpoints[curIndex] - cumulativeSegPos;
                if (attribution.offsets[attribution.offsets.length - 1] !== nextOffset) {
                    attribution.offsets.push(nextOffset);
                    attribution.seqs.push(currentInfo);
                }
                curIndex++;
            }
            if (posBreakpoints[curIndex] === cumulativeSegPos + segment.cachedLength) {
                currentInfo = seqs[curIndex];
            }
            segment.attribution = attribution;
            cumulativeSegPos += segment.cachedLength;
        }
    }
    /**
     * Condenses attribution information on consecutive segments into a `SerializedAttributionCollection`
     */
    static serializeAttributionCollections(segments) {
        var _a, _b;
        const posBreakpoints = [];
        const seqs = [];
        let mostRecentAttributionKey;
        let cumulativePos = 0;
        let segmentsWithAttribution = 0;
        let segmentsWithoutAttribution = 0;
        for (const segment of segments) {
            if (segment.attribution) {
                segmentsWithAttribution++;
                for (const { offset, key: info } of (_b = (_a = segment.attribution) === null || _a === void 0 ? void 0 : _a.getAll()) !== null && _b !== void 0 ? _b : []) {
                    if (info.seq !== mostRecentAttributionKey) {
                        posBreakpoints.push(offset + cumulativePos);
                        seqs.push(info.seq);
                    }
                    mostRecentAttributionKey = info.seq;
                }
            }
            else {
                segmentsWithoutAttribution++;
            }
            cumulativePos += segment.cachedLength;
        }
        (0, common_utils_1.assert)(segmentsWithAttribution === 0 || segmentsWithoutAttribution === 0, 0x446 /* Expected either all segments or no segments to have attribution information. */);
        const blobContents = { seqs, posBreakpoints, length: cumulativePos };
        return blobContents;
    }
}
exports.AttributionCollection = AttributionCollection;
//# sourceMappingURL=attributionCollection.js.map