import { ActivationContext } from "./ActivationContext";
import { ActivationError } from "./ActivationError";
import { ContainerBuilder } from "./ContainerBuilder";
import { DescriptorMap, IContainerBuilder, IDestroyable, ILifetimeManager, ServiceLocator } from "./interfaces";

export class Container<S> implements ServiceLocator<S>, IDestroyable {
    private readonly _services: DescriptorMap<S>;

    private readonly _lifetimeManager: ILifetimeManager;

    private _disposed: boolean;

    private readonly _onDestroyed: () => void;

    constructor(services: DescriptorMap<S>, lifetimeManager: ILifetimeManager, destroyed: () => void) {
        this._services = {
            ...services,
            container: {
                configurable: false,
                activate: () => this
            },
            childContainer: {
                configurable: false,
                activate: () => this.createChildBuilder(),
            }
        };

        this._disposed = false;
        this._lifetimeManager = lifetimeManager;
        this._onDestroyed = destroyed;
        
    }

    private _assertNotDestroyed() {
        if (this._disposed)
            throw new Error("The container is destroyed");
    }

    createChildBuilder(): IContainerBuilder<S, keyof S> {
        this._assertNotDestroyed();

        const lifetime = this._lifetimeManager.create<IDestroyable>();

        return new ContainerBuilder(this._services, lifetime);
    }

    resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
    resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
    resolve<K extends keyof S, T>(name: K, def?: T) {
        this._assertNotDestroyed();

        const d = this._services[name];
        if (d === undefined) {
            if (arguments.length > 1)
                return def;
            else
                throw new Error(`Service '${String(name)}' isn't found`);
        } else {
            const context = new ActivationContext(this._lifetimeManager, this._services, String(name), d);
            try {
                return d.activate(context);
            } catch (error) {
                throw new ActivationError(name.toString(), context.getStack(), error);
            }
        }
    }
    destroy() {
        if (this._disposed)
            return;
        this._disposed = true;
        this._lifetimeManager.destroy();
        (0,this._onDestroyed)();
    }
}
