/**
 * IndexedDB wrapper for offline audio storage.
 * Two object stores:
 *   - 'audio-blobs': trackId → Blob (the audio file)
 *   - 'track-meta':  trackId → Track metadata (for listing without loading blobs)
 */

import type { Track } from '@/components/music/music-provider'

const DB_NAME = 'soulsync-offline'
const DB_VERSION = 1
const AUDIO_STORE = 'audio-blobs'
const META_STORE = 'track-meta'

function openDB(): Promise<IDBDatabase> {
  return new Promise((resolve, reject) => {
    const req = indexedDB.open(DB_NAME, DB_VERSION)
    req.onupgradeneeded = () => {
      const db = req.result
      if (!db.objectStoreNames.contains(AUDIO_STORE)) {
        db.createObjectStore(AUDIO_STORE)
      }
      if (!db.objectStoreNames.contains(META_STORE)) {
        db.createObjectStore(META_STORE)
      }
    }
    req.onsuccess = () => resolve(req.result)
    req.onerror = () => reject(req.error)
  })
}

function tx(
  db: IDBDatabase,
  stores: string | string[],
  mode: IDBTransactionMode = 'readonly'
): IDBTransaction {
  return db.transaction(stores, mode)
}

/** Save an audio blob and track metadata */
export async function saveTrack(trackId: string, blob: Blob, meta: Track): Promise<void> {
  const db = await openDB()
  const t = tx(db, [AUDIO_STORE, META_STORE], 'readwrite')
  t.objectStore(AUDIO_STORE).put(blob, trackId)
  t.objectStore(META_STORE).put(meta, trackId)
  return new Promise((resolve, reject) => {
    t.oncomplete = () => { db.close(); resolve() }
    t.onerror = () => { db.close(); reject(t.error) }
  })
}

/** Get the audio blob for a track */
export async function getTrackBlob(trackId: string): Promise<Blob | undefined> {
  const db = await openDB()
  const t = tx(db, AUDIO_STORE)
  const req = t.objectStore(AUDIO_STORE).get(trackId)
  return new Promise((resolve, reject) => {
    req.onsuccess = () => { db.close(); resolve(req.result) }
    req.onerror = () => { db.close(); reject(req.error) }
  })
}

/** Check if a track is stored offline */
export async function hasTrack(trackId: string): Promise<boolean> {
  const db = await openDB()
  const t = tx(db, META_STORE)
  const req = t.objectStore(META_STORE).count(trackId)
  return new Promise((resolve, reject) => {
    req.onsuccess = () => { db.close(); resolve(req.result > 0) }
    req.onerror = () => { db.close(); reject(req.error) }
  })
}

/** Save only the audio blob (no metadata — used for pre-caching) */
export async function saveBlob(trackId: string, blob: Blob): Promise<void> {
  const db = await openDB()
  const t = tx(db, AUDIO_STORE, 'readwrite')
  t.objectStore(AUDIO_STORE).put(blob, trackId)
  return new Promise((resolve, reject) => {
    t.oncomplete = () => { db.close(); resolve() }
    t.onerror = () => { db.close(); reject(t.error) }
  })
}

/** Check if an audio blob exists (regardless of metadata) */
export async function hasBlob(trackId: string): Promise<boolean> {
  const db = await openDB()
  const t = tx(db, AUDIO_STORE)
  const req = t.objectStore(AUDIO_STORE).count(trackId)
  return new Promise((resolve, reject) => {
    req.onsuccess = () => { db.close(); resolve(req.result > 0) }
    req.onerror = () => { db.close(); reject(req.error) }
  })
}

/** Remove a track from offline storage */
export async function removeTrack(trackId: string): Promise<void> {
  const db = await openDB()
  const t = tx(db, [AUDIO_STORE, META_STORE], 'readwrite')
  t.objectStore(AUDIO_STORE).delete(trackId)
  t.objectStore(META_STORE).delete(trackId)
  return new Promise((resolve, reject) => {
    t.oncomplete = () => { db.close(); resolve() }
    t.onerror = () => { db.close(); reject(t.error) }
  })
}

/** List all offline tracks (metadata only) */
export async function listOfflineTracks(): Promise<Track[]> {
  const db = await openDB()
  const t = tx(db, META_STORE)
  const req = t.objectStore(META_STORE).getAll()
  return new Promise((resolve, reject) => {
    req.onsuccess = () => { db.close(); resolve(req.result) }
    req.onerror = () => { db.close(); reject(req.error) }
  })
}

/** Get all offline track IDs (for fast Set building) */
export async function listOfflineIds(): Promise<string[]> {
  const db = await openDB()
  const t = tx(db, META_STORE)
  const req = t.objectStore(META_STORE).getAllKeys()
  return new Promise((resolve, reject) => {
    req.onsuccess = () => { db.close(); resolve(req.result as string[]) }
    req.onerror = () => { db.close(); reject(req.error) }
  })
}

/** Get total storage used (sum of all blob sizes in bytes) */
export async function getTotalSize(): Promise<number> {
  const db = await openDB()
  const t = tx(db, AUDIO_STORE)
  const store = t.objectStore(AUDIO_STORE)
  const req = store.openCursor()
  let total = 0
  return new Promise((resolve, reject) => {
    req.onsuccess = () => {
      const cursor = req.result
      if (cursor) {
        const blob = cursor.value as Blob
        total += blob.size
        cursor.continue()
      } else {
        db.close()
        resolve(total)
      }
    }
    req.onerror = () => { db.close(); reject(req.error) }
  })
}

/** Remove all offline data */
export async function clearAll(): Promise<void> {
  const db = await openDB()
  const t = tx(db, [AUDIO_STORE, META_STORE], 'readwrite')
  t.objectStore(AUDIO_STORE).clear()
  t.objectStore(META_STORE).clear()
  return new Promise((resolve, reject) => {
    t.oncomplete = () => { db.close(); resolve() }
    t.onerror = () => { db.close(); reject(t.error) }
  })
}
