// #region Imports
// #endregion
/**
 * Symbol used to identify if a class is injectable.
 *
 * @internal
 * @hidden
 */
export const KEY_IS_INJECTABLE = Symbol('IS_INJECTABLE');
/**
 * Represents a dependency injection container.
 *
 * @public
 */
export class Container {
    // #region Fields
    static _instance;
    _registry;
    // #endregion
    // #region Ctor
    /**
     * Constructs a new instance of the `Container` class.
     *
     * @protected
     */
    constructor() {
        this._registry = new Map();
    }
    // #endregion
    // #region Properties
    /**
     * Gets the singleton instance of the `Container` class.
     *
     * @public
     * @static
     * @readonly
     */
    static get instance() {
        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
        if (!Container._instance) {
            Container._instance = new Container();
        }
        return Container._instance;
    }
    // #endregion
    // #region Methods
    /**
     * Registers a provider for the specified token.
     *
     * @public
     * @param token - The token to register.
     * @param clazz - The class to associate with the token. If not provided, the token itself will be used as the class.
     * @param lifetime - The optional lifetime of the instance. If not provided, the default lifetime is 'singleton'.
     */
    provide(token, clazz, lifetime) {
        const actualClass = clazz ?? token;
        if (actualClass.prototype.constructor.length && !this.isInjectable(actualClass)) {
            throw new Error(`${actualClass.name} is not decorated with @Injectable!`);
        }
        this._registry.set(token, {
            token: actualClass,
            lifetime: lifetime ?? 'singleton'
        });
    }
    /**
     * Resolves an instance for the specified token.
     *
     * @public
     * @param token - The token to resolve.
     * @returns The resolved instance.
     * @throws Error if no instance is found for the token.
     */
    get(token) {
        const item = this._registry.get(token);
        if (!item) {
            throw new Error(`Nothing found for token ${token.name}`);
        }
        if (item.lifetime === 'singleton') {
            if (!item.instance) {
                const params = this.getInjectedParams(item.token);
                const newInstance = Reflect.construct(item.token, params);
                item.instance = newInstance;
            }
            if (item.instance) {
                return item.instance;
            }
            throw new Error(`Instance for token ${token.name} is not of type ${token.name}`);
        }
        // For transient classes, always create a new instance
        const params = this.getInjectedParams(item.token);
        const instance = Reflect.construct(item.token, params);
        if (instance instanceof item.token) {
            if (instance instanceof item.token) {
                return instance;
            }
            throw new Error(`Instance for token ${token.name} is not of type ${token.name}`);
        }
        throw new Error(`Instance for token ${token.name} is not of type ${token.name}`);
    }
    /**
     * Check if target is decorated
     *
     * @internal
     * @hidden
     * @param target - The target to check.
     */
    isInjectable(target) {
        return Reflect.get(target, KEY_IS_INJECTABLE) === true;
    }
    /**
     * Read type information from metadata
     *
     * @private
     */
    getInjectedParams(clazz) {
        const argTypes = Reflect.getMetadata('design:paramtypes', clazz) ?? [];
        return argTypes.map((token) => {
            if (typeof token === 'function') {
                if (typeof token === 'function') {
                    return this.get(token);
                }
                throw new Error(`Invalid token type: ${typeof token}`);
            }
            throw new Error(`Invalid token type: ${typeof token}`);
        });
    }
}
//# sourceMappingURL=Container.js.map