/*!
 * 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 { NoopMarkType, } from "./types";
import { Changeset as ChangesetSchema } from "./format";
import { isNoopMark } from "./utils";
export const sequenceFieldChangeCodecFactory = (childCodec) => makeCodecFamily([[0, makeV0Codec(childCodec)]]);
function makeV0Codec(childCodec) {
    const markEffectCodec = {
        encode(effect) {
            const type = effect.type;
            switch (type) {
                case "MoveIn":
                    return {
                        moveIn: {
                            finalEndpoint: effect.finalEndpoint,
                            id: effect.id,
                        },
                    };
                case "Insert":
                    return { insert: { revision: effect.revision, id: effect.id } };
                case "Delete":
                    return {
                        delete: {
                            revision: effect.revision,
                            detachIdOverride: effect.detachIdOverride,
                            id: effect.id,
                        },
                    };
                case "MoveOut":
                    return {
                        moveOut: {
                            finalEndpoint: effect.finalEndpoint,
                            detachIdOverride: effect.detachIdOverride,
                            id: effect.id,
                        },
                    };
                case "AttachAndDetach":
                    return {
                        attachAndDetach: {
                            attach: markEffectCodec.encode(effect.attach),
                            detach: markEffectCodec.encode(effect.detach),
                        },
                    };
                case NoopMarkType:
                case "Placeholder":
                    fail(`Mark type: ${type} should not be encoded.`);
                default:
                    unreachableCase(type);
            }
        },
        decode(encoded) {
            return decoderLibrary.dispatch(encoded);
        },
    };
    const decoderLibrary = new DiscriminatedUnionDispatcher({
        moveIn(encoded) {
            const { id, finalEndpoint } = encoded;
            const mark = {
                type: "MoveIn",
                id,
            };
            if (finalEndpoint !== undefined) {
                mark.finalEndpoint = finalEndpoint;
            }
            return mark;
        },
        insert(encoded) {
            const { id, revision } = encoded;
            const mark = {
                type: "Insert",
                id,
            };
            if (revision !== undefined) {
                mark.revision = revision;
            }
            return mark;
        },
        delete(encoded) {
            const { id, revision, detachIdOverride } = encoded;
            const mark = {
                type: "Delete",
                id,
            };
            if (revision !== undefined) {
                mark.revision = revision;
            }
            if (detachIdOverride !== undefined) {
                mark.detachIdOverride = detachIdOverride;
            }
            return mark;
        },
        moveOut(encoded) {
            const { id, finalEndpoint, detachIdOverride } = encoded;
            const mark = {
                type: "MoveOut",
                id,
            };
            if (finalEndpoint !== undefined) {
                mark.finalEndpoint = finalEndpoint;
            }
            if (detachIdOverride !== undefined) {
                mark.detachIdOverride = detachIdOverride;
            }
            return mark;
        },
        attachAndDetach(encoded) {
            return {
                type: "AttachAndDetach",
                attach: decoderLibrary.dispatch(encoded.attach),
                detach: decoderLibrary.dispatch(encoded.detach),
            };
        },
    });
    const cellIdCodec = {
        encode: ({ localId, adjacentCells, lineage, revision }) => {
            const encoded = {
                localId,
                adjacentCells: adjacentCells?.map(({ id, count }) => [id, count]),
                // eslint-disable-next-line @typescript-eslint/no-shadow
                lineage: lineage?.map(({ revision, id, count, offset }) => [
                    revision,
                    id,
                    count,
                    offset,
                ]),
                revision,
            };
            return encoded;
        },
        decode: ({ localId, adjacentCells, lineage, revision }) => {
            // 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,
                    id,
                    count,
                    offset,
                }));
            }
            return decoded;
        },
    };
    return {
        encode: (changeset) => {
            const jsonMarks = [];
            for (const mark of changeset) {
                const encodedMark = {
                    count: mark.count,
                };
                if (!isNoopMark(mark)) {
                    encodedMark.effect = markEffectCodec.encode(mark);
                }
                if (mark.cellId !== undefined) {
                    encodedMark.cellId = cellIdCodec.encode(mark.cellId);
                }
                if (mark.changes !== undefined) {
                    encodedMark.changes = childCodec.encode(mark.changes);
                }
                jsonMarks.push(encodedMark);
            }
            return jsonMarks;
        },
        decode: (changeset) => {
            const marks = [];
            for (const mark of changeset) {
                const decodedMark = {
                    count: mark.count,
                };
                if (mark.effect !== undefined) {
                    Object.assign(decodedMark, markEffectCodec.decode(mark.effect));
                }
                if (mark.cellId !== undefined) {
                    decodedMark.cellId = cellIdCodec.decode(mark.cellId);
                }
                if (mark.changes !== undefined) {
                    decodedMark.changes = childCodec.decode(mark.changes);
                }
                marks.push(decodedMark);
            }
            return marks;
        },
        encodedSchema: ChangesetSchema(childCodec.encodedSchema ?? Type.Any()),
    };
}
//# sourceMappingURL=sequenceFieldCodecs.js.map