"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.decodeFieldSchema = exports.encodeFieldSchema = exports.storedSchemaDecodeDispatcher = exports.LeafNodeStoredSchema = exports.MapNodeStoredSchema = exports.ObjectNodeStoredSchema = exports.TreeNodeStoredSchema = exports.storedEmptyFieldSchema = exports.forbiddenFieldKindIdentifier = exports.ValueSchema = void 0;
const index_js_1 = require("../../codec/index.js");
const index_js_2 = require("../../util/index.js");
const format_js_1 = require("./format.js");
/**
 * Schema for what {@link TreeValue} is allowed on a Leaf node.
 * @internal
 */
var ValueSchema;
(function (ValueSchema) {
    ValueSchema[ValueSchema["Number"] = 0] = "Number";
    ValueSchema[ValueSchema["String"] = 1] = "String";
    ValueSchema[ValueSchema["Boolean"] = 2] = "Boolean";
    ValueSchema[ValueSchema["FluidHandle"] = 3] = "FluidHandle";
    ValueSchema[ValueSchema["Null"] = 4] = "Null";
})(ValueSchema || (exports.ValueSchema = ValueSchema = {}));
/**
 * Identifier used for the FieldKind for fields which must be empty.
 *
 * @remarks
 * This mainly show up in:
 * 1. The root default field for documents.
 * 2. The schema used for out of schema fields (which thus must be empty/not exist) on object and leaf nodes.
 *
 * @internal
 */
exports.forbiddenFieldKindIdentifier = "Forbidden";
/**
 * A schema for empty fields (fields which must always be empty).
 * There are multiple ways this could be encoded, but this is the most explicit.
 */
exports.storedEmptyFieldSchema = {
    // This kind requires the field to be empty.
    kind: (0, index_js_2.brand)(exports.forbiddenFieldKindIdentifier),
    // This type set also forces the field to be empty not not allowing any types as all.
    types: new Set(),
};
/**
 * @internal
 */
class TreeNodeStoredSchema {
}
exports.TreeNodeStoredSchema = TreeNodeStoredSchema;
/**
 * @internal
 */
class ObjectNodeStoredSchema extends TreeNodeStoredSchema {
    /**
     * @param objectNodeFields -
     * Schema for fields with keys scoped to this TreeNodeStoredSchema.
     * This refers to the TreeFieldStoredSchema directly
     * (as opposed to just supporting FieldSchemaIdentifier and having a central FieldKey -\> TreeFieldStoredSchema map).
     * This allows os short friendly field keys which can ergonomically used as field names in code.
     * It also interoperates well with mapFields being used as a map with arbitrary data as keys.
     */
    constructor(objectNodeFields) {
        super();
        this.objectNodeFields = objectNodeFields;
    }
    encode() {
        const fieldsObject = Object.create(null);
        // Sort fields to ensure output is identical for for equivalent schema (since field order is not considered significant).
        // This makes comparing schema easier, and ensures chunk reuse for schema summaries isn't needlessly broken.
        for (const key of [...this.objectNodeFields.keys()].sort()) {
            Object.defineProperty(fieldsObject, key, {
                enumerable: true,
                configurable: true,
                writable: true,
                value: encodeFieldSchema(this.objectNodeFields.get(key) ?? (0, index_js_2.fail)("missing field")),
            });
        }
        return (0, index_js_2.brandErased)({
            object: fieldsObject,
        });
    }
}
exports.ObjectNodeStoredSchema = ObjectNodeStoredSchema;
/**
 * @internal
 */
class MapNodeStoredSchema extends TreeNodeStoredSchema {
    /**
     * @param mapFields -
     * Allows using using the fields as a map, with the keys being
     * FieldKeys and the values being constrained by this TreeFieldStoredSchema.
     * Usually `FieldKind.Value` should NOT be used here
     * since no nodes can ever be in schema are in schema if you use `FieldKind.Value` here
     * (that would require infinite children).
     */
    constructor(mapFields) {
        super();
        this.mapFields = mapFields;
    }
    encode() {
        return (0, index_js_2.brandErased)({
            map: encodeFieldSchema(this.mapFields),
        });
    }
}
exports.MapNodeStoredSchema = MapNodeStoredSchema;
/**
 * @internal
 */
class LeafNodeStoredSchema extends TreeNodeStoredSchema {
    /**
     * @param leafValue -
     * There are several approaches for how to store actual data in the tree
     * (special node types, special field contents, data on nodes etc.)
     * as well as several options about how the data should be modeled at this level
     * (byte sequence? javascript type? json?),
     * as well as options for how much of this would be exposed in the schema language
     * (ex: would all nodes with values be special built-ins, or could any schema add them?)
     * A simple easy to do in javascript approach is taken here:
     * this is not intended to be a suggestion of what approach to take, or what to expose in the schema language.
     * This is simply one approach that can work for modeling them in the internal schema representation.
     */
    constructor(leafValue) {
        super();
        this.leafValue = leafValue;
    }
    encode() {
        return (0, index_js_2.brandErased)({
            leaf: encodeValueSchema(this.leafValue),
        });
    }
}
exports.LeafNodeStoredSchema = LeafNodeStoredSchema;
exports.storedSchemaDecodeDispatcher = new index_js_1.DiscriminatedUnionDispatcher({
    leaf: (data) => new LeafNodeStoredSchema(decodeValueSchema(data)),
    object: (data) => {
        const map = new Map();
        for (const [key, value] of Object.entries(data)) {
            map.set(key, decodeFieldSchema(value));
        }
        return new ObjectNodeStoredSchema(map);
    },
    map: (data) => new MapNodeStoredSchema(decodeFieldSchema(data)),
});
const valueSchemaEncode = new Map([
    [ValueSchema.Number, format_js_1.PersistedValueSchema.Number],
    [ValueSchema.String, format_js_1.PersistedValueSchema.String],
    [ValueSchema.Boolean, format_js_1.PersistedValueSchema.Boolean],
    [ValueSchema.FluidHandle, format_js_1.PersistedValueSchema.FluidHandle],
    [ValueSchema.Null, format_js_1.PersistedValueSchema.Null],
]);
const valueSchemaDecode = (0, index_js_2.invertMap)(valueSchemaEncode);
function encodeValueSchema(inMemory) {
    return valueSchemaEncode.get(inMemory) ?? (0, index_js_2.fail)("missing PersistedValueSchema");
}
function decodeValueSchema(inMemory) {
    return valueSchemaDecode.get(inMemory) ?? (0, index_js_2.fail)("missing ValueSchema");
}
function encodeFieldSchema(schema) {
    const out = {
        kind: schema.kind,
    };
    if (schema.types !== undefined) {
        out.types = [...schema.types];
    }
    return out;
}
exports.encodeFieldSchema = encodeFieldSchema;
function decodeFieldSchema(schema) {
    const out = {
        // TODO: maybe provide actual FieldKind objects here, error on unrecognized kinds.
        kind: schema.kind,
        types: schema.types === undefined ? undefined : new Set(schema.types),
    };
    return out;
}
exports.decodeFieldSchema = decodeFieldSchema;
//# sourceMappingURL=schema.js.map