"use strict";
/*!
 * 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;
Object.defineProperty(exports, "__esModule", { value: true });
exports.leafToFlexContent = exports.handleSchema = exports.nullSchema = exports.booleanSchema = exports.numberSchema = exports.stringSchema = exports.LeafNodeSchema = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const internal_2 = require("@fluidframework/runtime-utils/internal");
const internal_3 = require("@fluidframework/telemetry-utils/internal");
const index_js_1 = require("../core/index.js");
const index_js_2 = require("../feature-libraries/index.js");
const index_js_3 = require("../util/index.js");
const index_js_4 = require("./core/index.js");
const createContext_js_1 = require("./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.
 */
class LeafNodeSchema {
    create(data) {
        if ((0, index_js_2.isFlexTreeNode)(data)) {
            const value = data.value;
            (0, internal_1.assert)((0, index_js_2.valueSchemaAllows)(this.info, value), 0x916 /* invalid value */);
            return value;
        }
        return data;
    }
    createFromInsertable(data) {
        return data;
    }
    constructor(name, t) {
        this.kind = index_js_4.NodeKind.Leaf;
        this.implicitlyConstructable = true;
        this.childTypes = new Set();
        this[_a] = {
            idempotentInitialize: () => (__classPrivateFieldSet(this, _LeafNodeSchema_initializedData, __classPrivateFieldGet(this, _LeafNodeSchema_initializedData, "f") ?? (0, createContext_js_1.getTreeNodeSchemaInitializedData)(this, {
                shallowCompatibilityTest: (data) => shallowCompatibilityTest(this, data),
                toFlexContent: (data, allowedTypes) => leafToFlexContent(data, this, allowedTypes),
            }), "f")),
            childAllowedTypes: [],
        };
        _LeafNodeSchema_initializedData.set(this, void 0);
        this.metadata = {};
        this.identifier = name;
        this.info = t;
        this.leafKind = t;
    }
}
exports.LeafNodeSchema = LeafNodeSchema;
_LeafNodeSchema_initializedData = new WeakMap(), _a = index_js_4.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.
exports.stringSchema = makeLeaf("string", index_js_1.ValueSchema.String);
exports.numberSchema = makeLeaf("number", index_js_1.ValueSchema.Number);
exports.booleanSchema = makeLeaf("boolean", index_js_1.ValueSchema.Boolean);
exports.nullSchema = makeLeaf("null", index_js_1.ValueSchema.Null);
exports.handleSchema = makeLeaf("handle", index_js_1.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 ((0, index_js_2.isTreeValue)(data)) {
        return allowsValue(schema, data) ? index_js_4.CompatibilityLevel.Normal : index_js_4.CompatibilityLevel.None;
    }
    return index_js_4.CompatibilityLevel.None;
}
function allowsValue(schema, value) {
    if (schema.kind === index_js_4.NodeKind.Leaf) {
        return (0, index_js_2.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.
 */
function leafToFlexContent(data, schema, allowedTypes) {
    (0, internal_1.assert)(schema.kind === index_js_4.NodeKind.Leaf, 0x921 /* Expected a leaf schema. */);
    if (!(0, index_js_2.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 internal_3.UsageError(`Input data is incompatible with leaf schema: ${data}`);
    }
    const mappedValue = mapValueWithFallbacks(data, allowedTypes);
    const mappedSchema = [...allowedTypes].find((type) => allowsValue(type, mappedValue));
    (0, internal_1.assert)(mappedSchema !== undefined, 0x84a /* Unsupported schema for provided primitive. */);
    const result = [
        {
            value: mappedValue,
            type: (0, index_js_3.brand)(mappedSchema.identifier),
        },
        new Map(),
    ];
    return result;
}
exports.leafToFlexContent = leafToFlexContent;
/**
 * 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)) {
                return value;
            }
            else {
                // 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(exports.nullSchema)) {
                    return null;
                }
                else {
                    throw new internal_3.UsageError(`Received unsupported numeric value: ${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 || (0, internal_2.isFluidHandle)(value)) {
                return value;
            }
        }
        // fallthrough
        default: {
            throw new internal_3.UsageError(`Received unsupported leaf value: ${value}.`);
        }
    }
}
//# sourceMappingURL=leafNodeSchema.js.map