"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.runSynchronous = exports.schematizeView = exports.SharedTreeView = exports.createSharedTreeView = void 0;
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
const core_utils_1 = require("@fluidframework/core-utils");
const core_1 = require("../core");
const events_1 = require("../events");
const feature_libraries_1 = require("../feature-libraries");
const shared_tree_core_1 = require("../shared-tree-core");
const util_1 = require("../util");
const codec_1 = require("../codec");
const schematizedTree_1 = require("./schematizedTree");
/**
 * Creates a {@link SharedTreeView}.
 * @param args - an object containing optional components that will be used to build the view.
 * Any components not provided will be created by default.
 * @remarks This does not create a {@link SharedTree}, but rather a view with the minimal state
 * and functionality required to implement {@link ISharedTreeView}.
 */
function createSharedTreeView(args) {
    const schema = args?.schema ?? new core_1.InMemoryStoredSchemaRepository();
    const forest = args?.forest ?? (0, feature_libraries_1.buildForest)();
    const changeFamily = args?.changeFamily ?? new feature_libraries_1.DefaultChangeFamily({ jsonValidator: codec_1.noopValidator });
    const repairDataStoreProvider = args?.repairProvider ??
        new feature_libraries_1.ForestRepairDataStoreProvider(forest, schema, (change) => changeFamily.intoDelta(change));
    const undoRedoManager = core_1.UndoRedoManager.create(changeFamily);
    const branch = args?.branch ??
        new shared_tree_core_1.SharedTreeBranch({
            change: changeFamily.rebaser.compose([]),
            revision: (0, core_1.assertIsRevisionTag)("00000000-0000-4000-8000-000000000000"),
        }, changeFamily, repairDataStoreProvider, undoRedoManager);
    const nodeKeyManager = args?.nodeKeyManager ?? (0, feature_libraries_1.createNodeKeyManager)();
    const context = (0, feature_libraries_1.getEditableTreeContext)(forest, schema, branch.editor, nodeKeyManager, (0, util_1.brand)(feature_libraries_1.nodeKeyFieldKey));
    const nodeKeyIndex = args?.nodeKeyIndex ?? new feature_libraries_1.NodeKeyIndex((0, util_1.brand)(feature_libraries_1.nodeKeyFieldKey));
    const events = args?.events ?? (0, events_1.createEmitter)();
    const transaction = new Transaction(branch, changeFamily, forest);
    return new SharedTreeView(transaction, branch, changeFamily, schema, forest, context, nodeKeyManager, nodeKeyIndex, events);
}
exports.createSharedTreeView = createSharedTreeView;
class Transaction {
    constructor(branch, changeFamily, forest) {
        this.branch = branch;
        this.changeFamily = changeFamily;
        this.forest = forest;
    }
    start() {
        this.branch.startTransaction(new feature_libraries_1.ForestRepairDataStore(this.forest, (change) => this.changeFamily.intoDelta(change)));
        this.branch.editor.enterTransaction();
    }
    commit() {
        this.branch.commitTransaction();
        this.branch.editor.exitTransaction();
        return util_1.TransactionResult.Commit;
    }
    abort() {
        this.branch.abortTransaction();
        this.branch.editor.exitTransaction();
        return util_1.TransactionResult.Abort;
    }
    inProgress() {
        return this.branch.isTransacting();
    }
}
/**
 * An implementation of {@link ISharedTreeBranchView}.
 */
class SharedTreeView {
    constructor(transaction, branch, changeFamily, storedSchema, forest, context, nodeKeyManager, nodeKeyIndex, events) {
        this.transaction = transaction;
        this.branch = branch;
        this.changeFamily = changeFamily;
        this.storedSchema = storedSchema;
        this.forest = forest;
        this.context = context;
        this.nodeKeyManager = nodeKeyManager;
        this.nodeKeyIndex = nodeKeyIndex;
        this.events = events;
        this.nodeKey = {
            generate: () => this.nodeKeyManager.generateLocalNodeKey(),
            stabilize: (key) => this.nodeKeyManager.stabilizeNodeKey(key),
            localize: (key) => this.nodeKeyManager.localizeNodeKey(key),
            map: this.nodeKeyIndex,
        };
        branch.on("change", ({ change }) => {
            if (change !== undefined) {
                const delta = this.changeFamily.intoDelta(change);
                (0, core_1.applyDelta)(delta, this.forest.anchors);
                (0, core_1.applyDelta)(delta, this.forest);
                this.nodeKeyIndex.scanKeys(this.context);
                this.events.emit("afterBatch");
            }
        });
        branch.on("revertible", (type) => {
            this.events.emit("revertible", type);
        });
    }
    get rootEvents() {
        return this.forest.anchors;
    }
    get editor() {
        return this.branch.editor;
    }
    undo() {
        this.branch.undo();
    }
    redo() {
        this.branch.redo();
    }
    schematize(config) {
        schematizeView(this, config, this.storedSchema);
        return this;
    }
    editableTree2(viewSchema) {
        const context = (0, feature_libraries_1.getTreeContext)(viewSchema, this.forest, this.branch.editor, this.nodeKeyManager, this.nodeKeyIndex.fieldKey);
        return context.root;
    }
    locate(anchor) {
        return this.forest.anchors.locate(anchor);
    }
    fork() {
        const anchors = new core_1.AnchorSet();
        // TODO: ensure editing this clone of the schema does the right thing.
        const storedSchema = new core_1.InMemoryStoredSchemaRepository(this.storedSchema);
        const forest = this.forest.clone(storedSchema, anchors);
        const repairDataStoreProvider = new feature_libraries_1.ForestRepairDataStoreProvider(forest, storedSchema, (change) => this.changeFamily.intoDelta(change));
        const branch = this.branch.fork(repairDataStoreProvider);
        const context = (0, feature_libraries_1.getEditableTreeContext)(forest, storedSchema, branch.editor, this.nodeKeyManager, this.nodeKeyIndex.fieldKey);
        const transaction = new Transaction(branch, this.changeFamily, forest);
        return new SharedTreeView(transaction, branch, this.changeFamily, storedSchema, forest, context, this.nodeKeyManager, this.nodeKeyIndex.clone(context), (0, events_1.createEmitter)());
    }
    rebase(view) {
        view.branch.rebaseOnto(this.branch);
    }
    rebaseOnto(view) {
        view.rebase(this);
    }
    merge(view, disposeView = true) {
        (0, core_utils_1.assert)(!this.transaction.inProgress() || disposeView, 0x710 /* A view that is merged into an in-progress transaction must be disposed */);
        while (view.transaction.inProgress()) {
            view.transaction.commit();
        }
        this.branch.merge(view.branch);
        if (disposeView) {
            view.dispose();
        }
    }
    get root() {
        return this.context.unwrappedRoot;
    }
    setContent(data) {
        this.context.setContent(data);
    }
    /**
     * Dispose this view, freezing its state and allowing the SharedTree to release resources required by it.
     * Attempts to further mutate or dispose this view will error.
     */
    dispose() {
        this.branch.dispose();
    }
}
exports.SharedTreeView = SharedTreeView;
/**
 * @param view - view to edit.
 * @param config - config to apply.
 * @param storedSchema - provided separate from view since editing schema of view doesn't send ops properly.
 */
// TODO: once schematize is removed from ISharedTreeView, this should be moved/integrated into SharedTree.
function schematizeView(view, config, storedSchema) {
    // TODO:
    // When this becomes a more proper out of schema adapter, editing should be made lazy.
    // This will improve support for readonly documents, cross version collaboration and attribution.
    // Check for empty.
    // TODO: Better detection of empty case
    if (view.forest.isEmpty && (0, core_1.schemaDataIsEmpty)(storedSchema)) {
        view.transaction.start();
        (0, schematizedTree_1.initializeContent)(storedSchema, config.schema, () => view.setContent(config.initialTree));
        view.transaction.commit();
    }
    (0, schematizedTree_1.schematize)(view.events, storedSchema, config);
}
exports.schematizeView = schematizeView;
/**
 * Run a synchronous transaction on the given shared tree view.
 * This is a convenience helper around the {@link SharedTreeFork#transaction} APIs.
 * @param view - the view on which to run the transaction
 * @param transaction - the transaction function. This will be executed immediately. It is passed `view` as an argument for convenience.
 * If this function returns an `Abort` result then the transaction will be aborted. Otherwise, it will be committed.
 * @returns whether or not the transaction was committed or aborted
 * @alpha
 */
function runSynchronous(view, transaction) {
    view.transaction.start();
    const result = transaction(view);
    return result === util_1.TransactionResult.Abort
        ? view.transaction.abort()
        : view.transaction.commit();
}
exports.runSynchronous = runSynchronous;
//# sourceMappingURL=sharedTreeView.js.map