// Persistent cache for dictionary DAWG blobs, keyed by `${variant}@${version}`.
// It lives in its own IndexedDB database, separate from session.ts's 'scrabble'
// DB, so the multi-hundred-KB binary blobs never interfere with the session /
// prefs store or its schema versioning. Everything here is best-effort: any
// failure resolves to a miss or a no-op and the caller falls back to the network.

const DB_NAME = 'scrabble-dict';
const STORE = 'dawg';

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

function openDb(): Promise<IDBDatabase> | null {
  if (dbPromise !== undefined) return dbPromise;
  if (typeof indexedDB === 'undefined') {
    dbPromise = null;
    return null;
  }
  dbPromise = new Promise<IDBDatabase>((resolve, reject) => {
    const req = indexedDB.open(DB_NAME, 1);
    req.onupgradeneeded = () => req.result.createObjectStore(STORE);
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  }).catch(() => {
    dbPromise = null;
    throw new Error('indexedDB unavailable');
  });
  return dbPromise;
}

/** dictKey is the persistent cache key for a (variant, version) dictionary. */
export function dictKey(variant: string, version: string): string {
  return `${variant}@${version}`;
}

/** idbGetDawg returns the cached blob for key, or null on a miss or any failure. */
export async function idbGetDawg(key: string): Promise<ArrayBuffer | null> {
  const db = openDb();
  if (!db) return null;
  try {
    const d = await db;
    return await new Promise<ArrayBuffer | null>((resolve, reject) => {
      const r = d.transaction(STORE, 'readonly').objectStore(STORE).get(key);
      r.onsuccess = () => resolve((r.result ?? null) as ArrayBuffer | null);
      r.onerror = () => reject(r.error);
    });
  } catch {
    return null;
  }
}

/** idbPutDawg stores the blob under key, swallowing any failure (best-effort). */
export async function idbPutDawg(key: string, data: ArrayBuffer): Promise<void> {
  const db = openDb();
  if (!db) return;
  try {
    const d = await db;
    await new Promise<void>((resolve, reject) => {
      const tx = d.transaction(STORE, 'readwrite');
      tx.objectStore(STORE).put(data, key);
      tx.oncomplete = () => resolve();
      tx.onerror = () => reject(tx.error);
    });
  } catch {
    /* best-effort: a failed persist just re-downloads next time */
  }
}

/** idbDelDawg removes a cached blob — e.g. a stale or partial entry the reader rejected, so
 *  the next load re-fetches instead of failing on it forever. Best-effort. */
export async function idbDelDawg(key: string): Promise<void> {
  const db = openDb();
  if (!db) return;
  try {
    const d = await db;
    await new Promise<void>((resolve) => {
      const tx = d.transaction(STORE, 'readwrite');
      tx.objectStore(STORE).delete(key);
      tx.oncomplete = () => resolve();
      tx.onerror = () => resolve();
    });
  } catch {
    /* best-effort */
  }
}

let persistRequested = false;

/**
 * requestPersist asks the browser (once) not to evict this origin's storage, so a
 * cached dictionary survives storage pressure. The grant is honoured unevenly
 * across platforms (see docs), so it is only a hint — the cache stays best-effort.
 */
export async function requestPersist(): Promise<void> {
  if (persistRequested) return;
  persistRequested = true;
  try {
    if (typeof navigator !== 'undefined' && navigator.storage?.persist) {
      await navigator.storage.persist();
    }
  } catch {
    /* ignore — persistence is only a hint */
  }
}

/**
 * listCachedDicts returns the key (variant@version) and byte length of every cached
 * dictionary blob, for the debug panel's local-dictionary readout. Best-effort: an
 * empty list on any failure.
 */
export async function listCachedDicts(): Promise<Array<{ key: string; bytes: number }>> {
  const db = openDb();
  if (!db) return [];
  try {
    const d = await db;
    return await new Promise((resolve) => {
      const out: Array<{ key: string; bytes: number }> = [];
      const req = d.transaction(STORE, 'readonly').objectStore(STORE).openCursor();
      req.onsuccess = () => {
        const cur = req.result;
        if (!cur) {
          resolve(out);
          return;
        }
        const v = cur.value as ArrayBuffer;
        out.push({ key: String(cur.key), bytes: v?.byteLength ?? 0 });
        cur.continue();
      };
      req.onerror = () => resolve(out);
    });
  } catch {
    return [];
  }
}

/**
 * clearDictCache empties the dictionary blob store (backs the DebugPanel reset). The
 * store is cleared rather than the database deleted, to avoid a delete blocked on an
 * open handle. Best-effort: a failure just leaves the cache to be reused.
 */
export async function clearDictCache(): Promise<void> {
  const db = openDb();
  if (!db) return;
  try {
    const d = await db;
    await new Promise<void>((resolve, reject) => {
      const tx = d.transaction(STORE, 'readwrite');
      tx.objectStore(STORE).clear();
      tx.oncomplete = () => resolve();
      tx.onerror = () => reject(tx.error);
    });
  } catch {
    /* best-effort */
  }
}
