"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExtensibleUnionNode = void 0;
const internal_1 = require("@fluidframework/telemetry-utils/internal");
const index_js_1 = require("./shared-tree/index.js");
const index_js_2 = require("./simple-tree/index.js");
/**
 * Utilities for creating extensible unions using a node.
 * @remarks
 * Use {@link ExtensibleUnionNode.createSchema} to create the union schema.
 *
 * Unlike a schema union created using {@link SchemaStaticsBeta.staged | staged} allowed types, this union allows for unknown future types to exist in addition to the known types.
 * This allows for faster roll-outs of new types without waiting for old clients to be updated to be aware of them.
 * This does mean however that old clients may see types they do not know about, which are simply exposed as `undefined` children.
 *
 * `staged` types are lower overhead, and might gain support for `unknown` types in the future, so prefer them when possible.
 * This is simply an alternative for when future compatibility with unknown types is required.
 * It is built on top of the existing {@link ObjectSchemaOptions.allowUnknownOptionalFields | allowUnknownOptionalFields} feature.
 *
 * @example
 * ```typescript
 * const sf = new SchemaFactoryBeta("extensibleUnionNodeExample.items");
 * class ItemA extends sf.object("A", { x: sf.string }) {}
 * class ItemB extends sf.object("B", { x: sf.number }) {}
 *
 * class AnyItem extends ExtensibleUnionNode.createSchema(
 * 	[ItemA, ItemB], // Future versions may add more members here
 * 	sf,
 * 	"ExtensibleUnion",
 * ) {}
 * // Instances of the union are created using `create`.
 * const anyItem = AnyItem.create(new ItemA({ x: "hello" }));
 * // Reading the content from the union is done via the `union` property,
 * // which can be `undefined` to handle the case where a future version of this schema allows a type unknown to the current version.
 * const childNode: ItemA | ItemB | undefined = anyItem.union;
 * // To determine which member of the union was present, its schema can be inspected:
 * const aSchema = Tree.schema(childNode ?? assert.fail("No child"));
 * assert.equal(aSchema, ItemA);
 * ```
 * @beta
 */
var ExtensibleUnionNode;
(function (ExtensibleUnionNode) {
    /**
     * Create an extensible schema union which currently supports the types in `types`,
     * but tolerates collaboration with future versions that may include additional types.
     * @remarks
     * See {@link ExtensibleUnionNode} for an example use.
     * @beta
     */
    // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
    function createSchema(types, inputSchemaFactory, name) {
        const record = {};
        for (const type of types) {
            record[`_${type.identifier}`] = index_js_2.SchemaFactory.optional(type, { key: type.identifier });
        }
        const schemaFactory = (0, index_js_2.createCustomizedFluidFrameworkScopedFactory)(inputSchemaFactory, "extensibleUnionNode");
        class Union extends schemaFactory.object(name, record, { allowUnknownOptionalFields: true }) {
            get union() {
                if (!this.isValid()) {
                    throw new internal_1.UsageError(`This ExtensibleUnionNode (${Union.identifier}) is in an invalid state. It must have been edited by another client using a different schema or been directly imported or constructed in an invalid state.`);
                }
                for (const [_key, child] of index_js_1.TreeAlpha.children(this)) {
                    return child;
                }
                return undefined;
            }
            isValid() {
                // Use inner node, since it includes populated fields even when they are unknown.
                const inner = (0, index_js_2.getInnerNode)(this);
                // Fields only includes non-empty fields, so this is what we need to check the one child invariant.
                return [...inner.fields].length === 1;
            }
            static create(child) {
                const schema = index_js_1.Tree.schema(child);
                return index_js_2.TreeBeta.create(this, {
                    [`_${schema.identifier}`]: child,
                });
            }
        }
        return (0, index_js_2.eraseSchemaDetailsSubclassable)()(Union);
    }
    ExtensibleUnionNode.createSchema = createSchema;
})(ExtensibleUnionNode || (exports.ExtensibleUnionNode = ExtensibleUnionNode = {}));
//# sourceMappingURL=extensibleUnionNode.js.map