import { Injectable, signal } from '@angular/core';
import { StorageProvider } from './storage-provider';

const DB_NAME = 'storage';

@Injectable({ providedIn: 'root' })
export class IDBStorageProvider implements StorageProvider {
  #store = signal<Record<string, unknown>>({});

  #db!: IDBDatabase;

  async #openDB(): Promise<IDBDatabase> {
    if (this.#db) {
      return this.#db; // Datenbank bereits geöffnet, bestehende Verbindung zurückgeben
    }

    return new Promise<IDBDatabase>((resolve, reject) => {
      const request = indexedDB.open('isa-storage', 1);

      request.onerror = (event) => {
        reject(event);
      };

      request.onupgradeneeded = (event) => {
        this.#db = (event.target as IDBOpenDBRequest).result;

        if (!this.#db.objectStoreNames.contains(DB_NAME)) {
          this.#db.createObjectStore(DB_NAME, { keyPath: 'key' });
        }
      };

      request.onsuccess = (event) => {
        this.#db = (event.target as IDBOpenDBRequest).result;
        resolve(this.#db);
      };
    });
  }

  async #getObjectStore(
    mode: IDBTransactionMode = 'readonly',
  ): Promise<IDBObjectStore> {
    const db = await this.#openDB();
    const transaction = db.transaction(DB_NAME, mode);
    return transaction.objectStore(DB_NAME);
  }

  async init(): Promise<void> {
    const store = await this.#getObjectStore();

    return new Promise<void>((resolve, reject) => {
      const request = store.getAll();
      request.onsuccess = () => {
        const result = request.result as Array<{ key: string; value: unknown }>;
        const data: Record<string, unknown> = {};
        result.forEach((item) => {
          data[item.key] = item.value;
        });
        this.#store.set(data);
        resolve();
      };
      request.onerror = (event) => reject(event);
    });
  }

  async reload(): Promise<void> {
    await this.init();
  }

  #setState(state: Record<string, unknown>): void {
    this.#store.set(state);
    this.#writeStateToDB();
  }

  #writeStateToDB(): Promise<void> {
    const entries = Object.entries(this.#store());
    return new Promise<void>(async (resolve, reject) => {
      try {
        const store = await this.#getObjectStore('readwrite');
        const transaction = store.transaction;

        // Clear existing data first
        const clearRequest = store.clear();
        clearRequest.onsuccess = () => {
          // Add all current entries
          let completed = 0;
          const total = entries.length;

          if (total === 0) {
            resolve();
            return;
          }

          entries.forEach(([key, value]) => {
            const addRequest = store.add({ key, value });

            addRequest.onsuccess = () => {
              completed++;
              if (completed === total) {
                resolve();
              }
            };

            addRequest.onerror = () => {
              reject(addRequest.error);
            };
          });
        };

        clearRequest.onerror = () => {
          reject(clearRequest.error);
        };

        transaction.onerror = () => {
          reject(transaction.error);
        };
      } catch (error) {
        reject(error);
      }
    });
  }

  set(key: string, value: unknown): void {
    const current = this.#store();
    const content = structuredClone(current);
    content[key] = value;

    this.#setState(content);
  }

  get(key: string): unknown {
    return this.#store()[key];
  }

  clear(key: string): void {
    const current = this.#store();
    if (key in current) {
      const content = structuredClone(current);
      delete content[key];
      this.#setState(content);
    }
  }
}
