"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.prepareContentForHydration = exports.getTreeNodeForField = void 0;
const internal_1 = 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");
/**
 * Retrieve the associated {@link TreeNode} for the given field's content.
 */
function getTreeNodeForField(field) {
    function tryToUnboxLeaves(flexField) {
        const maybeContent = flexField.content;
        return (0, index_js_2.isFlexTreeNode)(maybeContent)
            ? (0, index_js_4.getOrCreateNodeFromInnerNode)(maybeContent)
            : maybeContent;
    }
    switch (field.schema) {
        case index_js_2.FieldKinds.required.identifier: {
            const typedField = field;
            return tryToUnboxLeaves(typedField);
        }
        case index_js_2.FieldKinds.optional.identifier: {
            const typedField = field;
            return tryToUnboxLeaves(typedField);
        }
        case index_js_2.FieldKinds.identifier.identifier: {
            // Identifier fields are just value fields that hold strings
            return field.content;
        }
        default:
            (0, index_js_3.fail)("invalid field kind");
    }
}
exports.getTreeNodeForField = getTreeNodeForField;
/**
 * Records any proxies in the given content tree and does the necessary bookkeeping to ensure they are synchronized with subsequent reads of the tree.
 * @remarks If the content tree contains any proxies, this function must be called just prior to inserting the content into the tree.
 * Specifically, no other content may be inserted into the tree between the invocation of this function and the insertion of `content`.
 * The insertion of `content` must occur or else this function will cause memory leaks.
 * @param content - the tree of content to be inserted, of which any of its object/map/array nodes might be a proxy
 * @param anchors - the {@link AnchorSet} for the tree
 * @returns The content after having all proxies replaced inline with plain javascript objects.
 * See {@link extractFactoryContent} for more details.
 */
function prepareContentForHydration(content, forest) {
    if ((0, index_js_3.isReadonlyArray)(content)) {
        return prepareArrayContentForHydration(content, forest);
    }
    if (content !== undefined) {
        const proxies = {
            rootPath: { parent: undefined, parentField: index_js_1.EmptyKey, parentIndex: 0 },
            proxyPaths: [],
        };
        walkMapTree(content, proxies.rootPath, (p, proxy) => {
            proxies.proxyPaths.push({ path: p, proxy });
        });
        bindProxies([proxies], forest);
    }
}
exports.prepareContentForHydration = prepareContentForHydration;
function prepareArrayContentForHydration(content, forest) {
    const proxyPaths = [];
    for (const item of content) {
        const proxyPath = {
            rootPath: {
                parent: undefined,
                parentField: index_js_1.EmptyKey,
                parentIndex: 0,
            },
            proxyPaths: [],
        };
        proxyPaths.push(proxyPath);
        walkMapTree(item, proxyPath.rootPath, (p, proxy) => {
            proxyPath.proxyPaths.push({ path: p, proxy });
        });
    }
    bindProxies(proxyPaths, forest);
}
function walkMapTree(mapTree, path, onVisitTreeNode) {
    if ((0, index_js_4.tryUnhydratedFlexTreeNode)(mapTree)?.parentField.parent.parent !== undefined) {
        throw new internal_1.UsageError("Attempted to insert a node which is already under a parent. If this is desired, remove the node from its parent before inserting it elsewhere.");
    }
    const nexts = [];
    for (let next = [path, mapTree]; next !== undefined; next = nexts.pop()) {
        const [p, m] = next;
        const mapTreeNode = (0, index_js_4.tryUnhydratedFlexTreeNode)(m);
        if (mapTreeNode !== undefined) {
            const treeNode = (0, index_js_4.tryGetTreeNodeFromMapNode)(mapTreeNode);
            if (treeNode !== undefined) {
                onVisitTreeNode(p, treeNode);
            }
        }
        for (const [key, field] of m.fields) {
            for (const [i, child] of field.entries()) {
                nexts.push([
                    {
                        parent: p,
                        parentField: key,
                        parentIndex: i,
                    },
                    child,
                ]);
            }
        }
    }
}
function bindProxies(proxies, forest) {
    // Only subscribe to the event if there is at least one proxy tree to hydrate - this is not the case when inserting an empty array [].
    if (proxies.length > 0) {
        // Creating a new array emits one event per element in the array, so listen to the event once for each element
        let i = 0;
        const off = forest.events.on("afterRootFieldCreated", (fieldKey) => {
            // Non null asserting here because of the length check above
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            proxies[i].rootPath.parentField = fieldKey;
            // Non null asserting here because of the length check above
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            for (const { path, proxy } of proxies[i].proxyPaths) {
                (0, index_js_4.getKernel)(proxy).anchorProxy(forest.anchors, path);
            }
            if (++i === proxies.length) {
                off();
            }
        });
    }
}
// #endregion Content insertion and proxy binding
//# sourceMappingURL=proxies.js.map