import { key } from "./traits";

export interface IDestroyable {
    destroy(): void;
}

/**
 * @template S Карта доступных зависимостей
 */
export interface Resolver<S> {
    /**
     * Функция для разрешения зависимостей, поддерживает создание фабричных методов,
     * отложенную активацию и значение по-умолчанию для сервисов 
     * @template K Ключ сервиса из {@linkcode S}
     * @template O Тип параметра {@linkcode opts} используется для выведения типа
     *   возвращаемого значения.
     * @param name Ключ сервиса, который будет разрешен.
     * @param {boolean=} opts.lazy Признак того, что требуется отложенная активация,
     *   будет возвращен фабричный метод для получения зависимости. Если не указан,
     *   то считается `false`.
     * @param {any=} opts.default Значение по умолчанию, если в контейнере указанный
     *   сервис не зарегистрирован
     * @returns Либо фабричный метод для получения зависимости, либо значение зависимости
     * @throws Error Если зависимость не найдена и не предоставлено значение по-умолчанию
     */
    <K extends keyof S, O extends { lazy: true; }>(name: K, opts?: O): () => NonNullable<S[K]> | InferDefault<O>;
    <K extends keyof S, O extends { lazy?: false; }>(name: K, opts?: O): NonNullable<S[K]> | InferDefault<O>;
}

export type DepsMap<S> = {
    [k in key]: Refs<S> | keyof S;
};

export type Refs<S> = {
    [k in keyof S]: Ref<k, S[k]>;
}[keyof S];

export type Ref<K extends key, D> = {
    /** The name of the service */
    name: K;

    /** Make a lazy reference, the resolved dependency will be a function */
    lazy?: boolean;

    /** The default value for the case where the service isn't defined.
     * When specified the dependency becomes optional, the default value can be
     * `null` or `undefined`
     */
    default?: D | null
};

export type Lazy<T, L extends boolean> = L extends true ? () => T : T;

/** Возвращает тип свойства `default` в типе {@link T} */
export type InferDefault<T> = T extends { default: infer D } ? D : never;

export type InferLazy<R> = R extends { lazy: infer L } ?
    L extends true ? true : false :
    false;
export type Resolve<S, R> =
    R extends keyof S ? NonNullable<S[R]> :
    R extends Ref<infer K, unknown> ?
    K extends keyof S ?
    Lazy<NonNullable<S[K]> | InferDefault<R>, InferLazy<R>> :
    never :
    never;

/**
 * Интерфейс для конфигурирования сервиса в контейнере. Конфигурирование сервиса
 * состоит из настройки различных параметров вызовами методов {@linkcode wants},
 * {@linkcode lifetime}, {@linkcode override}, {@linkcode cleanup}. Завершение настройки
 * сервиса осуществляется вызовом одного из методов {@linkcode factory} либо 
 * {@linkcode value}.
 * 
 * @template S Карта сервисов контейнера, доступных при описании дескриптора
 * @template T Тип сервиса
 * @template R Карта зависимостей, которая передается параметром фабрике
 * @template U Имена пользовательских сервисов, доступных для переопределения
 */
export interface IDescriptorBuilder<S, T, R, U extends keyof S> {

    /** Указывает фабрика для создания экземпляра сервиса, фабрика передается
     * в виде параметра. При вызове фабрике будет передан объект с зависимостями,
     * которые были предварительно указаны вызовами метода `wants(...)`
     * 
     * Вызов данного метода завершает конфигурирование сервиса.
     * 
     * @param f Фабрика для создания экземпляра сервиса.
     */
    factory(f: (refs: R) => NonNullable<T>): void;

    /**
     * Используется для указания зависимостей, которые потребуются фабричному
     * методу при создании нового экземпляра сервиса. Данный метод может быть
     * вызван несколько раз подряд, при этом вызовы этого метода имеют
     * кумулятивный эффект.
     * 
     * @template X Тип объекта с зависимостями, которые требуется получить при
     *   создании экземпляра фабрики при помощи фабричного метода.
     * @param refs Объект с описанием зависимостей
     * @returns Возвращает дескриптор сервиса, в котором указаны необходимые
     *   зависимости
     */
    wants<X extends DepsMap<S> & Record<keyof R & keyof X, never>>(refs: X):
        IDescriptorBuilder<S, T, R & {
            [k in keyof X]: Resolve<S, X[k]>;
        }, U>

    override<K extends U>(name: K, builder: BuildDescriptorFn<S, S[K], U>): this;
    override<X extends ConfigurationMapConstraint<S, U, keyof X>>(services: X): this;

    lifetime(lifetime: "singleton", typeId: string | number | object): this;
    lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;

    /** Указывает функцию для освобождения экземпляра сервиса для случаев, когда
     * время жизни привязано к контейнеру.
     */
    cleanup(cb: (item: T) => void): this;

    /**
     * Регистрирует в контейнере постоянное значение в качестве реализации сервиса.
     * 
     * @param v Экземпляр реализации сервиса.
     */
    value(v: NonNullable<T>): void;
}

export type BuildDescriptorFn<S, T, U extends keyof S> = (d: IDescriptorBuilder<S, T, Record<never, never>, U>) => void;

/**
 * Конфигурация контейнера, состоит из набора функций, которые выполняют конфигурацию.
 * 
 * Все параметры конфигурации являются обязательными, если требуется ввести
 * необязательные параметры, то нужно ограничить параметр типа {@linkcode K}
 * 
 * @template S Сервисы доступные в контейнере
 * @template K Сервисы участвующие в конфигурации
 */
export type ConfigurationMap<S, K extends keyof S, U extends keyof S> = {
    [k in K]-?: BuildDescriptorFn<S, S[k], U>
};

export type ConfigurationMapConstraint<S, U extends keyof S, X extends string | number | symbol> = {
    [k in X]-?: k extends U ? BuildDescriptorFn<S, S[k], U> : never;
};

/**
 * The type constraint useful to restrict type parameters to prevent defining
 * the services with the {@link ContainerKeys} names.
 * 
 * The constraint doesn't exclude using this keys but declares them as `never`
 * which effectively will lead using this keys to the error. 
 */
export type ContainerServicesConstraint<S> = {
    [k in keyof S]: k extends ContainerKeys ? never : S[k];
};

export interface Descriptor<S, T> {

    /** This flags indicates that this registration can be replaced or overridden. */
    readonly configurable?: boolean;

    /** If specified signals the activation context that a new service scope 
     * should be created to isolate service overrides.
     */
    readonly hasOverrides?: boolean;

    activate(context: IActivationContext<S>): NonNullable<T>;
}

export interface IActivationContext<S> extends ServiceLocator<S> {
    createLifetime<T>(): ILifetime<T>;

    createContainerLifetime<T>(): ILifetime<T>;

    register<K extends keyof S>(name: K, service: DescriptorMap<S>[K]): void;
}

/**
 * Descriptors map for the specified services {@linkcode S}. All entries are
 * optional regardless the required or optional services in the original map.
 * 
 * @template S Сервисы контекста активации
 * @template U Карта сервисов которые создаются дескрипторами
 */
export type DescriptorMap<S> = {
    [k in keyof S]?: Descriptor<S, S[k]>;
};

type ContainerKeys = keyof ContainerProvided<object>;

export type ContainerProvided<S extends ContainerServicesConstraint<S>> = {
    container: ServiceLocator<ContainerServices<S>>;

    childContainer: IContainerBuilder<ContainerServices<S>, Exclude<keyof S, ContainerKeys>>;
};


/**
 * Таблица сервисов, которые предоставляет контейнер.
 * 
 * Сервисы, предоставляемые контейнером не могут быть null или undefined.
 */
export type ContainerServices<S extends ContainerServicesConstraint<S>> = {
    [k in keyof S | ContainerKeys]:
        k extends ContainerKeys ? ContainerProvided<S>[k] :
        k extends keyof S ? S[k] : never
};


/**
 * Returns the service declared in the type map {@link S}.
 * 
 * 
 */
export interface ServiceLocator<S> {
    resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
    resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
}

export interface LifetimeContainer {
    createLifetime<T>(): ILifetime<T>;
}

export interface IContainerBuilder<S, U extends keyof S> {
    createServiceBuilder<K extends U>(name: K):
        IDescriptorBuilder<S, S[K], Record<never, never>, U>;

    build(): ServiceLocator<S>;
}


export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";

/**
 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
 * свой собственный объект `ILifetime`, который создается при первой активации
 */
export interface ILifetime<T> {
    /** Проверяет, что уже создан экземпляр объекта */
    has(): boolean;

    get(): NonNullable<T>;

    initialize(context: IActivationContext<unknown>): void;

    store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void): void;

    toString(): string;
}

export interface ILifetimeManager extends IDestroyable {
    create<T>(): ILifetime<T>;
}

export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] };

export type ExtractRequiredKeys<T, K extends keyof T = keyof T> = { [p in K]-?: undefined extends T[p] ? never : p }[K];