"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeSchemaCodec = exports.encodeRepo = exports.Format = void 0;
const typebox_1 = require("@sinclair/typebox");
const core_utils_1 = require("@fluidframework/core-utils");
const core_1 = require("../core");
const util_1 = require("../util");
const version = "1.0.0";
const FieldSchemaFormatBase = typebox_1.Type.Object({
    kind: core_1.FieldKindIdentifierSchema,
    types: typebox_1.Type.Optional(typebox_1.Type.Array(core_1.TreeSchemaIdentifierSchema)),
});
const noAdditionalProps = { additionalProperties: false };
const FieldSchemaFormat = typebox_1.Type.Composite([FieldSchemaFormatBase], noAdditionalProps);
const NamedFieldSchemaFormat = typebox_1.Type.Composite([
    FieldSchemaFormatBase,
    typebox_1.Type.Object({
        name: core_1.FieldKeySchema,
    }),
], noAdditionalProps);
const TreeSchemaFormat = typebox_1.Type.Object({
    name: core_1.TreeSchemaIdentifierSchema,
    objectNodeFields: typebox_1.Type.Array(NamedFieldSchemaFormat),
    mapFields: typebox_1.Type.Optional(FieldSchemaFormat),
    // TODO: don't use external type here!
    leafValue: typebox_1.Type.Optional(typebox_1.Type.Enum(core_1.ValueSchema)),
}, noAdditionalProps);
/**
 * Format for encoding as json.
 *
 * For consistency all lists are sorted and undefined values are omitted.
 *
 * This chooses to use lists of named objects instead of maps:
 * this choice is somewhat arbitrary, but avoids user data being used as object keys,
 * which can sometimes be an issue (for example handling that for "__proto__" can require care).
 */
exports.Format = typebox_1.Type.Object({
    version: typebox_1.Type.Literal(version),
    treeSchema: typebox_1.Type.Array(TreeSchemaFormat),
    rootFieldSchema: FieldSchemaFormat,
}, noAdditionalProps);
const Versioned = typebox_1.Type.Object({
    version: typebox_1.Type.String(),
});
function encodeRepo(repo) {
    const treeSchema = [];
    const rootFieldSchema = encodeField(repo.rootFieldSchema);
    for (const [name, schema] of repo.treeSchema) {
        treeSchema.push(encodeTree(name, schema));
    }
    treeSchema.sort(compareNamed);
    return {
        version,
        treeSchema,
        rootFieldSchema,
    };
}
exports.encodeRepo = encodeRepo;
function compareNamed(a, b) {
    if (a.name < b.name) {
        return -1;
    }
    if (a.name > b.name) {
        return 1;
    }
    return 0;
}
function encodeTree(name, schema) {
    const out = {
        name,
        mapFields: schema.mapFields === undefined ? undefined : encodeField(schema.mapFields),
        objectNodeFields: [...schema.objectNodeFields]
            .map(([k, v]) => encodeNamedField(k, v))
            .sort(compareNamed),
        leafValue: schema.leafValue,
    };
    return out;
}
function encodeField(schema) {
    const out = {
        kind: schema.kind.identifier,
    };
    if (schema.types !== undefined) {
        out.types = [...schema.types];
    }
    return out;
}
function encodeNamedField(name, schema) {
    return {
        ...encodeField(schema),
        name,
    };
}
function decode(f) {
    const treeSchema = new Map();
    for (const tree of f.treeSchema) {
        treeSchema.set((0, util_1.brand)(tree.name), decodeTree(tree));
    }
    return {
        rootFieldSchema: decodeField(f.rootFieldSchema),
        treeSchema,
    };
}
function decodeField(schema) {
    const out = {
        // TODO: maybe provide actual FieldKind objects here, error on unrecognized kinds.
        kind: { identifier: schema.kind },
        types: schema.types === undefined ? undefined : new Set(schema.types),
    };
    return out;
}
function decodeTree(schema) {
    const out = {
        mapFields: schema.mapFields === undefined ? undefined : decodeField(schema.mapFields),
        objectNodeFields: new Map(schema.objectNodeFields.map((field) => [
            (0, util_1.brand)(field.name),
            decodeField(field),
        ])),
        leafValue: schema.leafValue,
    };
    return out;
}
/**
 * Creates a codec which performs synchronous monolithic encoding of schema content.
 *
 * TODO: when perf matters, this should be replaced with a chunked async version using a binary format.
 */
function makeSchemaCodec({ jsonValidator: validator, }) {
    const versionedValidator = validator.compile(Versioned);
    const formatValidator = validator.compile(exports.Format);
    return {
        encode: (data) => {
            const encoded = encodeRepo(data);
            (0, core_utils_1.assert)(versionedValidator.check(encoded), 0x5c6 /* Encoded schema should be versioned */);
            (0, core_utils_1.assert)(formatValidator.check(encoded), 0x5c7 /* Encoded schema should validate */);
            return encoded;
        },
        decode: (data) => {
            if (!versionedValidator.check(data)) {
                (0, util_1.fail)("invalid serialized schema: did not have a version");
            }
            // When more versions exist, we can switch on the version here.
            if (data.version !== version) {
                (0, util_1.fail)("Unexpected version for serialized schema");
            }
            if (!formatValidator.check(data)) {
                (0, util_1.fail)("Serialized schema failed validation");
            }
            return decode(data);
        },
    };
}
exports.makeSchemaCodec = makeSchemaCodec;
//# sourceMappingURL=schemaIndexFormat.js.map