import { ServiceIdentifier, Either, Newable, LazyServiceIdentifier } from '@inversifyjs/common';

declare function getBindingId(): number;

type MetadataName = number | string | symbol;

type MetadataTag = number | string | symbol;

interface BindingConstraints {
    readonly name: MetadataName | undefined;
    readonly tags: Map<MetadataTag, unknown>;
    readonly serviceIdentifier: ServiceIdentifier;
    getAncestor(): BindingConstraints | undefined;
}

type BindingType = 'ConstantValue' | 'DynamicValue' | 'Factory' | 'Instance' | 'Provider' | 'ResolvedValue' | 'ServiceRedirection';
declare const bindingTypeValues: {
    [TKey in BindingType]: TKey;
};

interface BaseBinding<TType extends BindingType, TActivated> {
    readonly id: number;
    readonly moduleId: number | undefined;
    readonly serviceIdentifier: ServiceIdentifier<TActivated>;
    readonly type: TType;
    isSatisfiedBy(constraints: BindingConstraints): boolean;
}

type Resolved<TActivated> = SyncResolved<TActivated> | Promise<SyncResolved<TActivated>>;
type SyncResolved<TActivated> = TActivated;

type BindingScope = 'Singleton' | 'Transient' | 'Request';
declare const bindingScopeValues: {
    [TKey in BindingScope]: TKey;
};

interface GetOptionsTagConstraint {
    key: MetadataTag;
    value: unknown;
}

interface GetOptions {
    autobind?: boolean;
    name?: MetadataName;
    optional?: boolean;
    tag?: GetOptionsTagConstraint;
}

interface OptionalGetOptions extends GetOptions {
    optional: true;
}

interface ResolutionContext {
    get<TActivated>(serviceIdentifier: ServiceIdentifier<TActivated>, options: OptionalGetOptions): TActivated | undefined;
    get<TActivated>(serviceIdentifier: ServiceIdentifier<TActivated>, options?: GetOptions): TActivated;
    getAll<TActivated>(serviceIdentifier: ServiceIdentifier<TActivated>, options?: GetOptions): TActivated[];
    getAllAsync<TActivated>(serviceIdentifier: ServiceIdentifier<TActivated>, options?: GetOptions): Promise<TActivated[]>;
    getAsync<TActivated>(serviceIdentifier: ServiceIdentifier<TActivated>, options: OptionalGetOptions): Promise<TActivated | undefined>;
    getAsync<TActivated>(serviceIdentifier: ServiceIdentifier<TActivated>, options?: GetOptions): Promise<TActivated>;
}

type BindingActivation<T = unknown> = (context: ResolutionContext, injectable: T) => Resolved<T>;

type BindingDeactivation<T = unknown> = (injectable: T) => void | Promise<void>;

interface ScopedBinding<TType extends BindingType, TScope extends BindingScope, TActivated> extends BaseBinding<TType, TActivated> {
    cache: Either<undefined, Resolved<TActivated>>;
    readonly onActivation: BindingActivation<TActivated> | undefined;
    readonly onDeactivation: BindingDeactivation<TActivated> | undefined;
    readonly scope: TScope;
}

interface ConstantValueBinding<TActivated> extends ScopedBinding<typeof bindingTypeValues.ConstantValue, typeof bindingScopeValues.Singleton, TActivated> {
    readonly value: Resolved<TActivated>;
}

type DynamicValueBuilder<T> = (context: ResolutionContext) => Resolved<T>;

interface DynamicValueBinding<TActivated> extends ScopedBinding<typeof bindingTypeValues.DynamicValue, BindingScope, TActivated> {
    readonly value: DynamicValueBuilder<TActivated>;
}

type Factory<TActivated, in TArgs extends unknown[] = any[]> = (...args: TArgs) => TActivated;

interface FactoryBinding<TActivated extends Factory<unknown>> extends ScopedBinding<typeof bindingTypeValues.Factory, typeof bindingScopeValues.Singleton, TActivated> {
    readonly factory: (context: ResolutionContext) => TActivated;
}

interface InstanceBinding<TActivated> extends ScopedBinding<typeof bindingTypeValues.Instance, BindingScope, TActivated> {
    readonly implementationType: Newable<TActivated>;
}

type Provider<TActivated, in TArgs extends unknown[] = any[]> = (...args: TArgs) => Promise<TActivated>;

interface ProviderBinding<TActivated extends Provider<unknown>> extends ScopedBinding<typeof bindingTypeValues.Provider, typeof bindingScopeValues.Singleton, TActivated> {
    readonly provider: (context: ResolutionContext) => TActivated;
}

interface BaseResolvedValueElementMetadata<TKind> {
    kind: TKind;
}

declare enum ResolvedValueElementMetadataKind {
    multipleInjection = 0,
    singleInjection = 1
}

interface ResolvedValueElementMetadata extends BaseResolvedValueElementMetadata<ResolvedValueElementMetadataKind> {
    name: MetadataName | undefined;
    optional: boolean;
    tags: Map<MetadataTag, unknown>;
    value: ServiceIdentifier | LazyServiceIdentifier;
}

interface ResolvedValueMetadata {
    arguments: ResolvedValueElementMetadata[];
}

interface ResolvedValueBinding<TActivated> extends ScopedBinding<typeof bindingTypeValues.ResolvedValue, BindingScope, TActivated> {
    readonly factory: (...args: any[]) => TActivated | Promise<TActivated>;
    readonly metadata: ResolvedValueMetadata;
}

interface ServiceRedirectionBinding<TActivated> extends BaseBinding<typeof bindingTypeValues.ServiceRedirection, TActivated> {
    targetServiceIdentifier: ServiceIdentifier;
}

type Binding<TActivated = any> = ConstantValueBinding<TActivated> | DynamicValueBinding<TActivated> | (TActivated extends Factory<unknown> ? FactoryBinding<TActivated> : never) | InstanceBinding<TActivated> | (TActivated extends Provider<unknown> ? ProviderBinding<TActivated> : never) | ResolvedValueBinding<TActivated> | ServiceRedirectionBinding<TActivated>;

interface Cloneable<T> {
    clone(): T;
}

declare enum ActivationRelationKind {
    moduleId = "moduleId",
    serviceId = "serviceId"
}
interface BindingActivationRelation {
    [ActivationRelationKind.moduleId]?: number;
    [ActivationRelationKind.serviceId]: ServiceIdentifier;
}
declare class ActivationsService implements Cloneable<ActivationsService> {
    #private;
    private constructor();
    static build(parent: ActivationsService | undefined): ActivationsService;
    add(activation: BindingActivation, relation: BindingActivationRelation): void;
    clone(): ActivationsService;
    get(serviceIdentifier: ServiceIdentifier): Iterable<BindingActivation> | undefined;
    removeAllByModuleId(moduleId: number): void;
    removeAllByServiceId(serviceId: ServiceIdentifier): void;
}

declare class BindingService implements Cloneable<BindingService> {
    #private;
    private constructor();
    static build(parent: BindingService | undefined): BindingService;
    clone(): BindingService;
    get<TResolved>(serviceIdentifier: ServiceIdentifier): Iterable<Binding<TResolved>> | undefined;
    getById<TResolved>(id: number): Iterable<Binding<TResolved>> | undefined;
    getByModuleId<TResolved>(moduleId: number): Iterable<Binding<TResolved>> | undefined;
    getNonParentBindings<TResolved>(serviceId: ServiceIdentifier): Iterable<Binding<TResolved>> | undefined;
    getNonParentBoundServices(): Iterable<ServiceIdentifier>;
    removeById(id: number): void;
    removeAllByModuleId(moduleId: number): void;
    removeAllByServiceId(serviceId: ServiceIdentifier): void;
    set<TInstance>(binding: Binding<TInstance>): void;
}

declare enum DeactivationRelationKind {
    moduleId = "moduleId",
    serviceId = "serviceId"
}
interface BindingDeactivationRelation {
    [DeactivationRelationKind.moduleId]?: number;
    [DeactivationRelationKind.serviceId]: ServiceIdentifier;
}
declare class DeactivationsService implements Cloneable<DeactivationsService> {
    #private;
    private constructor();
    static build(parent: DeactivationsService | undefined): DeactivationsService;
    add(deactivation: BindingDeactivation, relation: BindingDeactivationRelation): void;
    clone(): DeactivationsService;
    get(serviceIdentifier: ServiceIdentifier): Iterable<BindingDeactivation> | undefined;
    removeAllByModuleId(moduleId: number): void;
    removeAllByServiceId(serviceId: ServiceIdentifier): void;
}

declare function decorate(decorators: ClassDecorator | ClassDecorator[], target: Function): void;
declare function decorate(decorators: ParameterDecorator | ParameterDecorator[], target: Function, parameterIndex: number): void;
declare function decorate(decorators: MethodDecorator | PropertyDecorator | MethodDecorator[] | PropertyDecorator[], target: Function, property: string | symbol): void;

interface BaseClassElementMetadata<TKind> {
    kind: TKind;
}

declare enum ClassElementMetadataKind {
    multipleInjection = 0,
    singleInjection = 1,
    unmanaged = 2
}

interface ManagedClassElementMetadata extends BaseClassElementMetadata<ClassElementMetadataKind.singleInjection | ClassElementMetadataKind.multipleInjection> {
    isFromTypescriptParamType?: true;
    name: MetadataName | undefined;
    optional: boolean;
    tags: Map<MetadataTag, unknown>;
    value: ServiceIdentifier | LazyServiceIdentifier;
}

type UnmanagedClassElementMetadata = BaseClassElementMetadata<ClassElementMetadataKind.unmanaged>;

type ClassElementMetadata = ManagedClassElementMetadata | UnmanagedClassElementMetadata;

interface ClassMetadataLifecycle {
    postConstructMethodName: string | symbol | undefined;
    preDestroyMethodName: string | symbol | undefined;
}

interface ClassMetadata {
    constructorArguments: ClassElementMetadata[];
    lifecycle: ClassMetadataLifecycle;
    properties: Map<string | symbol, ClassElementMetadata>;
    scope: BindingScope | undefined;
}

declare function getClassMetadata(type: Newable): ClassMetadata;

declare function inject(serviceIdentifier: ServiceIdentifier | LazyServiceIdentifier): MethodDecorator & ParameterDecorator & PropertyDecorator;

declare function injectable(scope?: BindingScope): ClassDecorator;

interface InjectFromBaseOptions {
    extendConstructorArguments?: boolean | undefined;
    extendProperties?: boolean | undefined;
}

declare function injectFromBase(options?: InjectFromBaseOptions): ClassDecorator;

declare function multiInject(serviceIdentifier: ServiceIdentifier | LazyServiceIdentifier): MethodDecorator & ParameterDecorator & PropertyDecorator;

declare function named(name: MetadataName): MethodDecorator & ParameterDecorator & PropertyDecorator;

declare function optional(): MethodDecorator & ParameterDecorator & PropertyDecorator;

declare function postConstruct(): MethodDecorator;

declare function preDestroy(): MethodDecorator;

declare function tagged(key: MetadataTag, value: unknown): MethodDecorator & ParameterDecorator & PropertyDecorator;

declare function unmanaged(): MethodDecorator & ParameterDecorator & PropertyDecorator;

interface BasePlanParamsAutobindOptions {
    scope: BindingScope;
}

interface BasePlanParams {
    autobindOptions: BasePlanParamsAutobindOptions | undefined;
    getBindings: <TInstance>(serviceIdentifier: ServiceIdentifier<TInstance>) => Iterable<Binding<TInstance>> | undefined;
    getClassMetadata: (type: Newable) => ClassMetadata;
    servicesBranch: ServiceIdentifier[];
    setBinding: <TInstance>(binding: Binding<TInstance>) => void;
}

interface PlanParamsTagConstraint {
    key: MetadataTag;
    value: unknown;
}

interface PlanParamsConstraint {
    name?: MetadataName;
    isMultiple: boolean;
    isOptional?: true;
    serviceIdentifier: ServiceIdentifier;
    tag?: PlanParamsTagConstraint;
}

interface PlanParams extends BasePlanParams {
    rootConstraints: PlanParamsConstraint;
}

interface PlanServiceRedirectionBindingNode<TBinding extends ServiceRedirectionBinding<any> = ServiceRedirectionBinding<any>> extends BaseBindingNode<TBinding> {
    redirections: PlanBindingNode[];
}

type BindingNodeParent = PlanServiceNode | PlanServiceRedirectionBindingNode;

interface BaseBindingNode<TBinding extends Binding<any> = Binding<any>> {
    readonly parent: BindingNodeParent;
    readonly binding: TBinding;
}

type LeafBindingNode<TActivated = any> = BaseBindingNode<ConstantValueBinding<TActivated> | DynamicValueBinding<TActivated> | (TActivated extends Factory<unknown> ? FactoryBinding<TActivated> : never) | (TActivated extends Provider<unknown> ? ProviderBinding<TActivated> : never)>;

interface InstanceBindingNode<TBinding extends InstanceBinding<any> = InstanceBinding<any>> extends BaseBindingNode<TBinding> {
    readonly classMetadata: ClassMetadata;
    readonly constructorParams: (PlanServiceNode | undefined)[];
    readonly propertyParams: Map<string | symbol, PlanServiceNode>;
}

interface ResolvedValueBindingNode<TBinding extends ResolvedValueBinding<any> = ResolvedValueBinding<any>> extends BaseBindingNode<TBinding> {
    readonly params: PlanServiceNode[];
}

type PlanServiceNodeParent<TActivated = any> = InstanceBindingNode<InstanceBinding<TActivated>> | ResolvedValueBindingNode<ResolvedValueBinding<TActivated>>;

type PlanBindingNode = PlanServiceNodeParent | PlanServiceRedirectionBindingNode | LeafBindingNode;

interface PlanServiceNode {
    readonly bindings: PlanBindingNode | PlanBindingNode[] | undefined;
    readonly parent: PlanServiceNodeParent | undefined;
    readonly serviceIdentifier: ServiceIdentifier;
}

interface PlanTree {
    readonly root: PlanServiceNode;
}

interface PlanResult {
    readonly tree: PlanTree;
}

declare function plan(params: PlanParams): PlanResult;

interface GetPlanOptionsTagConstraint {
    key: MetadataTag;
    value: unknown;
}
interface GetPlanOptions {
    serviceIdentifier: ServiceIdentifier;
    isMultiple: boolean;
    name: MetadataName | undefined;
    optional: boolean | undefined;
    tag: GetPlanOptionsTagConstraint | undefined;
}
/**
 * Service to cache plans.
 *
 * This class is used to cache plans and to notify PlanService subscribers when the cache is cleared.
 * The cache should be cleared when a new binding is registered or when a binding is unregistered.
 *
 * Subscribers are supposed to be plan services from child containers.
 *
 * Ancestor binding constraints are the reason to avoid reusing plans from plan children nodes.
 */
declare class PlanResultCacheService {
    #private;
    constructor();
    clearCache(): void;
    get(options: GetPlanOptions): PlanResult | undefined;
    set(options: GetPlanOptions, planResult: PlanResult): void;
    subscribe(subscriber: PlanResultCacheService): void;
}

interface ResolutionParams {
    context: ResolutionContext;
    getActivations: <TActivated>(serviceIdentifier: ServiceIdentifier<TActivated>) => Iterable<BindingActivation<TActivated>> | undefined;
    planResult: PlanResult;
    requestScopeCache: Map<number, unknown>;
}

declare function resolve(params: ResolutionParams): unknown;

interface DeactivationParams {
    getBindings: <TInstance>(serviceIdentifier: ServiceIdentifier<TInstance>) => Iterable<Binding<TInstance>> | undefined;
    getBindingsFromModule: <TInstance>(moduleId: number) => Iterable<Binding<TInstance>> | undefined;
    getClassMetadata: (type: Newable) => ClassMetadata;
    getDeactivations: <TActivated>(serviceIdentifier: ServiceIdentifier<TActivated>) => Iterable<BindingDeactivation<TActivated>> | undefined;
}

declare function resolveBindingsDeactivations(params: DeactivationParams, bindings: Iterable<Binding> | undefined): void | Promise<void>;

declare function resolveModuleDeactivations(params: DeactivationParams, moduleId: number): void | Promise<void>;

declare function resolveServiceDeactivations(params: DeactivationParams, serviceIdentifier: ServiceIdentifier): void | Promise<void>;

export { ActivationsService, BindingService, ClassElementMetadataKind, DeactivationsService, PlanResultCacheService, ResolvedValueElementMetadataKind, bindingScopeValues, bindingTypeValues, decorate, getBindingId, getClassMetadata, inject, injectFromBase, injectable, multiInject, named, optional, plan, postConstruct, preDestroy, resolve, resolveBindingsDeactivations, resolveModuleDeactivations, resolveServiceDeactivations, tagged, unmanaged };
export type { BaseBinding, BaseBindingNode, BasePlanParams, Binding, BindingActivation, BindingActivationRelation, BindingConstraints, BindingDeactivation, BindingDeactivationRelation, BindingScope, BindingType, ClassElementMetadata, ClassMetadata, ClassMetadataLifecycle, ConstantValueBinding, DeactivationParams, DynamicValueBinding, DynamicValueBuilder, Factory, FactoryBinding, GetOptions, GetOptionsTagConstraint, GetPlanOptions, InstanceBinding, LeafBindingNode, ManagedClassElementMetadata, MetadataName, MetadataTag, OptionalGetOptions, PlanBindingNode, PlanParams, PlanParamsConstraint, PlanParamsTagConstraint, PlanResult, PlanServiceNode, PlanServiceNodeParent, PlanServiceRedirectionBindingNode, PlanTree, Provider, ProviderBinding, ResolutionContext, ResolutionParams, Resolved, ResolvedValueBinding, ResolvedValueBindingNode, ResolvedValueElementMetadata, ResolvedValueMetadata, ScopedBinding, ServiceRedirectionBinding, UnmanagedClassElementMetadata };
