import 'dart:async';
import 'dart:html';
import 'dart:indexed_db';
/// Key-Value store abstraction over IndexedDB so that the sdk database can use
/// a single interface for all platforms. API is inspired by Hive.
class BoxCollection {
  final Database _db;
  final Set boxNames;
  final String _name;
  final IdbFactory _idbFactory;
  BoxCollection(this._db, this.boxNames, this._name, this._idbFactory);
  static Future open(
    String name,
    Set boxNames, {
    Object? sqfliteDatabase,
    Object? sqfliteFactory,
    IdbFactory? idbFactory,
  }) async {
    idbFactory ??= window.indexedDB!;
    final db = await idbFactory.open(name, version: 1,
        onUpgradeNeeded: (VersionChangeEvent event) {
      final db = event.target.result;
      for (final name in boxNames) {
        db.createObjectStore(name, autoIncrement: true);
      }
    });
    return BoxCollection(db, boxNames, name, idbFactory);
  }
  Box openBox(String name) {
    if (!boxNames.contains(name)) {
      throw ('Box with name $name is not in the known box names of this collection.');
    }
    return Box(name, this);
  }
  List Function(Transaction txn)>? _txnCache;
  Future transaction(
    Future Function() action, {
    List? boxNames,
    bool readOnly = false,
  }) async {
    boxNames ??= _db.objectStoreNames!.toList();
    _txnCache = [];
    await action();
    final cache = List Function(Transaction txn)>.from(_txnCache!);
    _txnCache = null;
    if (cache.isEmpty) return;
    final txn = _db.transaction(boxNames, readOnly ? 'readonly' : 'readwrite');
    for (final fun in cache) {
      // The IDB methods return a Future in Dart but must not be awaited in
      // order to have an actual transaction. They must only be performed and
      // then the transaction object must call `txn.completed;` which then
      // returns the actual future.
      // https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction
      unawaited(fun(txn));
    }
    await txn.completed;
    return;
  }
  Future clear() async {
    final txn = _db.transaction(boxNames, 'readwrite');
    for (final name in boxNames) {
      unawaited(txn.objectStore(name).clear());
    }
    await txn.completed;
  }
  Future close() async {
    assert(_txnCache == null, 'Database closed while in transaction!');
    return _db.close();
  }
  Future delete() => _idbFactory.deleteDatabase(_name);
}
class Box {
  final String name;
  final BoxCollection boxCollection;
  final Map _cache = {};
  /// _cachedKeys is only used to make sure that if you fetch all keys from a
  /// box, you do not need to have an expensive read operation twice. There is
  /// no other usage for this at the moment. So the cache is never partial.
  /// Once the keys are cached, they need to be updated when changed in put and
  /// delete* so that the cache does not become outdated.
  Set? _cachedKeys;
  bool get _keysCached => _cachedKeys != null;
  Box(this.name, this.boxCollection);
  Future> getAllKeys([Transaction? txn]) async {
    if (_keysCached) return _cachedKeys!.toList();
    txn ??= boxCollection._db.transaction(name, 'readonly');
    final store = txn.objectStore(name);
    final request = store.getAllKeys(null);
    await request.onSuccess.first;
    final keys = request.result.cast();
    _cachedKeys = keys.toSet();
    return keys;
  }
  Future