/* idb.js — thin promise wrapper over IndexedDB.
   Stores: games (databases), positions (explorer index), studies,
   repertoires, tournaments, matches, training. */
'use strict';

const DB_NAME = 'rushchess';
const DB_VERSION = 1;

let dbPromise = null;

export function openDb() {
  if (dbPromise) return dbPromise;
  dbPromise = new Promise((resolve, reject) => {
    const req = indexedDB.open(DB_NAME, DB_VERSION);
    req.onupgradeneeded = () => {
      const db = req.result;
      if (!db.objectStoreNames.contains('games')) {
        const s = db.createObjectStore('games', { keyPath: 'id', autoIncrement: true });
        s.createIndex('db', 'db');
        s.createIndex('white', 'white');
        s.createIndex('black', 'black');
        s.createIndex('event', 'event');
        s.createIndex('date', 'date');
        s.createIndex('dupKey', 'dupKey');
      }
      if (!db.objectStoreNames.contains('positions')) {
        const s = db.createObjectStore('positions', { keyPath: 'id', autoIncrement: true });
        s.createIndex('key', 'key');           // positionKey -> games/moves
        s.createIndex('gameId', 'gameId');
      }
      if (!db.objectStoreNames.contains('studies'))
        db.createObjectStore('studies', { keyPath: 'id', autoIncrement: true });
      if (!db.objectStoreNames.contains('repertoires'))
        db.createObjectStore('repertoires', { keyPath: 'id', autoIncrement: true });
      if (!db.objectStoreNames.contains('tournaments'))
        db.createObjectStore('tournaments', { keyPath: 'id', autoIncrement: true });
      if (!db.objectStoreNames.contains('matches'))
        db.createObjectStore('matches', { keyPath: 'id', autoIncrement: true });
      if (!db.objectStoreNames.contains('training'))
        db.createObjectStore('training', { keyPath: 'id' });
      if (!db.objectStoreNames.contains('meta'))
        db.createObjectStore('meta', { keyPath: 'key' });
    };
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
  return dbPromise;
}

function reqP(req) {
  return new Promise((resolve, reject) => {
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
}

export async function tx(store, mode, fn) {
  const db = await openDb();
  const t = db.transaction(store, mode);
  const s = Array.isArray(store) ? store.map(n => t.objectStore(n)) : t.objectStore(store);
  const result = await fn(s, t);
  return new Promise((resolve, reject) => {
    t.oncomplete = () => resolve(result);
    t.onerror = () => reject(t.error);
    t.onabort = () => reject(t.error || new Error('transaction aborted'));
  });
}

export const idb = {
  get: (store, key) => tx(store, 'readonly', s => reqP(s.get(key))),
  getAll: (store, query, count) => tx(store, 'readonly', s => reqP(s.getAll(query, count))),
  getAllKeys: (store, query) => tx(store, 'readonly', s => reqP(s.getAllKeys(query))),
  put: (store, value) => tx(store, 'readwrite', s => reqP(s.put(value))),
  add: (store, value) => tx(store, 'readwrite', s => reqP(s.add(value))),
  del: (store, key) => tx(store, 'readwrite', s => reqP(s.delete(key))),
  clear: store => tx(store, 'readwrite', s => reqP(s.clear())),
  count: (store, query) => tx(store, 'readonly', s => reqP(s.count(query))),
  index: (store, index, query, count) =>
    tx(store, 'readonly', s => reqP(s.index(index).getAll(query, count))),
  indexKeys: (store, index, query) =>
    tx(store, 'readonly', s => reqP(s.index(index).getAllKeys(query))),
  bulkAdd: (store, values) => tx(store, 'readwrite', s => {
    values.forEach(v => s.add(v));
    return Promise.resolve(values.length);
  }),
  bulkPut: (store, values) => tx(store, 'readwrite', s => {
    values.forEach(v => s.put(v));
    return Promise.resolve(values.length);
  }),
  bulkDel: (store, keys) => tx(store, 'readwrite', s => {
    keys.forEach(k => s.delete(k));
    return Promise.resolve(keys.length);
  }),
  /** iterate a cursor with early exit: fn(value) -> false stops */
  each: async (store, indexName, query, fn) => {
    const db = await openDb();
    return new Promise((resolve, reject) => {
      const t = db.transaction(store, 'readonly');
      const src = indexName ? t.objectStore(store).index(indexName) : t.objectStore(store);
      const req = src.openCursor(query);
      req.onsuccess = () => {
        const cur = req.result;
        if (!cur) return resolve();
        if (fn(cur.value, cur.key) === false) return resolve();
        cur.continue();
      };
      req.onerror = () => reject(req.error);
    });
  },
};
