import { LazyServiceIdentifier, ServiceIdentifier, Newable } from '@inversifyjs/common';
import { MetadataName, MetadataTag, BindingConstraints, BindingActivation, BindingDeactivation, DynamicValueBuilder, Factory, ResolutionContext, Provider, GetOptionsTagConstraint, OptionalGetOptions, GetOptions, BindingScope } from '@inversifyjs/core';

interface BindingIdentifier {
    readonly id: number;
}

type ResolvedValueInjectOptions<T> = LazyServiceIdentifier<T> | ResolvedValueMetadataInjectOptions<T> | ServiceIdentifier<T>;
type ResolvedValueMetadataInjectOptions<T> = [T] extends [
    (infer U)[] | undefined
] ? [T] extends [U[]] ? MultipleResolvedValueMetadataInjectOptions<U> : MultipleOptionalResolvedValueMetadataInjectOptions<U> : T extends undefined ? OptionalResolvedValueMetadataInjectOptions<T> : BaseResolvedValueMetadataInjectOptions<T>;
interface BaseResolvedValueMetadataInjectOptions<T> {
    name?: MetadataName;
    serviceIdentifier: ServiceIdentifier<T> | LazyServiceIdentifier<T>;
    tags?: ResolvedValueMetadataInjectTagOptions[];
}
interface BaseMultipleResolvedValueMetadataInjectOptions {
    isMultiple: true;
}
interface BaseOptionalResolvedValueMetadataInjectOptions {
    optional: true;
}
interface MultipleResolvedValueMetadataInjectOptions<T> extends BaseResolvedValueMetadataInjectOptions<T>, BaseMultipleResolvedValueMetadataInjectOptions {
}
interface MultipleOptionalResolvedValueMetadataInjectOptions<T> extends BaseResolvedValueMetadataInjectOptions<T>, BaseMultipleResolvedValueMetadataInjectOptions, BaseOptionalResolvedValueMetadataInjectOptions {
}
interface OptionalResolvedValueMetadataInjectOptions<T> extends BaseResolvedValueMetadataInjectOptions<T>, BaseOptionalResolvedValueMetadataInjectOptions {
}
interface ResolvedValueMetadataInjectTagOptions {
    key: MetadataTag;
    value: unknown;
}

type MapToResolvedValueInjectOptions<TArgs extends unknown[]> = {
    [K in keyof TArgs]-?: ResolvedValueInjectOptions<TArgs[K]>;
};

interface BoundServiceSyntax {
    getIdentifier(): BindingIdentifier;
}
interface BindToFluentSyntax<T> {
    to(type: Newable<T>): BindInWhenOnFluentSyntax<T>;
    toSelf(): BindInWhenOnFluentSyntax<T>;
    toConstantValue(value: T): BindWhenOnFluentSyntax<T>;
    toDynamicValue(builder: DynamicValueBuilder<T>): BindInWhenOnFluentSyntax<T>;
    toFactory(factory: T extends Factory<unknown, any> ? (context: ResolutionContext) => T : never): BindWhenOnFluentSyntax<T>;
    toProvider(provider: T extends Provider<unknown, any> ? (context: ResolutionContext) => T : never): BindWhenOnFluentSyntax<T>;
    toResolvedValue(factory: () => T): BindInWhenOnFluentSyntax<T>;
    toResolvedValue<TArgs extends unknown[] = any[]>(factory: (...args: TArgs) => T, injectOptions: MapToResolvedValueInjectOptions<TArgs>): BindInWhenOnFluentSyntax<T>;
    toService(service: ServiceIdentifier<T>): void;
}
interface BindInFluentSyntax<T> extends BoundServiceSyntax {
    inSingletonScope(): BindWhenOnFluentSyntax<T>;
    inTransientScope(): BindWhenOnFluentSyntax<T>;
    inRequestScope(): BindWhenOnFluentSyntax<T>;
}
interface BindInWhenOnFluentSyntax<T> extends BindInFluentSyntax<T>, BindWhenOnFluentSyntax<T> {
}
interface BindOnFluentSyntax<T> extends BoundServiceSyntax {
    onActivation(activation: BindingActivation<T>): BindWhenFluentSyntax<T>;
    onDeactivation(deactivation: BindingDeactivation<T>): BindWhenFluentSyntax<T>;
}
interface BindWhenOnFluentSyntax<T> extends BindWhenFluentSyntax<T>, BindOnFluentSyntax<T> {
}
interface BindWhenFluentSyntax<T> extends BoundServiceSyntax {
    when(constraint: (metadata: BindingConstraints) => boolean): BindOnFluentSyntax<T>;
    whenAnyAncestor(constraint: (metadata: BindingConstraints) => boolean): BindOnFluentSyntax<T>;
    whenAnyAncestorIs(serviceIdentifier: ServiceIdentifier): BindOnFluentSyntax<T>;
    whenAnyAncestorNamed(name: MetadataName): BindOnFluentSyntax<T>;
    whenAnyAncestorTagged(tag: MetadataTag, tagValue: unknown): BindOnFluentSyntax<T>;
    whenDefault(): BindOnFluentSyntax<T>;
    whenNamed(name: MetadataName): BindOnFluentSyntax<T>;
    whenNoAncestor(constraint: (metadata: BindingConstraints) => boolean): BindOnFluentSyntax<T>;
    whenNoAncestorIs(serviceIdentifier: ServiceIdentifier): BindOnFluentSyntax<T>;
    whenNoAncestorNamed(name: MetadataName): BindOnFluentSyntax<T>;
    whenNoAncestorTagged(tag: MetadataTag, tagValue: unknown): BindOnFluentSyntax<T>;
    whenNoParent(constraint: (metadata: BindingConstraints) => boolean): BindOnFluentSyntax<T>;
    whenNoParentIs(serviceIdentifier: ServiceIdentifier): BindOnFluentSyntax<T>;
    whenNoParentNamed(name: MetadataName): BindOnFluentSyntax<T>;
    whenNoParentTagged(tag: MetadataTag, tagValue: unknown): BindOnFluentSyntax<T>;
    whenParent(constraint: (metadata: BindingConstraints) => boolean): BindOnFluentSyntax<T>;
    whenParentIs(serviceIdentifier: ServiceIdentifier): BindOnFluentSyntax<T>;
    whenParentNamed(name: MetadataName): BindOnFluentSyntax<T>;
    whenParentTagged(tag: MetadataTag, tagValue: unknown): BindOnFluentSyntax<T>;
    whenTagged(tag: MetadataTag, tagValue: unknown): BindOnFluentSyntax<T>;
}

interface IsBoundOptions {
    name?: MetadataName;
    tag?: GetOptionsTagConstraint;
}

interface ContainerModuleLoadOptions {
    bind: <T>(serviceIdentifier: ServiceIdentifier<T>) => BindToFluentSyntax<T>;
    isBound: (serviceIdentifier: ServiceIdentifier, options?: IsBoundOptions) => boolean;
    onActivation: <T>(serviceIdentifier: ServiceIdentifier<T>, activation: BindingActivation<T>) => void;
    onDeactivation: <T>(serviceIdentifier: ServiceIdentifier<T>, deactivation: BindingDeactivation<T>) => void;
    rebind: <T>(serviceIdentifier: ServiceIdentifier<T>) => Promise<BindToFluentSyntax<T>>;
    rebindSync: <T>(serviceIdentifier: ServiceIdentifier<T>) => BindToFluentSyntax<T>;
    unbind: (identifier: BindingIdentifier | ServiceIdentifier) => Promise<void>;
    unbindSync: (identifier: BindingIdentifier | ServiceIdentifier) => void;
}
declare class ContainerModule {
    #private;
    constructor(load: (options: ContainerModuleLoadOptions) => void | Promise<void>);
    get id(): number;
    load(options: ContainerModuleLoadOptions): void | Promise<void>;
}

declare class Container {
    #private;
    constructor(options?: ContainerOptions);
    bind<T>(serviceIdentifier: ServiceIdentifier<T>): BindToFluentSyntax<T>;
    get<T>(serviceIdentifier: ServiceIdentifier<T>, options: OptionalGetOptions): T | undefined;
    get<T>(serviceIdentifier: ServiceIdentifier<T>, options?: GetOptions): T;
    getAll<T>(serviceIdentifier: ServiceIdentifier<T>, options?: GetOptions): T[];
    getAllAsync<T>(serviceIdentifier: ServiceIdentifier<T>, options?: GetOptions): Promise<T[]>;
    getAsync<T>(serviceIdentifier: ServiceIdentifier<T>, options: OptionalGetOptions): Promise<T | undefined>;
    getAsync<T>(serviceIdentifier: ServiceIdentifier<T>, options?: GetOptions): Promise<T>;
    isBound(serviceIdentifier: ServiceIdentifier, options?: IsBoundOptions): boolean;
    isCurrentBound(serviceIdentifier: ServiceIdentifier, options?: IsBoundOptions): boolean;
    load(...modules: ContainerModule[]): Promise<void>;
    loadSync(...modules: ContainerModule[]): void;
    onActivation<T>(serviceIdentifier: ServiceIdentifier<T>, activation: BindingActivation<T>): void;
    onDeactivation<T>(serviceIdentifier: ServiceIdentifier<T>, deactivation: BindingDeactivation<T>): void;
    register(pluginConstructor: Newable): void;
    restore(): void;
    rebind<T>(serviceIdentifier: ServiceIdentifier<T>): Promise<BindToFluentSyntax<T>>;
    rebindSync<T>(serviceIdentifier: ServiceIdentifier<T>): BindToFluentSyntax<T>;
    snapshot(): void;
    unbind(identifier: BindingIdentifier | ServiceIdentifier): Promise<void>;
    unbindAll(): Promise<void>;
    unbindSync(identifier: BindingIdentifier | ServiceIdentifier): void;
    unload(...modules: ContainerModule[]): Promise<void>;
    unloadSync(...modules: ContainerModule[]): void;
}

interface ContainerOptions {
    autobind?: true;
    defaultScope?: BindingScope | undefined;
    parent?: Container | undefined;
}

declare enum InversifyContainerErrorKind {
    invalidOperation = 0
}

declare const isAppErrorSymbol: unique symbol;
declare class InversifyContainerError extends Error {
    [isAppErrorSymbol]: true;
    kind: InversifyContainerErrorKind;
    constructor(kind: InversifyContainerErrorKind, message?: string, options?: ErrorOptions);
    static is(value: unknown): value is InversifyContainerError;
    static isErrorOfKind(value: unknown, kind: InversifyContainerErrorKind): value is InversifyContainerError;
}

export { Container, ContainerModule, InversifyContainerError, InversifyContainerErrorKind };
export type { BindInFluentSyntax, BindInWhenOnFluentSyntax, BindOnFluentSyntax, BindToFluentSyntax, BindWhenFluentSyntax, BindWhenOnFluentSyntax, BindingIdentifier, BoundServiceSyntax, ContainerModuleLoadOptions, ContainerOptions, IsBoundOptions, ResolvedValueInjectOptions, ResolvedValueMetadataInjectOptions, ResolvedValueMetadataInjectTagOptions };
