"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
var _TreeNode_brand;
Object.defineProperty(exports, "__esModule", { value: true });
exports.inPrototypeChain = exports.privateToken = exports.TreeNode = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const internal_2 = require("@fluidframework/telemetry-utils/internal");
const treeNodeKernel_js_1 = require("./treeNodeKernel.js");
const treeNodeSchema_js_1 = require("./treeNodeSchema.js");
// eslint-disable-next-line import/no-deprecated
const withType_js_1 = require("./withType.js");
const flexList_js_1 = require("./flexList.js");
/**
 * A non-{@link NodeKind.Leaf|leaf} SharedTree node. Includes objects, arrays, and maps.
 *
 * @remarks
 * Base type which all nodes extend.
 *
 * This type can be used as a type to indicate/document values which should be tree nodes.
 * Runtime use of this class object (for example when used with `instanceof` or extending it), is not currently supported.
 *
 * There are three ways to get instances of TreeNode:
 *
 * 1. From a {@link TreeView} loading nodes from an existing document, or creating local copies of nodes inserted by a remote collaborator.
 * This case provides an {@link InternalTreeNode} to the constructor: subclasses must not modify how the constructor handles this case.
 *
 * 2. Explicit construction of {@link Unhydrated} nodes using either {@link TreeNodeSchemaClass} as a constructor or {@link TreeNodeSchemaNonClass|TreeNodeSchemaNonClass.create}.
 * Either way the {@link TreeNodeSchema} produced must be produced using a {@link SchemaFactory}.
 *
 * 3. Implicit construction: Several APIs which logically require an unhydrated TreeNode also allow passing in a value which could be used to explicitly construct the node instead.
 * These APIs internally call the constructor with the provided value, so it's really just a special case of the above option.
 * Note that when constructing nodes, sometimes implicit construction is not allowed
 * (either at runtime due to ambiguous types or at compile time due to TypeScript limitations):
 * in such cases, explicit construction must be used.
 *
 * @privateRemarks
 * This is a class not an interface to enable stricter type checking (see {@link TreeNode.#brand})
 * and some runtime enforcement of schema class policy (see the the validation in the constructor).
 *
 * Not all node implementations include this in their prototype chain (some hide it with a proxy),
 * and thus cause the default/built in `instanceof` to return false despite our type checking and all other APIs treating them as TreeNodes.
 * This class provides a custom `Symbol.hasInstance` to fix `instanceof` for this class and all classes extending it.
 * @sealed @public
 */
class TreeNode {
    static [(_TreeNode_brand = new WeakMap(), Symbol.hasInstance)](value) {
        const schema = (0, treeNodeKernel_js_1.tryGetTreeNodeSchema)(value);
        if (schema === undefined || schema.kind === treeNodeSchema_js_1.NodeKind.Leaf) {
            return false;
        }
        (0, internal_1.assert)("prototype" in schema, 0x98a /* expected class based schema */);
        return inPrototypeChain(schema.prototype, this.prototype);
    }
    /**
     * TreeNodes must extend schema classes created by SchemaFactory, and therefore this constructor should not be invoked directly by code outside this package.
     * @privateRemarks
     * `token` must be the {@link privateToken} value, which is not package exported.
     * This is used to detect invalid subclasses.
     *
     * All valid subclass should use {@link TreeNodeValid}, but this code doesn't directly reference it to avoid cyclic dependencies.
     */
    constructor(token) {
        /**
         * This is added to prevent TypeScript from implicitly allowing non-TreeNode types to be used as TreeNodes.
         * @remarks
         * This field forces TypeScript to use nominal instead of structural typing,
         * preventing compiler error messages and tools like "add missing properties"
         * from adding the [type] field as a solution when using a non-TreeNode object where a TreeNode is required.
         * Instead TreeNodes must be created through the appropriate APIs, see the documentation on {@link TreeNode} for details.
         *
         * @privateRemarks
         * This is a JavaScript private field, so is not accessible from outside this class.
         * This prevents it from having name collisions with object fields.
         * Since this is private, the type of this field is stripped in the d.ts file.
         * To get matching type checking within and from outside the package, the least informative type (`unknown`) is used.
         * To avoid this having any runtime impact, the field is uninitialized.
         *
         * Making this field optional results in different type checking within this project than outside of it, since the d.ts file drops the optional aspect of the field.
         * This is extra confusing since since the tests get in-project typing for intellisense and separate project checking at build time.
         * To avoid all this mess, this field is required, not optional.
         *
         * Another option would be to use a symbol (possibly as a private field).
         * That approach ran into some strange difficulties causing SchemaFactory to fail to compile, and was not investigated further.
         *
         * The [type] symbol provides a lot of the value this private brand does, but is not all of it:
         * someone could manually (or via Intellisense auto-implement completion, or in response to a type error)
         * make an object literal with the [type] field and pass it off as a node: this private brand prevents that.
         */
        _TreeNode_brand.set(this, void 0);
        if (token !== exports.privateToken) {
            throw new internal_2.UsageError("TreeNodes must extend schema classes created by SchemaFactory");
        }
    }
}
exports.TreeNode = TreeNode;
// Class objects are functions (callable), so we need a strong way to distinguish between `schema` and `() => schema` when used as a `LazyItem`.
(0, flexList_js_1.markEager)(TreeNode);
/**
 * `token` to pass to {@link TreeNode}'s constructor used to detect invalid subclasses.
 */
exports.privateToken = {};
/**
 * Check if the prototype derived's prototype chain contains `base`.
 * @param derived - prototype to check
 * @param base - prototype to search for
 * @returns true iff `base` is in the prototype chain starting at `derived`.
 */
// eslint-disable-next-line @rushstack/no-new-null
function inPrototypeChain(derived, base) {
    let checking = derived;
    while (checking !== null) {
        if (base === checking) {
            return true;
        }
        checking = Reflect.getPrototypeOf(checking);
    }
    return false;
}
exports.inPrototypeChain = inPrototypeChain;
//# sourceMappingURL=treeNode.js.map