/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert, bufferToString, IsoBuffer } from "@fluidframework/common-utils";
import { fail } from "../util";
/**
 * Creates a codec family from a registry of codecs.
 * Any codec that is not a {@link IMultiFormatCodec} will be wrapped with a default binary encoding.
 */
export function makeCodecFamily(registry) {
    const codecs = new Map();
    for (const [formatVersion, codec] of registry) {
        if (codecs.has(formatVersion)) {
            fail("Duplicate codecs specified.");
        }
        codecs.set(formatVersion, ensureBinaryEncoding(codec));
    }
    return {
        resolve(formatVersion) {
            const codec = codecs.get(formatVersion);
            assert(codec !== undefined, 0x5e6 /* Requested coded for unsupported format. */);
            return codec;
        },
        getSupportedFormats() {
            return codecs.keys();
        },
    };
}
class DefaultBinaryCodec {
    constructor(jsonCodec) {
        this.jsonCodec = jsonCodec;
    }
    encode(change) {
        const jsonable = this.jsonCodec.encode(change);
        const json = JSON.stringify(jsonable);
        return IsoBuffer.from(json);
    }
    decode(change) {
        const json = bufferToString(change, "utf8");
        const jsonable = JSON.parse(json);
        return this.jsonCodec.decode(jsonable);
    }
}
function isJsonCodec(codec) {
    return typeof codec.encode === "function" && typeof codec.decode === "function";
}
/**
 * Constructs a {@link IMultiFormatCodec} from a `IJsonCodec` using a generic binary encoding that simply writes
 * the json representation of the object to a buffer.
 */
export function withDefaultBinaryEncoding(jsonCodec) {
    return {
        json: jsonCodec,
        binary: new DefaultBinaryCodec(jsonCodec),
    };
}
/**
 * Ensures that the provided single or multi-format codec has a binary encoding.
 * Adapts the json encoding using {@link withDefaultBinaryEncoding} if necessary.
 */
export function ensureBinaryEncoding(codec) {
    return isJsonCodec(codec) ? withDefaultBinaryEncoding(codec) : codec;
}
/**
 * Codec for objects which carry no information.
 */
export const unitCodec = {
    json: {
        encode: () => 0,
        decode: () => 0,
    },
    binary: {
        encode: () => IsoBuffer.from(""),
        decode: () => 0,
    },
};
/**
 * Creates a json codec for objects which are just a json compatible value
 * and can be serialized as-is.
 *
 * This type of encoding is only appropriate if the persisted type (which should be defined in a persisted format file)
 * happens to be convenient for in-memory usage as well.
 *
 * @remarks - Beware that this API can cause accidental extraneous data in the persisted format.
 * Consider the following example:
 * ```typescript
 * interface MyPersistedType {
 *     foo: string;
 *     id: number;
 * }
 * const MyPersistedType = Type.Object({
 *     foo: Type.String(),
 *     id: Type.Number()
 * });
 *
 * const codec = makeValueCodec<MyPersistedType>();
 *
 * // Later, in some other file...
 * interface SomeInMemoryType extends MyPersistedType {
 *     someOtherProperty: string;
 * }
 *
 * const someInMemoryObject: SomeInMemoryType = {
 *     foo:	"bar",
 *     id: 0,
 *     someOtherProperty: "this shouldn't be here and ends up in the persisted format"
 * }
 *
 * const encoded = codec.encode(someInMemoryObject);
 * ```
 * This all typechecks and passes at runtime, but the persisted format will contain the extraneous
 * `someOtherProperty` field.
 * It's unlikely a real-life example would be this simple, but the principle is the same.
 *
 * This issue can be avoided by using JSON schema that doesn't accept additional properties:
 *
 * ```typescript
 * const MyPersistedType = Type.Object({
 *     foo: Type.String(),
 *     id: Type.Number()
 * }, {
 *     additionalProperties: false
 * });
 * ```
 */
export function makeValueCodec(schema, validator) {
    return withSchemaValidation(schema, {
        encode: (x) => x,
        decode: (x) => x,
    }, validator);
}
/**
 * Wraps a codec with JSON schema validation for its encoded type.
 * @returns An {@link IJsonCodec} which validates the data it encodes and decodes matches the provided schema.
 */
export function withSchemaValidation(schema, codec, validator) {
    if (!validator) {
        return codec;
    }
    const compiledFormat = validator.compile(schema);
    return {
        encode: (obj) => {
            const encoded = codec.encode(obj);
            if (!compiledFormat.check(encoded)) {
                fail("Encoded schema should validate");
            }
            return encoded;
        },
        decode: (encoded) => {
            if (!compiledFormat.check(encoded)) {
                fail("Encoded schema should validate");
            }
            return codec.decode(encoded);
        },
    };
}
//# sourceMappingURL=codec.js.map