import * as plugins from './plugins.js';
import { Collection } from './classes.collection.js';
import { SmartdataDb } from './classes.db.js';
import { SmartDataDbDoc, svDb, unI } from './classes.doc.js';

/**
 * EasyStore allows the storage of easy objects. It also allows easy sharing of the object between different instances
 */
export class EasyStore<T> {
  // instance
  public smartdataDbRef: SmartdataDb;
  public nameId: string;

  private easyStoreClass = (() => {
    @Collection(() => this.smartdataDbRef)
    class SmartdataEasyStore extends SmartDataDbDoc<SmartdataEasyStore, SmartdataEasyStore> {
      @unI()
      public nameId!: string;

      @svDb()
      public ephemeral!: {
        activated: boolean;
        timeout: number;
      };

      @svDb()
      lastEdit!: number;

      @svDb()
      public data!: Partial<T>;
    }
    return SmartdataEasyStore;
  })();

  constructor(nameIdArg: string, smartdataDbRefArg: SmartdataDb) {
    this.smartdataDbRef = smartdataDbRefArg;
    this.nameId = nameIdArg;
  }

  private easyStorePromise?: Promise<InstanceType<typeof this.easyStoreClass>>;
  private async getEasyStore(): Promise<InstanceType<typeof this.easyStoreClass>> {
    if (this.easyStorePromise) {
      return this.easyStorePromise;
    }

    // first run from here
    const deferred = plugins.smartpromise.defer<InstanceType<typeof this.easyStoreClass>>();
    this.easyStorePromise = deferred.promise;

    try {
      let easyStore = await this.easyStoreClass.getInstance({
        nameId: this.nameId,
      });

      if (!easyStore) {
        easyStore = new this.easyStoreClass();
        easyStore.nameId = this.nameId;
        easyStore.data = {};
        await easyStore.save();
      }
      deferred.resolve(easyStore);
    } catch (error) {
      // settle the deferred and clear the cache so concurrent callers get the
      // error instead of awaiting forever, and later callers can retry
      this.easyStorePromise = undefined;
      deferred.reject(error);
    }
    return deferred.promise;
  }

  /**
   * reads all keyValue pairs at once and returns them
   */
  public async readAll() {
    const easyStore = await this.getEasyStore();
    return easyStore.data;
  }

  /**
   * reads a keyValueFile from disk
   */
  public async readKey<TKey extends keyof T>(keyArg: TKey): Promise<Partial<T>[TKey]> {
    const easyStore = await this.getEasyStore();
    return easyStore.data[keyArg];
  }

  /**
   * writes a specific key to the keyValueStore
   */
  public async writeKey<TKey extends keyof T>(keyArg: TKey, valueArg: T[TKey]) {
    const easyStore = await this.getEasyStore();
    easyStore.data[keyArg] = valueArg;
    await easyStore.save();
  }

  public async deleteKey(keyArg: keyof T) {
    const easyStore = await this.getEasyStore();
    delete easyStore.data[keyArg];
    await easyStore.save();
  }

  /**
   * merges all keyValue pairs from the object argument into the store.
   * Existing keys that are not present in `keyValueObject` are preserved.
   * To overwrite the entire store and drop missing keys, use `replace()`.
   */
  public async writeAll(keyValueObject: Partial<T>) {
    const easyStore = await this.getEasyStore();
    easyStore.data = { ...easyStore.data, ...keyValueObject };
    await easyStore.save();
  }

  /**
   * atomically replaces the entire store with the given object.
   * Unlike `writeAll` (which merges), `replace` clears any keys not
   * present in `keyValueObject`. Useful when you need to drop a key.
   */
  public async replace(keyValueObject: Partial<T>) {
    const easyStore = await this.getEasyStore();
    easyStore.data = { ...keyValueObject };
    await easyStore.save();
  }

  /**
   * wipes a key value store from disk
   */
  public async wipe() {
    const easyStore = await this.getEasyStore();
    easyStore.data = {};
    await easyStore.save();
  }

  public async cleanUpEphemeral() {
    // Clean up ephemeral data periodically while connected
    while (this.smartdataDbRef.status === 'connected') {
      await plugins.smartdelay.delayFor(60000); // Check every minute
      // TODO: Implement actual cleanup logic for ephemeral data
      // For now, this prevents the infinite CPU loop
    }
  }
}
