/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
    var useValue = arguments.length > 2;
    for (var i = 0; i < initializers.length; i++) {
        value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
    }
    return useValue ? value : void 0;
};
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
    function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
    var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
    var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
    var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
    var _, done = false;
    for (var i = decorators.length - 1; i >= 0; i--) {
        var context = {};
        for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
        for (var p in contextIn.access) context.access[p] = contextIn.access[p];
        context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
        var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
        if (kind === "accessor") {
            if (result === void 0) continue;
            if (result === null || typeof result !== "object") throw new TypeError("Object expected");
            if (_ = accept(result.get)) descriptor.get = _;
            if (_ = accept(result.set)) descriptor.set = _;
            if (_ = accept(result.init)) initializers.unshift(_);
        }
        else if (_ = accept(result)) {
            if (kind === "field") initializers.unshift(_);
            else descriptor[key] = _;
        }
    }
    if (target) Object.defineProperty(target, contextIn.name, descriptor);
    done = true;
};
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
    if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
    return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
};
import { assert } from "@fluidframework/core-utils/internal";
import { SummaryTreeBuilder } from "@fluidframework/runtime-utils/internal";
import { SharedObject, } from "@fluidframework/shared-object-base/internal";
import { RevisionTagCodec, } from "../core/index.js";
import { brand, Breakable, throwIfBroken, breakingClass, } from "../util/index.js";
import { getChangeReplaceType } from "./branch.js";
import { EditManager, minimumPossibleSequenceNumber } from "./editManager.js";
import { makeEditManagerCodec } from "./editManagerCodecs.js";
import { EditManagerSummarizer } from "./editManagerSummarizer.js";
import { makeMessageCodec } from "./messageCodecs.js";
import { NoOpChangeEnricher } from "./changeEnricher.js";
import { DefaultResubmitMachine } from "./defaultResubmitMachine.js";
import { BranchCommitEnricher } from "./branchCommitEnricher.js";
import { createChildLogger } from "@fluidframework/telemetry-utils/internal";
// TODO: Organize this to be adjacent to persisted types.
const summarizablesTreeKey = "indexes";
/**
 * Generic shared tree, which needs to be configured with indexes, field kinds and other configuration.
 */
let SharedTreeCore = (() => {
    let _classDecorators = [breakingClass];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = SharedObject;
    let _instanceExtraInitializers = [];
    let _summarizeCore_decorators;
    var SharedTreeCore = _classThis = class extends _classSuper {
        /**
         * Used to edit the state of the tree. Edits will be immediately applied locally to the tree.
         * If there is no transaction currently ongoing, then the edits will be submitted to Fluid immediately as well.
         */
        get editor() {
            return this.getLocalBranch().editor;
        }
        /**
         * Gets the revision at the head of the trunk.
         */
        get trunkHeadRevision() {
            return this.editManager.getTrunkHead().revision;
        }
        /**
         * @param summarizables - Summarizers for all indexes used by this tree
         * @param changeFamily - The change family
         * @param editManager - The edit manager
         * @param id - The id of the shared object
         * @param runtime - The IFluidDataStoreRuntime which contains the shared object
         * @param attributes - Attributes of the shared object
         * @param telemetryContextPrefix - The property prefix for telemetry pertaining to this object. See {@link ITelemetryContext}
         */
        constructor(summarizables, changeFamily, options, formatOptions, 
        // Base class arguments
        id, runtime, attributes, telemetryContextPrefix, schema, schemaPolicy, resubmitMachine, enricher) {
            super(id, runtime, attributes, telemetryContextPrefix);
            this.breaker = (__runInitializers(this, _instanceExtraInitializers), new Breakable("Shared Tree"));
            /**
             * The sequence number that this instance is at.
             * This number is artificial in that it is made up by this instance as opposed to being provided by the runtime.
             * Is `undefined` after (and only after) this instance is attached.
             */
            this.detachedRevision = minimumPossibleSequenceNumber;
            this.schemaAndPolicy = {
                schema,
                policy: schemaPolicy,
            };
            const rebaseLogger = createChildLogger({
                logger: this.logger,
                namespace: "Rebase",
            });
            assert(runtime.idCompressor !== undefined, 0x886 /* IdCompressor must be enabled to use SharedTree */);
            this.idCompressor = runtime.idCompressor;
            this.mintRevisionTag = () => this.idCompressor.generateCompressedId();
            /**
             * A random ID that uniquely identifies this client in the collab session.
             * This is sent alongside every op to identify which client the op originated from.
             * This is used rather than the Fluid client ID because the Fluid client ID is not stable across reconnections.
             */
            const localSessionId = runtime.idCompressor.localSessionId;
            this.editManager = new EditManager(changeFamily, localSessionId, this.mintRevisionTag, rebaseLogger);
            this.editManager.localBranch.on("transactionStarted", () => {
                this.commitEnricher.startNewTransaction();
            });
            this.editManager.localBranch.on("transactionAborted", () => {
                this.commitEnricher.abortCurrentTransaction();
            });
            this.editManager.localBranch.on("transactionCommitted", () => {
                this.commitEnricher.commitCurrentTransaction();
            });
            this.editManager.localBranch.on("beforeChange", (change) => {
                // Ensure that any previously prepared commits that have not been sent are purged.
                this.commitEnricher.purgePreparedCommits();
                if (this.detachedRevision !== undefined) {
                    // Edits submitted before the first attach do not need enrichment because they will not be applied by peers.
                }
                else if (change.type === "append") {
                    if (this.getLocalBranch().isTransacting()) {
                        for (const newCommit of change.newCommits) {
                            this.commitEnricher.ingestTransactionCommit(newCommit);
                        }
                    }
                    else {
                        for (const newCommit of change.newCommits) {
                            this.commitEnricher.prepareCommit(newCommit, false);
                        }
                    }
                }
                else if (change.type === "replace" &&
                    getChangeReplaceType(change) === "transactionCommit" &&
                    !this.getLocalBranch().isTransacting()) {
                    assert(change.newCommits.length === 1, 0x983 /* Unexpected number of commits when committing transaction */);
                    this.commitEnricher.prepareCommit(change.newCommits[0], true);
                }
            });
            this.editManager.localBranch.on("afterChange", (change) => {
                if (this.getLocalBranch().isTransacting()) {
                    // We do not submit ops for changes that are part of a transaction.
                    return;
                }
                if (change.type === "append" ||
                    (change.type === "replace" && getChangeReplaceType(change) === "transactionCommit")) {
                    if (this.detachedRevision !== undefined) {
                        for (const newCommit of change.newCommits) {
                            this.submitCommit(newCommit, this.schemaAndPolicy);
                        }
                    }
                    else {
                        for (const newCommit of change.newCommits) {
                            const prepared = this.commitEnricher.getPreparedCommit(newCommit);
                            this.submitCommit(prepared, this.schemaAndPolicy);
                        }
                    }
                }
            });
            const revisionTagCodec = new RevisionTagCodec(runtime.idCompressor);
            const editManagerCodec = makeEditManagerCodec(this.editManager.changeFamily.codecs, revisionTagCodec, options, formatOptions.editManager);
            this.summarizables = [
                new EditManagerSummarizer(this.editManager, editManagerCodec, this.idCompressor, this.schemaAndPolicy),
                ...summarizables,
            ];
            assert(new Set(this.summarizables.map((e) => e.key)).size === this.summarizables.length, 0x350 /* Index summary element keys must be unique */);
            this.messageCodec = makeMessageCodec(changeFamily.codecs, new RevisionTagCodec(runtime.idCompressor), options, formatOptions.message);
            const changeEnricher = enricher ?? new NoOpChangeEnricher();
            this.resubmitMachine =
                resubmitMachine ??
                    new DefaultResubmitMachine(changeFamily.rebaser.invert.bind(changeFamily.rebaser), changeEnricher);
            this.commitEnricher = new BranchCommitEnricher(changeFamily.rebaser, changeEnricher);
        }
        // TODO: SharedObject's merging of the two summary methods into summarizeCore is not what we want here:
        // We might want to not subclass it, or override/reimplement most of its functionality.
        summarizeCore(serializer, telemetryContext, incrementalSummaryContext) {
            const builder = new SummaryTreeBuilder();
            const summarizableBuilder = new SummaryTreeBuilder();
            // Merge the summaries of all summarizables together under a single ISummaryTree
            for (const s of this.summarizables) {
                summarizableBuilder.addWithStats(s.key, s.getAttachSummary((contents) => serializer.stringify(contents, this.handle), undefined, undefined, telemetryContext, incrementalSummaryContext));
            }
            builder.addWithStats(summarizablesTreeKey, summarizableBuilder.getSummaryTree());
            return builder.getSummaryTree();
        }
        async loadCore(services) {
            const loadSummaries = this.summarizables.map(async (summaryElement) => summaryElement.load(scopeStorageService(services, summarizablesTreeKey, summaryElement.key), (contents) => this.serializer.parse(contents)));
            await Promise.all(loadSummaries);
        }
        /**
         * Submits an op to the Fluid runtime containing the given commit
         * @param commit - the commit to submit
         * @returns the submitted commit. This is undefined if the underlying `SharedObject` is not attached,
         * and may differ from `commit` due to enrichments like detached tree refreshers.
         */
        submitCommit(commit, schemaAndPolicy, isResubmit = false) {
            assert(
            // Edits should not be submitted until all transactions finish
            !this.getLocalBranch().isTransacting() || isResubmit, 0x68b /* Unexpected edit submitted during transaction */);
            assert(this.isAttached() === (this.detachedRevision === undefined), 0x95a /* Detached revision should only be set when not attached */);
            // Edits submitted before the first attach are treated as sequenced because they will be included
            // in the attach summary that is uploaded to the service.
            // Until this attach workflow happens, this instance essentially behaves as a centralized data structure.
            if (this.detachedRevision !== undefined) {
                const newRevision = brand(this.detachedRevision + 1);
                this.detachedRevision = newRevision;
                this.editManager.addSequencedChange({ ...commit, sessionId: this.editManager.localSessionId }, newRevision, this.detachedRevision);
                this.editManager.advanceMinimumSequenceNumber(newRevision);
                return undefined;
            }
            const message = this.messageCodec.encode({
                commit,
                sessionId: this.editManager.localSessionId,
            }, {
                idCompressor: this.idCompressor,
                schema: schemaAndPolicy,
            });
            this.submitLocalMessage(message, {
                // Clone the schema to ensure that during resubmit the schema has not been mutated by later changes
                schema: schemaAndPolicy.schema.clone(),
                policy: schemaAndPolicy.policy,
            });
            this.resubmitMachine.onCommitSubmitted(commit);
        }
        processCore(message, local, localOpMetadata) {
            // Empty context object is passed in, as our decode function is schema-agnostic.
            const { commit, sessionId } = this.messageCodec.decode(message.contents, {
                idCompressor: this.idCompressor,
            });
            this.editManager.addSequencedChange({ ...commit, sessionId }, brand(message.sequenceNumber), brand(message.referenceSequenceNumber));
            this.resubmitMachine.onSequencedCommitApplied(local);
            this.editManager.advanceMinimumSequenceNumber(brand(message.minimumSequenceNumber));
        }
        /**
         * @returns the head commit of the root local branch
         */
        getLocalBranch() {
            return this.editManager.localBranch;
        }
        onDisconnect() { }
        didAttach() {
            if (this.detachedRevision !== undefined) {
                this.detachedRevision = undefined;
            }
        }
        reSubmitCore(content, localOpMetadata) {
            // Empty context object is passed in, as our decode function is schema-agnostic.
            const { commit: { revision }, } = this.messageCodec.decode(this.serializer.decode(content), {
                idCompressor: this.idCompressor,
            });
            const [commit] = this.editManager.findLocalCommit(revision);
            // If a resubmit phase is not already in progress, then this must be the first commit of a new resubmit phase.
            if (this.resubmitMachine.isInResubmitPhase === false) {
                const toResubmit = this.editManager.getLocalCommits();
                assert(commit === toResubmit[0], 0x95d /* Resubmit phase should start with the oldest local commit */);
                this.resubmitMachine.prepareForResubmit(toResubmit);
            }
            assert(isClonableSchemaPolicy(localOpMetadata), 0x95e /* Local metadata must contain schema and policy. */);
            assert(this.resubmitMachine.isInResubmitPhase !== false, 0x984 /* Invalid resubmit outside of resubmit phase */);
            const enrichedCommit = this.resubmitMachine.peekNextCommit();
            this.submitCommit(enrichedCommit, localOpMetadata, true);
        }
        applyStashedOp(content) {
            assert(!this.getLocalBranch().isTransacting(), 0x674 /* Unexpected transaction is open while applying stashed ops */);
            // Empty context object is passed in, as our decode function is schema-agnostic.
            const { commit: { revision, change }, } = this.messageCodec.decode(content, { idCompressor: this.idCompressor });
            this.editManager.localBranch.apply(change, revision);
        }
        getGCData(fullGC) {
            const gcNodes = super.getGCData(fullGC).gcNodes;
            for (const s of this.summarizables) {
                for (const [id, routes] of Object.entries(s.getGCData(fullGC).gcNodes)) {
                    gcNodes[id] ?? (gcNodes[id] = []);
                    for (const route of routes) {
                        gcNodes[id].push(route);
                    }
                }
            }
            return {
                gcNodes,
            };
        }
    };
    __setFunctionName(_classThis, "SharedTreeCore");
    (() => {
        const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
        _summarizeCore_decorators = [throwIfBroken];
        __esDecorate(_classThis, null, _summarizeCore_decorators, { kind: "method", name: "summarizeCore", static: false, private: false, access: { has: obj => "summarizeCore" in obj, get: obj => obj.summarizeCore }, metadata: _metadata }, null, _instanceExtraInitializers);
        __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
        SharedTreeCore = _classThis = _classDescriptor.value;
        if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        __runInitializers(_classThis, _classExtraInitializers);
    })();
    return SharedTreeCore = _classThis;
})();
export { SharedTreeCore };
function isClonableSchemaPolicy(maybeSchemaPolicy) {
    const schemaAndPolicy = maybeSchemaPolicy;
    return schemaAndPolicy.schema !== undefined && schemaAndPolicy.policy !== undefined;
}
/**
 * Compose an {@link IChannelStorageService} which prefixes all paths before forwarding them to the original service
 */
function scopeStorageService(service, ...pathElements) {
    const scope = `${pathElements.join("/")}/`;
    return {
        async readBlob(path) {
            return service.readBlob(`${scope}${path}`);
        },
        async contains(path) {
            return service.contains(`${scope}${path}`);
        },
        async list(path) {
            return service.list(`${scope}${path}`);
        },
    };
}
//# sourceMappingURL=sharedTreeCore.js.map