/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { strict as assert } from "assert";
import { leaf } from "../../domains/index.js";
import { cursorForJsonableTreeNode } from "../../feature-libraries/index.js";
import { visitDelta, makeDetachedFieldIndex, } from "../../core/index.js";
import { brand } from "../../util/index.js";
import { deepFreeze, rootFromDeltaFieldMap, testRevisionTagCodec } from "../utils.js";
function visit(delta, visitor, detachedFieldIndex) {
    deepFreeze(delta);
    visitDelta(delta, visitor, detachedFieldIndex ?? makeDetachedFieldIndex("", testRevisionTagCodec));
}
const visitorMethods = [
    "create",
    "destroy",
    "attach",
    "detach",
    "replace",
    "enterNode",
    "exitNode",
    "enterField",
    "exitField",
];
function testVisit(delta, expected, detachedFieldIndex) {
    let callIndex = 0;
    const result = [];
    const makeChecker = (name) => (...args) => {
        result.push([name, ...args]);
        // To break when the first off script event happens, enable this line:
        // assert.deepStrictEqual([name, ...args], expected[callIndex]);
        callIndex += 1;
    };
    const visitor = {};
    for (const methodName of visitorMethods) {
        visitor[methodName] = makeChecker(methodName);
    }
    visit(delta, visitor, detachedFieldIndex);
    assert.deepEqual(result, expected);
}
function testTreeVisit(marks, expected, detachedFieldIndex, build, destroy) {
    const rootDelta = rootFromDeltaFieldMap(new Map([[rootKey, marks]]), build, destroy);
    testVisit(rootDelta, expected, detachedFieldIndex);
}
const rootKey = brand("root");
const fooKey = brand("foo");
const barKey = brand("bar");
const nodeX = { type: leaf.string.name, value: "X" };
const content = cursorForJsonableTreeNode(nodeX);
const field0 = brand("-0");
const field1 = brand("-1");
const field2 = brand("-2");
const field3 = brand("-3");
describe("visitDelta", () => {
    it("empty delta", () => {
        testTreeVisit({}, [
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", rootKey],
            ["exitField", rootKey],
        ]);
    });
    it("insert root", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const node = { minor: 42 };
        const rootFieldDelta = {
            local: [{ count: 1, attach: node }],
        };
        const delta = {
            build: [{ id: node, trees: [content] }],
            fields: new Map([[rootKey, rootFieldDelta]]),
        };
        const expected = [
            ["create", [content], field0],
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", rootKey],
            ["attach", field0, 1, 0],
            ["exitField", rootKey],
        ];
        testVisit(delta, expected, index);
        assert.equal(index.entries().next().done, true);
    });
    it("idempotent insert", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const node = { minor: 42 };
        index.createEntry(node);
        const rootFieldDelta = {
            local: [{ count: 1, attach: node }],
        };
        const delta = {
            build: [{ id: node, trees: [content] }],
            fields: new Map([[rootKey, rootFieldDelta]]),
        };
        const expected = [
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", rootKey],
            ["attach", field0, 1, 0],
            ["exitField", rootKey],
        ];
        testVisit(delta, expected, index);
        assert.equal(index.entries().next().done, true);
    });
    it("insert child", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const buildId = { minor: 42 };
        const rootFieldDelta = {
            local: [
                {
                    count: 1,
                    fields: new Map([[fooKey, { local: [{ count: 1, attach: buildId }] }]]),
                },
            ],
        };
        const expected = [
            ["create", [content], field0],
            ["enterField", rootKey],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", rootKey],
            ["enterField", rootKey],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["attach", field0, 1, 0],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", rootKey],
        ];
        const delta = {
            build: [{ id: buildId, trees: [content] }],
            fields: new Map([[rootKey, rootFieldDelta]]),
        };
        testVisit(delta, expected, index);
        assert.equal(index.entries().next().done, true);
    });
    it("remove root", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const mark = {
            count: 2,
            detach: { minor: 42 },
        };
        const delta = { local: [{ count: 1 }, mark] };
        testTreeVisit(delta, [
            ["enterField", rootKey],
            ["detach", { start: 1, end: 2 }, field0],
            ["detach", { start: 1, end: 2 }, field1],
            ["exitField", rootKey],
            ["enterField", rootKey],
            ["exitField", rootKey],
        ], index);
        assert.deepEqual(Array.from(index.entries()), [
            { id: { minor: 42 }, root: 0 },
            { id: { minor: 43 }, root: 1 },
        ]);
    });
    it("remove child", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const remove = {
            count: 1,
            detach: { minor: 42 },
        };
        const mark = {
            count: 1,
            fields: new Map([[fooKey, { local: [{ count: 42 }, remove] }]]),
        };
        const delta = { local: [mark] };
        const expected = [
            ["enterField", rootKey],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["detach", { start: 42, end: 43 }, field0],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", rootKey],
            ["enterField", rootKey],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", rootKey],
        ];
        testTreeVisit(delta, expected, index);
        assert.deepEqual(Array.from(index.entries()), [{ id: { minor: 42 }, root: 0 }]);
    });
    it("changes under insert", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const moveId = { minor: 1 };
        const moveOut = {
            count: 1,
            detach: moveId,
        };
        const moveIn = {
            count: 1,
            attach: moveId,
        };
        const delta = {
            global: [
                {
                    id: { minor: 43 },
                    fields: new Map([[fooKey, { local: [{ count: 42 }, moveOut, moveIn] }]]),
                },
            ],
            local: [{ count: 1, attach: { minor: 43 } }],
        };
        const expected = [
            ["create", [content], field0],
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", field0],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["detach", { start: 42, end: 43 }, field1],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", field0],
            ["enterField", rootKey],
            ["attach", field0, 1, 0],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["attach", field1, 1, 42],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", rootKey],
        ];
        testTreeVisit(delta, expected, index, [{ id: { minor: 43 }, trees: [content] }]);
        assert.equal(index.entries().next().done, true);
    });
    it("move node to the right", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        // start with 0123 then move 1 so the order is 0213
        const moveId = { minor: 1 };
        const moveOut = {
            count: 1,
            detach: moveId,
        };
        const moveIn = {
            count: 1,
            attach: moveId,
        };
        const delta = { local: [{ count: 1 }, moveOut, { count: 1 }, moveIn] };
        const expected = [
            ["enterField", rootKey],
            ["detach", { start: 1, end: 2 }, field0],
            ["exitField", rootKey],
            ["enterField", rootKey],
            ["attach", field0, 1, 2],
            ["exitField", rootKey],
        ];
        testTreeVisit(delta, expected, index);
        assert.equal(index.entries().next().done, true);
    });
    it("move children to the left", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const moveId = { minor: 1 };
        const moveOut = {
            count: 2,
            detach: moveId,
        };
        const moveIn = {
            count: 2,
            attach: moveId,
        };
        const modify = {
            count: 1,
            fields: new Map([[fooKey, { local: [{ count: 2 }, moveIn, { count: 3 }, moveOut] }]]),
        };
        const delta = { local: [modify] };
        const expected = [
            ["enterField", rootKey],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["detach", { start: 5, end: 6 }, field0],
            ["detach", { start: 5, end: 6 }, field1],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", rootKey],
            ["enterField", rootKey],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["attach", field0, 1, 2],
            ["attach", field1, 1, 3],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", rootKey],
        ];
        testTreeVisit(delta, expected, index);
        assert.equal(index.entries().next().done, true);
    });
    it("move cousins", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const moveId = { minor: 1 };
        const moveOut = {
            count: 1,
            detach: moveId,
        };
        const moveIn = {
            count: 1,
            attach: moveId,
        };
        const modify = {
            count: 1,
            fields: new Map([
                [fooKey, { local: [moveIn] }],
                [barKey, { local: [moveOut] }],
            ]),
        };
        const delta = { local: [modify] };
        const expected = [
            ["enterField", rootKey],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["exitField", fooKey],
            ["enterField", barKey],
            ["detach", { start: 0, end: 1 }, field0],
            ["exitField", barKey],
            ["exitNode", 0],
            ["exitField", rootKey],
            ["enterField", rootKey],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["attach", field0, 1, 0],
            ["exitField", fooKey],
            ["enterField", barKey],
            ["exitField", barKey],
            ["exitNode", 0],
            ["exitField", rootKey],
        ];
        testTreeVisit(delta, expected, index);
        assert.equal(index.entries().next().done, true);
    });
    it("changes under remove", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const moveId = { minor: 1 };
        const moveOut = {
            count: 1,
            detach: moveId,
        };
        const moveIn = {
            count: 1,
            attach: moveId,
        };
        const remove = {
            detach: { minor: 42 },
            count: 1,
            fields: new Map([[fooKey, { local: [moveOut, moveIn] }]]),
        };
        const delta = { local: [remove] };
        const expected = [
            ["enterField", rootKey],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["detach", { start: 0, end: 1 }, field0],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["detach", { start: 0, end: 1 }, field1],
            ["exitField", rootKey],
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", field1],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["attach", field0, 1, 0],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", field1],
        ];
        testTreeVisit(delta, expected, index);
        assert.deepEqual(Array.from(index.entries()), [{ id: { minor: 42 }, root: 1 }]);
    });
    it("changes under destroy", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const node1 = { minor: 42 };
        index.createEntry(node1);
        const moveId = { minor: 1 };
        const moveOut = {
            count: 1,
            detach: moveId,
        };
        const moveIn = {
            count: 1,
            attach: moveId,
        };
        const nested = {
            id: node1,
            fields: new Map([[fooKey, { local: [moveOut, moveIn] }]]),
        };
        const delta = {
            global: [nested],
        };
        const expected = [
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", field0],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["detach", { start: 0, end: 1 }, field1],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", field0],
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", field0],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["attach", field1, 1, 0],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", field0],
            ["destroy", field0, 1],
        ];
        testTreeVisit(delta, expected, index, undefined, [{ id: node1, count: 1 }]);
        assert.equal(index.entries().next().done, true);
    });
    it("destroy (root level)", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const id = { minor: 42 };
        index.createEntry(id, 2);
        const delta = {
            destroy: [{ id, count: 2 }],
        };
        const expected = [
            ["destroy", field0, 1],
            ["destroy", field1, 1],
        ];
        testVisit(delta, expected, index);
        assert.equal(index.entries().next().done, true);
    });
    it("build-rename-destroy (field level)", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const buildId = { minor: 42 };
        const detachId = { minor: 43 };
        const delta = {
            rename: [{ oldId: buildId, newId: detachId, count: 1 }],
        };
        const expected = [
            ["create", [content], field0],
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", field0],
            ["detach", { start: 0, end: 1 }, field1],
            ["exitField", field0],
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["destroy", field1, 1],
        ];
        testTreeVisit(delta, expected, index, [{ id: buildId, trees: [content] }], [{ id: detachId, count: 1 }]);
        assert.equal(index.entries().next().done, true);
    });
    it("changes under move-out", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const moveId1 = { minor: 1 };
        const moveId2 = { minor: 2 };
        const moveIn1 = {
            count: 1,
            attach: moveId1,
        };
        const moveOut2 = {
            count: 1,
            detach: moveId2,
        };
        const moveIn2 = {
            count: 1,
            attach: moveId2,
        };
        const moveOut1 = {
            count: 1,
            detach: moveId1,
            fields: new Map([[fooKey, { local: [moveOut2, moveIn2] }]]),
        };
        const delta = { local: [moveOut1, moveIn1] };
        const expected = [
            ["enterField", rootKey],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["detach", { start: 0, end: 1 }, field0],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["detach", { start: 0, end: 1 }, field1],
            ["exitField", rootKey],
            ["enterField", rootKey],
            ["attach", field1, 1, 0],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["attach", field0, 1, 0],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", rootKey],
        ];
        testTreeVisit(delta, expected, index);
        assert.equal(index.entries().next().done, true);
    });
    it("changes under move-out of range", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const buildId = { minor: 1 };
        const moveId = { minor: 2 };
        const attach = { count: 1, attach: buildId };
        const moveIn = {
            count: 2,
            attach: moveId,
        };
        const moveOut1 = {
            count: 1,
            detach: moveId,
        };
        const moveOut2 = {
            count: 1,
            detach: { minor: 3 },
            fields: new Map([[fooKey, { local: [attach] }]]),
        };
        const rootChanges = { local: [moveOut1, moveOut2, moveIn] };
        const delta = {
            build: [{ id: buildId, trees: [content] }],
            fields: new Map([[rootKey, rootChanges]]),
        };
        const expected = [
            ["create", [content], field0],
            ["enterField", rootKey],
            ["detach", { start: 0, end: 1 }, field1],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["detach", { start: 0, end: 1 }, field2],
            ["exitField", rootKey],
            ["enterField", rootKey],
            ["attach", field1, 1, 0],
            ["attach", field2, 1, 1],
            ["enterNode", 1],
            ["enterField", fooKey],
            ["attach", field0, 1, 0],
            ["exitField", fooKey],
            ["exitNode", 1],
            ["exitField", rootKey],
        ];
        testVisit(delta, expected, index);
        assert.equal(index.entries().next().done, true);
    });
    it("replace nodes", () => {
        const buildId = { minor: 0 };
        const replace = {
            count: 2,
            detach: { minor: 2 },
            attach: buildId,
        };
        const rootChanges = { local: [replace] };
        const delta = {
            build: [{ id: buildId, trees: [content, content] }],
            fields: new Map([[rootKey, rootChanges]]),
        };
        const expected = [
            ["create", [content], field0],
            ["create", [content], field1],
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", rootKey],
            ["replace", field0, { start: 0, end: 1 }, field2],
            ["replace", field1, { start: 1, end: 2 }, field3],
            ["exitField", rootKey],
        ];
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        testVisit(delta, expected, index);
    });
    it("changes under replaced node", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const moveId1 = { minor: 1 };
        const moveId2 = { minor: 2 };
        const moveOut2 = {
            count: 1,
            detach: moveId2,
        };
        const moveOut1 = {
            count: 1,
            detach: moveId1,
        };
        const moveIn2 = {
            count: 1,
            attach: moveId2,
        };
        const replace = {
            count: 1,
            detach: { minor: 42 },
            attach: moveId1,
            fields: new Map([[fooKey, { local: [moveOut2, moveIn2] }]]),
        };
        const delta = { local: [replace, moveOut1] };
        const expected = [
            ["enterField", rootKey],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["detach", { start: 0, end: 1 }, field0],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["detach", { start: 1, end: 2 }, field1],
            ["exitField", rootKey],
            ["enterField", rootKey],
            ["replace", field1, { start: 0, end: 1 }, field2],
            ["exitField", rootKey],
            ["enterField", field2],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["attach", field0, 1, 0],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", field2],
        ];
        testTreeVisit(delta, expected, index);
        assert.deepEqual(Array.from(index.entries()), [{ id: { minor: 42 }, root: 2 }]);
    });
    it("changes under replacement node", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const moveId1 = { minor: 1 };
        const moveId2 = { minor: 2 };
        const moveOut2 = {
            count: 1,
            detach: moveId2,
        };
        const moveIn2 = {
            count: 1,
            attach: moveId2,
        };
        const moveOut1 = {
            count: 1,
            detach: moveId1,
            fields: new Map([[fooKey, { local: [moveOut2, moveIn2] }]]),
        };
        const replace = {
            count: 1,
            detach: { minor: 42 },
            attach: moveId1,
        };
        const delta = { local: [replace, moveOut1] };
        const expected = [
            ["enterField", rootKey],
            ["enterNode", 1],
            ["enterField", fooKey],
            ["detach", { start: 0, end: 1 }, field0],
            ["exitField", fooKey],
            ["exitNode", 1],
            ["detach", { start: 1, end: 2 }, field1],
            ["exitField", rootKey],
            ["enterField", rootKey],
            ["replace", field1, { start: 0, end: 1 }, field2],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["attach", field0, 1, 0],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", rootKey],
        ];
        testTreeVisit(delta, expected, index);
        assert.deepEqual(Array.from(index.entries()), [{ id: { minor: 42 }, root: 2 }]);
    });
    it("transient insert", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const delta = {
            rename: [{ oldId: { minor: 42 }, count: 1, newId: { minor: 43 } }],
        };
        const expected = [
            ["create", [content], field0],
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", field0],
            ["detach", { start: 0, end: 1 }, field1],
            ["exitField", field0],
            ["enterField", rootKey],
            ["exitField", rootKey],
        ];
        testTreeVisit(delta, expected, index, [{ id: { minor: 42 }, trees: [content] }]);
        assert.deepEqual(Array.from(index.entries()), [{ id: { minor: 43 }, root: 1 }]);
    });
    it("changes under transient", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const moveId = { minor: 1 };
        const moveOut = {
            count: 1,
            detach: moveId,
        };
        const moveIn = {
            count: 1,
            attach: moveId,
        };
        const buildId = { minor: 42 };
        const detachId = { minor: 43 };
        const delta = {
            global: [{ id: buildId, fields: new Map([[barKey, { local: [moveOut, moveIn] }]]) }],
            rename: [{ oldId: buildId, count: 1, newId: detachId }],
        };
        const expected = [
            ["create", [content], field0],
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", field0],
            ["enterNode", 0],
            ["enterField", barKey],
            ["detach", { start: 0, end: 1 }, field1],
            ["exitField", barKey],
            ["exitNode", 0],
            ["exitField", field0],
            ["enterField", field0],
            ["detach", { start: 0, end: 1 }, field2],
            ["exitField", field0],
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", field2],
            ["enterNode", 0],
            ["enterField", barKey],
            ["attach", field1, 1, 0],
            ["exitField", barKey],
            ["exitNode", 0],
            ["exitField", field2],
        ];
        testTreeVisit(delta, expected, index, [{ id: buildId, trees: [content] }]);
        assert.deepEqual(Array.from(index.entries()), [{ id: detachId, root: 2 }]);
    });
    it("restore", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const node1 = { minor: 1 };
        index.createEntry(node1);
        const restore = {
            count: 1,
            attach: node1,
        };
        const delta = { local: [restore] };
        const expected = [
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", rootKey],
            ["attach", field0, 1, 0],
            ["exitField", rootKey],
        ];
        testTreeVisit(delta, expected, index);
        assert.equal(index.entries().next().done, true);
    });
    it("move removed node", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const node1 = { minor: 1 };
        index.createEntry(node1);
        const moveId = { minor: 2 };
        const rename = {
            count: 1,
            oldId: node1,
            newId: moveId,
        };
        const moveIn = {
            count: 1,
            attach: moveId,
        };
        const delta = {
            rename: [rename],
            local: [moveIn],
        };
        const expected = [
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", field0],
            ["detach", { start: 0, end: 1 }, field1],
            ["exitField", field0],
            ["enterField", rootKey],
            ["attach", field1, 1, 0],
            ["exitField", rootKey],
        ];
        testTreeVisit(delta, expected, index);
        assert.equal(index.entries().next().done, true);
    });
    it("changes under removed node", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const node1 = { minor: 1 };
        index.createEntry(node1);
        const moveId = { minor: 2 };
        const moveOut = {
            count: 1,
            detach: moveId,
        };
        const moveIn = {
            count: 1,
            attach: moveId,
        };
        const modify = {
            id: { minor: 1 },
            fields: new Map([[fooKey, { local: [moveOut, moveIn] }]]),
        };
        const delta = { global: [modify] };
        const expected = [
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", field0],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["detach", { start: 0, end: 1 }, field1],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", field0],
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", field0],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["attach", field1, 1, 0],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", field0],
        ];
        testTreeVisit(delta, expected, index);
        assert.deepEqual(Array.from(index.entries()), [{ id: { minor: 1 }, root: 0 }]);
    });
    it("changes under transient move-in", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const moveId1 = { minor: 1 };
        const moveId2 = { minor: 2 };
        const detachId = { minor: 42 };
        const moveOut = {
            count: 1,
            detach: moveId1,
            fields: new Map([
                [
                    fooKey,
                    {
                        local: [
                            { count: 1, detach: moveId2 },
                            { count: 1, attach: moveId2 },
                        ],
                    },
                ],
            ]),
        };
        const moveIn = {
            count: 1,
            oldId: moveId1,
            newId: detachId,
        };
        const delta = { local: [moveOut], rename: [moveIn] };
        const expected = [
            ["enterField", rootKey],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["detach", { start: 0, end: 1 }, field0],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["detach", { start: 0, end: 1 }, field1],
            ["exitField", rootKey],
            ["enterField", field1],
            ["detach", { start: 0, end: 1 }, field2],
            ["exitField", field1],
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", field2],
            ["enterNode", 0],
            ["enterField", fooKey],
            ["attach", field0, 1, 0],
            ["exitField", fooKey],
            ["exitNode", 0],
            ["exitField", field2],
        ];
        testTreeVisit(delta, expected, index);
        assert.deepEqual(Array.from(index.entries()), [{ id: detachId, root: 2 }]);
    });
    it("transient restore", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const node1 = { minor: 1 };
        index.createEntry(node1);
        const restore = {
            count: 1,
            oldId: node1,
            newId: { minor: 42 },
        };
        const delta = { rename: [restore] };
        const expected = [
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", field0],
            ["detach", { start: 0, end: 1 }, field1],
            ["exitField", field0],
            ["enterField", rootKey],
            ["exitField", rootKey],
        ];
        testTreeVisit(delta, expected, index);
        assert.deepEqual(Array.from(index.entries()), [{ id: { minor: 42 }, root: 1 }]);
    });
    it("update detached node", () => {
        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
        const node1 = { minor: 1 };
        index.createEntry(node1);
        const buildId = { minor: 2 };
        const detachId = { minor: 42 };
        const renameOldNode = {
            count: 1,
            oldId: node1,
            newId: detachId,
        };
        const renameNewNode = {
            count: 1,
            oldId: buildId,
            newId: node1,
        };
        const delta = {
            rename: [renameOldNode, renameNewNode],
        };
        const expected = [
            ["create", [content], field1],
            ["enterField", rootKey],
            ["exitField", rootKey],
            ["enterField", field0],
            ["detach", { start: 0, end: 1 }, field2],
            ["exitField", field0],
            ["enterField", field1],
            ["detach", { start: 0, end: 1 }, field3],
            ["exitField", field1],
            ["enterField", rootKey],
            ["exitField", rootKey],
        ];
        testTreeVisit(delta, expected, index, [{ id: buildId, trees: [content] }]);
        assert.deepEqual(Array.from(index.entries()), [
            { id: detachId, root: 2 },
            { id: node1, root: 3 },
        ]);
    });
    describe("rename chains", () => {
        const pointA = { minor: 1 };
        for (const cycle of [false, true]) {
            describe(cycle ? "cyclic" : "acyclic", () => {
                const end = cycle ? pointA : { minor: 42 };
                describe("1-step", () => {
                    it("Rename ordering: 1/1", () => {
                        const index = makeDetachedFieldIndex("", testRevisionTagCodec);
                        index.createEntry(pointA);
                        const rename = {
                            count: 1,
                            oldId: pointA,
                            newId: end,
                        };
                        const delta = {
                            rename: [rename],
                        };
                        const expected = cycle
                            ? [
                                ["enterField", rootKey],
                                ["exitField", rootKey],
                                ["enterField", rootKey],
                                ["exitField", rootKey],
                            ]
                            : [
                                ["enterField", rootKey],
                                ["exitField", rootKey],
                                ["enterField", field0],
                                ["detach", { start: 0, end: 1 }, field1],
                                ["exitField", field0],
                                ["enterField", rootKey],
                                ["exitField", rootKey],
                            ];
                        testTreeVisit(delta, expected, index);
                        assert.deepEqual(Array.from(index.entries()), [
                            { id: end, root: cycle ? 0 : 1 },
                        ]);
                    });
                });
                describe("2-step", () => {
                    for (let ordering = 1; ordering <= 2; ordering++) {
                        it(`Rename ordering: ${ordering}/2`, () => {
                            const index = makeDetachedFieldIndex("", testRevisionTagCodec);
                            index.createEntry(pointA);
                            const pointB = { minor: 2 };
                            const rename1 = {
                                count: 1,
                                oldId: pointA,
                                newId: pointB,
                            };
                            const rename2 = {
                                count: 1,
                                oldId: pointB,
                                newId: cycle ? pointA : end,
                            };
                            const delta = {
                                rename: [
                                    [rename1, rename2],
                                    [rename2, rename1],
                                ][ordering - 1],
                            };
                            const expected = [
                                ["enterField", rootKey],
                                ["exitField", rootKey],
                                ["enterField", field0],
                                ["detach", { start: 0, end: 1 }, field1],
                                ["exitField", field0],
                                ["enterField", field1],
                                ["detach", { start: 0, end: 1 }, field2],
                                ["exitField", field1],
                                ["enterField", rootKey],
                                ["exitField", rootKey],
                            ];
                            testTreeVisit(delta, expected, index);
                            assert.deepEqual(Array.from(index.entries()), [{ id: end, root: 2 }]);
                        });
                    }
                });
                describe("3-step", () => {
                    for (let ordering = 1; ordering <= 6; ordering++) {
                        it(`Rename ordering: ${ordering}/6`, () => {
                            const pointB = { minor: 2 };
                            const pointC = { minor: 3 };
                            const ab = {
                                count: 1,
                                oldId: pointA,
                                newId: pointB,
                            };
                            const bc = {
                                count: 1,
                                oldId: pointB,
                                newId: pointC,
                            };
                            const cd = {
                                count: 1,
                                oldId: pointC,
                                newId: end,
                            };
                            const delta = {
                                rename: [
                                    [ab, bc, cd],
                                    [ab, cd, bc],
                                    [bc, ab, cd],
                                    [bc, cd, ab],
                                    [cd, ab, bc],
                                    [cd, bc, ab],
                                ][ordering - 1],
                            };
                            const expected = [
                                ["enterField", rootKey],
                                ["exitField", rootKey],
                                ["enterField", field0],
                                ["detach", { start: 0, end: 1 }, field1],
                                ["exitField", field0],
                                ["enterField", field1],
                                ["detach", { start: 0, end: 1 }, field2],
                                ["exitField", field1],
                                ["enterField", field2],
                                ["detach", { start: 0, end: 1 }, field3],
                                ["exitField", field2],
                                ["enterField", rootKey],
                                ["exitField", rootKey],
                            ];
                            const index = makeDetachedFieldIndex("", testRevisionTagCodec);
                            index.createEntry(pointA);
                            testTreeVisit(delta, expected, index);
                            assert.deepEqual(Array.from(index.entries()), [{ id: end, root: 3 }]);
                        });
                    }
                });
            });
        }
    });
});
//# sourceMappingURL=visitDelta.spec.js.map