/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { CloudSqlite } from "./CloudSqlite";
import { VersionedSqliteDb } from "./SQLiteDb";
/** @beta */
export var PropertyStore;
(function (PropertyStore) {
    /**
     * A SQLite database for storing PropertyName/PropertyValue pairs.
     */
    class PropertyDb extends VersionedSqliteDb {
        myVersion = "3.0.0";
        createDDL() {
            this.createTable({ tableName: "properties", columns: "name TEXT NOT NULL PRIMARY KEY,type,value", addTimestamp: true });
        }
        /** get the value of a Property by name.
         * @returns the property's value if it exists, `undefined` otherwise.
         */
        getProperty(name) {
            return this.withPreparedSqliteStatement("SELECT type,value from properties WHERE name=?", (stmt) => {
                stmt.bindString(1, name);
                if (!stmt.nextRow())
                    return undefined;
                switch (stmt.getValueString(0)) {
                    case "string":
                        return stmt.getValueString(1);
                    case "boolean":
                        return stmt.getValueInteger(1) !== 0;
                    case "blob":
                        return stmt.getValueBlob(1);
                    case "number":
                        return stmt.getValueDouble(1);
                    case "object":
                        return JSON.parse(stmt.getValueString(1));
                }
                return undefined;
            });
        }
        getString(name, defaultValue) {
            const out = this.getProperty(name);
            return typeof out === "string" ? out : defaultValue;
        }
        getBoolean(name, defaultValue) {
            const out = this.getProperty(name);
            return typeof out === "boolean" ? out : defaultValue;
        }
        getNumber(name, defaultValue) {
            const out = this.getProperty(name);
            return typeof out === "number" ? out : defaultValue;
        }
        getBlob(name, defaultValue) {
            const out = this.getProperty(name);
            return out instanceof Uint8Array ? out : defaultValue;
        }
        getObject(name, defaultValue) {
            const out = this.getProperty(name);
            return typeof out === "object" ? out : defaultValue;
        }
        /** call an iteration function for each property, optionally applying a filter */
        forAllProperties(iter, filter) {
            let sql = "SELECT name FROM properties WHERE name IS NOT NULL";
            if (filter?.sqlExpression)
                sql += ` AND ${filter.sqlExpression} `;
            if (filter?.value)
                sql += ` AND name ${filter.valueCompare ?? "="} @val`;
            if (filter?.orderBy)
                sql += ` ORDER BY name ${filter.orderBy} `;
            this.withSqliteStatement(sql, (stmt) => {
                if (filter?.value)
                    stmt.bindString("@val", filter.value);
                while (stmt.nextRow()) {
                    if (iter(stmt.getValueString(0)) === "stop")
                        return;
                }
            });
        }
        /** Delete a single property from this PropertyDb. If the value does not exist, this method does nothing.
         * @note the database must be opened for write
         */
        async deleteProperty(propName) {
            this.withSqliteStatement("DELETE from properties WHERE name=?", (stmt) => {
                stmt.bindString(1, propName);
                stmt.stepForWrite();
            });
        }
        /** Delete an array of properties from this PropertyDb. Any value that does not exist is ignored.
         * @note the database must be opened for write
         */
        async deleteProperties(propNames) {
            propNames.forEach(async (name) => this.deleteProperty(name));
        }
        validateName(name) {
            if (typeof name !== "string" || name.trim() !== name || name.length > 2 * 1024 || name.length < 2)
                throw new Error(`illegal property name[${name}]`);
        }
        /** Save a single property in this PropertyDb. If the property already exists, its value is overwritten.
         * @note the database must be opened for write
         */
        async saveProperty(name, value) {
            this.validateName(name);
            this.withSqliteStatement("INSERT OR REPLACE INTO properties(name,type,value) VALUES (?,?,?)", (stmt) => {
                stmt.bindString(1, name);
                switch (typeof value) {
                    case "string":
                        stmt.bindString(2, "string");
                        stmt.bindString(3, value);
                        break;
                    case "boolean":
                        stmt.bindString(2, "boolean");
                        stmt.bindInteger(3, value ? 1 : 0);
                        break;
                    case "number":
                        stmt.bindString(2, "number");
                        stmt.bindDouble(3, value);
                        break;
                    case "object":
                        if (value instanceof Uint8Array) {
                            stmt.bindString(2, "blob");
                            stmt.bindBlob(3, value);
                        }
                        else {
                            stmt.bindString(2, "object");
                            stmt.bindString(3, JSON.stringify(value));
                        }
                        break;
                    default:
                        throw new Error("illegal property value type");
                }
                stmt.stepForWrite();
            });
        }
        /** Save an array of properties in this PropertyDb. If a property already exists, its value is overwritten.
         * @note the database must be opened for write
         */
        async saveProperties(props) {
            for (const prop of props)
                await this.saveProperty(prop.name, prop.value);
        }
    }
    PropertyStore.PropertyDb = PropertyDb;
    const defaultDbName = "PropertyDb";
    /**
     * Provides access to a cloud-based `PropertyDb` to hold a set of values of type `PropertyType`, each with a unique `PropertyName`.
     * `PropertyStore.PropertyDb`s that are stored in cloud containers require an access token that grants permission to read and/or write them.
     * All write operations will fail without an access token that grants write permission.
     *
     * The database is cached on a local drive so reads are fast and inexpensive, and may even be done offline after a prefetch.
     * However, that means that callers are responsible for synchronizing the local cache to ensure it includes changes
     * made by others, as appropriate (see [[synchronizeWithCloud]]).
     */
    class CloudAccess extends CloudSqlite.DbAccess {
        constructor(props) {
            super({ dbType: PropertyDb, props, dbName: defaultDbName });
        }
        /**
         * Initialize a cloud container for use as a PropertyStore. This method is called by [[createNewContainer]].
         * It is only necessary to convert an existing container to a PropertyStore container.
         * @note this deletes any existing content in the container.
         * @internal
         */
        static async initializeDb(args) {
            return super._initializeDb({ ...args, dbType: PropertyDb, dbName: defaultDbName });
        }
        /** Create and initialize a new BlobContainer to hold a PropertyStore
         * @note the current user must have administrator rights to create containers.
         */
        static async createNewContainer(args) {
            const props = await this.createBlobContainer({ scope: args.scope, metadata: { ...args.metadata, containerType: "property-store" } });
            await this.initializeDb({ props });
            return props;
        }
    }
    PropertyStore.CloudAccess = CloudAccess;
})(PropertyStore || (PropertyStore = {}));
//# sourceMappingURL=PropertyStore.js.map