// Shared IndexedDB connection used by both the WebCrypto keystore
// and the IDB cache. Opens one versioned database (`galaxy-ui`)
// containing two object stores:
//
//   keypair: out-of-line keys; we always read/write the fixed slot
//            `device`. The row carries the `CryptoKey` handles
//            (private + public) and a cached raw 32-byte public-key
//            export so consumers do not call exportKey on every load.
//   cache:   keyPath = ['namespace', 'key']; rows are
//            { namespace, key, value } where `value` is whatever the
//            caller stored. Compound keys give the cache cheap
//            namespace-prefix scans without a secondary index.
//
// The database connection is cached per page; tests create their own
// per-test connection by calling [openGalaxyDB] directly with a
// distinct name so cases do not bleed state.

import { openDB, type DBSchema, type IDBPDatabase } from "idb";

export const DB_NAME = "galaxy-ui";
export const DB_VERSION = 1;

/** Fixed key for the single device keypair slot in the `keypair` store. */
export const DEVICE_KEYPAIR_KEY = "device";

export interface KeypairRow {
	privateKey: CryptoKey;
	publicKey: CryptoKey;
	publicKeyRaw: ArrayBuffer;
}

export interface CacheRow {
	namespace: string;
	key: string;
	value: unknown;
}

export interface GalaxyDB extends DBSchema {
	keypair: {
		key: string;
		value: KeypairRow;
	};
	cache: {
		key: [string, string];
		value: CacheRow;
	};
}

let cached: Promise<IDBPDatabase<GalaxyDB>> | undefined;

/**
 * dbConnection returns the cached `galaxy-ui` database connection,
 * opening it on first call. Production callers always go through
 * this; tests use [openGalaxyDB] with a per-test name instead.
 */
export function dbConnection(): Promise<IDBPDatabase<GalaxyDB>> {
	if (!cached) {
		cached = openGalaxyDB(DB_NAME);
	}
	return cached;
}

/**
 * resetDbConnection clears the cached connection. Tests call this
 * between cases that share `DB_NAME`; production code never does.
 */
export function resetDbConnection(): void {
	cached = undefined;
}

/**
 * openGalaxyDB opens a Galaxy UI database under the given name. The
 * upgrade callback installs the two stores defined by [GalaxyDB] on
 * the version-1 schema; later schema changes bump [DB_VERSION] and
 * extend the callback in place.
 */
export function openGalaxyDB(name: string): Promise<IDBPDatabase<GalaxyDB>> {
	return openDB<GalaxyDB>(name, DB_VERSION, {
		upgrade(db) {
			if (!db.objectStoreNames.contains("keypair")) {
				db.createObjectStore("keypair");
			}
			if (!db.objectStoreNames.contains("cache")) {
				db.createObjectStore("cache", {
					keyPath: ["namespace", "key"],
				});
			}
		},
	});
}
