import {  ConfigurationMap, ConfigurationMapConstraint, ContainerServices, ContainerServicesConstraint, ExtractRequiredKeys, IContainerBuilder } from "./interfaces";
import { argumentNotNull, each, isKey } from "./traits";

type ContainerExtensionConstraint<X, S> = ContainerServicesConstraint<Pick<S, keyof X & keyof S>>;

export class FluentConfiguration<S extends ContainerServicesConstraint<S>, Y extends keyof S = keyof S> {

    private _builders: Partial<ConfigurationMap<ContainerServices<S>, keyof S, keyof S>> = {};

    /** Adds a declaration of the services to the current config.
     * 
     * @template D The map of the services
     * @returns self
     */
    declare<D extends ContainerExtensionConstraint<D,S>>(): FluentConfiguration<S & D, Y | keyof D> {
        return this as unknown as FluentConfiguration<S & D, Y | keyof D>;
    }

    /** Adds compile-time information about the already provided services
     * 
     * @template P The map of the provided services
     * @returns self
     */
    provided<P extends Pick<S, keyof P & keyof S>>(): FluentConfiguration<S & P, Exclude<Y, keyof P>> {
        return this as unknown as FluentConfiguration<S & P, Exclude<Y, keyof P>>;
    }

    /** Register the service.
     * 
     * @param name The name of the service
     * @param builder The service builder
     * @returns self
     */
    register<K extends Y>(name: K, builder: ConfigurationMap<ContainerServices<S>,Y, keyof S>[K]): FluentConfiguration<S, Exclude<Y, K>>;
    /** Registers the collection of services
     * @param config The collection of services to register.
     * @returns self
     */
    register<X extends ConfigurationMapConstraint<ContainerServices<S>, Y, keyof X>>(config: X): FluentConfiguration<S, Exclude<Y, keyof X>>;
    register<K extends Y>(nameOrConfig: K | ConfigurationMap<ContainerServices<S>, K, keyof S>, builder?: ConfigurationMap<ContainerServices<S>, Y, keyof S>[K]) {
        if (isKey(nameOrConfig)) {
            argumentNotNull(builder, "builder");
            this._builders[nameOrConfig] = builder;
        } else {
            each(nameOrConfig, (v, k) => this.register(k, v));
        }

        return this; // as FluentConfiguration<S, Exclude<Y, K>>;
    }

    /**
     * This method is used to enable a compile time check of the configuration.
     * If there are not configured services in the configuration the compiler
     * will trigger the error.
     * 
     * @param missing Empty object literal should always be specified.
     * @returns self
     */
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    done(...args: ExtractRequiredKeys<S, Y> extends never ? [] : [services: {[ k in ExtractRequiredKeys<S, Y>]: "required"}]) {
    //done() {
        return this;
    }

    configure<C extends IContainerBuilder<ContainerServices<S>, keyof S>>(builder: C) {
        each(this._builders, (v, k) => {
            v(builder.createServiceBuilder(k));
        });
        return builder.build();
    }

}
