/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { unreachableCase } from "@fluidframework/common-utils";
import { singleTextCursor } from "../../treeTextCursor";
import { Delta } from "../../../core";
import { populateChildModifications } from "../../deltaUtils";
import { brand, brandOpaque, clone, fail, makeArray, OffsetListFactory, } from "../../../util";
import { isSkipMark } from "./utils";
/**
 * Converts a Changeset into a Delta.
 * @param changeset - The Changeset to convert
 * @returns A Delta for applying the changes described in the given Changeset.
 */
export function toDelta(changeset) {
    // Save result to a constant to work around linter bug:
    // https://github.com/typescript-eslint/typescript-eslint/issues/5014
    const out = convertFieldMarks(changeset.marks);
    return out;
}
function convertMarkList(marks) {
    const out = new OffsetListFactory();
    for (const mark of marks) {
        if (isSkipMark(mark)) {
            out.pushOffset(mark);
        }
        else {
            // Inline into `switch(mark.type)` once we upgrade to TS 4.7
            const type = mark.type;
            switch (type) {
                case "Insert": {
                    const insertMark = {
                        type: Delta.MarkType.Insert,
                        // TODO: can we skip this clone?
                        content: clone(mark.content).map(singleTextCursor),
                    };
                    out.pushContent(insertMark);
                    break;
                }
                case "MInsert": {
                    const insertMark = {
                        type: Delta.MarkType.Insert,
                        content: [singleTextCursor(mark.content)],
                    };
                    populateChildModifications(convertModify(mark), insertMark);
                    out.pushContent(insertMark);
                    break;
                }
                case "MoveIn": {
                    const moveMark = {
                        type: Delta.MarkType.MoveIn,
                        count: mark.count,
                        moveId: brandOpaque(mark.id),
                    };
                    out.pushContent(moveMark);
                    break;
                }
                case "MMoveIn":
                    fail("MMoveIn not implemented");
                case "Bounce":
                case "Intake":
                    // These have no impacts on the document state.
                    break;
                case "Modify": {
                    if (mark.tomb === undefined) {
                        out.pushContent(Object.assign({ type: Delta.MarkType.Modify }, convertModify(mark)));
                    }
                    break;
                }
                case "Delete": {
                    const deleteMark = {
                        type: Delta.MarkType.Delete,
                        count: mark.count,
                    };
                    out.pushContent(deleteMark);
                    break;
                }
                case "MDelete": {
                    const deleteMark = {
                        type: Delta.MarkType.Delete,
                        count: 1,
                    };
                    populateChildModifications(convertModify(mark), deleteMark);
                    out.pushContent(deleteMark);
                    break;
                }
                case "MoveOut": {
                    const moveMark = {
                        type: Delta.MarkType.MoveOut,
                        moveId: brandOpaque(mark.id),
                        count: mark.count,
                    };
                    out.pushContent(moveMark);
                    break;
                }
                case "Revive": {
                    const insertMark = {
                        type: Delta.MarkType.Insert,
                        // TODO: Restore the actual node
                        content: makeArray(mark.count, () => singleTextCursor({ type: DUMMY_REVIVED_NODE_TYPE })),
                    };
                    out.pushContent(insertMark);
                    break;
                }
                case "MRevive": {
                    const insertMark = {
                        type: Delta.MarkType.Insert,
                        // TODO: Restore the actual node
                        content: [singleTextCursor({ type: DUMMY_REVIVED_NODE_TYPE })],
                    };
                    out.pushContent(insertMark);
                    break;
                }
                case "MMoveOut":
                case "Return":
                case "MReturn":
                case "Gap":
                    fail("Gap not implemented");
                case "Tomb": {
                    // These tombs are only used to precisely describe the location of other attaches.
                    // They have no impact on the current state.
                    break;
                }
                default:
                    unreachableCase(type);
            }
        }
    }
    return out.list;
}
const DUMMY_REVIVED_NODE_TYPE = brand("RevivedNode");
/**
 * Converts tree modifications from the Changeset to the Delta format.
 */
function convertModify(modify) {
    const out = {};
    if (modify.value !== undefined) {
        out.setValue = modify.value.value;
    }
    const fields = modify.fields;
    if (fields !== undefined) {
        out.fields = convertFieldMarks(fields);
    }
    return out;
}
function convertFieldMarks(fields) {
    const outFields = new Map();
    for (const key of Object.keys(fields)) {
        const marks = convertMarkList(fields[key]);
        const brandedKey = brand(key);
        outFields.set(brandedKey, marks);
    }
    return outFields;
}
//# sourceMappingURL=toDelta.js.map