export interface ObservableLike {
	subscribe(observer: (value: any) => void): void;
}

export type Constructor = (new (...args: Array<any>) => any);

/** Specify one or more expectations the thrown error must satisfy. */
export type ThrowsExpectation = {
	/** The thrown error must have a code that equals the given string. */
	code?: string;

	/** The thrown error must be an instance of this constructor. */
	instanceOf?: Constructor;

	/** The thrown error must be strictly equal to this value. */
	is?: Error;

	/** The thrown error must have a message that equals the given string, or matches the regular expression. */
	message?: string | RegExp;

	/** The thrown error must have a name that equals the given string. */
	name?: string;
};

/** Options that can be passed to the `t.snapshot()` assertion. */
export type SnapshotOptions = {
	/** If provided and not an empty string, used to select the snapshot to compare the `expected` value against. */
	id?: string;
};

export interface Assertions {
	/** Assert that `actual` is [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to `expected`. */
	deepEqual: DeepEqualAssertion;

	/** Fail the test. */
	fail: FailAssertion;

	/** Assert that `actual` is strictly false. */
	false: FalseAssertion;

	/** Assert that `actual` is [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy). */
	falsy: FalsyAssertion;

	/**
	 * Assert that `actual` is [the same
	 * value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) as `expected`.
	 */
	is: IsAssertion;

	/**
	 * Assert that `actual` is not [the same
	 * value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) as `expected`.
	 */
	not: NotAssertion;

	/** Assert that `actual` is not [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to `expected`. */
	notDeepEqual: NotDeepEqualAssertion;

	/** Assert that `string` does not match the regular expression. */
	notRegex: NotRegexAssertion;

	/** Assert that the function does not throw. */
	notThrows: NotThrowsAssertion;

	/** Count a passing assertion. */
	pass: PassAssertion;

	/** Assert that `string` matches the regular expression. */
	regex: RegexAssertion;

	/**
	 * Assert that `expected` is [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to a
	 * previously recorded [snapshot](https://github.com/concordancejs/concordance#serialization-details), or if
	 * necessary record a new snapshot.
	 */
	snapshot: SnapshotAssertion;

	/**
	 * Assert that the function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error value.
	 */
	throws: ThrowsAssertion;

	/** Assert that `actual` is strictly true. */
	true: TrueAssertion;

	/** Assert that `actual` is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy). */
	truthy: TruthyAssertion;
}

export interface DeepEqualAssertion {
	/** Assert that `actual` is [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to `expected`. */
	<ValueType = any>(actual: ValueType, expected: ValueType, message?: string): void;

	/** Skip this assertion. */
	skip(actual: any, expected: any, message?: string): void;
}

export interface FailAssertion {
	/** Fail the test. */
	(message?: string): void;

	/** Skip this assertion. */
	skip(message?: string): void;
}

export interface FalseAssertion {
	/** Assert that `actual` is strictly false. */
	(actual: any, message?: string): void;

	/** Skip this assertion. */
	skip(actual: any, message?: string): void;
}

export interface FalsyAssertion {
	/** Assert that `actual` is [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy). */
	(actual: any, message?: string): void;

	/** Skip this assertion. */
	skip(actual: any, message?: string): void;
}

export interface IsAssertion {
	/**
	 * Assert that `actual` is [the same
	 * value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) as `expected`.
	 */
	<ValueType = any>(actual: ValueType, expected: ValueType, message?: string): void;

	/** Skip this assertion. */
	skip(actual: any, expected: any, message?: string): void;
}

export interface NotAssertion {
	/**
	 * Assert that `actual` is not [the same
	 * value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) as `expected`.
	 */
	<ValueType = any>(actual: ValueType, expected: ValueType, message?: string): void;

	/** Skip this assertion. */
	skip(actual: any, expected: any, message?: string): void;
}

export interface NotDeepEqualAssertion {
	/** Assert that `actual` is not [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to `expected`. */
	<ValueType = any>(actual: ValueType, expected: ValueType, message?: string): void;

	/** Skip this assertion. */
	skip(actual: any, expected: any, message?: string): void;
}

export interface NotRegexAssertion {
	/** Assert that `string` does not match the regular expression. */
	(string: string, regex: RegExp, message?: string): void;

	/** Skip this assertion. */
	skip(string: string, regex: RegExp, message?: string): void;
}

export interface NotThrowsAssertion {
	/** Assert that the function does not throw. */
	(fn: () => never, message?: string): void;

	/** Assert that the function returns an observable that does not error. You must await the result. */
	(fn: () => ObservableLike, message?: string): Promise<void>;

	/** Assert that the function returns a promise that does not reject. You must await the result. */
	(fn: () => PromiseLike<any>, message?: string): Promise<void>;

	/** Assert that the function does not throw. */
	(fn: () => any, message?: string): void;

	/** Assert that the observable does not error. You must await the result. */
	(observable: ObservableLike, message?: string): Promise<void>;

	/** Assert that the promise does not reject. You must await the result. */
	(promise: PromiseLike<any>, message?: string): Promise<void>;

	/** Skip this assertion. */
	skip(nonThrower: any, message?: string): void;
}

export interface PassAssertion {
	/** Count a passing assertion. */
	(message?: string): void;

	/** Skip this assertion. */
	skip(message?: string): void;
}

export interface RegexAssertion {
	/** Assert that `string` matches the regular expression. */
	(string: string, regex: RegExp, message?: string): void;

	/** Skip this assertion. */
	skip(string: string, regex: RegExp, message?: string): void;
}

export interface SnapshotAssertion {
	/**
	 * Assert that `expected` is [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to a
	 * previously recorded [snapshot](https://github.com/concordancejs/concordance#serialization-details), or if
	 * necessary record a new snapshot.
	 */
	(expected: any, message?: string): void;

	/**
	 * Assert that `expected` is [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to a
	 * previously recorded [snapshot](https://github.com/concordancejs/concordance#serialization-details) (selected
	 * through `options.id` if provided), or if necessary record a new snapshot.
	 */
	(expected: any, options: SnapshotOptions, message?: string): void;

	/** Skip this assertion. */
	skip(expected: any, message?: string): void;

	/** Skip this assertion. */
	(expected: any, options: SnapshotOptions, message?: string): void;
}

export interface ThrowsAssertion {
	/**
	 * Assert that the function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error value.
	 */
	(fn: () => never, expectations?: null, message?: string): any;

	/**
	 * Assert that the function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error value.
	 * The error must be an instance of the given constructor.
	 */
	(fn: () => never, constructor: Constructor, message?: string): any;

	/**
	 * Assert that the function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error value.
	 * The error must have a message that matches the regular expression.
	 */
	(fn: () => never, regex: RegExp, message?: string): any;

	/**
	 * Assert that the function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error value.
	 * The error must have a message equal to `errorMessage`.
	 */
	(fn: () => never, errorMessage: string, message?: string): any;

	/**
	 * Assert that the function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error value.
	 * The error must satisfy all expectations.
	 */
	(fn: () => never, expectations: ThrowsExpectation, message?: string): any;

	/**
	 * Assert that the function returns an observable that errors with [an error](https://www.npmjs.com/package/is-error).
	 * If so, returns the error value. You must await the result.
	 */
	(fn: () => ObservableLike, expectations?: null, message?: string): Promise<any>;

	/**
	 * Assert that the function returns an observable that errors with [an error](https://www.npmjs.com/package/is-error).
	 * If so, returns the error value. You must await the result. The error must be an instance of the given constructor.
	 */
	(fn: () => ObservableLike, constructor: Constructor, message?: string): Promise<any>;

	/**
	 * Assert that the function returns an observable that errors with [an error](https://www.npmjs.com/package/is-error).
	 * If so, returns the error value. You must await the result. The error must have a message that matches the regular
	 * expression.
	 */
	(fn: () => ObservableLike, regex: RegExp, message?: string): Promise<any>;

	/**
	 * Assert that the function returns an observable that errors with [an error](https://www.npmjs.com/package/is-error).
	 * If so, returns the error value. You must await the result. The error must have a message equal to `errorMessage`.
	 */
	(fn: () => ObservableLike, errorMessage: string, message?: string): Promise<any>;

	/**
	 * Assert that the function returns an observable that errors with [an error](https://www.npmjs.com/package/is-error).
	 * If so, returns the error value. You must await the result. The error must satisfy all expectations.
	 */
	(fn: () => ObservableLike, expectations: ThrowsExpectation, message?: string): Promise<any>;

	/**
	 * Assert that the function returns a promise that rejects with [an error](https://www.npmjs.com/package/is-error).
	 * If so, returns the rejection reason. You must await the result.
	 */
	(fn: () => PromiseLike<any>, expectations?: null, message?: string): Promise<any>;

	/**
	 * Assert that the function returns a promise that rejects with [an error](https://www.npmjs.com/package/is-error).
	 * If so, returns the rejection reason. You must await the result. The error must be an instance of the given
	 * constructor.
	 */
	(fn: () => PromiseLike<any>, constructor: Constructor, message?: string): Promise<any>;

	/**
	 * Assert that the function returns a promise that rejects with [an error](https://www.npmjs.com/package/is-error).
	 * If so, returns the rejection reason. You must await the result. The error must have a message that matches the
	 * regular expression.
	 */
	(fn: () => PromiseLike<any>, regex: RegExp, message?: string): Promise<any>;

	/**
	 * Assert that the function returns a promise that rejects with [an error](https://www.npmjs.com/package/is-error).
	 * If so, returns the rejection reason. You must await the result. The error must have a message equal to
	 * `errorMessage`.
	 */
	(fn: () => PromiseLike<any>, errorMessage: string, message?: string): Promise<any>;

	/**
	 * Assert that the function returns a promise that rejects with [an error](https://www.npmjs.com/package/is-error).
	 * If so, returns the rejection reason. You must await the result. The error must satisfy all expectations.
	 */
	(fn: () => PromiseLike<any>, expectations: ThrowsExpectation, message?: string): Promise<any>;

	/**
	 * Assert that the function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error value.
	 */
	(fn: () => any, expectations?: null, message?: string): any;

	/**
	 * Assert that the function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error value.
	 * The error must be an instance of the given constructor.
	 */
	(fn: () => any, constructor: Constructor, message?: string): any;

	/**
	 * Assert that the function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error value.
	 * The error must have a message that matches the regular expression.
	 */
	(fn: () => any, regex: RegExp, message?: string): any;

	/**
	 * Assert that the function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error value.
	 * The error must have a message equal to `errorMessage`.
	 */
	(fn: () => any, errorMessage: string, message?: string): any;

	/**
	 * Assert that the function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error value.
	 * The error must satisfy all expectations.
	 */
	(fn: () => any, expectations: ThrowsExpectation, message?: string): any;

	/**
	 * Assert that the observable errors with [an error](https://www.npmjs.com/package/is-error). If so, returns the error
	 * value. You must await the result.
	 */
	(promise: ObservableLike, expectations?: null, message?: string): Promise<any>;

	/**
	 * Assert that the observable errors with [an error](https://www.npmjs.com/package/is-error). If so, returns the error
	 * value. You must await the result. The error must be an instance of the given constructor.
	 */
	(promise: ObservableLike, constructor: Constructor, message?: string): Promise<any>;

	/**
	 * Assert that the observable errors with [an error](https://www.npmjs.com/package/is-error). If so, returns the error
	 * value. You must await the result. The error must have a message that matches the regular expression.
	 */
	(promise: ObservableLike, regex: RegExp, message?: string): Promise<any>;

	/**
	 * Assert that the observable errors with [an error](https://www.npmjs.com/package/is-error). If so, returns the error
	 * value. You must await the result. The error must have a message equal to `errorMessage`.
	 */
	(promise: ObservableLike, errorMessage: string, message?: string): Promise<any>;

	/**
	 * Assert that the observable errors with [an error](https://www.npmjs.com/package/is-error). If so, returns the error
	 * value. You must await the result. The error must satisfy all expectations.
	 */
	(promise: ObservableLike, expectations: ThrowsExpectation, message?: string): Promise<any>;

	/**
	 * Assert that the promise rejects with [an error](https://www.npmjs.com/package/is-error). If so, returns the
	 * rejection reason. You must await the result.
	 */
	(promise: PromiseLike<any>, expectations?: null, message?: string): Promise<any>;

	/**
	 * Assert that the promise rejects with [an error](https://www.npmjs.com/package/is-error). If so, returns the
	 * rejection reason. You must await the result. The error must be an instance of the given constructor.
	 */
	(promise: PromiseLike<any>, constructor: Constructor, message?: string): Promise<any>;

	/**
	 * Assert that the promise rejects with [an error](https://www.npmjs.com/package/is-error). If so, returns the
	 * rejection reason. You must await the result. The error must have a message that matches the regular expression.
	 */
	(promise: PromiseLike<any>, regex: RegExp, message?: string): Promise<any>;

	/**
	 * Assert that the promise rejects with [an error](https://www.npmjs.com/package/is-error). If so, returns the
	 * rejection reason. You must await the result. The error must have a message equal to `errorMessage`.
	 */
	(promise: PromiseLike<any>, errorMessage: string, message?: string): Promise<any>;

	/**
	 * Assert that the promise rejects with [an error](https://www.npmjs.com/package/is-error). If so, returns the
	 * rejection reason. You must await the result. The error must satisfy all expectations.
	 */
	(promise: PromiseLike<any>, expectations: ThrowsExpectation, message?: string): Promise<any>;

	/** Skip this assertion. */
	skip(thrower: any, expectations?: any, message?: string): void;
}

export interface TrueAssertion {
	/** Assert that `actual` is strictly true. */
	(actual: any, message?: string): void;

	/** Skip this assertion. */
	skip(actual: any, message?: string): void;
}

export interface TruthyAssertion {
	/** Assert that `actual` is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy). */
	(actual: any, message?: string): void;

	/** Skip this assertion. */
	skip(actual: any, message?: string): void;
}

/** The `t` value passed to test & hook implementations. */
export interface ExecutionContext<Context = {}> extends Assertions {
	/** Test context, shared with hooks. */
	context: Context;

	/** Title of the test or hook. */
	readonly title: string;

	log: LogFn;
	plan: PlanFn;
}

export interface LogFn {
	/** Log one or more values. */
	(...values: Array<any>): void;

	/** Skip logging. */
	skip(...values: Array<any>): void;
}

export interface PlanFn {
	/**
	 * Plan how many assertion there are in the test. The test will fail if the actual assertion count doesn't match the
	 * number of planned assertions. See [assertion planning](https://github.com/avajs/ava#assertion-planning).
	 */
	(count: number): void;

	/** Don't plan assertions. */
	skip(count: number): void;
}

/** The `t` value passed to implementations for tests & hooks declared with the `.cb` modifier. */
export interface CbExecutionContext<Context = {}> extends ExecutionContext<Context> {
	/**
	 * End the test. If `error` is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) the test or hook
	 * will fail.
	 */
	end(error?: any): void;
}

export type ImplementationResult = PromiseLike<void> | ObservableLike | Iterator<any> | void;
export type Implementation<Context = {}> = (t: ExecutionContext<Context>) => ImplementationResult;
export type CbImplementation<Context = {}> = (t: CbExecutionContext<Context>) => ImplementationResult;

/** A reusable test or hook implementation. */
export interface Macro<Context = {}> {
	(t: ExecutionContext<Context>, ...args: Array<any>): ImplementationResult;

	/**
	 * Implement this function to generate a test (or hook) title whenever this macro is used. `providedTitle` contains
	 * the title provided when the test or hook was declared. Also receives the remaining test arguments.
	 */
	title?: (providedTitle: string, ...args: Array<any>) => string;
}

/** A reusable test or hook implementation, for tests & hooks declared with the `.cb` modifier. */
export interface CbMacro<Context = {}> {
	(t: CbExecutionContext<Context>, ...args: Array<any>): ImplementationResult;
	title?: (providedTitle: string, ...args: Array<any>) => string;
}

export interface TestInterface<Context = {}> {
	/** Declare a concurrent test. */
	(title: string, implementation: Implementation<Context>): void;

	/** Declare a concurrent test that uses one or more macros. Additional arguments are passed to the macro. */
	(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

	/**
	 * Declare a concurrent test that uses one or more macros. Additional arguments are passed to the macro. The macro
	 * is responsible for generating a unique test title.
	 */
	(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

	/** Declare a hook that is run once, after all tests have passed. */
	after: AfterInterface<Context>;

	/** Declare a hook that is run after each passing test. */
	afterEach: AfterInterface<Context>;

	/** Declare a hook that is run once, before all tests. */
	before: BeforeInterface<Context>;

	/** Declare a hook that is run before each test. */
	beforeEach: BeforeInterface<Context>;

	/** Declare a test that must call `t.end()` when it's done. */
	cb: CbInterface<Context>;

	/** Declare a test that is expected to fail. */
	failing: FailingInterface<Context>;

	/** Declare tests and hooks that are run serially. */
	serial: SerialInterface<Context>;

	only: OnlyInterface<Context>;
	skip: SkipInterface<Context>;
	todo: TodoDeclaration;
}

export interface AfterInterface<Context = {}> {
	/** Declare a hook that is run once, after all tests have passed. */
	(implementation: Implementation<Context>): void;

	/** Declare a hook that is run once, after all tests have passed. Additional argumens are passed to the macro. */
	(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

	/** Declare a hook that is run once, after all tests have passed. */
	(title: string, implementation: Implementation<Context>): void;

	/** Declare a hook that is run once, after all tests have passed. Additional argumens are passed to the macro. */
	(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

	/** Declare a hook that is run once, after all tests are done. */
	always: AlwaysInterface<Context>;

	/** Declare a hook that must call `t.end()` when it's done. */
	cb: HookCbInterface<Context>;

	skip: HookSkipInterface<Context>;
}

export interface AlwaysInterface<Context = {}> {
	/** Declare a hook that is run once, after all tests are done. */
	(implementation: Implementation<Context>): void;

	/** Declare a hook that is run once, after all tests are done. Additional argumens are passed to the macro. */
	(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

	/** Declare a hook that is run once, after all tests are done. */
	(title: string, implementation: Implementation<Context>): void;

	/** Declare a hook that is run once, after all tests are done. Additional argumens are passed to the macro. */
	(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

	/** Declare a hook that must call `t.end()` when it's done. */
	cb: HookCbInterface<Context>;

	skip: HookSkipInterface<Context>;
}

export interface BeforeInterface<Context = {}> {
	/** Declare a hook that is run once, before all tests. */
	(implementation: Implementation<Context>): void;

	/** Declare a hook that is run once, before all tests. Additional argumens are passed to the macro. */
	(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

	/** Declare a hook that is run once, before all tests. */
	(title: string, implementation: Implementation<Context>): void;

	/** Declare a hook that is run once, before all tests. Additional argumens are passed to the macro. */
	(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

	/** Declare a hook that must call `t.end()` when it's done. */
	cb: HookCbInterface<Context>;

	skip: HookSkipInterface<Context>;
}

export interface CbInterface<Context = {}> {
	/** Declare a test that must call `t.end()` when it's done. */
	(title: string, implementation: CbImplementation<Context>): void;

	/**
	 * Declare a test that uses one or more macros. The macros must call `t.end()` when they're done.
	 * Additional arguments are passed to the macro.
	 */
	(title: string, macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;

	/**
	 * Declare a test that uses one or more macros. The macros must call `t.end()` when they're done.
	 * Additional arguments are passed to the macro. The macro is responsible for generating a unique test title.
	 */
	(macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;

	/** Declare a test that is expected to fail. */
	failing: CbFailingInterface<Context>;

	only: CbOnlyInterface<Context>;
	skip: CbSkipInterface<Context>;
}

export interface CbFailingInterface<Context = {}> {
	/** Declare a test that must call `t.end()` when it's done. The test is expected to fail. */
	(title: string, implementation: CbImplementation<Context>): void;

	/**
	 * Declare a test that uses one or more macros. The macros must call `t.end()` when they're done.
	 * Additional arguments are passed to the macro. The test is expected to fail.
	 */
	(title: string, macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;

	/**
	 * Declare a test that uses one or more macros. The macros must call `t.end()` when they're done.
	 * Additional arguments are passed to the macro. The macro is responsible for generating a unique test title.
	 * The test is expected to fail.
	 */
	(macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;

	only: CbOnlyInterface<Context>;
	skip: CbSkipInterface<Context>;
}

export interface CbOnlyInterface<Context = {}> {
	/**
	 * Declare a test that must call `t.end()` when it's done. Only this test and others declared with `.only()` are run.
	 */
	(title: string, implementation: CbImplementation<Context>): void;

	/**
	 * Declare a test that uses one or more macros. The macros must call `t.end()` when they're done.
	 * Additional arguments are passed to the macro. Only this test and others declared with `.only()` are run.
	 */
	(title: string, macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;

	/**
	 * Declare a test that uses one or more macros. The macros must call `t.end()` when they're done.
	 * Additional arguments are passed to the macro. The macro is responsible for generating a unique test title.
	 * Only this test and others declared with `.only()` are run.
	 */
	(macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;
}

export interface CbSkipInterface<Context = {}> {
	/** Skip this test. */
	(title: string, implementation: CbImplementation<Context>): void;

	/** Skip this test. */
	(title: string, macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;

	/** Skip this test. */
	(macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;
}

export interface FailingInterface<Context = {}> {
	/** Declare a concurrent test. The test is expected to fail. */
	(title: string, implementation: Implementation<Context>): void;

	/**
	 * Declare a concurrent test that uses one or more macros. Additional arguments are passed to the macro.
	 * The test is expected to fail.
	 */
	(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

	/**
	 * Declare a concurrent test that uses one or more macros. Additional arguments are passed to the macro. The macro
	 * is responsible for generating a unique test title. The test is expected to fail.
	 */
	(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

	only: OnlyInterface<Context>;
	skip: SkipInterface<Context>;
}

export interface HookCbInterface<Context = {}> {
	/** Declare a hook that must call `t.end()` when it's done. */
	(implementation: CbImplementation<Context>): void;

	/**
	 * Declare a hook that uses one or more macros. The macros must call `t.end()` when they're done.
	 * Additional arguments are passed to the macro.
	 */
	(macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;

	/** Declare a hook that must call `t.end()` when it's done. */
	(title: string, implementation: CbImplementation<Context>): void;

	/**
	 * Declare a hook that uses one or more macros. The macros must call `t.end()` when they're done.
	 * Additional arguments are passed to the macro.
	 */
	(title: string, macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;

	skip: HookCbSkipInterface<Context>;
}

export interface HookCbSkipInterface<Context = {}> {
	/** Skip this hook. */
	(implementation: CbImplementation<Context>): void;

	/** Skip this hook. */
	(macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;

	/** Skip this hook. */
	(title: string, implementation: CbImplementation<Context>): void;

	/** Skip this hook. */
	(title: string, macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;
}

export interface HookSkipInterface<Context = {}> {
	/** Skip this hook. */
	(implementation: Implementation<Context>): void;

	/** Skip this hook. */
	(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

	/** Skip this hook. */
	(title: string, implementation: Implementation<Context>): void;

	/** Skip this hook. */
	(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
}

export interface OnlyInterface<Context = {}> {
	/** Declare a test. Only this test and others declared with `.only()` are run. */
	(title: string, implementation: Implementation<Context>): void;

	/**
	 * Declare a test that uses one or more macros. Additional arguments are passed to the macro.
	 * Only this test and others declared with `.only()` are run.
	 */
	(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

	/**
	 * Declare a test that uses one or more macros. Additional arguments are passed to the macro. The macro
	 * is responsible for generating a unique test title. Only this test and others declared with `.only()` are run.
	 */
	(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
}

export interface SerialInterface<Context = {}> {
	/** Declare a serial test. */
	(title: string, implementation: Implementation<Context>): void;

	/** Declare a serial test that uses one or more macros. Additional arguments are passed to the macro. */
	(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

	/**
	 * Declare a serial test that uses one or more macros. Additional arguments are passed to the macro. The macro
	 * is responsible for generating a unique test title.
	 */
	(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

	/** Declare a serial hook that is run once, after all tests have passed. */
	after: AfterInterface<Context>;

	/** Declare a serial hook that is run after each passing test. */
	afterEach: AfterInterface<Context>;

	/** Declare a serial hook that is run once, before all tests. */
	before: BeforeInterface<Context>;

	/** Declare a serial hook that is run before each test. */
	beforeEach: BeforeInterface<Context>;

	/** Declare a serial test that must call `t.end()` when it's done. */
	cb: CbInterface<Context>;

	/** Declare a serial test that is expected to fail. */
	failing: FailingInterface<Context>;

	only: OnlyInterface<Context>;
	skip: SkipInterface<Context>;
	todo: TodoDeclaration;
}

export interface SkipInterface<Context = {}> {
	/** Skip this test. */
	(title: string, implementation: Implementation<Context>): void;

	/** Skip this test. */
	(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

	/** Skip this test. */
	(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
}

export interface TodoDeclaration {
	/** Declare a test that should be implemented later. */
	(title: string): void;
}

/** Call to declare a test, or chain to declare hooks or test modifiers */
declare const test: TestInterface;

/** Call to declare a test, or chain to declare hooks or test modifiers */
export default test;

export {test};

/** Call to declare a hook that is run once, after all tests have passed, or chain to declare modifiers. */
export const after: AfterInterface;

/** Call to declare a hook that is run after each passing test, or chain to declare modifiers. */
export const afterEach: AfterInterface;

/** Call to declare a hook that is run once, before all tests, or chain to declare modifiers. */
export const before: BeforeInterface;

/** Call to declare a hook that is run before each test, or chain to declare modifiers. */
export const beforeEach: BeforeInterface;

/** Call to declare a test that must invoke `t.end()` when it's done, or chain to declare modifiers. */
export const cb: CbInterface;

/** Call to declare a test that is expected to fail, or chain to declare modifiers. */
export const failing: FailingInterface;

/** Call to declare a test that is run exclusively, along with other tests declared with `.only()`. */
export const only: OnlyInterface;

/** Call to declare a serial test, or chain to declare serial hooks or test modifiers. */
export const serial: SerialInterface;

/** Skip this test. */
export const skip: SkipInterface;

/** Declare a test that should be implemented later. */
export const todo: TodoDeclaration;
