import { openDB, IDBPDatabase, IDBPObjectStore } from 'idb/with-async-ittr';
import { ContextListOptions } from '@api-client/core/build/browser.js';
import { HttpClientPersistance } from "./HttpClientPersistance.js";
import { Cursor } from './Cursor.js';
import { IListState } from './State.js';
import { HttpClientDB, StoreName } from './types.js';
import { IdbUrlHistory } from './idb/IdbUrlHistory.js';
import { IdbWsHistory } from './idb/IdbWsHistory.js';
import { IdbProjectUi } from './idb/IdbProjectUi.js';

/**
 * The implementation of the API Client app database in IDB.
 */
export class HttpClientIdbDatabase implements HttpClientPersistance {
  /**
   * The default limit of items returned by the list operation.
   * @default 35
   */
  defaultLimit = 35;

  protected _db?: IDBPDatabase<HttpClientDB>;

  /**
   * The reference to the currently opened database.
   * This throws when the `initialize()` method wasn't called.
   */
  get db(): IDBPDatabase<HttpClientDB> {
    const db = this._db;
    if (!db) {
      throw new Error(`Attempted to access the database before opening a connection.`);
    }
    return db;
  }
  
  #urlHistory?: IdbUrlHistory;

  /**
   * File metadata store.
   */
  get urlHistory(): IdbUrlHistory {
    const ref = this.#urlHistory;
    if (!ref) {
      throw new Error(`Store not initialized.`);
    }
    return ref;
  }

  #wsHistory?: IdbWsHistory;

  /**
   * File metadata store.
   */
  get wsHistory(): IdbWsHistory {
    const ref = this.#wsHistory;
    if (!ref) {
      throw new Error(`Store not initialized.`);
    }
    return ref;
  }

  #projectUi?: IdbProjectUi;

  /**
   * File metadata store.
   */
  get projectUi(): IdbProjectUi {
    const ref = this.#projectUi;
    if (!ref) {
      throw new Error(`Store not initialized.`);
    }
    return ref;
  }

  async initialize(): Promise<void> {
    const db = await this.open();

    this.#urlHistory = new IdbUrlHistory(this, db);
    this.#wsHistory = new IdbWsHistory(this, db);
    this.#projectUi = new IdbProjectUi(this, db);
    
    await this.#urlHistory.warmup();
    await this.#wsHistory.warmup();
    await this.#projectUi.warmup();
  }

  async cleanup(): Promise<void> {
    await this.urlHistory.cleanup();
    await this.wsHistory.cleanup();
    await this.projectUi.cleanup();

    this.db.close();
    this._db = undefined;
  }

  cursor = new Cursor();

  async readListState(source: ContextListOptions = {}): Promise<IListState> {
    if (source.cursor) {
      return this.cursor.decodeCursor(source.cursor);
    }
    return { ...source } as IListState;
  }

  /**
   * Creates a database cursor when there are results.
   * This sets `skip` and `lastKey` parameters.
   * 
   * @param state The last list state object
   * @param results The read results.
   * @returns A cursor to be used with pagination.
   */
  async createCursorIfNeeded(state: IListState, results: unknown[]): Promise<string | undefined> {
    if (!results.length) {
      return undefined;
    }

    const copy = { ...state } as IListState;
    const { key } = (results[results.length - 1] as { key: string });
    copy.skip = 1;
    copy.lastKey = key;
    return this.cursor.encodeCursor(copy);
  }

  /**
   * Opens the ARC store.
   * @returns The reference to the database
   */
  async open(): Promise<IDBPDatabase<HttpClientDB>> {
    if (this._db) {
      return this._db;
    }
    const dbResult = await openDB<HttpClientDB>('HttpClient', 1, {
      upgrade: (db) => {
        const stores: StoreName[] = [
          'UrlHistory', 'WsHistory', 'ProjectUi', 
        ];
        const names = db.objectStoreNames;
        for (const name of stores) {
          const exists = names.contains(name);
          const fnName = `_${name}UpgradeHandler` as unknown as  '_UrlHistoryUpgradeHandler' | '_WsHistoryUpgradeHandler' | '_ProjectUiUpgradeHandler';
          if (typeof this[fnName] === 'function') {
            this[fnName](db, exists);
          } else if (!exists) {
            this._defaultUpgradeHandler(name as StoreName, db, exists);
          }
        }
      },
    });
    this._db = dbResult;
    return dbResult;
  }

  /**
   * Creates a default store where the stored object is identified by the `data.key` path.
   * @param name The store name to create
   * @param db The handler to the database
   */
  private _defaultUpgradeHandler(name: StoreName, db: IDBPDatabase<HttpClientDB>, storeExists: boolean): void {
    if (storeExists) {
      return;
    }
    db.createObjectStore(name, { keyPath: 'data.key' });
  }

  private _UrlHistoryUpgradeHandler(db: IDBPDatabase<HttpClientDB>, storeExists: boolean): void {
    if (storeExists) {
      return;
    }
    db.createObjectStore('UrlHistory', { keyPath: 'key' });
  }

  private _WsHistoryUpgradeHandler(db: IDBPDatabase<HttpClientDB>, storeExists: boolean): void {
    if (storeExists) {
      return;
    }
    db.createObjectStore('WsHistory', { keyPath: 'key' });
  }

  private _ProjectUiUpgradeHandler(db: IDBPDatabase<HttpClientDB>, storeExists: boolean): void {
    if (storeExists) {
      return;
    }
    const store = db.createObjectStore('ProjectUi', { keyPath: ['meta.project', 'meta.key'] });
    const typed = store as IDBPObjectStore<HttpClientDB, ['ProjectUi'], 'ProjectUi', 'versionchange'>;
    typed.createIndex('project', 'meta.project', { unique: false });
  }
}
