/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { UsageError } from "@fluidframework/telemetry-utils";
import { fail } from "../util/index.js";
/**
 * Create a schema for a node with no state.
 * @remarks
 * This is commonly used in unions when the only information needed is which kind of node the value is.
 * Enums are a common example of this pattern.
 * @internal
 */
export function singletonSchema(factory, name) {
    class SingletonSchema extends factory.object(name, {}) {
        constructor(data) {
            super(data ?? {});
        }
        get value() {
            return name;
        }
    }
    // Returning SingletonSchema without a type conversion results in TypeScript generating something like `readonly "__#124291@#brand": unknown;`
    // for the private brand field of TreeNode.
    // This numeric id doesn't seem to be stable over incremental builds, and thus causes diffs if the API extractor reports.
    // This is avoided by doing this type conversion.
    // The conversion is done via assignment instead of `as` to get stronger type safety.
    const toReturn = SingletonSchema;
    return toReturn;
}
/**
 * Converts an enum into a collection of schema which can be used in a union.
 * @remarks
 * Currently only supports `string` enums.
 * @example
 * ```typescript
 * enum Mode {
 * 	a = "A",
 * 	b = "B",
 * }
 * const ModeNodes = adaptEnum(schema, Mode);
 * type ModeNodes = NodeFromSchema<(typeof ModeNodes)[keyof typeof ModeNodes]>;
 * const nodeFromString: ModeNodes = ModeNodes(Mode.a);
 * const nodeFromSchema: ModeNodes = new ModeNodes.a();
 * const nameFromNode: Mode = nodeFromSchema.value;
 * class Parent extends schemaFactory.object("Parent", {
 * 	mode: typedObjectValues(ModeNodes),
 * }) {}
 * ```
 * @privateRemarks
 * TODO:
 * Extends this to support numeric enums.
 * Maybe require an explicit nested scope to group them under, or at least a warning about collisions.
 * Maybe just provide `SchemaFactory.nested` to east creating nested scopes?
 * @internal
 */
export function adaptEnum(factory, members) {
    const values = Object.values(members);
    const inverse = new Map(Object.entries(members).map(([key, value]) => [value, key]));
    if (inverse.size !== values.length) {
        throw new UsageError("All members of enums must have distinct values.");
    }
    const factoryOut = (value) => {
        return new out[inverse.get(value) ?? fail("missing enum value")]({});
    };
    const out = factoryOut;
    for (const [key, value] of Object.entries(members)) {
        Object.defineProperty(out, key, {
            enumerable: true,
            configurable: false,
            writable: false,
            value: singletonSchema(factory, value),
        });
    }
    return out;
}
/**
 * `Object.values`, but with more specific types.
 * @internal
 */
export function typedObjectValues(object) {
    return Object.values(object);
}
/**
 * Converts an array of distinct strings into a collection of schema which can be used like an enum style union.
 * @remarks
 * The returned collection is also a function which can be used to convert strings into {@link Unhydrated} nodes in the union.
 * Each node type has a `.value` getter which returns the associated string.
 *
 * The produced nodes use the provided strings as their `name`, and don't store any data beyond that.
 * @example
 * ```typescript
 * const Mode = enumFromStrings(schemaFactory, ["Fun", "Cool"]);
 * type Mode = NodeFromSchema<(typeof Mode)[keyof typeof Mode]>;
 * const nodeFromString: Mode = Mode("Fun");
 * const nodeFromSchema: Mode = new Mode.Fun();
 * const nameFromNode = nodeFromSchema.value;
 *
 * class Parent extends schemaFactory.object("Parent", { mode: typedObjectValues(Mode) }) {}
 * ```
 * @internal
 */
export function enumFromStrings(factory, members) {
    const names = new Set(members);
    if (names.size !== members.length) {
        throw new UsageError("All members of enums must have distinct names");
    }
    const factoryOut = (value) => {
        return new out[value]({});
    };
    const out = factoryOut;
    for (const name of members) {
        Object.defineProperty(out, name, {
            enumerable: true,
            configurable: false,
            writable: false,
            value: singletonSchema(factory, name),
        });
    }
    return out;
}
// TODO: Why does this one generate an invalid d.ts file if exported?
// Tracked by https://github.com/microsoft/TypeScript/issues/56718
// TODO: replace enumFromStrings above with this simpler implementation when the TypeScript bug is resolved.
function _enumFromStrings2(factory, members) {
    const enumObject = Object.create(null);
    for (const name of members) {
        Object.defineProperty(enumObject, name, {
            enumerable: true,
            configurable: false,
            writable: false,
            value: name,
        });
    }
    return adaptEnum(factory, enumObject);
}
//# sourceMappingURL=schemaCreationUtilities.js.map