// `IDBCache` is the IndexedDB-backed [Cache] implementation. The
// underlying object store uses a compound key `[namespace, key]`, so
// `clear(namespace)` is a single bounded cursor walk rather than a
// full-store scan.

import type { IDBPDatabase } from "idb";
import type { Cache } from "./index";
import type { GalaxyDB } from "./idb";

export class IDBCache implements Cache {
	constructor(private readonly db: IDBPDatabase<GalaxyDB>) {}

	async get<T = unknown>(
		namespace: string,
		key: string,
	): Promise<T | undefined> {
		const row = await this.db.get("cache", [namespace, key]);
		return row?.value as T | undefined;
	}

	async put<T = unknown>(
		namespace: string,
		key: string,
		value: T,
	): Promise<void> {
		await this.db.put("cache", { namespace, key, value });
	}

	async delete(namespace: string, key: string): Promise<void> {
		await this.db.delete("cache", [namespace, key]);
	}

	async clear(namespace?: string): Promise<void> {
		if (namespace === undefined) {
			await this.db.clear("cache");
			return;
		}
		// IndexedDB orders compound keys lexicographically. The pair
		// [namespace, ""] is the lower bound and [namespace, ""]
		// is the upper bound for every key whose first component equals
		// `namespace` —  is the largest BMP code point and acts
		// as a sentinel above any realistic application-level key.
		const range = IDBKeyRange.bound(
			[namespace, ""],
			[namespace, ""],
		);
		const tx = this.db.transaction("cache", "readwrite");
		let cursor = await tx.store.openCursor(range);
		while (cursor) {
			cursor.delete();
			cursor = await cursor.continue();
		}
		await tx.done;
	}
}
