import { Program } from '../../engine';
import { IStorageService, StoredProgramSummary } from './storage-interface';

const DB_NAME = 'lego_ladder_db';
const STORE_NAME = 'ladder_programs';
const DB_VERSION = 1;

interface StoredProgram extends Program {
  id: string;
  lastModified: number;
  name: string;
}

export class IndexedDBStorageService implements IStorageService {
  private db: IDBDatabase | null = null;

  private async getDB(): Promise<IDBDatabase> {
    if (this.db) return this.db;

    return new Promise((resolve, reject) => {
      const request = indexedDB.open(DB_NAME, DB_VERSION);

      request.onupgradeneeded = (event) => {
        const db = (event.target as IDBOpenDBRequest).result as IDBDatabase;
        if (!db.objectStoreNames.contains(STORE_NAME)) {
          db.createObjectStore(STORE_NAME, { keyPath: 'id' });
        }
      };

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

      request.onerror = (event) => {
        reject(new Error(`IndexedDB error: ${(event.target as IDBOpenDBRequest).error}`));
      };
    });
  }

  async saveProgram(program: Program, id?: string): Promise<string> {
    const db = await this.getDB();
    const programId = id || crypto.randomUUID();
    const storedProgram: StoredProgram = {
      ...program,
      id: programId,
      lastModified: Date.now(),
    };

    return new Promise((resolve, reject) => {
      const transaction = db.transaction(STORE_NAME, 'readwrite');
      const store = transaction.objectStore(STORE_NAME);
      const request = store.put(storedProgram);

      request.onsuccess = () => resolve(programId);
      request.onerror = () => reject(request.error);
    });
  }

  async loadProgram(id: string): Promise<Program> {
    const db = await this.getDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction(STORE_NAME, 'readonly');
      const store = transaction.objectStore(STORE_NAME);
      const request = store.get(id);

      request.onsuccess = () => {
        const result = request.result as StoredProgram;
        if (result) {
          // Remove id and lastModified before returning to match Program interface
          const { id: _, lastModified: __, ...program } = result;
          resolve(program);
        } else {
          reject(new Error(`Program with id ${id} not found`));
        }
      };

      request.onerror = () => reject(request.error);
    });
  }

  async listPrograms(): Promise<StoredProgramSummary[]> {
    const db = await this.getDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction(STORE_NAME, 'readonly');
      const store = transaction.objectStore(STORE_NAME);
      const request = store.getAll();

      request.onsuccess = () => {
        const results = (request.result as StoredProgram[]).map((p) => ({
          id: p.id,
          name: p.name,
          lastModified: p.lastModified,
        }));
        resolve(results);
      };

      request.onerror = () => reject(request.error);
    });
  }

  async deleteProgram(id: string): Promise<void> {
    const db = await this.getDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction(STORE_NAME, 'readwrite');
      const store = transaction.objectStore(STORE_NAME);
      const request = store.delete(id);

      request.onsuccess = () => resolve();
      request.onerror = () => reject(request.error);
    });
  }

  async close(): Promise<void> {
    if (this.db) {
      this.db.close();
      this.db = null;
    }
  }

  async renameProgram(id: string, newName: string): Promise<void> {
    const db = await this.getDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction(STORE_NAME, 'readwrite');
      const store = transaction.objectStore(STORE_NAME);
      const getRequest = store.get(id);

      getRequest.onsuccess = () => {
        const program = getRequest.result as StoredProgram;
        if (program) {
          program.name = newName;
          program.lastModified = Date.now();
          store.put(program);
          transaction.oncomplete = () => resolve();
        } else {
          reject(new Error(`Program with id ${id} not found`));
        }
      };

      getRequest.onerror = () => reject(getRequest.error);
      transaction.onerror = () => reject(transaction.error);
    });
  }
}