/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert, Deferred, Lazy } from "@fluidframework/core-utils/internal";
import { createChildLogger, } from "@fluidframework/telemetry-utils/internal";
/** @internal */
export class DataStoreContexts {
    constructor(baseLogger) {
        this.notBoundContexts = new Set();
        /** Attached and loaded context proxies */
        this._contexts = new Map();
        /**
         * List of pending context waiting either to be bound or to arrive from another client.
         * This covers the case where a local context has been created but not yet bound,
         * or the case where a client knows a store will exist and is waiting on its creation,
         * so that a caller may await the deferred's promise until such a time as the context is fully ready.
         * This is a superset of _contexts, since contexts remain here once the Deferred resolves.
         */
        this.deferredContexts = new Map();
        this.disposeOnce = new Lazy(() => {
            // close/stop all store contexts
            for (const [fluidDataStoreId, contextD] of this.deferredContexts) {
                contextD.promise
                    .then((context) => {
                    context.dispose();
                })
                    .catch((contextError) => {
                    this._logger.sendErrorEvent({
                        eventName: "FluidDataStoreContextDisposeError",
                        fluidDataStoreId,
                    }, contextError);
                });
            }
        });
        this.dispose = () => this.disposeOnce.value;
        this._recentlyDeletedContexts = new Map();
        this._logger = createChildLogger({ logger: baseLogger });
    }
    [Symbol.iterator]() {
        return this._contexts.entries();
    }
    get size() {
        return this._contexts.size;
    }
    get disposed() {
        return this.disposeOnce.evaluated;
    }
    notBoundLength() {
        return this.notBoundContexts.size;
    }
    isNotBound(id) {
        return this.notBoundContexts.has(id);
    }
    has(id) {
        return this._contexts.has(id);
    }
    get(id) {
        return this._contexts.get(id);
    }
    delete(id) {
        this.deferredContexts.delete(id);
        this.notBoundContexts.delete(id);
        // Stash the context here in case it's requested in this session, we can log some details about it
        const context = this._contexts.get(id);
        this._recentlyDeletedContexts.set(id, context);
        return this._contexts.delete(id);
    }
    getRecentlyDeletedContext(id) {
        return this._recentlyDeletedContexts.get(id);
    }
    /**
     * Return the unbound local context with the given id,
     * or undefined if it's not found or not unbound.
     */
    getUnbound(id) {
        const context = this._contexts.get(id);
        if (context === undefined || !this.notBoundContexts.has(id)) {
            return undefined;
        }
        return context;
    }
    /**
     * Add the given context, marking it as to-be-bound
     */
    addUnbound(context) {
        const id = context.id;
        assert(!this._contexts.has(id), 0x158 /* "Creating store with existing ID" */);
        this._contexts.set(id, context);
        this.notBoundContexts.add(id);
        this.ensureDeferred(id);
    }
    /**
     * Get the context with the given id, once it exists locally and is attached.
     * e.g. If created locally, it must be bound, or if created remotely then it's fine as soon as it's sync'd in.
     * @param id - The id of the context to get
     * @param wait - If false, return undefined if the context isn't present and ready now. Otherwise, wait for it.
     */
    async getBoundOrRemoted(id, wait) {
        const deferredContext = this.ensureDeferred(id);
        if (!wait && !deferredContext.isCompleted) {
            return undefined;
        }
        return deferredContext.promise;
    }
    ensureDeferred(id) {
        const deferred = this.deferredContexts.get(id);
        if (deferred) {
            return deferred;
        }
        const newDeferred = new Deferred();
        this.deferredContexts.set(id, newDeferred);
        return newDeferred;
    }
    /**
     * Update this context as bound
     */
    bind(id) {
        const removed = this.notBoundContexts.delete(id);
        assert(removed, 0x159 /* "The given id was not found in notBoundContexts to delete" */);
        this.resolveDeferred(id);
    }
    /**
     * Triggers the deferred to resolve, indicating the context is not local-only
     * @param id - The id of the context to resolve to
     */
    resolveDeferred(id) {
        const context = this._contexts.get(id);
        assert(!!context, 0x15a /* "Cannot find context to resolve to" */);
        assert(!this.notBoundContexts.has(id), 0x15b /* "Expected this id to already be removed from notBoundContexts" */);
        const deferred = this.deferredContexts.get(id);
        assert(!!deferred, 0x15c /* "Cannot find deferred to resolve" */);
        deferred.resolve(context);
    }
    /**
     * Add the given context, marking it as not local-only.
     * This could be because it's a local context that's been bound, or because it's a remote context.
     * @param context - The context to add
     */
    addBoundOrRemoted(context) {
        const id = context.id;
        assert(!this._contexts.has(id), 0x15d /* "Creating store with existing ID" */);
        this._contexts.set(id, context);
        // Resolve the deferred immediately since this context is not unbound
        this.ensureDeferred(id);
        this.resolveDeferred(id);
    }
}
//# sourceMappingURL=dataStoreContexts.js.map