import * as idbKeyVal from "idb-keyval"

export class Storage<T> {
  cache: Map<string, any>

  private constructor(
    private store: idbKeyVal.UseStore,
    allKeys: IDBValidKey[],
    allValues: any[],
  ) {
    this.cache = new Map<string, any>(
      allKeys.map((key, i) => [key as string, allValues[i]]), //
    )
  }

  static async of<T>(dbName: string, storeName: string) {
    const store = idbKeyVal.createStore(dbName, storeName)
    const allKeys = await idbKeyVal.keys(store)
    return new Storage<T>(store, allKeys, await idbKeyVal.getMany(allKeys, store))
  }

  get(key: string): T | undefined {
    return this.cache.get(key)
  }

  has(key: string): boolean {
    return this.cache.has(key)
  }

  async update(key: string, updater: (value?: T) => T): Promise<void> {
    const value = this.get(key)
    const newValue = updater(value)
    await this.set(key, typeof newValue === "object" ? newValue : (value as T))
  }

  async updateAll(updater: (value?: T) => T): Promise<void> {
    const keys = this.keys()
    await Promise.all(keys.map(key => this.update(key, updater)))
  }

  async set(key: string, value: T): Promise<void> {
    this.cache.set(key, value)
    await idbKeyVal.set(key, value, this.store)
  }

  async delete(key: string): Promise<void> {
    this.cache.delete(key)
    await idbKeyVal.del(key, this.store)
  }

  keys(): string[] {
    return Array.from(this.cache.keys())
  }

  values(): T[] {
    return Array.from(this.cache.values())
  }

  entries(): [string, T][] {
    return Array.from(this.cache.entries())
  }

  async clear() {
    const keys = this.keys()
    await Promise.all(keys.map(key => this.delete(key)))
  }
}
