"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocalReferenceCollection = exports.LocalReference = void 0;
const mergeTree_1 = require("./mergeTree");
const ops_1 = require("./ops");
const properties_1 = require("./properties");
class LocalReference {
    constructor(client, initSegment, offset = 0, refType = ops_1.ReferenceType.Simple) {
        this.client = client;
        this.offset = offset;
        this.refType = refType;
        this.segment = initSegment;
    }
    min(b) {
        if (this.compare(b) < 0) {
            return this;
        }
        else {
            return b;
        }
    }
    max(b) {
        if (this.compare(b) > 0) {
            return this;
        }
        else {
            return b;
        }
    }
    compare(b) {
        if (this.segment === b.segment) {
            return this.offset - b.offset;
        }
        else {
            if (this.segment === undefined
                || (b.segment !== undefined &&
                    this.segment.ordinal < b.segment.ordinal)) {
                return -1;
            }
            else {
                return 1;
            }
        }
    }
    toPosition() {
        if (this.segment && this.segment.parent) {
            return this.getOffset() + this.client.getPosition(this.segment);
        }
        else {
            return LocalReference.DetachedPosition;
        }
    }
    hasTileLabels() {
        return !!this.getTileLabels();
    }
    hasRangeLabels() {
        return !!this.getRangeLabels();
    }
    hasTileLabel(label) {
        return mergeTree_1.refHasTileLabel(this, label);
    }
    hasRangeLabel(label) {
        return mergeTree_1.refHasRangeLabel(this, label);
    }
    getTileLabels() {
        return mergeTree_1.refGetTileLabels(this);
    }
    getRangeLabels() {
        return mergeTree_1.refGetRangeLabels(this);
    }
    isLeaf() {
        return false;
    }
    addProperties(newProps, op) {
        this.properties = properties_1.addProperties(this.properties, newProps, op);
    }
    getClient() {
        return this.client;
    }
    getSegment() {
        return this.segment;
    }
    getOffset() {
        var _a;
        if ((_a = this.segment) === null || _a === void 0 ? void 0 : _a.removedSeq) {
            return 0;
        }
        return this.offset;
    }
    getProperties() {
        return this.properties;
    }
}
exports.LocalReference = LocalReference;
LocalReference.DetachedPosition = -1;
class LocalReferenceCollection {
    constructor(segment, initialRefsByfOffset = new Array(segment.cachedLength)) {
        this.segment = segment;
        this.hierRefCount = 0;
        this.refCount = 0;
        // Since javascript arrays are sparse the above won't populate any of the
        // indicies, but it will ensure the length property of the array matches
        // the length of the segment.
        this.refsByOffset = initialRefsByfOffset;
    }
    static append(seg1, seg2) {
        if (seg2.localRefs && !seg2.localRefs.empty) {
            if (!seg1.localRefs) {
                seg1.localRefs = new LocalReferenceCollection(seg1);
            }
            seg1.localRefs.append(seg2.localRefs);
        }
    }
    [Symbol.iterator]() {
        const subiterators = [];
        for (const refs of this.refsByOffset) {
            if (refs) {
                if (refs.before) {
                    subiterators.push(refs.before[Symbol.iterator]());
                }
                if (refs.at) {
                    subiterators.push(refs.at[Symbol.iterator]());
                }
                if (refs.after) {
                    subiterators.push(refs.after[Symbol.iterator]());
                }
            }
        }
        const iterator = {
            next() {
                while (subiterators.length > 0) {
                    const next = subiterators[0].next();
                    if (next.done === true) {
                        subiterators.shift();
                    }
                    else {
                        return next;
                    }
                }
                return { value: undefined, done: true };
            },
            [Symbol.iterator]() {
                return this;
            },
        };
        return iterator;
    }
    clear() {
        this.refCount = 0;
        this.hierRefCount = 0;
        const detachSegments = (refs) => {
            if (refs) {
                refs.forEach((r) => {
                    if (r.segment === this.segment) {
                        r.segment = undefined;
                    }
                });
            }
        };
        for (let i = 0; i < this.refsByOffset.length; i++) {
            const refsAtOffset = this.refsByOffset[i];
            if (refsAtOffset) {
                detachSegments(refsAtOffset.before);
                detachSegments(refsAtOffset.at);
                detachSegments(refsAtOffset.before);
                this.refsByOffset[i] = undefined;
            }
        }
    }
    get empty() {
        return this.refCount === 0;
    }
    addLocalRef(lref) {
        const refsAtOffset = this.refsByOffset[lref.offset];
        if (refsAtOffset === undefined) {
            this.refsByOffset[lref.offset] = {
                at: [lref],
            };
        }
        else {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            refsAtOffset.at.push(lref);
        }
        if (lref.hasRangeLabels() || lref.hasTileLabels()) {
            this.hierRefCount++;
        }
        this.refCount++;
    }
    removeLocalRef(lref) {
        const tryRemoveRef = (refs) => {
            if (refs) {
                const index = refs.indexOf(lref);
                if (index >= 0) {
                    refs.splice(index, 1);
                    if (lref.hasRangeLabels() || lref.hasTileLabels()) {
                        this.hierRefCount--;
                    }
                    this.refCount--;
                    return lref;
                }
            }
        };
        const refAtOffset = this.refsByOffset[lref.offset];
        if (refAtOffset !== undefined) {
            let ref = tryRemoveRef(refAtOffset.before);
            if (ref) {
                return ref;
            }
            ref = tryRemoveRef(refAtOffset.at);
            if (ref) {
                return ref;
            }
            ref = tryRemoveRef(refAtOffset.after);
            if (ref) {
                return ref;
            }
        }
    }
    /**
     * Called by 'append()' implementations to append local refs from the given 'other' segment to the
     * end of 'this' segment.
     *
     * Note: This method should be invoked after the caller has ensured that segments can be merged,
     *       but before 'this' segment's cachedLength has changed, or the adjustment to the local refs
     *       will be incorrect.
     */
    append(other) {
        if (!other || other.empty) {
            return;
        }
        this.hierRefCount += other.hierRefCount;
        this.refCount += other.refCount;
        other.hierRefCount = 0;
        for (const lref of other) {
            lref.segment = this.segment;
            lref.offset += this.refsByOffset.length;
        }
        this.refsByOffset.push(...other.refsByOffset);
    }
    split(offset, splitSeg) {
        if (!this.empty) {
            const localRefs = new LocalReferenceCollection(splitSeg, this.refsByOffset.splice(offset, this.refsByOffset.length - offset));
            splitSeg.localRefs = localRefs;
            for (const lref of localRefs) {
                lref.segment = splitSeg;
                lref.offset -= offset;
                if (lref.hasRangeLabels() || lref.hasTileLabels()) {
                    this.hierRefCount--;
                    localRefs.hierRefCount++;
                }
                this.refCount--;
                localRefs.refCount++;
            }
        }
    }
    addBeforeTombstones(...refs) {
        const beforeRefs = [];
        for (const iterable of refs) {
            for (const lref of iterable) {
                // eslint-disable-next-line no-bitwise
                if (lref.refType & ops_1.ReferenceType.SlideOnRemove) {
                    beforeRefs.push(lref);
                    lref.segment = this.segment;
                    lref.offset = 0;
                    if (lref.hasRangeLabels() || lref.hasTileLabels()) {
                        this.hierRefCount++;
                    }
                    this.refCount++;
                }
                else {
                    lref.segment = undefined;
                }
            }
        }
        if (beforeRefs.length > 0) {
            if (this.refsByOffset[0] === undefined) {
                this.refsByOffset[0] = { before: beforeRefs };
            }
            else if (this.refsByOffset[0].before === undefined) {
                this.refsByOffset[0].before = beforeRefs;
            }
            else {
                this.refsByOffset[0].before.unshift(...beforeRefs);
            }
        }
    }
    addAfterTombstones(...refs) {
        const afterRefs = [];
        for (const iterable of refs) {
            for (const lref of iterable) {
                // eslint-disable-next-line no-bitwise
                if (lref.refType & ops_1.ReferenceType.SlideOnRemove) {
                    afterRefs.push(lref);
                    lref.segment = this.segment;
                    lref.offset = this.segment.cachedLength - 1;
                    if (lref.hasRangeLabels() || lref.hasTileLabels()) {
                        this.hierRefCount++;
                    }
                    this.refCount++;
                }
                else {
                    lref.segment = undefined;
                }
            }
        }
        if (afterRefs.length > 0) {
            const refsAtOffset = this.refsByOffset[this.segment.cachedLength - 1];
            if (refsAtOffset === undefined) {
                this.refsByOffset[this.segment.cachedLength - 1] = { after: afterRefs };
            }
            else if (refsAtOffset.after === undefined) {
                refsAtOffset.after = afterRefs;
            }
            else {
                refsAtOffset.after.push(...afterRefs);
            }
        }
    }
}
exports.LocalReferenceCollection = LocalReferenceCollection;
//# sourceMappingURL=localReference.js.map