import { Faker } from '@faker-js/faker';
import { LocaleDefinition } from '@faker-js/faker';
import { Options } from 'ajv';
import { Randomizer } from '@faker-js/faker';

declare type AfterBuildHook<T> = (obj: T) => Promise<T> | T;

declare type BeforeBuildHook<T> = (params: Partial<T>) => Partial<T> | Promise<Partial<T>>;

declare interface CreateManyOptions<T> {
    adapter?: PersistenceAdapter<T>;
}

declare interface CreateOptions<T> {
    adapter?: PersistenceAdapter<T>;
}

declare interface DateSequenceOptions {
    start: Date | number | string;
    step?: number;
    unit?: 'day' | 'hour' | 'minute';
}

/**
 * A factory class for generating type-safe mock data by extending Faker.js functionality.
 * Provides methods for creating single instances, batches, and complex object compositions
 * with support for circular references through depth control and persistence.
 *
 * @template T - The type of objects this factory generates
 * @template O - The type of factory options
 */
declare class Factory<T, O extends FactoryOptions = FactoryOptions, F extends FactoryFunction<T> | PartialFactoryFunction<T> = FactoryFunction<T>> extends Faker {
    #private;
    readonly options?: {
        maxDepth: number;
    } & Omit<O, 'locale' | 'maxDepth' | 'randomizer'>;
    protected afterBuildHooks: AfterBuildHook<T>[];
    protected beforeBuildHooks: BeforeBuildHook<T>[];
    protected readonly factory: F;
    private defaultAdapter?;
    readonly sequence: SequenceFactory;
    constructor(factory: F, { locale, randomizer, ...rest }?: Partial<O>);
    /**
     * Adds a hook that will be executed after building the instance.
     * Hooks are executed in the order they were added and can be either synchronous or asynchronous.
     * This method returns the factory instance for method chaining.
     *
     * @param hook Function that receives the built instance and returns the modified instance
     * @returns The current Factory instance for method chaining
     */
    afterBuild(hook: AfterBuildHook<T>): this;
    /**
     * Generates an array of instances using the factory's schema.
     * Supports both uniform overrides (same for all instances) and individual overrides per instance.
     *
     * @param size Number of instances to generate (must be non-negative integer)
     * @param kwargs Either a single partial object (applied to all) or an array of partials (one per instance)
     * @returns Array of generated instances
     * @throws {Error} If size is negative or not an integer
     */
    batch: (size: number, kwargs?: Partial<T> | Partial<T>[]) => T[];
    /**
     * Creates multiple instances asynchronously, allowing use of async factory functions.
     * This method supports both synchronous and asynchronous factory functions and hooks.
     *
     * @param size Number of instances to generate (must be non-negative integer)
     * @param kwargs Either a single partial object (applied to all) or an array of partials (one per instance)
     * @returns Promise that resolves to an array of generated instances
     * @throws {Error} If size is negative or not an integer
     *
     * @example
     * ```typescript
     * const UserFactory = new Factory<User>(async (factory) => ({
     *   id: factory.string.uuid(),
     *   email: factory.internet.email(),
     *   apiKey: await generateApiKey() // async operation
     * }));
     *
     * const users = await UserFactory.batchAsync(5);
     * ```
     */
    batchAsync(size: number, kwargs?: Partial<T> | Partial<T>[]): Promise<F extends FactoryFunction<T> ? T[] : Partial<T>[]>;
    /**
     * Adds a hook that will be executed before building the instance.
     * Hooks receive the partial parameters (kwargs) and can modify them before the instance is built.
     * Multiple hooks are executed in the order they were added.
     *
     * @param hook Function that receives partial parameters and returns modified parameters
     * @returns The current Factory instance for method chaining
     */
    beforeBuild(hook: BeforeBuildHook<T>): this;
    /**
     * Generates a single instance of type T using the factory's schema.
     * Properties can be overridden by passing a partial object.
     * Synchronous hooks are automatically applied if registered.
     * If async hooks are registered, a ConfigurationError is thrown.
     *
     * @param kwargs Properties to override in the generated instance
     * @param options Factory options including fixture generation
     * @returns A new instance with factory-generated values merged with any overrides
     * @throws {ConfigurationError} If async hooks are registered
     * @throws {FixtureError} If fixture operations fail
     * @throws {FixtureValidationError} If fixture validation fails
     */
    build: (kwargs?: Partial<T>, options?: Partial<O>) => F extends FactoryFunction<T> ? T : Partial<T>;
    /**
     * Builds an instance asynchronously with all registered hooks applied in sequence.
     * This method supports both synchronous and asynchronous hooks.
     * Hooks are executed in the order they were registered.
     *
     * @param kwargs Optional properties to override in the generated instance
     * @param options Factory options including fixture generation
     * @returns A promise that resolves to the built and processed instance
     * @throws {Error} If any hook throws an error during execution
     * @throws {FixtureError} If fixture operations fail
     * @throws {FixtureValidationError} If fixture validation fails
     */
    buildAsync(kwargs?: Partial<T>, options?: Partial<O>): Promise<F extends FactoryFunction<T> ? T : Partial<T>>;
    /**
     * Creates a new factory by merging this factory's schema with additional properties.
     * Composed properties can be static values or other factory instances.
     *
     * @template U The composed type (must extend the base type T)
     * @param composition Object mapping property names to values or factories
     * @returns A new factory that generates objects with combined properties
     */
    compose<U extends T>(composition: FactoryComposition<U>): Factory<U>;
    /**
     * Creates and persists a single instance using the factory's schema.
     * Uses the configured persistence adapter if available.
     *
     * @param kwargs Optional properties to override in the generated instance
     * @param options Options including an optional persistence adapter
     * @returns Promise that resolves with the persisted instance
     * @throws {ConfigurationError} If no persistence adapter is configured
     *
     * @example
     * ```typescript
     * // With default adapter
     * const userFactory = new Factory<User>((faker) => ({
     *   email: faker.internet.email(),
     *   name: faker.person.fullName()
     * })).withAdapter(mongooseAdapter);
     *
     * const user = await userFactory.create({ name: 'John' });
     *
     * // With adapter in options
     * const user2 = await userFactory.create(
     *   { name: 'Jane' },
     *   { adapter: prismaAdapter }
     * );
     * ```
     */
    create(kwargs?: Partial<T>, options?: CreateOptions<T>): Promise<T>;
    /**
     * Creates and persists multiple instances in a batch operation.
     * Uses the configured persistence adapter if available.
     *
     * @param size Number of instances to create and persist
     * @param kwargs Optional overrides for the instances
     * @param options Options including an optional persistence adapter
     * @returns Promise that resolves with the persisted instances
     * @throws {ConfigurationError} If no persistence adapter is configured
     *
     * @example
     * ```typescript
     * // Create 5 users with default adapter
     * const users = await userFactory.createMany(5);
     *
     * // With individual overrides
     * const users2 = await userFactory.createMany(3, [
     *   { role: 'admin' },
     *   { role: 'user' },
     *   { role: 'guest' }
     * ]);
     *
     * // With adapter in options
     * const users3 = await userFactory.createMany(
     *   10,
     *   undefined,
     *   { adapter: typeormAdapter }
     * );
     * ```
     */
    createMany(size: number, kwargs?: Partial<T> | Partial<T>[], options?: CreateManyOptions<T>): Promise<T[]>;
    /**
     * Creates a new factory that inherits from this factory's schema with modifications.
     * Unlike compose(), extend() provides access to the factory instance for dynamic property generation.
     *
     * @template U The extended type (must extend the base type T)
     * @param factoryFn Function that returns properties to merge with the base schema
     * @returns A new factory with inherited and extended properties
     */
    extend<U extends T>(factoryFn: FactoryFunction<U>): Factory<U>;
    /**
     * Creates a generator that yields values from an iterable in sequential, cyclic order.
     * Values must be explicitly requested via the generator's next() method. When all values
     * have been yielded, the generator starts over from the beginning.
     *
     * @template T The type of elements in the iterable
     * @param iterable An iterable containing values to cycle through (must not be empty)
     * @returns A generator that yields values in order, restarting after the last element
     * @throws {Error} If the iterable is empty
     */
    iterate<U>(iterable: Iterable<U>): Generator<U, U, U>;
    /**
     * Creates a new factory where all properties are optional (Partial<T>).
     * This is useful for creating test data where only specific fields need to be set.
     *
     * @returns A new factory that produces Partial<T> objects
     */
    partial(): Factory<Partial<T>>;
    /**
     * Creates a generator that yields random values from an iterable without consecutive duplicates.
     * Each value is randomly selected with replacement, but the generator ensures the same value
     * is never returned twice in a row (unless the iterable contains only one element).
     *
     * @template T The type of elements in the iterable
     * @param iterable An iterable containing values to sample from (must not be empty)
     * @returns A generator that yields random values without consecutive repetition
     * @throws {Error} If the iterable is empty
     */
    sample<U>(iterable: Iterable<U>): Generator<U, U, U>;
    /**
     * Creates a reference to a function call for lazy evaluation within factory definitions.
     * The function and its arguments are stored but not executed until the factory builds an object.
     * Essential for creating relationships between factories without causing infinite recursion.
     *
     * @template C The function type
     * @param handler The function to call during object generation
     * @param args Arguments to pass to the function when called
     * @returns A reference that will execute the function during build
     */
    use<R, A extends unknown[]>(handler: (...args: A) => R, ...args: A): R;
    /**
     * Sets the default persistence adapter for this factory instance.
     *
     * @param adapter The persistence adapter to use as default
     * @returns The current Factory instance for method chaining
     *
     * @example
     * ```typescript
     * import { MongooseAdapter } from './adapters/mongoose-adapter';
     *
     * const userFactory = new Factory<User>((faker) => ({
     *   id: faker.string.uuid(),
     *   email: faker.internet.email(),
     *   name: faker.person.fullName()
     * }));
     *
     * // Set default adapter
     * const factoryWithDb = userFactory.withAdapter(
     *   new MongooseAdapter(UserModel)
     * );
     *
     * // Now all create/createMany calls will use this adapter
     * const user = await factoryWithDb.create();
     * const users = await factoryWithDb.createMany(5);
     * ```
     */
    withAdapter(adapter: PersistenceAdapter<T>): this;
    protected buildWithFixture(filePath: string, kwargs: Partial<T> | undefined, _options: FactoryOptions & O): F extends FactoryFunction<T> ? T : Partial<T>;
    protected buildWithFixtureAsync(filePath: string, kwargs: Partial<T> | undefined, _options: FactoryOptions & O): Promise<F extends FactoryFunction<T> ? T : Partial<T>>;
    protected calculateSignature(config: Required<FixtureConfiguration>): string;
    /* Excluded from this release type: createDepthLimitedProxy */
    protected getDefaultFixturePath(): string;
    protected getFixtureConfig(): Required<FixtureConfiguration>;
    /* Excluded from this release type: isDepthExceeded */
    protected parseFixturePath(filePath: string, config: Required<FixtureConfiguration>): {
        fixturesDir: string;
        fullPath: string;
    };
    protected readFixture(fullPath: string): FixtureMetadata | null;
    protected validateFixture(metadata: FixtureMetadata, config: Required<FixtureConfiguration>): void;
    protected writeFixture(parsedPath: {
        fixturesDir: string;
        fullPath: string;
    }, data: unknown, config: Required<FixtureConfiguration>): void;
}

declare type FactoryComposition<T> = {
    [K in keyof T]?: Factory<T[K]> | T[K];
};

declare type FactoryFunction<T> = (factory: Factory<T>, iteration: number, kwargs?: Partial<T>) => FactorySchema<T> | Promise<FactorySchema<T>>;

declare interface FactoryOptions {
    /**
     * Fixture configuration for caching generated data
     */
    fixtures?: FixtureConfiguration;
    /**
     * Enable fixture generation/loading for this build.
     * - true: Use default fixture path based on call stack
     * - string: Use as fixture file path
     */
    generateFixture?: boolean | string;
    locale?: LocaleDefinition | LocaleDefinition[];
    maxDepth?: number;
    randomizer?: Randomizer;
}

declare type FactorySchema<T> = {
    [K in keyof T]: Generator<T[K], T[K], T[K]> | Ref<T[K], (...args: unknown[]) => T[K]> | T[K];
};

declare interface FixtureConfiguration {
    /**
     * Base directory for storing fixtures. Defaults to process.cwd()
     */
    basePath?: string;
    /**
     * Custom directory name for fixtures. Defaults to '__fixtures__'
     * Set to empty string to store fixtures in the same directory as the file path
     */
    directory?: string;
    /**
     * Whether to include the factory function source in signature calculation. Defaults to true
     */
    includeSource?: boolean;
    /**
     * Whether to use subdirectories for fixtures. Defaults to true
     * When true: fixtures are stored in a subdirectory (e.g., /path/__fixtures__/file.json)
     * When false: fixtures are stored directly in the path (e.g., /path/file.json)
     */
    useSubdirectory?: boolean;
    /**
     * Whether to validate factory signature changes. Defaults to true
     */
    validateSignature?: boolean;
}

declare interface FixtureMetadata {
    /**
     * ISO 8601 timestamp when the fixture was created
     */
    createdAt: string;
    /**
     * The actual fixture data
     */
    data: unknown;
    /**
     * SHA-256 hash of the factory configuration
     */
    signature: string;
    /**
     * Fixture version for future compatibility
     */
    version: number;
}

declare interface IncrementSequenceOptions {
    start?: number;
    step?: number;
}

export declare type JsonSchema = boolean | JsonSchemaObject;

export declare class JsonSchemaFactory<T = unknown, O extends JsonSchemaFactoryOptions = JsonSchemaFactoryOptions> extends Factory<T, O> {
    #private;
    batch: (size: number, kwargs?: Partial<T> | Partial<T>[]) => T[];
    batchAsync: (size: number, kwargs?: Partial<T> | Partial<T>[]) => Promise<T[]>;
    build: (kwargs?: Partial<T>, options?: Partial<O>) => T;
    buildAsync: (kwargs?: Partial<T>, options?: Partial<O>) => Promise<T>;
    constructor(schema: JsonSchema, optionsOrFactory?: O | PartialFactoryFunction<T>, options?: O);
    get schema(): JsonSchema;
}

export declare interface JsonSchemaFactoryOptions extends FactoryOptions {
    ajv?: Options;
}

export declare interface JsonSchemaObject {
    $defs?: Record<string, JsonSchema>;
    $ref?: string;
    additionalProperties?: boolean | JsonSchema;
    anyOf?: JsonSchema[];
    const?: unknown;
    definitions?: Record<string, JsonSchema>;
    enum?: unknown[];
    exclusiveMaximum?: boolean | number;
    exclusiveMinimum?: boolean | number;
    format?: 'date' | 'date-time' | 'email' | 'time' | 'uri' | 'uuid' | string;
    items?: JsonSchema | JsonSchema[];
    maxItems?: number;
    maxLength?: number;
    maximum?: number;
    minItems?: number;
    minLength?: number;
    minimum?: number;
    multipleOf?: number;
    oneOf?: JsonSchema[];
    pattern?: string;
    properties?: Record<string, JsonSchema>;
    required?: string[];
    type?: JsonSchemaPrimitiveType | JsonSchemaPrimitiveType[];
}

export declare type JsonSchemaPrimitiveType = 'array' | 'boolean' | 'integer' | 'null' | 'number' | 'object' | 'string';

declare type PartialFactoryFunction<T> = (factory: Factory<T>, iteration: number) => Partial<FactorySchema<T>> | Promise<Partial<FactorySchema<T>>>;

/**
 * Interface for a persistence adapter.
 * Adapters are responsible for interacting with the underlying data store
 * (e.g., database, API) to save generated objects.
 */
declare interface PersistenceAdapter<T, R = T> {
    /**
     * Persists a single generated object to the data store.
     *
     * @param data The object to persist.
     * @returns A promise that resolves with the persisted object,
     * including any properties assigned by the database (e.g., auto-generated IDs).
     */
    create(data: T): Promise<R>;
    /**
     * Persists multiple generated objects to the data store in a batch.
     *
     * @param data An array of objects to persist.
     * @returns A promise that resolves with an array of the persisted objects,
     * including any properties assigned by the database.
     */
    createMany(data: T[]): Promise<R[]>;
}

/**
 * Encapsulates a function and its arguments for deferred execution within factories.
 * This enables lazy evaluation of nested factory calls, preventing infinite recursion
 * and allowing complex object relationships to be defined declaratively.
 *
 * @template T - The return type of the encapsulated function
 * @template C - The function type that returns T
 */
declare class Ref<T, C extends (...args: never[]) => T> {
    private readonly args;
    private readonly handler;
    constructor({ args, handler }: {
        args: Parameters<C>;
        handler: C;
    });
    callHandler(): T;
}

declare interface SequenceFactory {
    date(options: DateSequenceOptions): Generator<Date, Date, Date>;
    increment(options?: IncrementSequenceOptions): Generator<number, number, number>;
    template(pattern: string, options?: TemplateSequenceOptions): Generator<string, string, string>;
}

declare interface TemplateSequenceOptions {
    start?: number;
}

export { }
