import { EntityManager, EntitySchema, MetadataStorage, MikroORM } from '@mikro-orm/core';
import { Scope } from '@nestjs/common';
import { MIKRO_ORM_MODULE_OPTIONS, getEntityManagerToken, getMikroORMToken, getRepositoryToken, logger, } from './mikro-orm.common.js';
import { MikroOrmEntitiesStorage } from './mikro-orm.entities.storage.js';
export function createMikroOrmProvider(contextName, type = MikroORM) {
    if (!contextName && type !== MikroORM) {
        return {
            provide: type,
            useFactory: orm => orm, // just a simple alias
            inject: [MikroORM], // depend on the ORM from core package
        };
    }
    return {
        provide: contextName ? getMikroORMToken(contextName) : type,
        useFactory: async (options) => {
            options = { logger: logger.log.bind(logger), ...options };
            if (options.autoLoadEntities) {
                options.entities = [
                    ...(options.entities || []),
                    ...MikroOrmEntitiesStorage.getEntities(contextName),
                ];
                options.entitiesTs = [
                    ...(options.entitiesTs || []),
                    ...MikroOrmEntitiesStorage.getEntities(contextName),
                ];
                delete options.autoLoadEntities;
            }
            return MikroORM.init(options);
        },
        inject: [MIKRO_ORM_MODULE_OPTIONS],
    };
}
export function createEntityManagerProvider(scope = Scope.DEFAULT, entityManager = EntityManager, contextName, forkOptions) {
    if (!contextName && entityManager !== EntityManager) {
        return {
            provide: entityManager,
            scope,
            useFactory: (em) => em, // just a simple alias, unlike `useExisting` from nest, this works with request scopes too
            inject: [EntityManager], // depend on the EM from core package
        };
    }
    return {
        provide: contextName ? getEntityManagerToken(contextName) : entityManager,
        scope,
        useFactory: (orm) => (scope === Scope.DEFAULT ? orm.em : orm.em.fork({ useContext: true, ...forkOptions })),
        inject: [contextName ? getMikroORMToken(contextName) : MikroORM],
    };
}
export function createMikroOrmAsyncOptionsProvider(options) {
    if (options.useFactory) {
        return {
            provide: MIKRO_ORM_MODULE_OPTIONS,
            useFactory: async (...args) => {
                const factoryOptions = await options.useFactory(...args);
                return options.contextName ? { contextName: options.contextName, ...factoryOptions } : factoryOptions;
            },
            inject: options.inject || [],
        };
    }
    const inject = [];
    if (options.useClass || options.useExisting) {
        inject.push(options.useClass ?? options.useExisting);
    }
    return {
        provide: MIKRO_ORM_MODULE_OPTIONS,
        useFactory: async (optionsFactory) => await optionsFactory.createMikroOrmOptions(options.contextName),
        inject,
    };
}
export function createAsyncProviders(options) {
    if (options.useExisting || options.useFactory) {
        return [createMikroOrmAsyncOptionsProvider(options)];
    }
    if (options.useClass) {
        return [createMikroOrmAsyncOptionsProvider(options), { provide: options.useClass, useClass: options.useClass }];
    }
    throw new Error('Invalid MikroORM async options: one of `useClass`, `useExisting` or `useFactory` should be defined.');
}
export function createMikroOrmRepositoryProviders(entities, contextName) {
    const metadata = Object.values(MetadataStorage.getMetadata());
    const providers = [];
    const inject = contextName ? getEntityManagerToken(contextName) : EntityManager;
    (entities || []).forEach(entity => {
        const meta = entity instanceof EntitySchema
            ? entity.meta
            : (typeof entity === 'function' ? EntitySchema.REGISTRY.get(entity)?.meta : undefined) ?? metadata.find(meta => meta.class === entity);
        const repository = meta?.repository;
        if (repository) {
            providers.push({
                provide: repository(),
                useFactory: em => em.getRepository(entity),
                inject: [inject],
            });
        }
        providers.push({
            provide: getRepositoryToken(entity, contextName),
            useFactory: em => em.getRepository(entity),
            inject: [inject],
        });
    });
    return providers;
}
