/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { DiscriminatedUnionDispatcher } from "../../codec/index.js";
import { brand, fail, invertMap } from "../../util/index.js";
import { PersistedValueSchema, } from "./format.js";
/**
 * Schema for what {@link TreeValue} is allowed on a Leaf node.
 * @internal
 */
export 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 || (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
 */
export const 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.
 */
export const storedEmptyFieldSchema = {
    // This kind requires the field to be empty.
    kind: brand(forbiddenFieldKindIdentifier),
    // This type set also forces the field to be empty not not allowing any types as all.
    types: new Set(),
};
/**
 * Identifier used for the FieldKind for fields of type identifier.
 *
 * @internal
 */
export const identifierFieldKindIdentifier = "Identifier";
function toErasedTreeNodeSchemaDataFormat(data) {
    return data;
}
export function toTreeNodeSchemaDataFormat(data) {
    return data;
}
/**
 * @internal
 */
export class TreeNodeStoredSchema {
}
/**
 * @internal
 */
export 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 us short friendly field keys which can be 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) ?? fail("missing field")),
            });
        }
        return toErasedTreeNodeSchemaDataFormat({
            object: fieldsObject,
        });
    }
}
/**
 * @internal
 */
export class MapNodeStoredSchema extends TreeNodeStoredSchema {
    /**
     * @param mapFields -
     * Allows 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 if you use `FieldKind.Value` here
     * (that would require infinite children).
     */
    constructor(mapFields) {
        super();
        this.mapFields = mapFields;
    }
    encode() {
        return toErasedTreeNodeSchemaDataFormat({
            map: encodeFieldSchema(this.mapFields),
        });
    }
}
/**
 * @internal
 */
export 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 toErasedTreeNodeSchemaDataFormat({
            leaf: encodeValueSchema(this.leafValue),
        });
    }
}
export const storedSchemaDecodeDispatcher = new 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, PersistedValueSchema.Number],
    [ValueSchema.String, PersistedValueSchema.String],
    [ValueSchema.Boolean, PersistedValueSchema.Boolean],
    [ValueSchema.FluidHandle, PersistedValueSchema.FluidHandle],
    [ValueSchema.Null, PersistedValueSchema.Null],
]);
const valueSchemaDecode = invertMap(valueSchemaEncode);
function encodeValueSchema(inMemory) {
    return valueSchemaEncode.get(inMemory) ?? fail("missing PersistedValueSchema");
}
function decodeValueSchema(inMemory) {
    return valueSchemaDecode.get(inMemory) ?? fail("missing ValueSchema");
}
export function encodeFieldSchema(schema) {
    const out = {
        kind: schema.kind,
    };
    if (schema.types !== undefined) {
        out.types = [...schema.types];
    }
    return out;
}
export 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;
}
//# sourceMappingURL=schema.js.map