/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { AttachState } from "@fluidframework/container-definitions";
import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
import { asLegacyAlpha, } from "@fluidframework/runtime-definitions/internal";
import { TelemetryDataTag, UsageError, } from "@fluidframework/telemetry-utils/internal";
import { ContainerMessageType } from "./messageTypes.js";
/**
 * Type guard that returns true if the given alias message is actually an instance of
 * a class which implements {@link IDataStoreAliasMessage}
 * @param maybeDataStoreAliasMessage - message object to be validated
 * @returns True if the {@link IDataStoreAliasMessage} is fully implemented, false otherwise
 */
export const isDataStoreAliasMessage = (maybeDataStoreAliasMessage) => {
    return (typeof maybeDataStoreAliasMessage?.internalId ===
        "string" &&
        typeof maybeDataStoreAliasMessage?.alias === "string");
};
export const channelToDataStore = (fluidDataStoreChannel, internalId, channelCollection, logger) => new DataStore(fluidDataStoreChannel, internalId, channelCollection, logger);
var AliasState;
(function (AliasState) {
    AliasState["Aliased"] = "Aliased";
    AliasState["Aliasing"] = "Aliasing";
    AliasState["None"] = "None";
})(AliasState || (AliasState = {}));
class DataStore {
    /**
     * {@inheritDoc @fluidframework/runtime-definitions#IDataStore.trySetAlias}
     */
    async trySetAlias(alias) {
        if (alias.includes("/")) {
            throw new UsageError(`The alias cannot contain slashes: '${alias}'`);
        }
        if (asLegacyAlpha(this.parentContext.containerRuntime).inStagingMode === true) {
            throw new UsageError("Cannot set aliases while in staging mode");
        }
        switch (this.aliasState) {
            // If we're already aliasing, check if it's for the same value and return
            // the stored promise, otherwise return 'AlreadyAliased'
            case AliasState.Aliasing: {
                assert(this.aliasResult !== undefined, 0x316 /* There should be a cached promise of in-progress aliasing */);
                await this.aliasResult;
                return this.alias === alias ? "Success" : "AlreadyAliased";
            }
            // If this datastore is already aliased, return true only if this
            // is a repeated call for the same alias
            case AliasState.Aliased: {
                return this.alias === alias ? "Success" : "AlreadyAliased";
            }
            case AliasState.None: {
                const existingAlias = this.pendingAliases.get(alias);
                if (existingAlias !== undefined) {
                    // There is already another datastore which will be aliased
                    // to the same name
                    return "Conflict";
                }
                // There is no current or past alias operation for this datastore,
                // or for this alias, so it is safe to continue execution
                break;
            }
            default: {
                unreachableCase(this.aliasState);
            }
        }
        this.aliasState = AliasState.Aliasing;
        this.aliasResult = this.trySetAliasInternal(alias);
        this.pendingAliases.set(alias, this.aliasResult);
        return this.aliasResult;
    }
    async trySetAliasInternal(alias) {
        const message = {
            internalId: this.internalId,
            alias,
        };
        this.fluidDataStoreChannel.makeVisibleAndAttachGraph();
        if (this.parentContext.attachState === AttachState.Detached) {
            const localResult = this.channelCollection.processAliasMessageCore(this.internalId, alias);
            // Explicitly lock-out future attempts of aliasing,
            // regardless of result
            this.aliasState = AliasState.Aliased;
            return localResult ? "Success" : "Conflict";
        }
        const aliased = await this.ackBasedPromise((resolve) => {
            this.parentContext.submitMessage({ type: ContainerMessageType.Alias, contents: message }, resolve);
        })
            .catch((error) => {
            this.logger.sendErrorEvent({
                eventName: "AliasingException",
                alias: {
                    value: alias,
                    tag: TelemetryDataTag.UserData,
                },
                internalId: {
                    value: this.internalId,
                    tag: TelemetryDataTag.CodeArtifact,
                },
            }, error);
            return false;
        })
            .finally(() => {
            this.pendingAliases.delete(alias);
        });
        if (!aliased) {
            this.aliasState = AliasState.None;
            this.aliasResult = undefined;
            return "Conflict";
        }
        this.alias = alias;
        this.aliasState = AliasState.Aliased;
        return "Success";
    }
    /**
     * {@inheritDoc @fluidframework/runtime-definitions#IDataStore.entryPoint}
     */
    get entryPoint() {
        return this.fluidDataStoreChannel.entryPoint;
    }
    constructor(fluidDataStoreChannel, internalId, channelCollection, logger, parentContext = channelCollection.parentContext) {
        this.fluidDataStoreChannel = fluidDataStoreChannel;
        this.internalId = internalId;
        this.channelCollection = channelCollection;
        this.logger = logger;
        this.parentContext = parentContext;
        this.aliasState = AliasState.None;
        this.pendingAliases = channelCollection.pendingAliases;
    }
    async ackBasedPromise(executor) {
        let rejectBecauseDispose;
        return new Promise((resolve, reject) => {
            rejectBecauseDispose = () => reject(new Error("ContainerRuntime disposed while this ack-based Promise was pending"));
            if (this.parentContext.containerRuntime.disposed) {
                rejectBecauseDispose();
                return;
            }
            this.parentContext.containerRuntime.on("dispose", rejectBecauseDispose);
            executor(resolve, reject);
        }).finally(() => {
            this.parentContext.containerRuntime.off("dispose", rejectBecauseDispose);
        });
    }
}
//# sourceMappingURL=dataStore.js.map