import type {
  EntityDTO,
  EntityKey,
  EntityProperty,
  FilterQuery,
  IPrimaryKey,
  Loaded,
  LoadedCollection,
  Populate,
  Primary,
} from '../typings.js';
import { Reference } from './Reference.js';
import type { Transaction } from '../connections/Connection.js';
import type { CountOptions, FindOptions } from '../drivers/IDatabaseDriver.js';
import type { EntityLoaderOptions } from './EntityLoader.js';
/** Options for the `Collection.matching()` method to query a subset of collection items from the database. */
export interface MatchingOptions<T extends object, P extends string = never> extends FindOptions<T, P> {
  /** Additional filtering conditions for the query. */
  where?: FilterQuery<T>;
  /** Whether to store the matched items in the collection (makes it read-only). */
  store?: boolean;
  /** Transaction context for the query. */
  ctx?: Transaction;
}
/** Represents a to-many relation (1:m or m:n) as an iterable, managed collection of entities. */
export declare class Collection<T extends object, O extends object = object> {
  #private;
  readonly owner: O;
  [k: number]: T;
  constructor(owner: O, items?: T[], initialized?: boolean);
  /**
   * Creates new Collection instance, assigns it to the owning entity and sets the items to it (propagating them to their inverse sides)
   */
  static create<T extends object, O extends object = object>(
    owner: O,
    prop: EntityKey<O>,
    items: undefined | T[],
    initialized: boolean,
  ): Collection<T, O>;
  /**
   * Ensures the collection is loaded first (without reloading it if it already is loaded).
   * Returns the Collection instance (itself), works the same as `Reference.load()`.
   */
  load<TT extends T, P extends string = never>(
    options?: InitCollectionOptions<TT, P>,
  ): Promise<LoadedCollection<Loaded<TT, P>>>;
  private setSerializationContext;
  /**
   * Initializes the collection and returns the items
   */
  loadItems<TT extends T, P extends string = never>(options?: InitCollectionOptions<TT, P>): Promise<Loaded<TT, P>[]>;
  /**
   * Gets the count of collection items from database instead of counting loaded items.
   * The value is cached (unless you use the `where` option), use `refresh: true` to force reload it.
   */
  loadCount(options?: LoadCountOptions<T> | boolean): Promise<number>;
  /** Queries a subset of the collection items from the database with custom filtering, ordering, and pagination. */
  matching<TT extends T, P extends string = never>(options: MatchingOptions<T, P>): Promise<Loaded<TT, P>[]>;
  /**
   * Returns the items (the collection must be initialized)
   */
  getItems(check?: boolean): T[];
  /** Serializes the collection items to plain JSON objects. Returns an empty array if not initialized. */
  toJSON<TT extends T>(): EntityDTO<TT>[];
  /** Adds one or more items to the collection, propagating the change to the inverse side. Returns the number of items added. */
  add<TT extends T>(
    entity: TT | Reference<TT> | Iterable<TT | Reference<TT>>,
    ...entities: (TT | Reference<TT>)[]
  ): number;
  /**
   * Remove specified item(s) from the collection. Note that removing item from collection does not necessarily imply deleting the target entity,
   * it means we are disconnecting the relation - removing items from collection, not removing entities from database - `Collection.remove()`
   * is not the same as `em.remove()`. If we want to delete the entity by removing it from collection, we need to enable `orphanRemoval: true`,
   * which tells the ORM we don't want orphaned entities to exist, so we know those should be removed.
   */
  remove<TT extends T>(
    entity: TT | Reference<TT> | Iterable<TT | Reference<TT>> | ((item: TT) => boolean),
    ...entities: (TT | Reference<TT>)[]
  ): number;
  /** Checks whether the collection contains the given item. */
  contains<TT extends T>(item: TT | Reference<TT>, check?: boolean): boolean;
  /** Returns the number of items in the collection. Throws if the collection is not initialized. */
  count(): number;
  /** Returns true if the collection has no items. Throws if the collection is not initialized. */
  isEmpty(): boolean;
  /** Returns whether this collection should be included in serialization based on its populated state. */
  shouldPopulate(populated?: boolean): boolean;
  /** Marks the collection as populated or not for serialization purposes. */
  populated(populated?: boolean | undefined): void;
  /** Initializes the collection by loading its items from the database. */
  init<TT extends T, P extends string = never>(
    options?: InitCollectionOptions<TT, P>,
  ): Promise<LoadedCollection<Loaded<TT, P>>>;
  private getEntityManager;
  private createCondition;
  private createManyToManyCondition;
  private createLoadCountCondition;
  private checkInitialized;
  /**
   * re-orders items after searching with `$in` operator
   */
  private reorderItems;
  private cancelOrphanRemoval;
  private validateModification;
  /** Converts all items in the collection to plain DTO objects. */
  toArray<TT extends T>(): EntityDTO<TT>[];
  /** Returns the primary key values (or a specific field) of all items in the collection. */
  getIdentifiers<U extends IPrimaryKey = Primary<T> & IPrimaryKey>(field?: string | string[]): U[];
  /**
   * @internal
   */
  addWithoutPropagation(entity: T): void;
  /** Replaces all items in the collection with the given items. */
  set(items: Iterable<T | Reference<T>>): void;
  private compare;
  /**
   * @internal
   */
  hydrate(items: T[], forcePropagate?: boolean, partial?: boolean): void;
  /**
   * Remove all items from the collection. Note that removing items from collection does not necessarily imply deleting the target entity,
   * it means we are disconnecting the relation - removing items from collection, not removing entities from database - `Collection.remove()`
   * is not the same as `em.remove()`. If we want to delete the entity by removing it from collection, we need to enable `orphanRemoval: true`,
   * which tells the ORM we don't want orphaned entities to exist, so we know those should be removed.
   */
  removeAll(): void;
  /**
   * @internal
   */
  removeWithoutPropagation(entity: T): void;
  /**
   * Extracts a slice of the collection items starting at position start to end (exclusive) of the collection.
   * If end is null it returns all elements from start to the end of the collection.
   */
  slice(start?: number, end?: number): T[];
  /**
   * Tests for the existence of an element that satisfies the given predicate.
   */
  exists(cb: (item: T) => boolean): boolean;
  /**
   * Returns the first element of this collection that satisfies the predicate.
   */
  find<S extends T>(cb: (item: T, index: number) => item is S): S | undefined;
  /**
   * Returns the first element of this collection that satisfies the predicate.
   */
  find(cb: (item: T, index: number) => boolean): T | undefined;
  /**
   * Extracts a subset of the collection items.
   */
  filter<S extends T>(cb: (item: T, index: number) => item is S): S[];
  /**
   * Extracts a subset of the collection items.
   */
  filter(cb: (item: T, index: number) => boolean): T[];
  /**
   * Maps the collection items based on your provided mapper function.
   */
  map<R>(mapper: (item: T, index: number) => R): R[];
  /**
   * Maps the collection items based on your provided mapper function to a single object.
   */
  reduce<R>(cb: (obj: R, item: T, index: number) => R, initial?: R): R;
  /**
   * Maps the collection items to a dictionary, indexed by the key you specify.
   * If there are more items with the same key, only the first one will be present.
   */
  indexBy<K1 extends keyof T, K2 extends keyof T = never>(key: K1): Record<T[K1] & PropertyKey, T>;
  /**
   * Maps the collection items to a dictionary, indexed by the key you specify.
   * If there are more items with the same key, only the first one will be present.
   */
  indexBy<K1 extends keyof T, K2 extends keyof T = never>(key: K1, valueKey: K2): Record<T[K1] & PropertyKey, T[K2]>;
  /** Returns whether the collection has been initialized. Pass `fully = true` to also check that all items are initialized. */
  isInitialized(fully?: boolean): boolean;
  isDirty(): boolean;
  /** Returns whether the collection was partially loaded (propagation is disabled for partial collections). */
  isPartial(): boolean;
  setDirty(dirty?: boolean): void;
  get length(): number;
  [Symbol.iterator](): IterableIterator<T>;
  /**
   * @internal
   */
  takeSnapshot(forcePropagate?: boolean): void;
  /**
   * @internal
   */
  getSnapshot(): T[] | undefined;
  /**
   * @internal
   */
  get property(): EntityProperty;
  /**
   * @internal
   */
  set property(prop: EntityProperty);
  protected propagate(item: T, method: 'add' | 'remove' | 'takeSnapshot'): void;
  protected propagateToInverseSide(item: T, method: 'add' | 'remove' | 'takeSnapshot'): void;
  protected propagateToOwningSide(item: T, method: 'add' | 'remove' | 'takeSnapshot'): void;
  protected shouldPropagateToCollection(
    collection: Collection<O, T>,
    method: 'add' | 'remove' | 'takeSnapshot',
  ): boolean;
  protected incrementCount(value: number): void;
}
/** Options for initializing a collection via `init()` or `load()`. */
export interface InitCollectionOptions<
  T,
  P extends string = never,
  F extends string = '*',
  E extends string = never,
> extends EntityLoaderOptions<T, F, E> {
  /** Whether to use the dataloader for batching collection loads. */
  dataloader?: boolean;
  /** Relations to populate on the loaded items. */
  populate?: Populate<T, P>;
  /** Populate only references (without loading full entities). Works only with M:N collections that use pivot table. */
  ref?: boolean;
}
/** Options for the `Collection.loadCount()` method. */
export interface LoadCountOptions<T extends object> extends CountOptions<T, '*'> {
  /** Whether to reload the count from the database even if it is already cached. */
  refresh?: boolean;
  /** Additional filtering conditions for the count query. */
  where?: FilterQuery<T>;
}
