/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsISupports.idl"
#include "nsIVariant.idl"

interface nsIKeyValueImporter;
interface nsIKeyValueDatabaseImportOptions;
interface nsIKeyValueDatabaseCallback;
interface nsIKeyValueEnumeratorCallback;
interface nsIKeyValuePairCallback;
interface nsIKeyValueVariantCallback;
interface nsIKeyValueVoidCallback;
interface nsIKeyValuePair;

/**
 * The nsIKeyValue* interfaces provide a simple, asynchronous API to a key/value
 * storage engine.  Basic put/get/has/delete operations are supported, as is
 * enumeration of key/value pairs and the use of multiple named databases within
 * a single storage directory.  Operations have ACID semantics.
 *
 * This API does not (yet) support transactions, so it will not be appropriate
 * for all use cases.  Extension of this API to support transactions is tracked
 * by bug 1499238.
 *
 * The kvstore.sys.mjs module wraps this API in a more idiomatic, Promise-based
 * JS API that supports async/await.  In most cases, you're better off using
 * that API from JS rather than using this one directly.  Bug 1512319 tracks
 * native support for Promise in Rust-implemented XPCOM methods.
 */

/**
 * The key/value service.  Enables retrieval of handles to key/value databases.
 */
[scriptable, builtinclass, rust_sync, uuid(46c893dd-4c14-4de0-b33d-a1be18c6d062)]
interface nsIKeyValueService : nsISupports {
    cenum RecoveryStrategy: 8 {
        ERROR,
        DISCARD,
        RENAME,
    };

    /**
     * Get a handle to an existing database or a newly-created one
     * at the specified path and with the given name.
     *
     * The service supports multiple named databases at the same path
     * (i.e. within the same storage directory), so you can call this method
     * multiple times with the same path and different names to retrieve
     * multiple databases stored in the same location on disk.
     */
    void getOrCreate(
        in nsIKeyValueDatabaseCallback callback,
        in AString path,
        in AUTF8String name);

    void getOrCreateWithOptions(
        in nsIKeyValueDatabaseCallback callback,
        in AString path,
        in AUTF8String name,
        [optional] in nsIKeyValueService_RecoveryStrategy recoveryStrategy);

    /**
     * Create an importer that copies one or more databases of the given type
     * into the destination storage directory at the specified path.
     */
    nsIKeyValueImporter createImporter(
        in AUTF8String type,
        in AString path);
};

/** Configures a directory containing one or more databases to import. */
[scriptable, builtinclass, rust_sync, uuid(ed0980f5-64e8-4ca6-af97-b49a766fe016)]
interface nsIKeyValueImportSourceSpec : nsISupports {
    /** The path to this directory. */
    readonly attribute AString path;

    /**
     * Configure the importer to import all key-value pairs
     * from a named database in this directory.
     *
     * `addDatabase()` may be called multiple times, if you want to
     * import multiple databases. Duplicate `addDatabase()` calls are
     * also allowed, and will import all key-value pairs from that
     * database again. The importer will import named databases
     * in order of `addDatabase()` calls.
     *
     * Throws if `addAllDatabases()` was already called on this spec.
     */
    nsIKeyValueDatabaseImportOptions addDatabase(in AUTF8String name);

    /**
     * Configure the importer to import all key-value pairs
     * from all databases in this directory.
     *
     * Throws if `addDatabase()` or `addAllDatabases()`
     * was already called on this spec.
     */
    nsIKeyValueDatabaseImportOptions addAllDatabases();
};

/**
 * Imports key-value pairs from databases of a different type.
 *
 * An importer can be configured to import all or some databases, from
 * one or more storage directories.
 *
 * To import databases of a different type that are already in the destination
 * directory, call `importer.addDatabase()` or `importer.addAllDatabases()`.
 * To import databases of a different type from another storage directory, call
 * `importer.addPath()` to get a spec for that directory, then call
 * `spec.addDatabase()` or `spec.addAllDatabases()`.
 *
 * After you've configured the importer, call `importer.import()` to
 * import the databases.
 *
 * Imports are atomic for all databases in a storage directory, but not
 * across multiple directories. That is:
 *
 *   - Either all configured databases in a storage directory will be
 *     imported successfully, or none will.
 *   - If the importer successfully imports databases from some directories,
 *     then encounters an error in another directory, the successfully
 *     imported databases will remain.
 */
[scriptable, builtinclass, rust_sync, uuid(470d71c0-d601-4867-8c09-810af7ffa67e)]
interface nsIKeyValueImporter : nsIKeyValueImportSourceSpec {
    /**
     * The action to take when a key already exists and has a different value.
     */
    cenum ConflictPolicy : 8 {
        /**
         * Fail the import with an error, and roll back any key-value pairs
         * that have been imported so far.
         */
        ERROR_ON_CONFLICT,

        /** Keep the existing value for the key, and continue the import. */
        IGNORE_ON_CONFLICT,

        /**
         * Overwrite the existing value for the key with the value from
         * the other database, and continue the import.
         */
        REPLACE_ON_CONFLICT,
    };

    /** The action to take after a successful import. */
    cenum CleanupPolicy : 8 {
        /** Keep all key-value pairs in the other database. */
        KEEP_AFTER_IMPORT,

        /** Delete all key-value pairs from the other database. */
        DELETE_AFTER_IMPORT,
    };

    /** The type of databases that this importer will import. */
    readonly attribute AUTF8String type;

    /**
     * Configure the importer to import databases from another
     * storage directory.
     *
     * `addPath()` may be called multiple times, if you want to
     * import databases from multiple directories. Duplicate `addPath()`
     * calls are also allowed; you can use this to import databases from
     * different directories in a specific order. The importer will import
     * databases in order of `addPath()` calls.
     */
    nsIKeyValueImportSourceSpec addPath(in AString path);

    /** Import all key-value pairs from all configured databases. */
    void import(in nsIKeyValueVoidCallback callback);
};

/**
 * Configures the import behavior for one or more databases.
 *
 * All `nsIKeyValueDatabaseImportOptions` methods mutate and return the same
 * options object, to allow for method chaining.
 */
[scriptable, builtinclass, rust_sync, uuid(8e983362-fb16-4811-b1fe-8f9921932fd5)]
interface nsIKeyValueDatabaseImportOptions : nsISupports {
    /**
     * Set the action to take when a key in the source database already exists
     * and has a different value in the destination database.
     *
     * Defaults to `ConflictPolicy::ERROR_ON_CONFLICT` if not set.
     */
    nsIKeyValueDatabaseImportOptions setConflictPolicy(
        in nsIKeyValueImporter_ConflictPolicy conflictPolicy);

    /**
     * Set the cleanup behavior for successfully imported pairs in the
     * source database.
     *
     * Defaults to `CleanupPolicy::KEEP_AFTER_IMPORT` if not set.
     */
    nsIKeyValueDatabaseImportOptions setCleanupPolicy(
        in nsIKeyValueImporter_CleanupPolicy cleanupPolicy);
};

/**
 * A key/value database.
 *
 * All methods are asynchronous and take a callback as their first argument.
 * The types of the callbacks vary, but they can all be implemented in JS
 * via an object literal with the relevant methods.
 */
[scriptable, builtinclass, rust_sync, uuid(c449398e-174c-425b-8195-da6aa0ccd9a5)]
interface nsIKeyValueDatabase : nsISupports {
    /**
     * Determine whether or not the database is empty.
     */
    void isEmpty(in nsIKeyValueVariantCallback callback);

    /**
     * Count the number of key/value pairs in the database.
     */
    void count(in nsIKeyValueVariantCallback callback);

    /**
     * Calculate the size, in bytes, of all keys and values in the database.
     */
    void size(in nsIKeyValueVariantCallback callback);

    /**
     * Write the specified key/value pair to the database.
     */
    void put(
        in nsIKeyValueVoidCallback callback,
        in AUTF8String key,
        in nsIVariant value);

    /**
     * Write multiple key/value pairs to the database.
     *
     * It supports two types of write:
     *   * Put a key/value pair into the database. It takes a nsIKeyValuePair
     *     where its key and value follow the same types as the put() method.
     *   * Delete a key/value pair from database. It takes a nsIkeyValuePair
     *     where its value property must be null or undefined.
     *
     * This features the "all-or-nothing" semantics, i.e. if any error occurs
     * during the call, it will rollback the previous writes and terminate the
     * call. In addition, writeMany should be more efficient than calling "put"
     * or "delete" for every single key/value pair since it does all the writes
     * in a single transaction.
     *
     * Note:
     *   * If there are multiple values with the same key in the specified
     *     pairs, only the last value will be stored in the database.
     *   * Deleting a key that is not in the database will be silently ignored.
     *   * If the same key gets put and deleted for multiple times, the final
     *     state of that key is subject to the ordering of the put(s) and delete(s).
     */
    void writeMany(
        in nsIKeyValueVoidCallback callback,
        in Array<nsIKeyValuePair> pairs);

    /**
     * Retrieve the value of the specified key from the database.
     *
     * If the key/value pair doesn't exist in the database, and you specify
     * a default value, then the default value will be returned.  Otherwise,
     * the callback's resolve() method will be called with a variant
     * of type VTYPE_EMPTY, which translates to the JS `null` value.
     */
    void get(
        in nsIKeyValueVariantCallback callback,
        in AUTF8String key,
        [optional] in nsIVariant defaultValue);

    /**
     * Determine whether or not the key exists in the database.
     */
    void has(
        in nsIKeyValueVariantCallback callback,
        in AUTF8String key);

    /**
     * Remove the key/value pair with the given key from the database.
     *
     * If the given key doesn't exist in the database, this operation doesn't
     * fail; or rather, it fails silently, calling the resolve() method
     * of its callback rather than reject().  If you want to know whether
     * or not a key exists when deleting it, call the has() method first.
     */
    void delete(
        in nsIKeyValueVoidCallback callback,
        in AUTF8String key);

    /**
     * Remove all key/value pairs in a lexicographical range from the database,
     * including the "from" key and excluding the "to" key.
     *
     * If either key is omitted, the range extends to the first or last key
     * in the database. If both keys are omitted, `deleteRange()` behaves
     * the same as `clear()`. If the "from" key is greater than the "to" key,
     * `deleteRange()` does nothing.
     */
    void deleteRange(
        in nsIKeyValueVoidCallback callback,
        [optional] in AUTF8String fromKey,
        [optional] in AUTF8String toKey);

    /**
     * Clear all the key/value pairs from the database.
     */
    void clear(in nsIKeyValueVoidCallback callback);

    /**
     * Enumerate key/value pairs, starting with the first key equal to
     * or greater than the "from" key (inclusive) and ending with the last key
     * less than the "to" key (exclusive) sorted lexicographically.
     *
     * If either key is omitted, the range extends to the first and/or last key
     * in the database. If the "from" key is greater than the "to" key,
     * no pairs are enumerated.
     */
    void enumerate(
        in nsIKeyValueEnumeratorCallback callback,
        [optional] in AUTF8String fromKey,
        [optional] in AUTF8String toKey);

    /**
     * Close the database.
     *
     * Subsequent attempts to read from or write to the database will fail.
     * If the database is already closed, closing it again succeeds
     * without effect.
     *
     * This operation always succeeds if the implementation supports
     * closing a database (that is, it's not possible for a database to
     * fail to close), and always fails if it doesn't.
     */
    void close(in nsIKeyValueVoidCallback callback);
};

/**
 * A key/value pair.  Returned by nsIKeyValueEnumerator.getNext().
 */
[scriptable, uuid(bc37b06a-23b5-4b32-8281-4b8479601c7e)]
interface nsIKeyValuePair : nsISupports {
    readonly attribute AUTF8String key;
    readonly attribute nsIVariant value;
};

/**
 * An enumerator of key/value pairs.  Although its methods are similar
 * to those of nsISimpleEnumerator, this interface's getNext() method returns
 * an nsIKeyValuePair rather than an nsISupports, so consumers don't need
 * to QI it to that interface; but this interface doesn't implement the JS
 * iteration protocol (because the Rust-XPCOM bindings don't yet support it),
 * which is another reason why you should use the kvstore.sys.mjs module from JS
 * instead of accessing this API directly.
 */
[scriptable, builtinclass, rust_sync, uuid(b9ba7116-b7ff-4717-9a28-a08e6879b199)]
interface nsIKeyValueEnumerator : nsISupports {
    boolean hasMoreElements();
    nsIKeyValuePair getNext();
};

/**
 * A callback for the nsIKeyValueService.getOrCreate() method.
 *
 * The result is an nsIKeyValueDatabase.
 */
[scriptable, uuid(2becc1f8-2d80-4b63-92a8-24ee8f79ee45)]
interface nsIKeyValueDatabaseCallback : nsISupports {
    void resolve(in nsIKeyValueDatabase database);
    void reject(in AUTF8String message);
};

/**
 * A callback for the nsIKeyValueDatabase.enumerate() method.
 *
 * The result is an nsIKeyValueEnumerator.
 */
[scriptable, uuid(b7ea2183-880b-4424-ab24-5aa1555b775d)]
interface nsIKeyValueEnumeratorCallback : nsISupports {
    void resolve(in nsIKeyValueEnumerator enumerator);
    void reject(in AUTF8String message);
};

/**
 * A callback for the nsIKeyValueEnumerator.getNext() method.
 *
 * The result is the next key/value pair, expressed as separate key and value
 * parameters.
 */
[scriptable, uuid(50f65485-ec1e-4307-812b-b8a15e1f382e)]
interface nsIKeyValuePairCallback : nsISupports {
    void resolve(in nsIKeyValuePair pair);
    void reject(in AUTF8String message);
};

/**
 * A callback for the nsIKeyValueDatabase.has() and .get() methods.
 *
 * The result is an nsIVariant, which is always a boolean for the has() method
 * and can be any supported data type for the get() method.
 */
[scriptable, uuid(174ebfa1-74ea-42a7-aa90-85bbaf1da4bf)]
interface nsIKeyValueVariantCallback : nsISupports {
    void resolve(in nsIVariant result);
    void reject(in AUTF8String message);
};

/**
 * A callback for the nsIKeyValueDatabase.put() and .delete() methods.
 *
 * There is no result, but the resolve() method is still called when those
 * async operations complete, to notify consumers of completion.
 */
[scriptable, uuid(0c17497a-ccf8-451a-838d-9dfa7f846379)]
interface nsIKeyValueVoidCallback : nsISupports {
    void resolve();
    void reject(in AUTF8String message);
};
