/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert } from "@fluidframework/core-utils";
import { brand, fail, nestedMapFromFlatList, nestedMapToFlatList, } from "../../util";
import { makeCodecFamily, } from "../../codec";
import { genericFieldKind } from "./genericFieldKind";
import { EncodedModularChangeset, EncodedNodeChangeset, } from "./modularChangeFormat";
function makeV0Codec(fieldKinds, revisionTagCodec, { jsonValidator: validator }) {
    const nodeChangesetCodec = {
        encode: encodeNodeChangesForJson,
        decode: decodeNodeChangesetFromJson,
        encodedSchema: EncodedNodeChangeset,
    };
    const getMapEntry = (field) => {
        const codec = field.changeHandler
            .codecsFactory(nodeChangesetCodec, revisionTagCodec)
            .resolve(0);
        return {
            codec,
            compiledSchema: codec.json.encodedSchema
                ? validator.compile(codec.json.encodedSchema)
                : undefined,
        };
    };
    const fieldChangesetCodecs = new Map([[genericFieldKind.identifier, getMapEntry(genericFieldKind)]]);
    fieldKinds.forEach((fieldKind, identifier) => {
        fieldChangesetCodecs.set(identifier, getMapEntry(fieldKind));
    });
    const getFieldChangesetCodec = (fieldKind) => {
        const entry = fieldChangesetCodecs.get(fieldKind);
        assert(entry !== undefined, 0x5ea /* Tried to encode unsupported fieldKind */);
        return entry;
    };
    function encodeFieldChangesForJson(change) {
        const encodedFields = [];
        for (const [field, fieldChange] of change) {
            const { codec, compiledSchema } = getFieldChangesetCodec(fieldChange.fieldKind);
            const encodedChange = codec.json.encode(fieldChange.change);
            if (compiledSchema !== undefined && !compiledSchema.check(encodedChange)) {
                fail("Encoded change didn't pass schema validation.");
            }
            const fieldKey = field;
            const encodedField = {
                fieldKey,
                fieldKind: fieldChange.fieldKind,
                change: encodedChange,
            };
            encodedFields.push(encodedField);
        }
        return encodedFields;
    }
    function encodeNodeChangesForJson(change) {
        const encodedChange = {};
        const { fieldChanges, nodeExistsConstraint } = change;
        if (fieldChanges !== undefined) {
            encodedChange.fieldChanges = encodeFieldChangesForJson(fieldChanges);
        }
        if (nodeExistsConstraint !== undefined) {
            encodedChange.nodeExistsConstraint = nodeExistsConstraint;
        }
        return encodedChange;
    }
    function decodeFieldChangesFromJson(encodedChange) {
        const decodedFields = new Map();
        for (const field of encodedChange) {
            const { codec, compiledSchema } = getFieldChangesetCodec(field.fieldKind);
            if (compiledSchema !== undefined && !compiledSchema.check(field.change)) {
                fail("Encoded change didn't pass schema validation.");
            }
            const fieldChangeset = codec.json.decode(field.change);
            const fieldKey = brand(field.fieldKey);
            decodedFields.set(fieldKey, {
                fieldKind: field.fieldKind,
                change: brand(fieldChangeset),
            });
        }
        return decodedFields;
    }
    function decodeNodeChangesetFromJson(encodedChange) {
        const decodedChange = {};
        const { fieldChanges, nodeExistsConstraint } = encodedChange;
        if (fieldChanges !== undefined) {
            decodedChange.fieldChanges = decodeFieldChangesFromJson(fieldChanges);
        }
        if (nodeExistsConstraint !== undefined) {
            decodedChange.nodeExistsConstraint = nodeExistsConstraint;
        }
        return decodedChange;
    }
    function encodeBuilds(builds) {
        if (builds === undefined) {
            return undefined;
        }
        const encoded = nestedMapToFlatList(builds).map(([r, i, t]) => 
        // `undefined` does not round-trip through JSON strings, so it needs special handling.
        // Most entries will have an undefined revision due to the revision information being inherited from the `ModularChangeset`.
        // We therefore optimize for the common case by omitting the revision when it is undefined.
        r !== undefined ? [revisionTagCodec.encode(r), i, t] : [i, t]);
        return encoded.length === 0 ? undefined : encoded;
    }
    function decodeBuilds(encoded) {
        if (encoded === undefined || encoded.length === 0) {
            return undefined;
        }
        const list = encoded.map((tuple) => tuple.length === 3
            ? [revisionTagCodec.decode(tuple[0]), tuple[1], tuple[2]]
            : [undefined, ...tuple]);
        return nestedMapFromFlatList(list);
    }
    function encodeRevisionInfos(revisions) {
        const encodedRevisions = [];
        for (const revision of revisions) {
            const encodedRevision = {
                revision: revisionTagCodec.encode(revision.revision),
            };
            if (revision.rollbackOf !== undefined) {
                encodedRevision.rollbackOf = revisionTagCodec.encode(revision.rollbackOf);
            }
            encodedRevisions.push(encodedRevision);
        }
        return encodedRevisions;
    }
    function decodeRevisionInfos(revisions) {
        const decodedRevisions = [];
        for (const revision of revisions) {
            const decodedRevision = {
                revision: revisionTagCodec.decode(revision.revision),
            };
            if (revision.rollbackOf !== undefined) {
                decodedRevision.rollbackOf = revisionTagCodec.decode(revision.rollbackOf);
            }
            decodedRevisions.push(decodedRevision);
        }
        return decodedRevisions;
    }
    return {
        encode: (change) => {
            return {
                maxId: change.maxId,
                revisions: change.revisions === undefined
                    ? change.revisions
                    : encodeRevisionInfos(change.revisions),
                changes: encodeFieldChangesForJson(change.fieldChanges),
                builds: encodeBuilds(change.builds),
            };
        },
        decode: (change) => {
            const encodedChange = change;
            const decoded = {
                fieldChanges: decodeFieldChangesFromJson(encodedChange.changes),
            };
            if (encodedChange.builds !== undefined) {
                decoded.builds = decodeBuilds(encodedChange.builds);
            }
            if (encodedChange.revisions !== undefined) {
                decoded.revisions = decodeRevisionInfos(encodedChange.revisions);
            }
            if (encodedChange.maxId !== undefined) {
                decoded.maxId = encodedChange.maxId;
            }
            return decoded;
        },
        encodedSchema: EncodedModularChangeset,
    };
}
export function makeModularChangeCodecFamily(fieldKinds, revisionTagCodec, options) {
    return makeCodecFamily([[0, makeV0Codec(fieldKinds, revisionTagCodec, options)]]);
}
//# sourceMappingURL=modularChangeCodecs.js.map