/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _LeafNodeSchema_initializedData, _a;
import { assert } from "@fluidframework/core-utils/internal";
import { UsageError } from "@fluidframework/telemetry-utils/internal";
import { isFluidHandle } from "@fluidframework/runtime-utils/internal";
import { LeafNodeStoredSchema, ValueSchema } from "../core/index.js";
import { isFlexTreeNode, isTreeValue, valueSchemaAllows, } from "../feature-libraries/index.js";
import { NodeKind, privateDataSymbol, CompatibilityLevel, } from "./core/index.js";
import { brand } from "../util/index.js";
import { getTreeNodeSchemaInitializedData } from "./createContext.js";
/**
 * Instances of this class are schema for leaf nodes.
 * @remarks
 * Unlike other schema, leaf schema are class instances instead of classes themselves.
 * This is because the instance type (the tree node type) for leaves are not objects,
 * so those instances can't be instances of a schema based class.
 * @privateRemarks
 * This class refers to the underlying flex tree schema in its constructor, so this class can't be included in the package API.
 */
export class LeafNodeSchema {
    create(data) {
        if (isFlexTreeNode(data)) {
            const value = data.value;
            assert(valueSchemaAllows(this.info, value), 0x916 /* invalid value */);
            return value;
        }
        return data;
    }
    createFromInsertable(data) {
        return data;
    }
    constructor(name, t) {
        this.kind = NodeKind.Leaf;
        this.implicitlyConstructable = true;
        this.childTypes = new Set();
        this[_a] = {
            idempotentInitialize: () => (__classPrivateFieldSet(this, _LeafNodeSchema_initializedData, __classPrivateFieldGet(this, _LeafNodeSchema_initializedData, "f") ?? getTreeNodeSchemaInitializedData(this, {
                shallowCompatibilityTest: (data) => shallowCompatibilityTest(this, data),
                toFlexContent: (data, allowedTypes) => leafToFlexContent(data, this, allowedTypes),
            }), "f")),
            childAnnotatedAllowedTypes: [],
            toStored: () => new LeafNodeStoredSchema(this.leafKind),
        };
        _LeafNodeSchema_initializedData.set(this, void 0);
        this.metadata = {};
        this.identifier = name;
        this.info = t;
        this.leafKind = t;
    }
}
_LeafNodeSchema_initializedData = new WeakMap(), _a = privateDataSymbol;
/**
 * Wrapper around LeafNodeSchema's constructor that provides the return type that is desired in the package public API.
 */
function makeLeaf(name, t) {
    // Names in this domain follow https://en.wikipedia.org/wiki/Reverse_domain_name_notation
    return new LeafNodeSchema(`com.fluidframework.leaf.${name}`, t);
}
// Leaf schema shared between all SchemaFactory instances.
export const stringSchema = makeLeaf("string", ValueSchema.String);
export const numberSchema = makeLeaf("number", ValueSchema.Number);
export const booleanSchema = makeLeaf("boolean", ValueSchema.Boolean);
export const nullSchema = makeLeaf("null", ValueSchema.Null);
export const handleSchema = makeLeaf("handle", ValueSchema.FluidHandle);
/**
 * Checks if data might be schema-compatible.
 *
 * @returns false if `data` is incompatible with `type` based on a cheap/shallow check.
 *
 * Note that this may return true for cases where data is incompatible, but it must not return false in cases where the data is compatible.
 */
function shallowCompatibilityTest(schema, data) {
    if (isTreeValue(data)) {
        return allowsValue(schema, data) ? CompatibilityLevel.Normal : CompatibilityLevel.None;
    }
    return CompatibilityLevel.None;
}
function allowsValue(schema, value) {
    if (schema.kind === NodeKind.Leaf) {
        return valueSchemaAllows(schema.info, value);
    }
    return false;
}
/**
 * Transforms data under a Leaf schema.
 * @param data - The tree data to be transformed. Must be a {@link TreeValue}.
 * @param schema - The schema associated with the value.
 * @param allowedTypes - The allowed types specified by the parent.
 * Used to determine which fallback values may be appropriate.
 */
export function leafToFlexContent(data, schema, allowedTypes) {
    assert(schema.kind === NodeKind.Leaf, 0x921 /* Expected a leaf schema. */);
    if (!isTreeValue(data)) {
        // This rule exists to protect against useless `toString` output like `[object Object]`.
        // In this case, that's actually reasonable behavior, since object input is not compatible with Leaf schemas.
        // eslint-disable-next-line @typescript-eslint/no-base-to-string
        throw new UsageError(`Input data is incompatible with leaf schema: ${data}`);
    }
    const mappedValue = mapValueWithFallbacks(data, allowedTypes);
    const mappedSchema = [...allowedTypes].find((type) => allowsValue(type, mappedValue));
    assert(mappedSchema !== undefined, 0x84a /* Unsupported schema for provided primitive. */);
    const result = [
        {
            value: mappedValue,
            type: brand(mappedSchema.identifier),
        },
        new Map(),
    ];
    return result;
}
/**
 * Checks an incoming {@link TreeLeafValue} to ensure it is compatible with its requirements.
 * For unsupported values with a schema-compatible replacement, return the replacement value.
 * For unsupported values without a schema-compatible replacement, throw.
 * For supported values, return the input.
 */
function mapValueWithFallbacks(value, allowedTypes) {
    switch (typeof value) {
        case "number": {
            if (Object.is(value, -0)) {
                // Our serialized data format does not support -0.
                // Map such input to +0.
                return 0;
            }
            else if (!Number.isFinite(value)) {
                // Our serialized data format does not support NaN nor +/-∞.
                // If the schema supports `null`, fall back to that. Otherwise, throw.
                // This is intended to match JSON's behavior for such values.
                if (allowedTypes.has(nullSchema)) {
                    return null;
                }
                else {
                    throw new UsageError(`Received unsupported numeric value: ${value}.`);
                }
            }
            else {
                return value;
            }
        }
        case "string":
        // TODO:
        // This should detect invalid strings. Something like @stdlib/regexp-utf16-unpaired-surrogate could be used to do this.
        // See SchemaFactory.string for details.
        case "boolean":
            return value;
        case "object": {
            if (value === null || isFluidHandle(value)) {
                return value;
            }
        }
        default:
            throw new UsageError(`Received unsupported leaf value: ${value}.`);
    }
}
//# sourceMappingURL=leafNodeSchema.js.map