import type {
  AddEager,
  AddOptional,
  Dictionary,
  EntityClass,
  EntityKey,
  EntityProperty,
  Loaded,
  LoadedReference,
  Primary,
  Ref,
} from '../typings.js';
import type { FindOneOptions, FindOneOrFailOptions } from '../drivers/IDatabaseDriver.js';
/** Wrapper around an entity that provides lazy loading capabilities and identity-preserving reference semantics. */
export declare class Reference<T extends object> {
  private entity;
  private property?;
  constructor(entity: T);
  /** Creates a Reference wrapper for the given entity, preserving identity if one already exists. */
  static create<T extends object>(entity: T | Ref<T>): Ref<T>;
  /** Creates a Reference wrapper for an entity identified by its primary key, wrapped in a Ref. */
  static createFromPK<T extends object>(
    entityType: EntityClass<T>,
    pk: Primary<T>,
    options?: {
      schema?: string;
    },
  ): Ref<T>;
  /** Creates an uninitialized entity reference by primary key without wrapping it in a Reference. */
  static createNakedFromPK<T extends object>(
    entityType: EntityClass<T>,
    pk: Primary<T>,
    options?: {
      schema?: string;
    },
  ): T;
  /**
   * Checks whether the argument is instance of `Reference` wrapper.
   */
  static isReference<T extends object>(data: any): data is Reference<T>;
  /**
   * Wraps the entity in a `Reference` wrapper if the property is defined as `ref`.
   */
  static wrapReference<T extends object, O extends object>(
    entity: T | Reference<T>,
    prop: EntityProperty<O, T>,
  ): Reference<T> | T;
  /**
   * Returns wrapped entity.
   */
  static unwrapReference<T extends object>(ref: T | Reference<T> | ScalarReference<T> | Ref<T>): T;
  /**
   * Ensures the underlying entity is loaded first (without reloading it if it already is loaded). Returns the entity.
   * If the entity is not found in the database (e.g. it was deleted in the meantime, or currently active filters disallow loading of it)
   * the method returns `null`. Use `loadOrFail()` if you want an error to be thrown in such a case.
   */
  load<TT extends T, P extends string = never, F extends string = '*', E extends string = never>(
    options?: LoadReferenceOptions<TT, P, F, E>,
  ): Promise<Loaded<TT, P, F, E> | null>;
  /**
   * Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
   * Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
   */
  loadOrFail<TT extends T, P extends string = never, F extends string = '*', E extends string = never>(
    options?: LoadReferenceOrFailOptions<TT, P, F, E>,
  ): Promise<Loaded<TT, P, F, E>>;
  private set;
  /** Returns the underlying entity without checking initialization state. */
  unwrap(): T;
  /** Returns the underlying entity, throwing an error if the reference is not initialized. */
  getEntity(): T;
  /** Returns the value of a property on the underlying entity. Throws if the reference is not initialized. */
  getProperty<K extends keyof T>(prop: K): T[K];
  /** Loads the entity if needed, then returns the value of the specified property. */
  loadProperty<TT extends T, P extends string = never, K extends keyof TT = keyof TT>(
    prop: K,
    options?: LoadReferenceOrFailOptions<TT, P>,
  ): Promise<Loaded<TT, P>[K]>;
  /** Returns whether the underlying entity has been fully loaded from the database. */
  isInitialized(): boolean;
  /** Marks the underlying entity as populated or not for serialization purposes. */
  populated(populated?: boolean): void;
  /** Serializes the underlying entity to a plain JSON object. */
  toJSON(...args: any[]): Dictionary;
}
/** Wrapper for lazy scalar properties that provides on-demand loading from the database. */
export declare class ScalarReference<Value> {
  #private;
  private value?;
  private entity?;
  constructor(value?: Value | undefined, initialized?: boolean);
  /**
   * Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
   * Returns either the whole entity, or the requested property.
   */
  load(options?: Omit<LoadReferenceOptions<any, any>, 'populate' | 'fields' | 'exclude'>): Promise<Value | undefined>;
  /**
   * Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
   * Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
   */
  loadOrFail(options?: Omit<LoadReferenceOrFailOptions<any, any>, 'populate' | 'fields' | 'exclude'>): Promise<Value>;
  /** Sets the scalar value and marks the reference as initialized. */
  set(value: Value): void;
  /** Binds this scalar reference to a specific entity and property for lazy loading support. */
  bind<Entity extends object>(entity: Entity, property: EntityKey<Entity>): void;
  /** Returns the current scalar value, or undefined if not yet loaded. */
  unwrap(): Value | undefined;
  /** Returns whether the scalar value has been loaded. */
  isInitialized(): boolean;
}
/** Options for `Reference.load()` to control how the referenced entity is loaded. */
export interface LoadReferenceOptions<
  T extends object,
  P extends string = never,
  F extends string = '*',
  E extends string = never,
> extends FindOneOptions<T, P, F, E> {
  /** Whether to use the dataloader for batching reference loads. */
  dataloader?: boolean;
}
/** Options for `Reference.loadOrFail()` which throws when the entity is not found. */
export interface LoadReferenceOrFailOptions<
  T extends object,
  P extends string = never,
  F extends string = '*',
  E extends string = never,
> extends FindOneOrFailOptions<T, P, F, E> {
  /** Whether to use the dataloader for batching reference loads. */
  dataloader?: boolean;
}
/**
 * shortcut for `wrap(entity).toReference()`
 */
export declare function ref<I extends unknown | Ref<unknown> | undefined | null, T extends I & {}>(
  entity: I,
): (Ref<T> & LoadedReference<Loaded<T, AddEager<T>>>) | AddOptional<typeof entity>;
/**
 * shortcut for `Reference.createFromPK(entityType, pk)`
 */
export declare function ref<I extends unknown | undefined | null, T, PKV extends Primary<T> = Primary<T>>(
  entityType: EntityClass<T>,
  pk: I,
): Ref<T> | AddOptional<typeof pk>;
/**
 * shortcut for `Reference.createNakedFromPK(entityType, pk)`
 */
export declare function rel<T, PK extends Primary<T>>(entityType: EntityClass<T>, pk: T | PK): T;
export { Reference as Ref };
