declare module 'rewiremock' {

    export type Plugin = any;
    export type PluginNames =  'childOnly' | 'nodejs' | 'protectNodeModules' | 'relative' | 'webpackAlias' | 'toBeUsed' | 'disabledByDefault' | 'mockThroughByDefault' | 'usedByDefault' | 'alwaysMatchOrigin' | 'directChild';
    export type Plugins = {
        [Key in PluginNames]: any
    };

    export interface OverloadedModule {
        name: String,
        fileName: String,
        parent: Object,
        original: Object,
        requireActual: Function
    }

    export type IStubFactory = (name: string, value: any) => any;

    /**
     * Base non-strict module export interface
     */
    export interface BaseMock {
        /**
         * Enabled call thought original module, making all the original methods accessible.
         * @example
         * mock.callThrough();
         */
        callThrough(): this,

        /**
         * Mimic the original file, replacing all the original methods by mocks.
         * @param {IStubFactory} [stubFactory] - stub factory function
         * @example
         * mock.mockThrough();
         * mock.mockThrough( () => sinon.stub() );
         * mock.mockThrough( (name, value) => typeof value === 'function' ? sinon.stub() : value );
         */
        mockThrough(stubFactory?: IStubFactory): this,

        /**
         * enables hot mock updates
         * @return {this}
         */
        dynamic(): this,

        /**
         * Setting es6 behaviour for a module
         */
        es6(): this,

        /**
         * Overriding export of one module by another
         * @example
         * mock.by('otherModuleName');
         */
        by(module: string): BaseMock,

        /**
         * Overriding export of one module by something generated by a function
         * @example
         * mock.by( originalModule => cache || cache = originalModule.requireActual('./nestedDep'));
         */
        by(module: (module: OverloadedModule) => Object): BaseMock,

        enable(): this,

        disable(): this,

        /**
         * will mock this only first (directly nested) children.
         */
        directChildOnly(): this;

        /**
         * will mocks this regardless of position
         */
        atAnyPlace(): this;

        /**
         * mocks only if parent were mocked
         */
        calledFromMock(): this;

        calledFromAnywhere(): this;

        /**
         * Force mock to be used, or throw an error otherwise
         */
        toBeUsed(): this,

        notToBeUsed(): this,

        /**
         * checks mocks against implementation
         * @return {this}
         */
        toMatchOrigin(): this,

        /**
         * Bypass shouldMock and always mock
         */
        always(): this,
    }

    /**
     * Typed mock export interface
     */
    export interface NamedModuleMock<T> extends BaseMock {
        /**
         * Overriding export of a module
         */
        with(keys: {[P in keyof T]?: T[P]}): this;

        /**
         * Append overrides
         */
        append(keys: {[P in keyof T]?: T[P]}): this;

        /**
         * Washes away the types
         */
        nonStrict(): AnyModuleMock;
    }

    export interface HasDefault {
        default: any
    }

    /**
     * Module with default export mock export interface
     */
    export interface DefaultModuleMock<T extends HasDefault> extends NamedModuleMock<T> {
        /**
         * Setting es6 behavior for a current module and overriding default export
         */
        withDefault(fn: T['default']): this;
    }

    /**
     * Non-strict mock export interface
     */
    export interface AnyModuleMock extends BaseMock {
        /**
         * Setting es6 behavior for a current module and overriding default export
         */
        withDefault(stubs: any): this;

        /**
         * Overriding export of a module
         */
        with(stubs: any): this;

        /**
         * Append overrides
         */
        append(stubs: any): this;
    }

    export type ModuleMock = AnyModuleMock;
    export type ProxyFunction = (r: ModuleMock) => Object;
    export type RequireFunction<T> = () => T;
    export type ImportFunction<T> = () => Promise<T>;
    export type AnyImportFunction<T> = RequireFunction<T> | ImportFunction<T>;

    /**
     * @name rewiremock
     * @class
     * Proxies imports/require in order to allow overriding dependencies during testing.
     */
    export interface Rewiremock {
        /**
         * Define an overload for a given module
         * @param {String} module
         */
        (module: string): ModuleMock;

        /**
         * Define an overload for a given module with default export
         * @param {Function} module
         */
          <T extends HasDefault>(module: ImportFunction<T>): DefaultModuleMock<T>
        /**
         * Define an overload for a given module using import or require function
         * @param {Function} module
         */
          <T>(module: ImportFunction<T>): NamedModuleMock<T>

        /**
         * returns existing mock
         * @return {"rewiremock".ModuleMock}
         */
        getMock(module: string): ModuleMock;
        getMock<T extends HasDefault>(module: ImportFunction<T>): DefaultModuleMock<T>
        getMock<T>(module: ImportFunction<T>): NamedModuleMock<T>

        /**
         * Enables rewiremock and prepares module system (cleans cache)
         */
        enable(): Rewiremock;

        /**
         * Disables rewiremock and cleans cache
         */
        disable(): Rewiremock;

        /**
         * executes module in a sandbox
         * @param {Function} loader - loader of target module. You can use import or require. May return a Promise
         * @param {Function} [creator] - mock creator. You may add any mocks inside.
         */
        around<T>(loader: AnyImportFunction<T>, creator?: (r: Rewiremock) => any): Promise<T>;

        inScope(callback: Function): Rewiremock;

        /**
         * Loads a file and hooks deps in a `proxyquire` way
         * @param {String|Function} fileName
         * @param {Object|Function} overrides, with key==filename, and value==data
         */
        proxy<T>(fileName: String | RequireFunction<T>, overrides?: Object | ProxyFunction): T;

        /**
         * Loads a file and hooks deps in a `proxyquire` way
         * @param {Function} fileLoader. Require or Import desired module
         * @param {Object} overrides, with key==filename, and value==data
         */
        module<T>(fileLoader: ImportFunction<T>, overrides?: Object | ProxyFunction): Promise<T>;

        flush(): void;

        clear(): void;

        /**
         * Define stub factory for mockThrough command
         * @param {IStubFactory} stubFactory
         */
        stubFactory(stubFactory: IStubFactory): void;

        /**
         * converts module name
         * @param module
         */
        resolve(module: string): string,

        /**
         * Activates module isolation
         * @param {Boolean} [options.noAutoPassBy] excludes mocked modules to a isolation scope. Use it with mock.callThrough.
         * @param {Boolean} [options.noParentPassBy] disable allowing any module, with allowed parent
         */
        isolation(options?: { noAutoPassBy?: boolean, noParentPassBy?: boolean}): Rewiremock;

        /**
         * Deactivates isolation
         */
        withoutIsolation(): Rewiremock;

        /**
         * set aggressive politics to cache operation, restoring to the the previous values on end.
         * false: (default) removes all new elements from the cache. Old data from "old" cache is transferred to a new one. New modules are kept.
         * true: removes mocked modules from the cache. New modules are kept
         * 'nocache': completely restores old modules
         */
        forceCacheClear(mode?: boolean | 'nocache'): Rewiremock;

        setCacheControl(enable: boolean): Rewiremock;

        /**
         * Adding new isolationpassby record
         */
        passBy(pattern: any): Rewiremock;

        /**
         * Adds a plugin
         */
        addPlugin(plugin: any): Rewiremock;

        /**
         * low-level require
         */
        requireActual(fileName: string): any;

        /**
         * low-level import
         */
        importActual(fileName: string): any;

        /**
         * low-level API override
         */
        overrideEntryPoint(module:any): void;
    }


    var rewiremockdefault: Rewiremock;
    /**
     * rewiremock main export
     * @example
     * rewiremock('module').with({});
     * rewiremock.enable();
     */
    export default rewiremockdefault;

    /**
     * Adds a plugin
     * @param plugin
     */
    export function addPlugin(plugin:Plugin):void;

    /**
     * Removes a plugin
     * @param plugin
     */
    export function removePlugins(plugin:Plugin):void;

    /**
     * Sets given module as a top level parent
     * @param module
     */
    export function overrideEntryPoint(module:any):void;

    /**
     * Configures extensions to handle
     * @param extensions
     */
    export function resolveExtensions(extensions: string[]):void;

    /**
     * List of available plugins
     */
    export var plugins: Plugins;
}
