// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { Database, Databases } from "./client/Database/index.js";
import { Offer, Offers } from "./client/Offer/index.js";
import { ClientContext } from "./ClientContext.js";
import { parseConnectionString } from "./common/index.js";
import { Constants } from "./common/constants.js";
import { getUserAgent } from "./common/platform.js";
import { determineDiagnosticLevel, getDiagnosticLevelFromEnvironment, } from "./diagnostics/index.js";
import { DiagnosticNodeType } from "./diagnostics/DiagnosticNodeInternal.js";
import { defaultConnectionPolicy } from "./documents/index.js";
import { EncryptionManager } from "./encryption/EncryptionManager.js";
import { GlobalEndpointManager } from "./globalEndpointManager.js";
import { ResourceResponse } from "./request/index.js";
import { checkURL } from "./utils/checkURL.js";
import { getEmptyCosmosDiagnostics, withDiagnostics } from "./utils/diagnostics.js";
import { GlobalPartitionEndpointManager } from "./globalPartitionEndpointManager.js";
/**
 * Provides a client-side logical representation of the Azure Cosmos DB database account.
 * This client is used to configure and execute requests in the Azure Cosmos DB database service.
 * @example Instantiate a client and create a new database
 * ```ts snippet:CosmosClientCreate
 * import { CosmosClient } from "@azure/cosmos";
 *
 * const endpoint = "https://your-account.documents.azure.com";
 * const key = "<database account masterkey>";
 * const client = new CosmosClient({ endpoint, key });
 * ```
 * @example Instantiate a client with custom Connection Policy
 * ```ts snippet:CosmosClientWithConnectionPolicy
 * import { CosmosClient } from "@azure/cosmos";
 *
 * const endpoint = "https://your-account.documents.azure.com";
 * const key = "<database account masterkey>";
 * const client = new CosmosClient({
 *   endpoint,
 *   key,
 *   connectionPolicy: {
 *     requestTimeout: 10000,
 *   },
 * });
 * ```
 * @example Instantiate a client with AAD authentication and custom scope
 * ```ts snippet:CosmosClientWithAADScope
 * import { DefaultAzureCredential } from "@azure/identity";
 * import { CosmosClient } from "@azure/cosmos";
 *
 * const endpoint = "https://your-account.documents.azure.com";
 * const aadCredentials = new DefaultAzureCredential();
 * const client = new CosmosClient({
 *   endpoint,
 *   aadCredentials,
 *   aadScope: "https://cosmos.azure.com/.default", // Optional custom scope
 * });
 * ```
 */
export class CosmosClient {
    /**
     * Used for creating new databases, or querying/reading all databases.
     *
     * Use `.database(id)` to read, replace, or delete a specific, existing database by id.
     *
     * @example Create a new database
     * ```ts snippet:CosmosClientDatabases
     * import { CosmosClient } from "@azure/cosmos";
     *
     * const endpoint = "https://your-account.documents.azure.com";
     * const key = "<database account masterkey>";
     * const client = new CosmosClient({ endpoint, key });
     * const { resource: databaseDefinition, database } = await client.databases.create({
     *   id: "<name here>",
     * });
     * ```
     */
    databases;
    /**
     * Used for querying & reading all offers.
     *
     * Use `.offer(id)` to read, or replace existing offers.
     */
    offers;
    clientContext;
    endpointRefresher;
    /**
     * @internal
     */
    encryptionManager;
    /** @internal */
    globalPartitionEndpointManager;
    constructor(optionsOrConnectionString) {
        if (typeof optionsOrConnectionString === "string") {
            optionsOrConnectionString = parseConnectionString(optionsOrConnectionString);
        }
        else if (optionsOrConnectionString.connectionString) {
            const { endpoint, key } = parseConnectionString(optionsOrConnectionString.connectionString);
            optionsOrConnectionString.endpoint = endpoint;
            optionsOrConnectionString.key = key;
        }
        const endpoint = checkURL(optionsOrConnectionString.endpoint);
        if (!endpoint) {
            throw new Error("Invalid endpoint specified");
        }
        if (optionsOrConnectionString.clientEncryptionOptions) {
            if (!optionsOrConnectionString.clientEncryptionOptions.keyEncryptionKeyResolver) {
                throw new Error("KeyEncryptionKeyResolver needs to be provided to enable client-side encryption.");
            }
            if (optionsOrConnectionString.clientEncryptionOptions.encryptionKeyTimeToLiveInSeconds &&
                optionsOrConnectionString.clientEncryptionOptions.encryptionKeyTimeToLiveInSeconds < 60) {
                throw new Error("EncryptionKeyTimeToLiveInSeconds needs to be >= 60 seconds.");
            }
            this.encryptionManager = new EncryptionManager(optionsOrConnectionString.clientEncryptionOptions.keyEncryptionKeyResolver, optionsOrConnectionString.clientEncryptionOptions.encryptionKeyTimeToLiveInSeconds);
        }
        const clientConfig = this.initializeClientConfigDiagnostic(optionsOrConnectionString);
        optionsOrConnectionString.connectionPolicy = Object.assign({}, defaultConnectionPolicy, optionsOrConnectionString.connectionPolicy);
        // If endpoint discovery is disabled, automatically disable partition level features
        if (!optionsOrConnectionString.connectionPolicy.enableEndpointDiscovery) {
            optionsOrConnectionString.connectionPolicy.enablePartitionLevelFailover = false;
            optionsOrConnectionString.connectionPolicy.enablePartitionLevelCircuitBreaker = false;
        }
        optionsOrConnectionString.defaultHeaders = optionsOrConnectionString.defaultHeaders || {};
        optionsOrConnectionString.defaultHeaders[Constants.HttpHeaders.CacheControl] = "no-cache";
        optionsOrConnectionString.defaultHeaders[Constants.HttpHeaders.Version] =
            Constants.CurrentVersion;
        if (optionsOrConnectionString.consistencyLevel !== undefined) {
            optionsOrConnectionString.defaultHeaders[Constants.HttpHeaders.ConsistencyLevel] =
                optionsOrConnectionString.consistencyLevel;
        }
        if (optionsOrConnectionString.throughputBucket !== undefined) {
            optionsOrConnectionString.defaultHeaders[Constants.HttpHeaders.ThroughputBucket] =
                optionsOrConnectionString.throughputBucket;
        }
        const userAgent = getUserAgent(optionsOrConnectionString);
        optionsOrConnectionString.defaultHeaders[Constants.HttpHeaders.UserAgent] = userAgent;
        optionsOrConnectionString.defaultHeaders[Constants.HttpHeaders.CustomUserAgent] = userAgent;
        const globalEndpointManager = new GlobalEndpointManager(optionsOrConnectionString, async (diagnosticNode, opts) => this.getDatabaseAccountInternal(diagnosticNode, opts));
        if (optionsOrConnectionString.connectionPolicy.enablePartitionLevelFailover ||
            optionsOrConnectionString.connectionPolicy.enablePartitionLevelCircuitBreaker) {
            this.globalPartitionEndpointManager = new GlobalPartitionEndpointManager(optionsOrConnectionString, globalEndpointManager);
            globalEndpointManager.onEnablePartitionLevelFailoverConfigChanged = (isEnabled) => {
                this.globalPartitionEndpointManager?.changeCircuitBreakerFailbackLoop(isEnabled);
            };
        }
        this.clientContext = new ClientContext(optionsOrConnectionString, globalEndpointManager, clientConfig, determineDiagnosticLevel(optionsOrConnectionString.diagnosticLevel, getDiagnosticLevelFromEnvironment()), this.globalPartitionEndpointManager);
        if (optionsOrConnectionString.connectionPolicy?.enableEndpointDiscovery &&
            optionsOrConnectionString.connectionPolicy?.enableBackgroundEndpointRefreshing) {
            this.backgroundRefreshEndpointList(globalEndpointManager, optionsOrConnectionString.connectionPolicy.endpointRefreshRateInMs ||
                defaultConnectionPolicy.endpointRefreshRateInMs);
        }
        this.databases = new Databases(this, this.clientContext, this.encryptionManager);
        this.offers = new Offers(this, this.clientContext);
    }
    initializeClientConfigDiagnostic(optionsOrConnectionString) {
        return {
            endpoint: optionsOrConnectionString.endpoint,
            resourceTokensConfigured: optionsOrConnectionString.resourceTokens !== undefined,
            tokenProviderConfigured: optionsOrConnectionString.tokenProvider !== undefined,
            aadCredentialsConfigured: optionsOrConnectionString.aadCredentials !== undefined,
            connectionPolicyConfigured: optionsOrConnectionString.connectionPolicy !== undefined,
            consistencyLevel: optionsOrConnectionString.consistencyLevel,
            defaultHeaders: optionsOrConnectionString.defaultHeaders,
            agentConfigured: optionsOrConnectionString.agent !== undefined,
            userAgentSuffix: optionsOrConnectionString.userAgentSuffix,
            diagnosticLevel: optionsOrConnectionString.diagnosticLevel,
            pluginsConfigured: optionsOrConnectionString.plugins !== undefined,
            sDKVersion: Constants.SDKVersion,
            aadScopeOverride: optionsOrConnectionString.aadScope !== undefined,
        };
    }
    /**
     * Get information about the current {@link DatabaseAccount} (including which regions are supported, etc.)
     */
    async getDatabaseAccount(options) {
        return withDiagnostics(async (diagnosticNode) => {
            return this.getDatabaseAccountInternal(diagnosticNode, options);
        }, this.clientContext);
    }
    /**
     * @hidden
     */
    async getDatabaseAccountInternal(diagnosticNode, options) {
        const response = await this.clientContext.getDatabaseAccount(diagnosticNode, options);
        return new ResourceResponse(response.result, response.headers, response.code, getEmptyCosmosDiagnostics(), response.substatus);
    }
    /**
     * Gets the currently used write endpoint url. Useful for troubleshooting purposes.
     *
     * The url may contain a region suffix (e.g. "-eastus") if we're using location specific endpoints.
     */
    async getWriteEndpoint() {
        return withDiagnostics(async (diagnosticNode) => {
            return this.clientContext.getWriteEndpoint(diagnosticNode);
        }, this.clientContext);
    }
    /**
     * Gets the currently used read endpoint. Useful for troubleshooting purposes.
     *
     * The url may contain a region suffix (e.g. "-eastus") if we're using location specific endpoints.
     */
    async getReadEndpoint() {
        return withDiagnostics(async (diagnosticNode) => {
            return this.clientContext.getReadEndpoint(diagnosticNode);
        }, this.clientContext);
    }
    /**
     * Gets the known write endpoints. Useful for troubleshooting purposes.
     *
     * The urls may contain a region suffix (e.g. "-eastus") if we're using location specific endpoints.
     */
    getWriteEndpoints() {
        return this.clientContext.getWriteEndpoints();
    }
    /**
     * Gets the currently used read endpoint. Useful for troubleshooting purposes.
     *
     * The url may contain a region suffix (e.g. "-eastus") if we're using location specific endpoints.
     */
    getReadEndpoints() {
        return this.clientContext.getReadEndpoints();
    }
    /**
     * Used for reading, updating, or deleting a existing database by id or accessing containers belonging to that database.
     *
     * This does not make a network call. Use `.read` to get info about the database after getting the {@link Database} object.
     *
     * @param id - The id of the database.
     * @example Create a new container off of an existing database
     * ```ts snippet:CosmosClientDatabaseCreateContainer
     * import { CosmosClient } from "@azure/cosmos";
     *
     * const endpoint = "https://your-account.documents.azure.com";
     * const key = "<database account masterkey>";
     * const client = new CosmosClient({ endpoint, key });
     * const container = client.database("<database id>").containers.create({
     *   id: "<name here>",
     * });
     * ```
     *
     * @example Delete an existing database
     * ```ts snippet:CosmosClientDatabaseDelete
     * import { CosmosClient } from "@azure/cosmos";
     *
     * const endpoint = "https://your-account.documents.azure.com";
     * const key = "<database account masterkey>";
     * const client = new CosmosClient({ endpoint, key });
     * await client.database("<id here>").delete();
     * ```
     */
    database(id) {
        return new Database(this, id, this.clientContext, this.encryptionManager);
    }
    /**
     * Used for reading, or updating a existing offer by id.
     * @param id - The id of the offer.
     */
    offer(id) {
        return new Offer(this, id, this.clientContext);
    }
    /**
     * Clears background endpoint refresher. Use client.dispose() when destroying the CosmosClient within another process.
     */
    dispose() {
        clearTimeout(this.endpointRefresher);
        if (this.clientContext.enableEncryption) {
            clearTimeout(this.encryptionManager.encryptionKeyStoreProvider.cacheRefresher);
            clearTimeout(this.encryptionManager.protectedDataEncryptionKeyCache.cacheRefresher);
        }
        if (this.globalPartitionEndpointManager) {
            this.globalPartitionEndpointManager.dispose();
        }
    }
    async backgroundRefreshEndpointList(globalEndpointManager, refreshRate) {
        this.endpointRefresher = setInterval(() => {
            try {
                return withDiagnostics(async (diagnosticNode) => {
                    return globalEndpointManager.refreshEndpointList(diagnosticNode);
                }, this.clientContext, DiagnosticNodeType.BACKGROUND_REFRESH_THREAD);
            }
            catch (e) {
                console.warn("Failed to refresh endpoints", e);
            }
        }, refreshRate);
        if (this.endpointRefresher.unref && typeof this.endpointRefresher.unref === "function") {
            this.endpointRefresher.unref();
        }
    }
    /**
     * Update the host framework. If provided host framework will be used to generate the defualt SDK user agent.
     * @param hostFramework - A custom string.
     * @internal
     */
    async updateHostFramework(hostFramework) {
        this.clientContext.refreshUserAgent(hostFramework);
    }
}
//# sourceMappingURL=CosmosClient.js.map