/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert, unreachableCase } from "@fluidframework/common-utils";
import { UsageError } from "@fluidframework/container-utils";
import { List } from "./collections";
import { EndOfTreeSegment } from "./endOfTreeSegment";
import { LocalReferenceCollection } from "./localReference";
import { toRemovalInfo } from "./mergeTreeNodes";
import { depthFirstNodeWalk } from "./mergeTreeNodeWalk";
import { TrackingGroup } from "./mergeTreeTracking";
import { MergeTreeDeltaType, ReferenceType, } from "./ops";
import { matchProperties } from "./properties";
import { DetachedReferencePosition } from "./referencePositions";
function appendLocalInsertToRevertibles(deltaArgs, revertibles) {
    var _a;
    if (((_a = revertibles[revertibles.length - 1]) === null || _a === void 0 ? void 0 : _a.operation) !== MergeTreeDeltaType.INSERT) {
        revertibles.push({
            operation: MergeTreeDeltaType.INSERT,
            trackingGroup: new TrackingGroup(),
        });
    }
    const last = revertibles[revertibles.length - 1];
    deltaArgs.deltaSegments.forEach((t) => last.trackingGroup.link(t.segment));
    return revertibles;
}
function appendLocalRemoveToRevertibles(driver, deltaArgs, revertibles) {
    var _a;
    if (((_a = revertibles[revertibles.length - 1]) === null || _a === void 0 ? void 0 : _a.operation) !== MergeTreeDeltaType.REMOVE) {
        revertibles.push({
            operation: MergeTreeDeltaType.REMOVE,
            trackingGroup: new TrackingGroup(),
        });
    }
    const last = revertibles[revertibles.length - 1];
    deltaArgs.deltaSegments.forEach((t) => {
        var _a, _b;
        const props = {
            segSpec: t.segment.toJSONObject(),
            referenceSpace: "mergeTreeDeltaRevertible",
        };
        const ref = driver.createLocalReferencePosition(t.segment, 0, ReferenceType.SlideOnRemove, props);
        const internalDriver = driver;
        const driverRevertibleProps = (_a = internalDriver.__mergeTreeRevertible) !== null && _a !== void 0 ? _a : (internalDriver.__mergeTreeRevertible = {});
        ref.callbacks = (_b = driverRevertibleProps.refCallbacks) !== null && _b !== void 0 ? _b : (driverRevertibleProps.refCallbacks = {
            afterSlide: (r) => {
                var _a, _b;
                if (driver.localReferencePositionToPosition(r) === DetachedReferencePosition) {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    const detached = (_a = driverRevertibleProps.detachedReferences) !== null && _a !== void 0 ? _a : (driverRevertibleProps.detachedReferences = new EndOfTreeSegment(r.getSegment()));
                    const refs = (_b = detached.localRefs) !== null && _b !== void 0 ? _b : (detached.localRefs = new LocalReferenceCollection(detached));
                    refs.addAfterTombstones([r]);
                }
            },
        });
        t.segment.trackingCollection.trackingGroups.forEach((tg) => {
            tg.link(ref);
            tg.unlink(t.segment);
        });
        last.trackingGroup.link(ref);
    });
    return revertibles;
}
function appendLocalAnnotateToRevertibles(deltaArgs, revertibles) {
    let last = revertibles[revertibles.length - 1];
    deltaArgs.deltaSegments.forEach((ds) => {
        const propertyDeltas = ds.propertyDeltas;
        if (propertyDeltas) {
            if ((last === null || last === void 0 ? void 0 : last.operation) === MergeTreeDeltaType.ANNOTATE
                && matchProperties(last === null || last === void 0 ? void 0 : last.propertyDeltas, propertyDeltas)) {
                last.trackingGroup.link(ds.segment);
            }
            else {
                last = {
                    operation: MergeTreeDeltaType.ANNOTATE,
                    propertyDeltas,
                    trackingGroup: new TrackingGroup(),
                };
                last.trackingGroup.link(ds.segment);
                revertibles.push(last);
            }
        }
    });
    return revertibles;
}
/**
 * Revertibles are new and require the option
 * mergeTreeUseNewLengthCalculations to be set as true on the underlying merge tree
 * in order to function correctly.
 * @alpha
 */
export function appendToMergeTreeDeltaRevertibles(driver, deltaArgs, revertibles) {
    switch (deltaArgs.operation) {
        case MergeTreeDeltaType.INSERT:
            appendLocalInsertToRevertibles(deltaArgs, revertibles);
            break;
        case MergeTreeDeltaType.REMOVE:
            appendLocalRemoveToRevertibles(driver, deltaArgs, revertibles);
            break;
        case MergeTreeDeltaType.ANNOTATE:
            appendLocalAnnotateToRevertibles(deltaArgs, revertibles);
            break;
        default:
            throw new UsageError(`Unsupported event delta type: ${deltaArgs.operation}`);
    }
}
/**
 * Revertibles are new and require the option
 * mergeTreeUseNewLengthCalculations to be set as true on the underlying merge tree
 * in order to function correctly.
 * @alpha
 */
export function discardMergeTreeDeltaRevertible(revertibles) {
    revertibles.forEach((r) => {
        r.trackingGroup.tracked.forEach((t) => {
            var _a, _b;
            t.trackingCollection.unlink(r.trackingGroup);
            // remove untracked local references
            if (t.trackingCollection.empty && !t.isLeaf()) {
                (_b = (_a = t.getSegment()) === null || _a === void 0 ? void 0 : _a.localRefs) === null || _b === void 0 ? void 0 : _b.removeLocalRef(t);
            }
        });
    });
}
function revertLocalInsert(driver, revertible) {
    while (revertible.trackingGroup.size > 0) {
        const tracked = revertible.trackingGroup.tracked[0];
        assert(tracked.trackingCollection.unlink(revertible.trackingGroup), 0x3f1 /* tracking group removed */);
        assert(tracked.isLeaf(), 0x3f2 /* inserts must track segments */);
        if (toRemovalInfo(tracked) === undefined) {
            const start = driver.getPosition(tracked);
            driver.removeRange(start, start + tracked.cachedLength);
        }
    }
}
function revertLocalRemove(driver, revertible) {
    var _a, _b, _c, _d, _e, _f, _g, _h, _j;
    while (revertible.trackingGroup.size > 0) {
        const tracked = revertible.trackingGroup.tracked[0];
        assert(tracked.trackingCollection.unlink(revertible.trackingGroup), 0x3f3 /* tracking group removed */);
        assert(!tracked.isLeaf(), 0x3f4 /* removes must track local refs */);
        let realPos = driver.localReferencePositionToPosition(tracked);
        const refSeg = tracked.getSegment();
        // References which are on EndOfStringSegment don't return detached for pos,
        // they will return the length of the merge-tree. this case just catches
        // random references, likely not created in the revertible flow,
        // that are tying to be reverted for some reason.
        if (realPos === DetachedReferencePosition || refSeg === undefined) {
            throw new UsageError("Cannot insert at detached references position");
        }
        if (toRemovalInfo(refSeg) === undefined
            && ((_a = refSeg.localRefs) === null || _a === void 0 ? void 0 : _a.isAfterTombstone(tracked))) {
            realPos++;
        }
        const props = tracked.properties;
        driver.insertFromSpec(realPos, props.segSpec);
        const insertSegment = driver.getContainingSegment(realPos).segment;
        assert(insertSegment !== undefined, 0x3f5 /* insert segment must exist at position */);
        const localSlideFilter = (lref) => { var _a; return ((_a = lref.properties) === null || _a === void 0 ? void 0 : _a.referenceSpace) === "mergeTreeDeltaRevertible"; };
        const insertRef = {};
        const forward = insertSegment.ordinal < refSeg.ordinal;
        const refHandler = (lref) => {
            var _a, _b;
            // once we reach it keep the original reference where it is
            // we'll move tracking groups, and remove it as a last step.
            if (tracked === lref) {
                return false;
            }
            if (localSlideFilter(lref)) {
                if (forward) {
                    const before = (_a = insertRef.before) !== null && _a !== void 0 ? _a : (insertRef.before = new List());
                    before.push(lref);
                }
                else {
                    const after = (_b = insertRef.after) !== null && _b !== void 0 ? _b : (insertRef.after = new List());
                    after.unshift(lref);
                }
            }
        };
        depthFirstNodeWalk(
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        insertSegment.parent, insertSegment, undefined, (seg) => {
            var _a;
            if (((_a = seg.localRefs) === null || _a === void 0 ? void 0 : _a.empty) === false) {
                return seg.localRefs.walkReferences(refHandler, undefined, forward);
            }
            return true;
        }, undefined, forward);
        const internalDriver = driver;
        if ((_d = (_c = (_b = internalDriver.__mergeTreeRevertible) === null || _b === void 0 ? void 0 : _b.detachedReferences) === null || _c === void 0 ? void 0 : _c.localRefs) === null || _d === void 0 ? void 0 : _d.has(tracked)) {
            assert(forward, 0x3f6 /* forward should always be true when detached */);
            internalDriver.__mergeTreeRevertible.detachedReferences.localRefs.walkReferences(refHandler);
        }
        if (insertRef !== undefined) {
            const localRefs = (_e = insertSegment.localRefs) !== null && _e !== void 0 ? _e : (insertSegment.localRefs = new LocalReferenceCollection(insertSegment));
            if (((_f = insertRef.before) === null || _f === void 0 ? void 0 : _f.empty) === false) {
                localRefs.addBeforeTombstones(insertRef.before.map((n) => n.data));
            }
            if (((_g = insertRef.after) === null || _g === void 0 ? void 0 : _g.empty) === false) {
                localRefs.addAfterTombstones(insertRef.after.map((n) => n.data));
            }
        }
        tracked.trackingCollection.trackingGroups.forEach((tg) => {
            tg.link(insertSegment);
            tg.unlink(tracked);
        });
        (_j = (_h = tracked.getSegment()) === null || _h === void 0 ? void 0 : _h.localRefs) === null || _j === void 0 ? void 0 : _j.removeLocalRef(tracked);
    }
}
function revertLocalAnnotate(driver, revertible) {
    while (revertible.trackingGroup.size > 0) {
        const tracked = revertible.trackingGroup.tracked[0];
        const unlinked = tracked.trackingCollection.unlink(revertible.trackingGroup);
        assert(unlinked && tracked.isLeaf(), 0x3f7 /* annotates must track segments */);
        if (toRemovalInfo(tracked) === undefined) {
            const start = driver.getPosition(tracked);
            driver.annotateRange(start, start + tracked.cachedLength, revertible.propertyDeltas);
        }
    }
}
/**
 * Revertibles are new and require the option
 * mergeTreeUseNewLengthCalculations to be set as true on the underlying merge tree
 * in order to function correctly.
 * @alpha
 */
export function revertMergeTreeDeltaRevertibles(driver, revertibles) {
    while (revertibles.length > 0) {
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        const r = revertibles.pop();
        const operation = r.operation;
        switch (operation) {
            case MergeTreeDeltaType.INSERT:
                revertLocalInsert(driver, r);
                break;
            case MergeTreeDeltaType.REMOVE:
                revertLocalRemove(driver, r);
                break;
            case MergeTreeDeltaType.ANNOTATE:
                revertLocalAnnotate(driver, r);
                break;
            default:
                unreachableCase(operation);
        }
    }
}
//# sourceMappingURL=revertibles.js.map