/**
 * A process-wide database registry. The framework configures it at server
 * startup from `wrnexus.config.ts`: the `db` setting becomes the **default**
 * connection, and each entry under `databases` becomes a **named** connection.
 * Pages and API routes then call `getDb()` for the default, or `getDb("<name>")`
 * for a named one, to run queries (including the generated typed functions).
 *
 *   const users = await getDb().all("SELECT * FROM users");            // default db
 *   const events = await getDb("analytics").all("SELECT * FROM hits"); // named db
 */

import type { Db } from "./driver.ts";

const DEFAULT = "default";
type DbFactory = () => Db;
type RegistryEntry = { db?: Db; factory?: DbFactory };
const registry = new Map<string, RegistryEntry>();

/** Set the default database (called by the runtime at startup). */
export function setDb(db: Db): Db;
/** Set a named database (from `databases.<name>` in config). */
export function setDb(name: string, db: Db): Db;
export function setDb(a: string | Db, b?: Db): Db {
  const name = typeof a === "string" ? a : DEFAULT;
  const db = typeof a === "string" ? b! : a;
  registry.set(name, { db });
  return db;
}

/** Register a named database. Alias of `setDb(name, db)` for readability. */
export function registerDb(name: string, db: Db): Db {
  return setDb(name, db);
}

/**
 * Register a named database without opening its connection pool. The first
 * `getDb(name)` call creates and caches the connection.
 */
export function registerLazyDb(name: string, factory: DbFactory): void {
  registry.set(name, { factory });
}

/** The default database, or a named one. Throws if it isn't configured. */
export function getDb(name = DEFAULT): Db {
  const entry = registry.get(name);
  if (!entry) {
    throw new Error(
      name === DEFAULT
        ? "No database configured. Add `db: { driver, url }` to wrnexus.config.ts."
        : `No database named '${name}'. Add it under \`databases\` in wrnexus.config.ts ` +
            `(e.g. databases: { ${name}: { driver, url } }).`,
    );
  }
  if (!entry.db) {
    if (!entry.factory) throw new Error(`Database '${name}' has no connection factory.`);
    entry.db = entry.factory();
  }
  return entry.db;
}

/** Whether the default (or a named) database has been configured. */
export function hasDb(name = DEFAULT): boolean {
  return registry.has(name);
}

/** Names of all configured databases (the default appears as "default"). */
export function databaseNames(): string[] {
  return [...registry.keys()];
}

/** Close every configured database and clear the registry. */
export async function closeDatabases(): Promise<void> {
  const databases = [...registry.values()].flatMap((entry) => (entry.db ? [entry.db] : []));
  registry.clear();
  const results = await Promise.allSettled(databases.map((db) => db.close()));
  const errors = results.flatMap((result) => (result.status === "rejected" ? [result.reason] : []));
  if (errors.length) throw new AggregateError(errors, "One or more databases failed to close");
}
