/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
import { DiscriminatedUnionDispatcher } from "../../codec/index.js";
import { brand, fail } from "../../util/index.js";
import { makeChangeAtomIdCodec } from "../changeAtomIdCodec.js";
import { Changeset as ChangesetSchema, DetachIdOverrideType, } from "./formatV1.js";
import { NoopMarkType, } from "./types.js";
import { isNoopMark, normalizeCellRename } from "./utils.js";
import { EncodedNodeChangeset } from "../modular-schema/index.js";
export function makeV1Codec(revisionTagCodec) {
    const changeAtomIdCodec = makeChangeAtomIdCodec(revisionTagCodec);
    const markEffectCodec = {
        encode(effect, context) {
            function encodeRevision(revision) {
                if (revision === undefined || revision === context.revision) {
                    return undefined;
                }
                return revisionTagCodec.encode(revision, context);
            }
            const type = effect.type;
            switch (type) {
                case "MoveIn":
                    return {
                        moveIn: {
                            revision: encodeRevision(effect.revision),
                            finalEndpoint: effect.finalEndpoint === undefined
                                ? undefined
                                : changeAtomIdCodec.encode(effect.finalEndpoint, context),
                            id: effect.id,
                        },
                    };
                case "Insert":
                    return {
                        insert: {
                            revision: encodeRevision(effect.revision),
                            id: effect.id,
                        },
                    };
                case "Remove":
                    return {
                        delete: {
                            revision: encodeRevision(effect.revision),
                            idOverride: effect.idOverride === undefined
                                ? undefined
                                : {
                                    // An arbitrary override type is chosen here. It only had an impact on lineage logic which was not enabled in V1.
                                    type: DetachIdOverrideType.Unattach,
                                    id: cellIdCodec.encode(effect.idOverride, context),
                                },
                            id: effect.id,
                        },
                    };
                case "MoveOut":
                    return {
                        moveOut: {
                            revision: encodeRevision(effect.revision),
                            finalEndpoint: effect.finalEndpoint === undefined
                                ? undefined
                                : changeAtomIdCodec.encode(effect.finalEndpoint, context),
                            idOverride: effect.idOverride === undefined
                                ? undefined
                                : {
                                    // An arbitrary override type is chosen here. It only had an impact on lineage logic which was not enabled in V1.
                                    type: DetachIdOverrideType.Unattach,
                                    id: cellIdCodec.encode(effect.idOverride, context),
                                },
                            id: effect.id,
                        },
                    };
                case "AttachAndDetach":
                    return {
                        attachAndDetach: {
                            attach: markEffectCodec.encode(effect.attach, context),
                            detach: markEffectCodec.encode(effect.detach, context),
                        },
                    };
                case "Rename":
                    // In documents generated by clients on release >=2.2 (i.e., running the code from the PR that added this comment),
                    // renames are encoded as AttachAndDetach with a special id.
                    // This ensures forward-compatibility of clients on release <=2.1 with documents/ops generated by clients on release >=2.2.
                    return markEffectCodec.encode({
                        type: "AttachAndDetach",
                        attach: { type: "MoveIn", id: renameLocalId },
                        detach: { type: "MoveOut", id: renameLocalId, idOverride: effect.idOverride },
                    }, context);
                case NoopMarkType:
                    fail("Mark type: NoopMarkType should not be encoded.");
                default:
                    unreachableCase(type);
            }
        },
        decode(encoded, context) {
            return decoderLibrary.dispatch(encoded, context);
        },
    };
    function decodeRevision(encodedRevision, context) {
        if (encodedRevision === undefined) {
            assert(context.revision !== undefined, 0x965 /* Implicit revision should be provided */);
            return context.revision;
        }
        return revisionTagCodec.decode(encodedRevision, context);
    }
    const decoderLibrary = new DiscriminatedUnionDispatcher({
        moveIn(encoded, context) {
            const { id, finalEndpoint, revision } = encoded;
            const mark = {
                type: "MoveIn",
                id,
            };
            mark.revision = decodeRevision(revision, context);
            if (finalEndpoint !== undefined) {
                mark.finalEndpoint = changeAtomIdCodec.decode(finalEndpoint, context);
            }
            return mark;
        },
        insert(encoded, context) {
            const { id, revision } = encoded;
            const mark = {
                type: "Insert",
                id,
            };
            mark.revision = decodeRevision(revision, context);
            return mark;
        },
        delete(encoded, context) {
            const { id, revision, idOverride } = encoded;
            const mark = {
                type: "Remove",
                id,
            };
            mark.revision = decodeRevision(revision, context);
            if (idOverride !== undefined) {
                mark.idOverride = cellIdCodec.decode(idOverride.id, context);
            }
            return mark;
        },
        moveOut(encoded, context) {
            const { id, finalEndpoint, idOverride, revision } = encoded;
            const mark = {
                type: "MoveOut",
                id,
            };
            mark.revision = decodeRevision(revision, context);
            if (finalEndpoint !== undefined) {
                mark.finalEndpoint = changeAtomIdCodec.decode(finalEndpoint, context);
            }
            if (idOverride !== undefined) {
                mark.idOverride = cellIdCodec.decode(idOverride.id, context);
            }
            return mark;
        },
        attachAndDetach(encoded, context) {
            const attach = decoderLibrary.dispatch(encoded.attach, context);
            const detach = decoderLibrary.dispatch(encoded.detach, context);
            // In documents generated by clients on release >=2.2 (i.e., running the code from the PR that added this comment),
            // renames are encoded as AttachAndDetach with a special id.
            // This ensures forward-compatibility of clients on release <=2.1 with documents/ops generated by clients on release >=2.2.
            if (attach.id === renameLocalId) {
                assert(detach.idOverride !== undefined, 0x9f7 /* Rename must have idOverride */);
                return {
                    type: "Rename",
                    idOverride: detach.idOverride,
                };
            }
            return {
                type: "AttachAndDetach",
                attach,
                detach,
            };
        },
    });
    const cellIdCodec = {
        encode: (cellId, context) => {
            const encoded = {
                atom: changeAtomIdCodec.encode(cellId, context),
            };
            return encoded;
        },
        decode: ({ atom }, context) => {
            return changeAtomIdCodec.decode(atom, context);
        },
    };
    return {
        encode: (changeset, context) => {
            const jsonMarks = [];
            for (const mark of changeset) {
                const encodedMark = {
                    count: mark.count,
                };
                if (!isNoopMark(mark)) {
                    encodedMark.effect = markEffectCodec.encode(mark, context.baseContext);
                }
                if (mark.cellId !== undefined) {
                    encodedMark.cellId = cellIdCodec.encode(mark.cellId, context.baseContext);
                }
                if (mark.changes !== undefined) {
                    encodedMark.changes = context.encodeNode(mark.changes);
                }
                jsonMarks.push(encodedMark);
            }
            return jsonMarks;
        },
        decode: (changeset, context) => {
            const marks = [];
            for (const mark of changeset) {
                const decodedMark = {
                    count: mark.count,
                };
                if (mark.effect !== undefined) {
                    Object.assign(decodedMark, markEffectCodec.decode(mark.effect, context.baseContext));
                }
                if (mark.cellId !== undefined) {
                    decodedMark.cellId = cellIdCodec.decode(mark.cellId, context.baseContext);
                }
                // Type deduction wrongly narrows the type of `decodedMark` to `NoopMark & HasMarkFields`.
                // We declare a new casted copy of `decodedMark` to recover the correct type.
                let decodedMark2 = decodedMark;
                if (decodedMark2.cellId !== undefined && decodedMark2.type === "AttachAndDetach") {
                    // In documents generated by clients on release <=2.1 (i.e., not running the code from the PR that added this comment),
                    // rename-like AttachAndDetach marks are not normalized to Rename marks thus requiring this normalization step.
                    // This ensures backward compatibility with documents generated by clients on release <=2.1.
                    decodedMark2 = normalizeCellRename(decodedMark2.cellId, decodedMark2.count, decodedMark2.attach, decodedMark2.detach);
                }
                if (mark.changes !== undefined) {
                    decodedMark2.changes = context.decodeNode(mark.changes);
                }
                marks.push(decodedMark2);
            }
            return marks;
        },
        encodedSchema: ChangesetSchema(EncodedNodeChangeset),
    };
}
/**
 * Arbitrary ID that is used to indicate a Rename effect.
 */
const renameLocalId = brand(-1);
//# sourceMappingURL=sequenceFieldCodecV1.js.map