/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module SQLiteDb
 */
import { CloudSqlite } from "./CloudSqlite";
import { VersionedSqliteDb } from "./SQLiteDb";
import { BriefcaseDb, IModelDb } from "./IModelDb";
import { DbResult, OpenMode } from "@itwin/core-bentley";
import { IModelError } from "@itwin/core-common";
import { IModelNative } from "./internal/NativePlatform";
import { _nativeDb } from "./internal/Symbols";
/** @internal */
export var SchemaSync;
(function (SchemaSync) {
    const lockParams = { retryDelayMs: 1000, nRetries: 30 };
    /** A CloudSqlite database for synchronizing schema changes across briefcases.  */
    class SchemaSyncDb extends VersionedSqliteDb {
        myVersion = "4.0.0";
        createDDL() { }
    }
    SchemaSync.SchemaSyncDb = SchemaSyncDb;
    const syncProperty = { namespace: "itwinjs", name: "SchemaSync" };
    const defaultDbName = "SchemaSyncDb";
    const testSyncCachePropKey = "test.schema_sync.cache_name";
    // for tests only
    SchemaSync.setTestCache = (iModel, cacheName) => {
        if (cacheName)
            iModel[_nativeDb].saveLocalValue(testSyncCachePropKey, cacheName);
        else
            iModel[_nativeDb].deleteLocalValue(testSyncCachePropKey);
    };
    const getCloudAccess = async (arg) => {
        let nativeDb;
        const argIsIModelDb = arg instanceof IModelDb;
        if (argIsIModelDb) {
            nativeDb = arg[_nativeDb];
        }
        else {
            nativeDb = new IModelNative.platform.DgnDb();
            nativeDb.openIModel(arg.fileName, OpenMode.Readonly);
        }
        const propsString = nativeDb.queryFileProperty(syncProperty, true);
        if (!propsString)
            throw new Error("iModel does not have a SchemaSyncDb");
        try {
            const props = JSON.parse(propsString);
            const accessToken = await CloudSqlite.requestToken(props);
            const access = new CloudAccess({ ...props, accessToken });
            Object.assign(access.lockParams, lockParams);
            const testSyncCache = nativeDb.queryLocalValue(testSyncCachePropKey);
            if (testSyncCache)
                access.setCache(CloudSqlite.CloudCaches.getCache({ cacheName: testSyncCache }));
            return access;
        }
        finally {
            if (!argIsIModelDb) {
                nativeDb.closeFile();
            }
        }
    };
    SchemaSync.withLockedAccess = async (iModel, args, operation) => {
        const access = await getCloudAccess(iModel);
        try {
            await access.withLockedDb(args, async () => operation(access));
        }
        finally {
            access.close();
        }
    };
    SchemaSync.withReadonlyAccess = async (iModel, operation) => {
        const access = await getCloudAccess(iModel);
        access.synchronizeWithCloud();
        access.openForRead();
        try {
            await operation(access);
        }
        finally {
            access.close();
        }
    };
    SchemaSync.isEnabled = (iModel) => {
        return iModel[_nativeDb].schemaSyncEnabled();
    };
    /** Synchronize local briefcase schemas with cloud container */
    SchemaSync.pull = async (iModel) => {
        if (iModel[_nativeDb].schemaSyncEnabled() && !iModel.isReadonly) {
            await SchemaSync.withReadonlyAccess(iModel, async (syncAccess) => {
                const schemaSyncDbUri = syncAccess.getUri();
                iModel.clearCaches();
                iModel[_nativeDb].schemaSyncPull(schemaSyncDbUri);
                iModel.saveChanges("schema synchronized with cloud container");
            });
        }
    };
    SchemaSync.initializeForIModel = async (arg) => {
        const props = { baseUri: arg.containerProps.baseUri, containerId: arg.containerProps.containerId, storageType: arg.containerProps.storageType }; // sanitize to only known properties
        const iModel = arg.iModel;
        const briefcase = iModel instanceof BriefcaseDb ? iModel : undefined;
        await iModel.acquireSchemaLock();
        if (briefcase) {
            if (briefcase.txns.hasLocalChanges) {
                throw new IModelError(DbResult.BE_SQLITE_ERROR, "Enabling SchemaSync for iModel failed. There are unsaved or un-pushed local changes.");
            }
            await briefcase.pullChanges();
        }
        try {
            iModel.saveFileProperty(syncProperty, JSON.stringify(props));
            await SchemaSync.withLockedAccess(arg.iModel, { operationName: "initialize schemaSync", openMode: OpenMode.Readonly }, async (syncAccess) => {
                iModel[_nativeDb].schemaSyncInit(syncAccess.getUri(), props.containerId, arg.overrideContainer ?? false);
                iModel.saveChanges(`Enable SchemaSync  (container id: ${props.containerId})`);
            });
        }
        catch (err) {
            throw err;
        }
        finally {
            iModel.abandonChanges();
        }
        if (briefcase) {
            if (arg.overrideContainer)
                await briefcase.pushChanges({ description: `Overriding SchemaSync for iModel with container-id: ${props.containerId}` });
            else
                await briefcase.pushChanges({ description: `Enable SchemaSync for iModel with container-id: ${props.containerId}` });
        }
    };
    /** Provides access to a cloud-based `SchemaSyncDb` to hold ECSchemas.  */
    class CloudAccess extends CloudSqlite.DbAccess {
        constructor(props) {
            super({ dbType: SchemaSyncDb, props, dbName: defaultDbName });
        }
        getUri() {
            return `${this.getCloudDb()[_nativeDb].getFilePath()}?vfs=${this.container.cache?.name}&writable=${this.container.isWriteable ? 1 : 0}`;
        }
        /**
       * Initialize a cloud container for use as a SchemaSync. The container must first be created via its storage supplier api (e.g. Azure, or AWS).
       * A valid sasToken that grants write access must be supplied. This function creates and uploads an empty ChannelDb into the container.
       * @note this deletes any existing content in the container.
       */
        static async initializeDb(props) {
            return super._initializeDb({ props, dbType: SchemaSyncDb, dbName: defaultDbName });
        }
    }
    SchemaSync.CloudAccess = CloudAccess;
})(SchemaSync || (SchemaSync = {}));
//# sourceMappingURL=SchemaSync.js.map