"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.runSynchronous = exports.SharedTreeFork = exports.SharedTreeFactory = exports.SharedTree = exports.identifierKeySymbol = exports.identifierKey = void 0;
const common_utils_1 = require("@fluidframework/common-utils");
const core_1 = require("../core");
const shared_tree_core_1 = require("../shared-tree-core");
const feature_libraries_1 = require("../feature-libraries");
const events_1 = require("../events");
const util_1 = require("../util");
const schematizedTree_1 = require("./schematizedTree");
/**
 * The key for the special identifier field, which allows nodes to be given identifiers that can be used
 * to find the nodes via the identifier index
 * @alpha
 */
exports.identifierKey = (0, util_1.brand)("identifier");
/**
 * The global field key symbol that corresponds to {@link identifierKey}
 * @alpha
 */
exports.identifierKeySymbol = (0, core_1.symbolFromKey)(exports.identifierKey);
/**
 * Shared tree, configured with a good set of indexes and field kinds which will maintain compatibility over time.
 * TODO: node identifier index.
 *
 * TODO: detail compatibility requirements.
 */
class SharedTree extends shared_tree_core_1.SharedTreeCore {
    constructor(id, runtime, attributes, telemetryContextPrefix) {
        const anchors = new core_1.AnchorSet();
        const schema = new core_1.InMemoryStoredSchemaRepository(feature_libraries_1.defaultSchemaPolicy);
        const forest = (0, feature_libraries_1.buildForest)(schema, anchors);
        const schemaSummarizer = new feature_libraries_1.SchemaSummarizer(runtime, schema);
        const forestSummarizer = new feature_libraries_1.ForestSummarizer(runtime, forest);
        super([schemaSummarizer, forestSummarizer], feature_libraries_1.defaultChangeFamily, anchors, new feature_libraries_1.ForestRepairDataStoreProvider(forest, schema), id, runtime, attributes, telemetryContextPrefix);
        this.events = (0, events_1.createEmitter)();
        this.forest = forest;
        this.storedSchema = new feature_libraries_1.SchemaEditor(schema, (op) => this.submitLocalMessage(op));
        this.transaction = {
            start: () => this.startTransaction((0, feature_libraries_1.repairDataStoreFromForest)(this.forest)),
            commit: () => this.commitTransaction(),
            abort: () => this.abortTransaction(),
            inProgress: () => this.isTransacting(),
        };
        this.context = (0, feature_libraries_1.getEditableTreeContext)(forest, this.editor);
        this.identifiedNodes = new feature_libraries_1.IdentifierIndex(exports.identifierKey);
        this.changeEvents.on("newLocalState", (changeDelta) => {
            this.forest.applyDelta(changeDelta);
            this.finishBatch();
        });
    }
    get rootEvents() {
        return this.forest.anchors;
    }
    schematize(config) {
        return (0, schematizedTree_1.schematizeView)(this, config);
    }
    locate(anchor) {
        return this.forest.anchors.locate(anchor);
    }
    get root() {
        return this.context.unwrappedRoot;
    }
    set root(data) {
        this.context.unwrappedRoot = data;
    }
    get undoRedoManager() {
        return this.localBranchUndoRedoManager;
    }
    fork() {
        const anchors = new core_1.AnchorSet();
        const schema = this.storedSchema.inner.clone();
        const forest = this.forest.clone(schema, anchors);
        const branch = this.createBranch(new feature_libraries_1.ForestRepairDataStoreProvider(forest, schema), anchors);
        const context = (0, feature_libraries_1.getEditableTreeContext)(forest, branch.editor);
        return new SharedTreeFork(branch, feature_libraries_1.defaultChangeFamily, schema, forest, context, this.identifiedNodes.clone(context));
    }
    merge(view) {
        this.mergeBranch(getForkBranch(view));
    }
    getLocalBranchHead() {
        return super.getLocalBranchHead();
    }
    /**
     * TODO: Shared tree needs a pattern for handling non-changeset operations.
     * Whatever pattern is adopted should probably also handle multiple versions of changeset operations.
     * A single top level enum listing all ops (including their different versions),
     * with at least fine grained enough detail to direct them to the correct subsystem would be a good approach.
     * The current use-case (with an op applying to a specific index) is a temporary hack,
     * and its not clear how it would fit into such a system if implemented in shared-tree-core:
     * maybe op dispatch is part of the shared-tree level?
     */
    processCore(message, local, localOpMetadata) {
        if (!this.storedSchema.tryHandleOp(message)) {
            super.processCore(message, local, localOpMetadata);
        }
    }
    reSubmitCore(content, localOpMetadata) {
        if (!this.storedSchema.tryResubmitOp(content)) {
            super.reSubmitCore(content, localOpMetadata);
        }
    }
    async loadCore(services) {
        await super.loadCore(services);
        this.finishBatch();
    }
    /** Finish a batch (see {@link ViewEvents}) */
    finishBatch() {
        this.identifiedNodes.scanIdentifiers(this.context);
        this.events.emit("afterBatch");
    }
}
exports.SharedTree = SharedTree;
/**
 * A channel factory that creates {@link ISharedTree}s.
 * @alpha
 */
class SharedTreeFactory {
    constructor() {
        this.type = "SharedTree";
        this.attributes = {
            type: this.type,
            snapshotFormatVersion: "0.0.0",
            packageVersion: "0.0.0",
        };
    }
    async load(runtime, id, services, channelAttributes) {
        const tree = new SharedTree(id, runtime, channelAttributes, "SharedTree");
        await tree.load(services);
        return tree;
    }
    create(runtime, id) {
        const tree = new SharedTree(id, runtime, this.attributes, "SharedTree");
        tree.initializeLocal();
        return tree;
    }
}
exports.SharedTreeFactory = SharedTreeFactory;
class SharedTreeFork {
    constructor(branch, changeFamily, storedSchema, forest, context, identifiedNodes) {
        this.branch = branch;
        this.changeFamily = changeFamily;
        this.storedSchema = storedSchema;
        this.forest = forest;
        this.context = context;
        this.identifiedNodes = identifiedNodes;
        this.events = (0, events_1.createEmitter)();
        this.transaction = {
            start: () => this.branch.startTransaction((0, feature_libraries_1.repairDataStoreFromForest)(this.forest)),
            commit: () => this.branch.commitTransaction(),
            abort: () => this.branch.abortTransaction(),
            inProgress: () => this.branch.isTransacting(),
        };
        branch.on("change", ({ change }) => {
            if (change !== undefined) {
                const delta = this.changeFamily.intoDelta(change);
                this.forest.applyDelta(delta);
                this.identifiedNodes.scanIdentifiers(this.context);
                this.events.emit("afterBatch");
            }
        });
    }
    get rootEvents() {
        return this.forest.anchors;
    }
    get editor() {
        return this.branch.editor;
    }
    undo() {
        this.branch.undo();
    }
    redo() {
        this.branch.redo();
    }
    schematize(config) {
        return (0, schematizedTree_1.schematizeView)(this, config);
    }
    locate(anchor) {
        return this.forest.anchors.locate(anchor);
    }
    fork() {
        const anchors = new core_1.AnchorSet();
        const storedSchema = this.storedSchema.clone();
        const forest = this.forest.clone(storedSchema, anchors);
        const repairDataStoreProvider = new feature_libraries_1.ForestRepairDataStoreProvider(forest, storedSchema);
        const branch = this.branch.fork(repairDataStoreProvider, anchors);
        const context = (0, feature_libraries_1.getEditableTreeContext)(forest, branch.editor);
        return new SharedTreeFork(branch, this.changeFamily, storedSchema, forest, context, this.identifiedNodes.clone(context));
    }
    rebaseOnto(view) {
        this.branch.rebaseOnto(getHeadCommit(view), getUndoRedoManager(view));
    }
    merge(view) {
        this.branch.merge(getForkBranch(view));
    }
    get root() {
        return this.context.unwrappedRoot;
    }
    set root(data) {
        this.context.unwrappedRoot = data;
    }
    dispose() {
        this.branch.dispose();
    }
}
exports.SharedTreeFork = SharedTreeFork;
/**
 * 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;
// #region Extraction functions
// The following three functions assume the underlying classes/implementations of `ISharedTreeView` and `ISharedTreeFork`.
// While `instanceof` checks are in general bad practice or code smell, these are justifiable because:
// 1. `SharedTree` and `SharedTreeFork` are private and meant to be the only implementations of `ISharedTreeView` and `ISharedTreeFork`.
// 2. The `ISharedTreeView` and `ISharedTreeFork` interfaces are not meant to specify input contracts, but exist solely to reduce the API provided by the underlying classes.
//    It is never expected that a user would create their own object or class which satisfies `ISharedTreeView` or `ISharedTreeFork`.
function getHeadCommit(view) {
    if (view instanceof SharedTree) {
        return view.getLocalBranchHead();
    }
    else if (view instanceof SharedTreeFork) {
        return view.branch.getHead();
    }
    (0, util_1.fail)("Unsupported ISharedTreeView implementation");
}
function getForkBranch(fork) {
    (0, common_utils_1.assert)(fork instanceof SharedTreeFork, 0x5ca /* Unsupported ISharedTreeFork implementation */);
    return fork.branch;
}
function getUndoRedoManager(view) {
    if (view instanceof SharedTree) {
        return view.undoRedoManager;
    }
    else if (view instanceof SharedTreeFork) {
        return view.branch.undoRedoManager;
    }
    (0, util_1.fail)("Unsupported ISharedTreeView implementation");
}
// #endregion Extraction functions
//# sourceMappingURL=sharedTree.js.map