// Persistent store for local (offline) games, keyed by game id, in its own IndexedDB database
// (separate from the dict blob cache in lib/dict/store.ts and from the session store). Mirrors that
// module's shape: everything is best-effort — any failure resolves to null / an empty list / a
// no-op, and the caller degrades gracefully (a game that cannot be read is simply absent).
//
// A record is small (seed + rules + seat metadata + the move journal — see localgame/serialize),
// so games are stored whole; the live board/bag/racks are rebuilt by replay on load.

import type { LocalGameRecord } from './serialize';

const DB_NAME = 'scrabble-localgames';
const STORE = 'games';

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, { keyPath: 'id' });
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  }).catch(() => {
    dbPromise = null;
    throw new Error('indexedDB unavailable');
  });
  return dbPromise;
}

/** saveLocalGame stores (inserts or replaces) a game record, swallowing any failure (best-effort). */
export async function saveLocalGame(record: LocalGameRecord): 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(record);
      tx.oncomplete = () => resolve();
      tx.onerror = () => reject(tx.error);
    });
  } catch {
    /* best-effort: a failed save just loses this snapshot */
  }
}

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

/** listLocalGames returns every stored game record (unordered), or an empty list on any failure —
 *  the offline lobby sorts and filters them. */
export async function listLocalGames(): Promise<LocalGameRecord[]> {
  const db = openDb();
  if (!db) return [];
  try {
    const d = await db;
    return await new Promise<LocalGameRecord[]>((resolve) => {
      const req = d.transaction(STORE, 'readonly').objectStore(STORE).getAll();
      req.onsuccess = () => resolve((req.result ?? []) as LocalGameRecord[]);
      req.onerror = () => resolve([]);
    });
  } catch {
    return [];
  }
}

/** repointLocalGameSeats rewrites the account id on every stored game's seats from oldId to
 *  newId. It is run when an account merge retires the account a device-local game was seated
 *  under (a guest that logged into a durable account): the surviving account then owns the
 *  games, so the lobby and the game header identify "me" again — without it the human seat no
 *  longer matches the active account and the game shows every seat as an opponent. Best-effort;
 *  the records read from IndexedDB are plain objects, so mutating and re-saving them is safe. */
export async function repointLocalGameSeats(oldId: string, newId: string): Promise<void> {
  if (!oldId || !newId || oldId === newId) return;
  for (const g of await listLocalGames()) {
    let touched = false;
    for (const s of g.seats) {
      if (s.accountId === oldId) {
        s.accountId = newId;
        touched = true;
      }
    }
    if (touched) await saveLocalGame(g);
  }
}

/** deleteLocalGame removes a game record, swallowing any failure (best-effort). */
export async function deleteLocalGame(id: 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(id);
      tx.oncomplete = () => resolve();
      tx.onerror = () => resolve();
    });
  } catch {
    /* best-effort */
  }
}
