import { ValidationError } from '../errors.js';
import { Utils } from '../utils/Utils.js';
/** Repository class providing a type-safe API for querying and persisting a specific entity type. */
export class EntityRepository {
  em;
  entityName;
  constructor(em, entityName) {
    this.em = em;
    this.entityName = entityName;
  }
  /**
   * Finds first entity matching your `where` query.
   */
  async findOne(where, options) {
    return this.getEntityManager().findOne(this.entityName, where, options);
  }
  /**
   * Finds first entity matching your `where` query. If nothing is found, it will throw an error.
   * You can override the factory for creating this method via `options.failHandler` locally
   * or via `Configuration.findOneOrFailHandler` globally.
   */
  async findOneOrFail(where, options) {
    return this.getEntityManager().findOneOrFail(this.entityName, where, options);
  }
  /**
   * Creates or updates the entity, based on whether it is already present in the database.
   * This method performs an `insert on conflict merge` query ensuring the database is in sync, returning a managed
   * entity instance. The method accepts either `entityName` together with the entity `data`, or just entity instance.
   *
   * ```ts
   * // insert into "author" ("age", "email") values (33, 'foo@bar.com') on conflict ("email") do update set "age" = 41
   * const author = await em.getRepository(Author).upsert({ email: 'foo@bar.com', age: 33 });
   * ```
   *
   * The entity data needs to contain either the primary key, or any other unique property. Let's consider the following example, where `Author.email` is a unique property:
   *
   * ```ts
   * // insert into "author" ("age", "email") values (33, 'foo@bar.com') on conflict ("email") do update set "age" = 41
   * // select "id" from "author" where "email" = 'foo@bar.com'
   * const author = await em.getRepository(Author).upsert({ email: 'foo@bar.com', age: 33 });
   * ```
   *
   * Depending on the driver support, this will either use a returning query, or a separate select query, to fetch the primary key if it's missing from the `data`.
   *
   * If the entity is already present in current context, there won't be any queries - instead, the entity data will be assigned and an explicit `flush` will be required for those changes to be persisted.
   */
  async upsert(entityOrData, options) {
    return this.getEntityManager().upsert(this.entityName, entityOrData, options);
  }
  /**
   * Creates or updates the entity, based on whether it is already present in the database.
   * This method performs an `insert on conflict merge` query ensuring the database is in sync, returning a managed
   * entity instance.
   *
   * ```ts
   * // insert into "author" ("age", "email") values (33, 'foo@bar.com') on conflict ("email") do update set "age" = 41
   * const authors = await em.getRepository(Author).upsertMany([{ email: 'foo@bar.com', age: 33 }, ...]);
   * ```
   *
   * The entity data needs to contain either the primary key, or any other unique property. Let's consider the following example, where `Author.email` is a unique property:
   *
   * ```ts
   * // insert into "author" ("age", "email") values (33, 'foo@bar.com'), (666, 'lol@lol.lol') on conflict ("email") do update set "age" = excluded."age"
   * // select "id" from "author" where "email" = 'foo@bar.com'
   * const author = await em.getRepository(Author).upsertMany([
   *   { email: 'foo@bar.com', age: 33 },
   *   { email: 'lol@lol.lol', age: 666 },
   * ]);
   * ```
   *
   * Depending on the driver support, this will either use a returning query, or a separate select query, to fetch the primary key if it's missing from the `data`.
   *
   * If the entity is already present in current context, there won't be any queries - instead, the entity data will be assigned and an explicit `flush` will be required for those changes to be persisted.
   */
  async upsertMany(entitiesOrData, options) {
    return this.getEntityManager().upsertMany(this.entityName, entitiesOrData, options);
  }
  /**
   * Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
   */
  async find(where, options) {
    return this.getEntityManager().find(this.entityName, where, options);
  }
  /**
   * Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as tuple
   * where first element is the array of entities, and the second is the count.
   */
  async findAndCount(where, options) {
    return this.getEntityManager().findAndCount(this.entityName, where, options);
  }
  /**
   * @inheritDoc EntityManager.findByCursor
   */
  async findByCursor(options) {
    return this.getEntityManager().findByCursor(this.entityName, options);
  }
  /**
   * Finds all entities of given type. You can pass additional options via the `options` parameter.
   */
  async findAll(options) {
    return this.getEntityManager().findAll(this.entityName, options);
  }
  /**
   * @inheritDoc EntityManager.stream
   */
  async *stream(options) {
    yield* this.getEntityManager().stream(this.entityName, options);
  }
  /**
   * @inheritDoc EntityManager.insert
   */
  async insert(data, options) {
    return this.getEntityManager().insert(this.entityName, data, options);
  }
  /**
   * @inheritDoc EntityManager.insert
   */
  async insertMany(data, options) {
    return this.getEntityManager().insertMany(this.entityName, data, options);
  }
  /**
   * Fires native update query. Calling this has no side effects on the context (identity map).
   */
  async nativeUpdate(where, data, options) {
    return this.getEntityManager().nativeUpdate(this.entityName, where, data, options);
  }
  /**
   * Fires native delete query. Calling this has no side effects on the context (identity map).
   */
  async nativeDelete(where, options) {
    return this.getEntityManager().nativeDelete(this.entityName, where, options);
  }
  /**
   * Maps raw database result to an entity and merges it to this EntityManager.
   */
  map(result, options) {
    return this.getEntityManager().map(this.entityName, result, options);
  }
  /**
   * Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
   */
  getReference(id, options) {
    return this.getEntityManager().getReference(this.entityName, id, options);
  }
  /**
   * Checks whether given property can be populated on the entity.
   */
  canPopulate(property) {
    return this.getEntityManager().canPopulate(this.entityName, property);
  }
  /**
   * Loads specified relations in batch. This will execute one query for each relation, that will populate it on all the specified entities.
   */
  async populate(entities, populate, options) {
    this.validateRepositoryType(entities, 'populate');
    // @ts-ignore hard to type
    return this.getEntityManager().populate(entities, populate, options);
  }
  /**
   * Creates new instance of given entity and populates it with given data.
   * The entity constructor will be used unless you provide `{ managed: true }` in the `options` parameter.
   * The constructor will be given parameters based on the defined constructor of the entity. If the constructor
   * parameter matches a property name, its value will be extracted from `data`. If no matching property exists,
   * the whole `data` parameter will be passed. This means we can also define `constructor(data: Partial<T>)` and
   * `em.create()` will pass the data into it (unless we have a property named `data` too).
   *
   * The parameters are strictly checked, you need to provide all required properties. You can use `OptionalProps`
   * symbol to omit some properties from this check without making them optional. Alternatively, use `partial: true`
   * in the options to disable the strict checks for required properties. This option has no effect on runtime.
   *
   * The newly created entity will be automatically marked for persistence via `em.persist` unless you disable this
   * behavior, either locally via `persist: false` option, or globally via `persistOnCreate` ORM config option.
   */
  create(data, options) {
    return this.getEntityManager().create(this.entityName, data, options);
  }
  /**
   * Shortcut for `wrap(entity).assign(data, { em })`
   */
  assign(entity, data, options) {
    this.validateRepositoryType(entity, 'assign');
    return this.getEntityManager().assign(entity, data, options);
  }
  /**
   * Merges given entity to this EntityManager so it becomes managed. You can force refreshing of existing entities
   * via second parameter. By default it will return already loaded entities without modifying them.
   */
  merge(data, options) {
    return this.getEntityManager().merge(this.entityName, data, options);
  }
  /**
   * Returns total number of entities matching your `where` query.
   */
  async count(where = {}, options = {}) {
    return this.getEntityManager().count(this.entityName, where, options);
  }
  /** Returns the entity class name associated with this repository. */
  getEntityName() {
    return Utils.className(this.entityName);
  }
  /**
   * Returns the underlying EntityManager instance
   */
  getEntityManager() {
    return this.em;
  }
  validateRepositoryType(entities, method) {
    entities = Utils.asArray(entities);
    if (entities.length === 0) {
      return;
    }
    const entityName = entities[0].constructor.name;
    const repoType = Utils.className(this.entityName);
    if (entityName && repoType !== entityName) {
      throw ValidationError.fromWrongRepositoryType(entityName, repoType, method);
    }
  }
}
