// IndexedDB-backed project persistence.
//
// Schema (DB v2):
//   - "projects" store: project metadata list + per-project state snapshots
//     Keyed by project id (uuid). Each row stores: name, savedAt, snapshot.
//   - "blobs" store: per-project asset blobs (File + AudioBuffer)
//     Keyed by `${projectId}:${assetId}` so multiple projects can share
//     asset ids without collision.
//
// Multi-project support: we keep a "current" pointer in a special record
// `currentProjectId` so the editor remembers which project was active.

const DB_NAME = 'capcut-web';
const DB_VERSION = 2;
const STORE_PROJECTS = 'projects';
const STORE_BLOBS = 'blobs';
const STORE_META = 'meta';
const CURRENT_PROJECT_KEY = 'current';

// Type-only alias for the IndexedDB cursor (lib.dom doesn't expose it).
type IDBCursor = {
  value: unknown;
  delete(): void;
  continue(): void;
};

export interface StoredProjectRecord {
  id: string;            // uuid
  name: string;          // user-facing name
  createdAt: number;
  updatedAt: number;
  // Snapshot fields (denormalised for simpler restore)
  tracks: unknown;
  clips: unknown;
  subtitles: unknown;
  textOverlays?: unknown;
  silenceSegments: unknown;
  selection: unknown;
  assetsMeta: unknown;
  exportSettings?: unknown;
  subtitleStyle?: unknown;
}

export interface ProjectSummary {
  id: string;
  name: string;
  updatedAt: number;
  clipCount: number;
}

export interface StoredAssetBlob {
  /** Composite key `${projectId}:${assetId}` */
  id: string;
  projectId: string;
  assetId: string;
  file: Blob;
  audioBuffer?: ArrayBuffer;
}

let dbPromise: Promise<IDBDatabase> | null = null;

function openDB(): Promise<IDBDatabase> {
  if (typeof indexedDB === 'undefined') {
    return Promise.reject(new Error('IndexedDB ไม่พร้อมใช้งาน'));
  }
  if (dbPromise) return dbPromise;
  dbPromise = new Promise((resolve, reject) => {
    const req = indexedDB.open(DB_NAME, DB_VERSION);
    req.onupgradeneeded = (event) => {
      const db = req.result;
      const oldVersion = event.oldVersion;
      if (oldVersion < 1) {
        if (!db.objectStoreNames.contains(STORE_PROJECTS)) {
          db.createObjectStore(STORE_PROJECTS, { keyPath: 'id' });
        }
        if (!db.objectStoreNames.contains(STORE_BLOBS)) {
          db.createObjectStore(STORE_BLOBS, { keyPath: 'id' });
        }
        if (!db.objectStoreNames.contains(STORE_META)) {
          db.createObjectStore(STORE_META);
        }
      }
      if (oldVersion < 2) {
        // Same schema for v2 but add indexes if needed.
      }
    };
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error ?? new Error('เปิด IndexedDB ล้มเหลว'));
  });
  return dbPromise;
}

async function tx(stores: string[], mode: IDBTransactionMode): Promise<IDBTransaction> {
  const db = await openDB();
  return db.transaction(stores, mode);
}

function awaitTx(tx: IDBTransaction): Promise<void> {
  return new Promise((resolve, reject) => {
    tx.oncomplete = () => resolve();
    tx.onerror = () => reject(tx.error);
    tx.onabort = () => reject(tx.error ?? new Error('tx aborted'));
  });
}

// ----- Projects -----

export async function saveProject(
  projectId: string,
  name: string,
  createdAt: number,
  snapshot: Omit<
    StoredProjectRecord,
    'id' | 'name' | 'createdAt' | 'updatedAt'
  >,
): Promise<number> {
  const transaction = await tx([STORE_PROJECTS], 'readwrite');
  const store = transaction.objectStore(STORE_PROJECTS);
  const updatedAt = Date.now();
  const record: StoredProjectRecord = {
    id: projectId,
    name,
    createdAt,
    updatedAt,
    ...snapshot,
  };
  store.put(record);
  await awaitTx(transaction);
  return updatedAt;
}

export async function loadProject(projectId: string): Promise<StoredProjectRecord | null> {
  try {
    const transaction = await tx([STORE_PROJECTS], 'readonly');
    const store = transaction.objectStore(STORE_PROJECTS);
    return await new Promise<StoredProjectRecord | null>((resolve, reject) => {
      const req = store.get(projectId);
      req.onsuccess = () => resolve((req.result as StoredProjectRecord) ?? null);
      req.onerror = () => reject(req.error);
    });
  } catch {
    return null;
  }
}

export async function listProjects(): Promise<ProjectSummary[]> {
  try {
    const db = await openDB();
    return await new Promise<ProjectSummary[]>((resolve, reject) => {
      const transaction = db.transaction(STORE_PROJECTS, 'readonly');
      const store = transaction.objectStore(STORE_PROJECTS);
      const req = store.getAll();
      req.onsuccess = () => {
        const all = (req.result as StoredProjectRecord[]).map((p) => ({
          id: p.id,
          name: p.name,
          updatedAt: p.updatedAt,
          clipCount: Array.isArray(p.clips) ? (p.clips as unknown[]).length : 0,
        }));
        all.sort((a, b) => b.updatedAt - a.updatedAt);
        resolve(all);
      };
      req.onerror = () => reject(req.error);
    });
  } catch {
    return [];
  }
}

export async function deleteProject(projectId: string): Promise<void> {
  const transaction = await tx([STORE_PROJECTS], 'readwrite');
  transaction.objectStore(STORE_PROJECTS).delete(projectId);
  await awaitTx(transaction);
  // Cascade: delete all asset blobs for this project.
  await deleteAllBlobsForProject(projectId);
}

export async function setCurrentProjectId(id: string): Promise<void> {
  const transaction = await tx([STORE_META], 'readwrite');
  transaction.objectStore(STORE_META).put(id, CURRENT_PROJECT_KEY);
  await awaitTx(transaction);
}

export async function getCurrentProjectId(): Promise<string | null> {
  try {
    const db = await openDB();
    return await new Promise<string | null>((resolve, reject) => {
      const transaction = db.transaction(STORE_META, 'readonly');
      const store = transaction.objectStore(STORE_META);
      const req = store.get(CURRENT_PROJECT_KEY);
      req.onsuccess = () => resolve((req.result as string) ?? null);
      req.onerror = () => reject(req.error);
    });
  } catch {
    return null;
  }
}

// ----- Asset blobs -----

function blobKey(projectId: string, assetId: string): string {
  return `${projectId}:${assetId}`;
}

export async function saveAssetBlob(
  projectId: string,
  assetId: string,
  file: Blob,
  audioBuffer?: ArrayBuffer,
): Promise<void> {
  const transaction = await tx([STORE_BLOBS], 'readwrite');
  const blob: StoredAssetBlob = {
    id: blobKey(projectId, assetId),
    projectId,
    assetId,
    file,
    audioBuffer,
  };
  transaction.objectStore(STORE_BLOBS).put(blob);
  await awaitTx(transaction);
}

export async function deleteAssetBlob(
  projectId: string,
  assetId: string,
): Promise<void> {
  const transaction = await tx([STORE_BLOBS], 'readwrite');
  transaction.objectStore(STORE_BLOBS).delete(blobKey(projectId, assetId));
  await awaitTx(transaction);
}

export async function loadAssetBlob(
  projectId: string,
  assetId: string,
): Promise<{ file: Blob; audioBuffer?: ArrayBuffer } | null> {
  try {
    const transaction = await tx([STORE_BLOBS], 'readonly');
    const store = transaction.objectStore(STORE_BLOBS);
    return await new Promise<{ file: Blob; audioBuffer?: ArrayBuffer } | null>(
      (resolve, reject) => {
        const req = store.get(blobKey(projectId, assetId));
        req.onsuccess = () => {
          const r = req.result as StoredAssetBlob | undefined;
          resolve(r ? { file: r.file, audioBuffer: r.audioBuffer } : null);
        };
        req.onerror = () => reject(req.error);
      },
    );
  } catch {
    return null;
  }
}

export async function deleteAllBlobsForProject(projectId: string): Promise<void> {
  const transaction = await tx([STORE_BLOBS], 'readwrite');
  const store = transaction.objectStore(STORE_BLOBS);
  const req = store.openCursor();
  await new Promise<void>((resolve, reject) => {
    req.onsuccess = (event) => {
      const cursor = (event.target as IDBRequest<IDBCursor>).result;
      if (cursor) {
        const value = cursor.value as StoredAssetBlob;
        if (value.projectId === projectId) cursor.delete();
        cursor.continue();
      } else {
        resolve();
      }
    };
    req.onerror = () => reject(req.error);
  });
  await awaitTx(transaction);
}

export async function listAssetIdsForProject(projectId: string): Promise<string[]> {
  try {
    const transaction = await tx([STORE_BLOBS], 'readonly');
    const store = transaction.objectStore(STORE_BLOBS);
    return await new Promise<string[]>((resolve, reject) => {
      const req = store.openCursor();
      const ids: string[] = [];
      req.onsuccess = (event) => {
        const cursor = (event.target as IDBRequest<IDBCursor>).result;
        if (cursor) {
          const value = cursor.value as StoredAssetBlob;
          if (value.projectId === projectId) ids.push(value.assetId);
          cursor.continue();
        } else {
          resolve(ids);
        }
      };
      req.onerror = () => reject(req.error);
    });
  } catch {
    return [];
  }
}