import { Container } from "./Container";
import { DescriptorBuilder } from "./DescriptorBuilder";
import { Descriptor, IContainerBuilder, IDescriptorBuilder, DescriptorMap, ServiceLocator, ILifetime, IDestroyable } from "./interfaces";
import { emptyLifetime, LifetimeManager } from "./LifetimeManager";
import { isDestroyable } from "./traits";

/**
 * Container builder used to prepare service descriptors and create a IoC container
 */
export class ContainerBuilder<S, U extends keyof S> implements
    IContainerBuilder<S, U> {

    private _pending = 1;

    private readonly _services: DescriptorMap<S>;

    private readonly _lifetimeManager = new LifetimeManager();

    private readonly _lifetime: ILifetime<IDestroyable>;

    constructor(parentServices?: DescriptorMap<S>, lifetime?: ILifetime<IDestroyable>) {
        this._services = Object.create(parentServices ? parentServices : null) as DescriptorMap<S>;
        this._lifetimeManager = new LifetimeManager();
        this._lifetime = lifetime ?? emptyLifetime();
    }
    createServiceBuilder<K extends U>(name: K):
        IDescriptorBuilder<S, S[K], Record<never, never>, U> {

        return new DescriptorBuilder(this._lifetimeManager, this._register(name), this._fail);

    }

    build(): ServiceLocator<S> {
        this._assertBuilding();
        if (!this._complete())
            throw new Error("The configuration didn't complete.");

        const lifetime = this._lifetime;

        const detach = isDestroyable(lifetime) ? () => lifetime.destroy() : () => void (0);

        const container = new Container(this._services, this._lifetimeManager, detach);
        lifetime.store(container);

        return container;
    }

    private readonly _register = <K extends U>(name: K) =>
        (descriptor: Descriptor<S, S[K]>) => {
            this._complete();
            this._services[name] = descriptor;
        };

    private readonly _fail = (ex: unknown) => {
        throw ex;
    };

    private _assertBuilding() {
        if (!this._pending)
            throw new Error("The descriptor builder is finalized");
    }

    private _complete() {
        return !(--this._pending);
    }

}