import { resolver } from "@rocicorp/resolver";
import { assertNotNull } from "../../../shared/src/asserts.js";
import { mustGetBrowserGlobal } from "../../../shared/src/browser-env.js";
import { promiseVoid } from "../../../shared/src/resolved-promises.js";
import { deepFreezeAllowUndefined } from "../frozen-json.js";
import { storeIsClosedRejection, transactionIsClosedRejection } from "./throw-if-closed.js";
import { WriteImplBase, deleteSentinel } from "./write-impl-base.js";
const RELAXED = { durability: "relaxed" };
const OBJECT_STORE = "chunks";
class IDBStore {
  #db;
  #closed = false;
  #idbDeleted = false;
  constructor(name) {
    this.#db = openDatabase(name);
  }
  read() {
    if (this.#closed) {
      return storeIsClosedRejection();
    }
    return this.#withReopen(readImpl);
  }
  write() {
    if (this.#closed) {
      return storeIsClosedRejection();
    }
    return this.#withReopen(writeImpl);
  }
  async close() {
    if (!this.#idbDeleted) {
      const db = await this.#db;
      db.close();
    }
    this.#closed = true;
  }
  get closed() {
    return this.#closed;
  }
  async #withReopen(fn) {
    const reopenExistingDB = async (name) => {
      const { promise, resolve, reject } = resolver();
      const req = indexedDB.open(name);
      req.onupgradeneeded = () => {
        const tx = req.transaction;
        assertNotNull(tx);
        tx.abort();
        this.#idbDeleted = true;
        reject(
          new IDBNotFoundError(
            `Expected IndexedDB not found: ${name}. This likely means that the user deleted IndexedDB instances while the app was running. This is non-fatal. The app will continue running in memory until reload.`
          )
        );
      };
      req.onsuccess = () => resolve(req.result);
      req.onerror = () => reject(req.error);
      const db2 = await promise;
      db2.onversionchange = () => db2.close();
      return db2;
    };
    const db = await this.#db;
    try {
      return fn(db);
    } catch (e) {
      if (!this.#closed && e instanceof DOMException) {
        if (e.name === "InvalidStateError") {
          this.#db = reopenExistingDB(db.name);
          const reopened = await this.#db;
          return fn(reopened);
        } else if (e.name === "NotFoundError") {
          this.#idbDeleted = true;
          mustGetBrowserGlobal("indexedDB").deleteDatabase(db.name);
          throw new IDBNotFoundError(
            `Expected IndexedDB ${db.name} missing object store. Deleting db. This is non-fatal, the app will continue working in memory until it is reloaded.`
          );
        }
      }
      throw e;
    }
  }
}
class ReadImpl {
  #tx;
  #closed = false;
  constructor(tx) {
    this.#tx = tx;
  }
  has(key) {
    if (this.#closed) {
      return transactionIsClosedRejection();
    }
    return new Promise((resolve, reject) => {
      const req = objectStore(this.#tx).count(key);
      req.onsuccess = () => resolve(req.result > 0);
      req.onerror = () => reject(req.error);
    });
  }
  get(key) {
    if (this.#closed) {
      return transactionIsClosedRejection();
    }
    return new Promise((resolve, reject) => {
      const req = objectStore(this.#tx).get(key);
      req.onsuccess = () => resolve(deepFreezeAllowUndefined(req.result));
      req.onerror = () => reject(req.error);
    });
  }
  release() {
    this.#closed = true;
  }
  get closed() {
    return this.#closed;
  }
}
class WriteImpl extends WriteImplBase {
  #tx;
  #closed = false;
  constructor(tx) {
    super(new ReadImpl(tx));
    this.#tx = tx;
  }
  commit() {
    if (this.#closed) {
      return transactionIsClosedRejection();
    }
    if (this._pending.size === 0) {
      return promiseVoid;
    }
    return new Promise((resolve, reject) => {
      const tx = this.#tx;
      const store = objectStore(tx);
      for (const [key, val] of this._pending) {
        if (val === deleteSentinel) {
          store.delete(key);
        } else {
          store.put(val, key);
        }
      }
      tx.oncomplete = () => resolve();
      tx.onerror = () => reject(tx.error);
    });
  }
  release() {
    this.#closed = true;
    super.release();
  }
  get closed() {
    return this.#closed;
  }
}
function writeImpl(db) {
  const tx = db.transaction(OBJECT_STORE, "readwrite", RELAXED);
  return new WriteImpl(tx);
}
function readImpl(db) {
  const tx = db.transaction(OBJECT_STORE, "readonly");
  return new ReadImpl(tx);
}
function objectStore(tx) {
  return tx.objectStore(OBJECT_STORE);
}
function openDatabase(name) {
  const idb = mustGetBrowserGlobal("indexedDB");
  return new Promise((resolve, reject) => {
    const req = idb.open(name);
    req.onupgradeneeded = () => {
      req.result.createObjectStore(OBJECT_STORE);
    };
    req.onsuccess = () => {
      const db = req.result;
      db.onversionchange = () => db.close();
      resolve(db);
    };
    req.onerror = () => reject(req.error);
  });
}
class IDBNotFoundError extends Error {
  name = "IDBNotFoundError";
}
export {
  IDBNotFoundError,
  IDBStore
};
//# sourceMappingURL=idb-store.js.map
