// IndexedDB-backed storage for the user's custom incoming ringtone.
//
// Only one slot is exposed (`incoming`) — outgoing ringtone stays tied to the
// bundled oscillator pattern. The blob is stored alongside its mime type +
// original filename so playback + UI can show what's currently in use.

const DB_NAME = 'netralax-ringtones';
const DB_VERSION = 1;
const STORE_NAME = 'ringtones';
const SLOT_INCOMING = 'incoming';

export interface StoredRingtone {
  blob: Blob;
  mime: string;
  filename: string;
  updatedAt: number;
}

export const MAX_RINGTONE_BYTES = 8 * 1024 * 1024; // 8 MB cap

export const SUPPORTED_RINGTONE_MIMES = [
  'audio/mpeg',
  'audio/mp3',
  'audio/wav',
  'audio/x-wav',
  'audio/ogg',
  'audio/webm',
  'audio/mp4',
  'audio/aac',
  'audio/x-m4a',
  'audio/m4a',
];

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

function openDb(): Promise<IDBDatabase> {
  if (dbPromise) return dbPromise;
  dbPromise = new Promise<IDBDatabase>((resolve, reject) => {
    const req = indexedDB.open(DB_NAME, DB_VERSION);
    req.onupgradeneeded = () => {
      const db = req.result;
      if (!db.objectStoreNames.contains(STORE_NAME)) {
        db.createObjectStore(STORE_NAME);
      }
    };
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error ?? new Error('indexedDB open failed'));
  });
  return dbPromise;
}

function runTx<T>(
  mode: IDBTransactionMode,
  fn: (store: IDBObjectStore) => IDBRequest<T> | void,
): Promise<T | undefined> {
  return openDb().then(
    (db) =>
      new Promise<T | undefined>((resolve, reject) => {
        const tx = db.transaction(STORE_NAME, mode);
        const store = tx.objectStore(STORE_NAME);
        let result: T | undefined = undefined;
        const maybeReq = fn(store);
        if (maybeReq) {
          maybeReq.onsuccess = () => {
            result = maybeReq.result;
          };
          maybeReq.onerror = () => reject(maybeReq.error);
        }
        tx.oncomplete = () => resolve(result);
        tx.onerror = () => reject(tx.error);
        tx.onabort = () => reject(tx.error);
      }),
  );
}

export async function saveIncomingRingtone(file: File): Promise<void> {
  if (file.size === 0) throw new Error('empty file');
  if (file.size > MAX_RINGTONE_BYTES) {
    throw new Error('ringtone_too_large');
  }
  const mime = file.type || 'application/octet-stream';
  if (!mime.startsWith('audio/')) {
    throw new Error('ringtone_not_audio');
  }
  const stored: StoredRingtone = {
    blob: file,
    mime,
    filename: file.name,
    updatedAt: Date.now(),
  };
  await runTx('readwrite', (store) => store.put(stored, SLOT_INCOMING));
}

export async function getIncomingRingtone(): Promise<StoredRingtone | null> {
  const result = await runTx<StoredRingtone | undefined>('readonly', (store) =>
    store.get(SLOT_INCOMING) as IDBRequest<StoredRingtone | undefined>,
  );
  return result ?? null;
}

export async function clearIncomingRingtone(): Promise<void> {
  await runTx('readwrite', (store) => store.delete(SLOT_INCOMING));
}

export async function hasIncomingRingtone(): Promise<boolean> {
  const cur = await getIncomingRingtone();
  return cur !== null;
}
