/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { unreachableCase } from "@fluidframework/core-utils";
import { Type } from "@sinclair/typebox";
import { fail } from "../../util";
import { DiscriminatedUnionDispatcher, makeCodecFamily } from "../../codec";
import { makeChangeAtomIdCodec } from "../changeAtomIdCodec";
import { NoopMarkType, } from "./types";
import { Changeset as ChangesetSchema } from "./format";
import { isNoopMark } from "./utils";
export const sequenceFieldChangeCodecFactory = (childCodec, revisionTagCodec) => makeCodecFamily([
    [0, makeV0Codec(childCodec, revisionTagCodec)],
]);
function makeV0Codec(childCodec, revisionTagCodec) {
    const changeAtomIdCodec = makeChangeAtomIdCodec(revisionTagCodec);
    const markEffectCodec = {
        encode(effect, originatorId) {
            const type = effect.type;
            switch (type) {
                case "MoveIn":
                    return {
                        moveIn: {
                            revision: effect.revision === undefined
                                ? undefined
                                : revisionTagCodec.encode(effect.revision, originatorId),
                            finalEndpoint: effect.finalEndpoint === undefined
                                ? undefined
                                : changeAtomIdCodec.encode(effect.finalEndpoint, originatorId),
                            id: effect.id,
                        },
                    };
                case "Insert":
                    return {
                        insert: {
                            revision: effect.revision === undefined
                                ? undefined
                                : revisionTagCodec.encode(effect.revision, originatorId),
                            id: effect.id,
                        },
                    };
                case "Delete":
                    return {
                        delete: {
                            revision: effect.revision === undefined
                                ? undefined
                                : revisionTagCodec.encode(effect.revision, originatorId),
                            idOverride: effect.idOverride === undefined
                                ? undefined
                                : {
                                    type: effect.idOverride.type,
                                    id: cellIdCodec.encode(effect.idOverride.id, originatorId),
                                },
                            id: effect.id,
                        },
                    };
                case "MoveOut":
                    return {
                        moveOut: {
                            revision: effect.revision === undefined
                                ? undefined
                                : revisionTagCodec.encode(effect.revision, originatorId),
                            finalEndpoint: effect.finalEndpoint === undefined
                                ? undefined
                                : changeAtomIdCodec.encode(effect.finalEndpoint, originatorId),
                            idOverride: effect.idOverride === undefined
                                ? undefined
                                : {
                                    type: effect.idOverride.type,
                                    id: cellIdCodec.encode(effect.idOverride.id, originatorId),
                                },
                            id: effect.id,
                        },
                    };
                case "AttachAndDetach":
                    return {
                        attachAndDetach: {
                            attach: markEffectCodec.encode(effect.attach, originatorId),
                            detach: markEffectCodec.encode(effect.detach, originatorId),
                        },
                    };
                case NoopMarkType:
                    fail(`Mark type: ${type} should not be encoded.`);
                default:
                    unreachableCase(type);
            }
        },
        decode(encoded, originatorId) {
            return decoderLibrary.dispatch(encoded, originatorId);
        },
    };
    const decoderLibrary = new DiscriminatedUnionDispatcher({
        moveIn(encoded, originatorId) {
            const { id, finalEndpoint, revision } = encoded;
            const mark = {
                type: "MoveIn",
                id,
            };
            if (revision !== undefined) {
                mark.revision = revisionTagCodec.decode(revision, originatorId);
            }
            if (finalEndpoint !== undefined) {
                mark.finalEndpoint = changeAtomIdCodec.decode(finalEndpoint, originatorId);
            }
            return mark;
        },
        insert(encoded, originatorId) {
            const { id, revision } = encoded;
            const mark = {
                type: "Insert",
                id,
            };
            if (revision !== undefined) {
                mark.revision = revisionTagCodec.decode(revision, originatorId);
            }
            return mark;
        },
        delete(encoded, originatorId) {
            const { id, revision, idOverride } = encoded;
            const mark = {
                type: "Delete",
                id,
            };
            if (revision !== undefined) {
                mark.revision = revisionTagCodec.decode(revision, originatorId);
            }
            if (idOverride !== undefined) {
                mark.idOverride = {
                    type: idOverride.type,
                    id: cellIdCodec.decode(idOverride.id, originatorId),
                };
            }
            return mark;
        },
        moveOut(encoded, originatorId) {
            const { id, finalEndpoint, idOverride, revision } = encoded;
            const mark = {
                type: "MoveOut",
                id,
            };
            if (revision !== undefined) {
                mark.revision = revisionTagCodec.decode(revision, originatorId);
            }
            if (finalEndpoint !== undefined) {
                mark.finalEndpoint = changeAtomIdCodec.decode(finalEndpoint, originatorId);
            }
            if (idOverride !== undefined) {
                mark.idOverride = {
                    type: idOverride.type,
                    id: cellIdCodec.decode(idOverride.id, originatorId),
                };
            }
            return mark;
        },
        attachAndDetach(encoded, originatorId) {
            return {
                type: "AttachAndDetach",
                attach: decoderLibrary.dispatch(encoded.attach, originatorId),
                detach: decoderLibrary.dispatch(encoded.detach, originatorId),
            };
        },
    });
    const cellIdCodec = {
        encode: ({ localId, adjacentCells, lineage, revision }, originatorId) => {
            const encoded = {
                atom: changeAtomIdCodec.encode({ localId, revision }, originatorId),
                adjacentCells: adjacentCells?.map(({ id, count }) => [id, count]),
                // eslint-disable-next-line @typescript-eslint/no-shadow
                lineage: lineage?.map(({ revision, id, count, offset }) => [
                    revisionTagCodec.encode(revision, originatorId),
                    id,
                    count,
                    offset,
                ]),
            };
            return encoded;
        },
        decode: ({ atom, adjacentCells, lineage }, originatorId) => {
            const { localId, revision } = changeAtomIdCodec.decode(atom, originatorId);
            // Note: this isn't inlined on decode so that round-tripping changes compare as deep-equal works,
            // which is mostly just a convenience for tests. On encode, JSON.stringify() takes care of removing
            // explicit undefined properties.
            const decoded = { localId };
            if (revision !== undefined) {
                decoded.revision = revision;
            }
            if (adjacentCells !== undefined) {
                decoded.adjacentCells = adjacentCells.map(([id, count]) => ({
                    id,
                    count,
                }));
            }
            if (lineage !== undefined) {
                // eslint-disable-next-line @typescript-eslint/no-shadow
                decoded.lineage = lineage.map(([revision, id, count, offset]) => ({
                    revision: revisionTagCodec.decode(revision, originatorId),
                    id,
                    count,
                    offset,
                }));
            }
            return decoded;
        },
    };
    return {
        encode: (changeset, originatorId) => {
            const jsonMarks = [];
            for (const mark of changeset) {
                const encodedMark = {
                    count: mark.count,
                };
                if (!isNoopMark(mark)) {
                    encodedMark.effect = markEffectCodec.encode(mark, originatorId);
                }
                if (mark.cellId !== undefined) {
                    encodedMark.cellId = cellIdCodec.encode(mark.cellId, originatorId);
                }
                if (mark.changes !== undefined) {
                    encodedMark.changes = childCodec.encode(mark.changes, originatorId);
                }
                jsonMarks.push(encodedMark);
            }
            return jsonMarks;
        },
        decode: (changeset, originatorId) => {
            const marks = [];
            for (const mark of changeset) {
                const decodedMark = {
                    count: mark.count,
                };
                if (mark.effect !== undefined) {
                    Object.assign(decodedMark, markEffectCodec.decode(mark.effect, originatorId));
                }
                if (mark.cellId !== undefined) {
                    decodedMark.cellId = cellIdCodec.decode(mark.cellId, originatorId);
                }
                if (mark.changes !== undefined) {
                    decodedMark.changes = childCodec.decode(mark.changes, originatorId);
                }
                marks.push(decodedMark);
            }
            return marks;
        },
        encodedSchema: ChangesetSchema(childCodec.encodedSchema ?? Type.Any()),
    };
}
//# sourceMappingURL=sequenceFieldCodecs.js.map