import { MetadataDiscovery } from './metadata/MetadataDiscovery.js';
import { MetadataStorage } from './metadata/MetadataStorage.js';
import { Configuration } from './utils/Configuration.js';
import { loadEnvironmentVars } from './utils/env-vars.js';
import { Utils } from './utils/Utils.js';
import { colors } from './logging/colors.js';
async function tryRegisterExtension(name, pkg, extensions) {
  try {
    const url = import.meta.resolve(pkg);
    const mod = await import(url);
    if (mod[name]) {
      extensions.push(mod[name]);
    }
  } catch {
    // not installed
  }
}
/** @internal */
export async function loadOptionalDependencies(options) {
  await import('@mikro-orm/core/fs-utils').then(m => m.fs.init()).catch(() => null);
  const extensions = options.extensions ?? [];
  const exists = name => extensions.some(ext => ext.name === name);
  if (!exists('SeedManager')) {
    await tryRegisterExtension('SeedManager', '@mikro-orm/seeder', extensions);
  }
  if (!exists('Migrator')) {
    await tryRegisterExtension('Migrator', '@mikro-orm/migrations', extensions);
  }
  /* v8 ignore if */
  if (!exists('Migrator')) {
    await tryRegisterExtension('Migrator', '@mikro-orm/migrations-mongodb', extensions);
  }
  if (!exists('EntityGenerator')) {
    await tryRegisterExtension('EntityGenerator', '@mikro-orm/entity-generator', extensions);
  }
  options.extensions = extensions;
  const metadataCacheEnabled = options.metadataCache?.enabled || options.metadataProvider?.useCache?.();
  if (metadataCacheEnabled) {
    options.metadataCache ??= {};
    options.metadataCache.adapter ??= await import('@mikro-orm/core/fs-utils').then(m => m.FileCacheAdapter);
  }
}
/**
 * The main class used to configure and bootstrap the ORM.
 *
 * @example
 * ```ts
 * // import from driver package
 * import { MikroORM, defineEntity, p } from '@mikro-orm/sqlite';
 *
 * const User = defineEntity({
 *   name: 'User',
 *   properties: {
 *     id: p.integer().primary(),
 *     name: p.string(),
 *   },
 * });
 *
 * const orm = new MikroORM({
 *   entities: [User],
 *   dbName: 'my.db',
 * });
 * await orm.schema.update();
 *
 * const em = orm.em.fork();
 * const u1 = em.create(User, { name: 'John' });
 * const u2 = em.create(User, { name: 'Ben' });
 * await em.flush();
 * ```
 */
export class MikroORM {
  /** The global EntityManager instance. If you are using `RequestContext` helper, it will automatically pick the request specific context under the hood */
  em;
  /** The database driver instance used by this ORM. */
  driver;
  /** The ORM configuration instance. */
  config;
  #metadata;
  #logger;
  #discovery;
  /**
   * Initialize the ORM, load entity metadata, create EntityManager and connect to the database.
   * If you omit the `options` parameter, your CLI config will be used.
   */
  static async init(options) {
    /* v8 ignore next */
    if (!options) {
      throw new Error(`options parameter is required`);
    }
    options = { ...options };
    options.discovery ??= {};
    options.discovery.skipSyncDiscovery ??= true;
    await loadOptionalDependencies(options);
    const orm = new this(options);
    const preferTs = orm.config.get('preferTs', Utils.detectTypeScriptSupport());
    orm.#metadata = await orm.#discovery.discover(preferTs);
    orm.createEntityManager();
    return orm;
  }
  /**
   * Synchronous variant of the `init` method with some limitations:
   * - folder-based discovery not supported
   * - ORM extensions are not autoloaded
   * - when metadata cache is enabled, `FileCacheAdapter` needs to be explicitly set in the config
   */
  constructor(options) {
    const env = loadEnvironmentVars();
    options = options.preferEnvVars ? Utils.merge(options, env) : Utils.merge(env, options);
    this.config = new Configuration(options);
    const discovery = this.config.get('discovery');
    this.driver = this.config.getDriver();
    this.#logger = this.config.getLogger();
    this.#logger.log('info', `MikroORM version: ${colors.green(Utils.getORMVersion())}`);
    this.#discovery = new MetadataDiscovery(new MetadataStorage(), this.driver.getPlatform(), this.config);
    this.driver.getPlatform().init(this);
    for (const extension of this.config.get('extensions')) {
      extension.register(this);
    }
    if (!discovery.skipSyncDiscovery) {
      this.#metadata = this.#discovery.discoverSync();
      this.createEntityManager();
    }
  }
  /**
   * Connects to the database.
   */
  async connect() {
    await this.driver.connect();
    return this.driver;
  }
  /**
   * Reconnects, possibly to a different database.
   */
  async reconnect(options = {}) {
    /* v8 ignore next */
    for (const key of Utils.keys(options)) {
      this.config.set(key, options[key]);
    }
    await this.driver.reconnect();
  }
  /**
   * Checks whether the database connection is active.
   */
  async isConnected() {
    return this.driver.getConnection().isConnected();
  }
  /**
   * Checks whether the database connection is active, returns the reason if not.
   */
  async checkConnection() {
    return this.driver.getConnection().checkConnection();
  }
  /**
   * Closes the database connection.
   */
  async close(force = false) {
    await this.driver.close(force);
    await this.config.getMetadataCacheAdapter()?.close?.();
    await this.config.getResultCacheAdapter()?.close?.();
  }
  /**
   * Gets the `MetadataStorage` (without parameters) or `EntityMetadata` instance when provided with the `entityName` parameter.
   */
  getMetadata(entityName) {
    if (entityName) {
      return this.#metadata.get(entityName);
    }
    return this.#metadata;
  }
  createEntityManager() {
    this.driver.setMetadata(this.#metadata);
    this.em = this.driver.createEntityManager();
    this.em.global = true;
    this.#metadata.decorate(this.em);
    this.driver.setMetadata(this.#metadata);
  }
  /**
   * Allows dynamically discovering new entity by reference, handy for testing schema diffing.
   */
  discoverEntity(entities, reset) {
    for (const className of Utils.asArray(reset)) {
      this.#metadata.reset(className);
      this.#discovery.reset(className);
    }
    const tmp = this.#discovery.discoverReferences(Utils.asArray(entities));
    const metadata = this.#discovery.processDiscoveredEntities(tmp);
    for (const meta of metadata) {
      this.#metadata.set(meta.class, meta);
      meta.root = this.#metadata.get(meta.root.class);
    }
    this.#metadata.decorate(this.em);
  }
  /**
   * Gets the SchemaGenerator.
   */
  get schema() {
    return this.config.getExtension('@mikro-orm/schema-generator');
  }
  /**
   * Gets the SeedManager
   */
  get seeder() {
    return this.driver.getPlatform().getExtension('SeedManager', '@mikro-orm/seeder', '@mikro-orm/seeder', this.em);
  }
  /**
   * Gets the Migrator.
   */
  get migrator() {
    return this.driver.getPlatform().getExtension('Migrator', '@mikro-orm/migrator', '@mikro-orm/migrations', this.em);
  }
  /**
   * Gets the EntityGenerator.
   */
  get entityGenerator() {
    return this.driver
      .getPlatform()
      .getExtension('EntityGenerator', '@mikro-orm/entity-generator', '@mikro-orm/entity-generator', this.em);
  }
}
