/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { Type } from "@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
import { TypeCompiler } from "@sinclair/typebox/compiler";
import { assert } from "@fluidframework/common-utils";
import { FieldKindIdentifierSchema, GlobalFieldKeySchema, LocalFieldKeySchema, TreeSchemaIdentifierSchema, ValueSchema, } from "../core";
import { brand, fail } from "../util";
const Baz = Type.Recursive((Baz2) => Type.Object({
    child: Type.Optional(Baz2),
}));
const _baz = { child: { child: {} } };
const Foo = Type.Recursive((Foo2) => Type.Object({
    child1: Type.Object({
        child2: Type.Optional(Foo2),
    }),
}));
const _bar = { child2: { child1: {} } };
const version = "1.0.0";
const FieldSchemaFormatBase = Type.Object({
    kind: FieldKindIdentifierSchema,
    types: Type.Optional(Type.Array(TreeSchemaIdentifierSchema)),
});
const FieldSchemaFormat = Type.Intersect([FieldSchemaFormatBase], { additionalProperties: false });
const NamedLocalFieldSchemaFormat = Type.Intersect([
    FieldSchemaFormatBase,
    Type.Object({
        name: LocalFieldKeySchema,
    }),
], { additionalProperties: false });
const NamedGlobalFieldSchemaFormat = Type.Intersect([
    FieldSchemaFormatBase,
    Type.Object({
        name: GlobalFieldKeySchema,
    }),
], { additionalProperties: false });
const TreeSchemaFormat = Type.Object({
    name: TreeSchemaIdentifierSchema,
    localFields: Type.Array(NamedLocalFieldSchemaFormat),
    globalFields: Type.Array(GlobalFieldKeySchema),
    extraLocalFields: FieldSchemaFormat,
    extraGlobalFields: Type.Boolean(),
    // TODO: don't use external type here.
    value: Type.Enum(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 = Type.Object({
    version: Type.Literal(version),
    treeSchema: Type.Array(TreeSchemaFormat),
    globalFieldSchema: Type.Array(NamedGlobalFieldSchemaFormat),
}, { additionalProperties: false });
const CompiledFormat = TypeCompiler.Compile(Format);
const Versioned = TypeCompiler.Compile(Type.Object({
    version: 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(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) => [
            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.
 */
export function getSchemaString(data) {
    const encoded = encodeRepo(data);
    assert(Versioned.Check(encoded), 0x5c6 /* Encoded schema should be versioned */);
    assert(CompiledFormat.Check(encoded), 0x5c7 /* Encoded schema should validate */);
    // Currently no Fluid handles are used, so just use JSON.stringify.
    return JSON.stringify(encoded);
}
/**
 * Parses data, asserts format is the current one.
 */
export function parseSchemaString(data) {
    // Currently no Fluid handles are used, so just use JSON.parse.
    const parsed = JSON.parse(data);
    if (!Versioned.Check(parsed)) {
        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) {
            fail("Unexpected version for serialized schema");
        }
        fail("Serialized schema failed validation");
    }
    return decode(parsed);
}
//# sourceMappingURL=schemaIndexFormat.js.map