"use strict";
/*
 * @adonisjs/fold
 *
 * (c) Harminder Virk <virk@adonisjs.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.Registrar = void 0;
const path_1 = require("path");
const utils_1 = require("@poppinss/utils");
const helpers_1 = require("@poppinss/utils/build/helpers");
/**
 * Registrar is used to register and boot the providers
 */
class Registrar {
    constructor(providerConstructorParams, basePath) {
        this.providerConstructorParams = providerConstructorParams;
        this.basePath = basePath;
        /**
         * The first level of provider paths provided to the registrar
         */
        this.providersPaths = [];
        /**
         * An array of loaded providers. Their can be more providers than the
         * `_providersPaths` array, since each provider can provide it's
         * own sub providers
         */
        this.providers = [];
        /**
         * Method to instantiate provider instances. One can also defined
         * a custom instantiater function
         */
        this.providersInstantiater = (provider) => new provider(...this.providerConstructorParams);
        /**
         * Whether or not the providers can be collected
         */
        this.collected = false;
    }
    /**
     * Load the provider by requiring the file from the disk
     * and instantiate it. If ioc container is using ES6
     * imports, then default exports are handled
     * automatically.
     */
    async loadProvider(providerPath, basePath) {
        providerPath = this.basePath
            ? (0, helpers_1.resolveFrom)(basePath || this.basePath, providerPath)
            : providerPath;
        const provider = (0, utils_1.esmRequire)(providerPath);
        if (typeof provider !== 'function') {
            throw new utils_1.Exception(`"${providerPath}" provider must use export default statement`);
        }
        return {
            provider: this.providersInstantiater(provider),
            resolvedPath: (0, path_1.dirname)(providerPath),
        };
    }
    /**
     * Loop's over an array of provider paths and pushes them to the
     * `providers` collection. This collection is later used to
     * register and boot providers
     */
    async collect(providerPaths, basePath) {
        for (let providerPath of providerPaths) {
            const { provider, resolvedPath } = await this.loadProvider(providerPath, basePath);
            this.providers.push(provider);
            if (provider.provides) {
                await this.collect(provider.provides, resolvedPath);
            }
        }
    }
    /**
     * Register an array of provider paths
     */
    useProviders(providersPaths, callback) {
        this.providersPaths = providersPaths;
        if (typeof callback === 'function') {
            this.providersInstantiater = callback;
        }
        return this;
    }
    /**
     * Register all the providers by instantiating them and
     * calling the `register` method.
     *
     * The provider instance will be returned, which can be used
     * to boot them as well.
     */
    async register() {
        if (this.collected) {
            return this.providers;
        }
        this.collected = true;
        await this.collect(this.providersPaths);
        /**
         * Register collected providers
         */
        this.providers.forEach((provider) => {
            if (typeof provider.register === 'function') {
                provider.register();
            }
        });
        return this.providers;
    }
    /**
     * Boot all the providers by calling the `boot` method.
     * Boot methods are called in series.
     */
    async boot() {
        const providers = await this.register();
        for (let provider of providers) {
            if (typeof provider.boot === 'function') {
                await provider.boot();
            }
        }
    }
    /**
     * Register an boot providers together.
     */
    async registerAndBoot() {
        const providers = await this.register();
        await this.boot();
        return providers;
    }
}
exports.Registrar = Registrar;
