"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.ForestRepairDataStoreProvider = exports.ForestRepairDataStore = void 0;
const core_utils_1 = require("@fluidframework/core-utils");
const core_1 = require("../core");
const chunked_forest_1 = require("./chunked-forest");
const repairDataFactory = () => ({});
const undefinedFactory = () => undefined;
class ForestRepairDataStore {
    constructor(forest, intoDelta) {
        this.forest = forest;
        this.intoDelta = intoDelta;
        this.root = new core_1.SparseNode(core_1.EmptyKey, 0, undefined, undefined);
    }
    capture(change, revision) {
        /**
         * Cursor used to traverse the forest and build fetch the repair data.
         * Note that the cursor is implicitly captured by the functions below, which have requirements for the cursor.
         * Calling those functions requires that the cursor be in the appropriate state.
         */
        const cursor = this.forest.allocateCursor();
        const delta = this.intoDelta(change);
        /**
         * Visits the node `cursor` is positioned at.
         * Restores the `cursor` to that same position before exiting.
         */
        const visitFieldMarks = (fields, parent) => {
            for (const [key, field] of fields) {
                if (parent !== this.root) {
                    cursor.enterField(key);
                }
                else {
                    (0, core_1.moveToDetachedField)(this.forest, cursor, (0, core_1.keyAsDetachedField)(key));
                }
                visitField(field, parent, key);
                if (parent !== this.root) {
                    cursor.exitField();
                }
            }
        };
        /**
         * Visits the field `cursor` is positioned at.
         * Restores the `cursor` to that same position before exiting.
         */
        function visitField(field, parent, key) {
            let index = 0;
            for (const mark of field) {
                if (typeof mark === "number") {
                    // Untouched nodes
                    index += mark;
                }
                else {
                    // Inline into `switch(mark.type)` once we upgrade to TS 4.7
                    const type = mark.type;
                    switch (type) {
                        case core_1.Delta.MarkType.MoveOut:
                        case core_1.Delta.MarkType.Delete: {
                            cursor.enterNode(index);
                            const child = parent.getOrCreateChild(key, index, repairDataFactory);
                            visitModify(mark, child);
                            cursor.exitNode();
                            onDelete(parent, key, index, mark.count);
                            index += mark.count;
                            break;
                        }
                        case core_1.Delta.MarkType.Modify: {
                            cursor.enterNode(index);
                            const child = parent.getOrCreateChild(key, index, undefinedFactory);
                            visitModify(mark, child);
                            cursor.exitNode();
                            index += 1;
                            break;
                        }
                        case core_1.Delta.MarkType.Insert:
                        case core_1.Delta.MarkType.MoveIn:
                            break;
                        default:
                            (0, core_utils_1.unreachableCase)(type);
                    }
                }
            }
        }
        /**
         * Requires that `cursor` be positioned on a node.
         * Restores the `cursor` to that same position before exiting.
         */
        function visitModify(modify, node) {
            if (modify.fields !== undefined) {
                visitFieldMarks(modify.fields, node);
            }
        }
        /**
         * Requires that `cursor` be positioned in the field where the deletion takes place.
         * Does not move `cursor`.
         */
        function onDelete(parent, key, startIndex, count) {
            for (let i = 0; i < count; ++i) {
                const fork = cursor.fork();
                const index = startIndex + i;
                fork.enterNode(index);
                const nodeData = (0, chunked_forest_1.chunkTree)((0, core_1.castCursorToSynchronous)(fork), chunked_forest_1.defaultChunkPolicy);
                fork.free();
                const child = parent.getOrCreateChild(key, index, repairDataFactory);
                if (child.data === undefined) {
                    child.data = repairDataFactory();
                }
                if (child.data.node === undefined) {
                    child.data.node = new Map();
                }
                child.data.node.set(revision, nodeData);
            }
        }
        visitFieldMarks(delta, this.root);
        cursor.free();
    }
    getNodes(revision, path, field, index, count) {
        const parent = (0, core_1.getDescendant)(this.root, path);
        const sparseField = parent.children.get(field);
        (0, core_utils_1.assert)(sparseField !== undefined, 0x47a /* No repair data found */);
        // TODO: should do more optimized search (ex: binary search).
        const sparseIndex = sparseField.findIndex((child) => child.parentIndex === index);
        (0, core_utils_1.assert)(sparseIndex !== -1, 0x47b /* No repair data found */);
        (0, core_utils_1.assert)(sparseField[sparseIndex + count - 1]?.parentIndex === index + count - 1, 0x47c /* No repair data found */);
        return sparseField.slice(sparseIndex, sparseIndex + count).map((node) => {
            const repair = node.data?.node?.get(revision);
            (0, core_utils_1.assert)(repair !== undefined, 0x47d /* No repair data found */);
            const cursor = repair.cursor();
            // TODO: leverage TreeChunk's ability to represent a range of contiguous nodes
            (0, core_utils_1.assert)(cursor.getFieldLength() === 1, 0x55b /* only one node should have been chunked */);
            cursor.firstNode();
            return cursor;
        });
    }
}
exports.ForestRepairDataStore = ForestRepairDataStore;
class ForestRepairDataStoreProvider {
    constructor(forest, 
    // TODO: Its unclear what it means if this schema is modified, and why its separate from the one in the forest. Maybe this should be removed or replaced with SchemaData?
    storedSchema, intoDelta) {
        this.forest = forest;
        this.storedSchema = storedSchema;
        this.intoDelta = intoDelta;
    }
    freeze() {
        // TODO: ensure editing this clone of the schema does the right thing (probably error?).
        this.frozenForest = this.forest.clone(new core_1.InMemoryStoredSchemaRepository(this.storedSchema), new core_1.AnchorSet());
    }
    applyChange(change) {
        if (this.frozenForest === undefined) {
            (0, core_1.applyDelta)(this.intoDelta(change), this.forest);
        }
    }
    createRepairData() {
        const repairDataStore = this.frozenForest !== undefined
            ? new ForestRepairDataStore(this.frozenForest, this.intoDelta)
            : new ForestRepairDataStore(this.forest, this.intoDelta);
        this.frozenForest = undefined;
        return repairDataStore;
    }
    clone(forest) {
        // TODO: ensure editing this clone of the schema does the right thing (probably error?).
        const storedSchema = new core_1.InMemoryStoredSchemaRepository(this.storedSchema);
        return new ForestRepairDataStoreProvider(forest ?? this.forest.clone(storedSchema, new core_1.AnchorSet()), storedSchema, this.intoDelta);
    }
}
exports.ForestRepairDataStoreProvider = ForestRepairDataStoreProvider;
//# sourceMappingURL=forestRepairDataStore.js.map