"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseSchemaString = exports.getSchemaString = void 0;
const typebox_1 = require("@sinclair/typebox");
// TODO:
// It is unclear if we would want to use the TypeBox compiler
// (which generates code at runtime for maximum validation perf).
// This might be an issue with security policies (ex: no eval) and/or more bundle size that we want.
// We could disable validation or pull in a different validator (like ajv).
// Only using its validation when testing is another option.
// typebox documents using this internal module, so it should be ok to access.
// eslint-disable-next-line import/no-internal-modules
const compiler_1 = require("@sinclair/typebox/compiler");
const common_utils_1 = require("@fluidframework/common-utils");
const core_1 = require("../core");
const util_1 = require("../util");
const Baz = typebox_1.Type.Recursive((Baz2) => typebox_1.Type.Object({
    child: typebox_1.Type.Optional(Baz2),
}));
const _baz = { child: { child: {} } };
const Foo = typebox_1.Type.Recursive((Foo2) => typebox_1.Type.Object({
    child1: typebox_1.Type.Object({
        child2: typebox_1.Type.Optional(Foo2),
    }),
}));
const _bar = { child2: { child1: {} } };
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 FieldSchemaFormat = typebox_1.Type.Intersect([FieldSchemaFormatBase], { additionalProperties: false });
const NamedLocalFieldSchemaFormat = typebox_1.Type.Intersect([
    FieldSchemaFormatBase,
    typebox_1.Type.Object({
        name: core_1.LocalFieldKeySchema,
    }),
], { additionalProperties: false });
const NamedGlobalFieldSchemaFormat = typebox_1.Type.Intersect([
    FieldSchemaFormatBase,
    typebox_1.Type.Object({
        name: core_1.GlobalFieldKeySchema,
    }),
], { additionalProperties: false });
const TreeSchemaFormat = typebox_1.Type.Object({
    name: core_1.TreeSchemaIdentifierSchema,
    localFields: typebox_1.Type.Array(NamedLocalFieldSchemaFormat),
    globalFields: typebox_1.Type.Array(core_1.GlobalFieldKeySchema),
    extraLocalFields: FieldSchemaFormat,
    extraGlobalFields: typebox_1.Type.Boolean(),
    // TODO: don't use external type here.
    value: typebox_1.Type.Enum(core_1.ValueSchema),
}, { additionalProperties: false });
/**
 * 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).
 */
const Format = typebox_1.Type.Object({
    version: typebox_1.Type.Literal(version),
    treeSchema: typebox_1.Type.Array(TreeSchemaFormat),
    globalFieldSchema: typebox_1.Type.Array(NamedGlobalFieldSchemaFormat),
}, { additionalProperties: false });
const CompiledFormat = compiler_1.TypeCompiler.Compile(Format);
const Versioned = compiler_1.TypeCompiler.Compile(typebox_1.Type.Object({
    version: typebox_1.Type.String(),
}));
function encodeRepo(repo) {
    const treeSchema = [];
    const globalFieldSchema = [];
    for (const [name, schema] of repo.treeSchema) {
        treeSchema.push(encodeTree(name, schema));
    }
    for (const [name, schema] of repo.globalFieldSchema) {
        globalFieldSchema.push(encodeNamedField(name, schema));
    }
    treeSchema.sort(compareNamed);
    globalFieldSchema.sort(compareNamed);
    return {
        version,
        treeSchema,
        globalFieldSchema,
    };
}
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,
        extraGlobalFields: schema.extraGlobalFields,
        extraLocalFields: encodeField(schema.extraLocalFields),
        globalFields: [...schema.globalFields].sort(),
        localFields: [...schema.localFields]
            .map(([k, v]) => encodeNamedField(k, v))
            .sort(compareNamed),
        value: schema.value,
    };
    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 Object.assign(Object.assign({}, encodeField(schema)), { name });
}
function decode(f) {
    const globalFieldSchema = new Map();
    const treeSchema = new Map();
    for (const field of f.globalFieldSchema) {
        globalFieldSchema.set(field.name, decodeField(field));
    }
    for (const tree of f.treeSchema) {
        treeSchema.set((0, util_1.brand)(tree.name), decodeTree(tree));
    }
    return {
        globalFieldSchema,
        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 = {
        extraGlobalFields: schema.extraGlobalFields,
        extraLocalFields: decodeField(schema.extraLocalFields),
        globalFields: new Set(schema.globalFields),
        localFields: new Map(schema.localFields.map((field) => [
            (0, util_1.brand)(field.name),
            decodeField(field),
        ])),
        value: schema.value,
    };
    return out;
}
/**
 * Synchronous monolithic summarization of schema content.
 *
 * TODO: when perf matters, this should be replaced with a chunked async version using a binary format.
 *
 * @returns a snapshot of the schema as a string.
 */
function getSchemaString(data) {
    const encoded = encodeRepo(data);
    (0, common_utils_1.assert)(Versioned.Check(encoded), 0x5c6 /* Encoded schema should be versioned */);
    (0, common_utils_1.assert)(CompiledFormat.Check(encoded), 0x5c7 /* Encoded schema should validate */);
    // Currently no Fluid handles are used, so just use JSON.stringify.
    return JSON.stringify(encoded);
}
exports.getSchemaString = getSchemaString;
/**
 * Parses data, asserts format is the current one.
 */
function parseSchemaString(data) {
    // Currently no Fluid handles are used, so just use JSON.parse.
    const parsed = JSON.parse(data);
    if (!Versioned.Check(parsed)) {
        (0, util_1.fail)("invalid serialized schema: did not have a version");
    }
    // When more versions exist, we can switch on the version here.
    if (!CompiledFormat.Check(parsed)) {
        if (parsed.version !== version) {
            (0, util_1.fail)("Unexpected version for serialized schema");
        }
        (0, util_1.fail)("Serialized schema failed validation");
    }
    return decode(parsed);
}
exports.parseSchemaString = parseSchemaString;
//# sourceMappingURL=schemaIndexFormat.js.map