declare namespace Chai {
    type Message = string | (() => string);
    type ObjectProperty = string | symbol | number;

    interface PathInfo {
        parent: object;
        name: string;
        value?: any;
        exists: boolean;
    }

    interface Constructor<T> {
        new(...args: any[]): T;
    }

    interface ErrorConstructor {
        new(...args: any[]): Error;
    }

    interface ChaiUtils {
        addChainableMethod(
            // object to define the method on, e.g. chai.Assertion.prototype
            ctx: object,
            // method name
            name: string,
            // method itself; any arguments
            method: (...args: any[]) => void,
            // called when property is accessed
            chainingBehavior?: () => void,
        ): void;
        overwriteChainableMethod(
            ctx: object,
            name: string,
            method: (...args: any[]) => void,
            chainingBehavior?: () => void,
        ): void;
        addLengthGuard(
            fn: Function,
            assertionName: string,
            isChainable: boolean,
        ): void;
        addMethod(ctx: object, name: string, method: Function): void;
        addProperty(ctx: object, name: string, getter: () => any): void;
        overwriteMethod(ctx: object, name: string, method: Function): void;
        overwriteProperty(ctx: object, name: string, getter: (this: AssertionStatic, _super: any) => any): void;
        compareByInspect(a: object, b: object): -1 | 1;
        expectTypes(obj: object, types: string[]): void;
        flag(obj: object, key: string, value?: any): any;
        getActual(obj: object, args: AssertionArgs): any;
        getProperties(obj: object): string[];
        getEnumerableProperties(obj: object): string[];
        getOwnEnumerablePropertySymbols(obj: object): symbol[];
        getOwnEnumerableProperties(obj: object): Array<string | symbol>;
        getMessage(errorLike: Error | string): string;
        getMessage(obj: any, args: AssertionArgs): string;
        inspect(obj: any, showHidden?: boolean, depth?: number, colors?: boolean): string;
        isProxyEnabled(): boolean;
        objDisplay(obj: object): void;
        proxify(obj: object, nonChainableMethodName: string): object;
        test(obj: object, args: AssertionArgs): boolean;
        transferFlags(assertion: Assertion, obj: object, includeAll?: boolean): void;
        compatibleInstance(thrown: Error, errorLike: Error | ErrorConstructor): boolean;
        compatibleConstructor(thrown: Error, errorLike: Error | ErrorConstructor): boolean;
        compatibleMessage(thrown: Error, errMatcher: string | RegExp): boolean;
        getConstructorName(constructorFn: Function): string;
        getFuncName(constructorFn: Function): string | null;

        // Reexports from pathval:
        hasProperty(obj: object | undefined | null, name: ObjectProperty): boolean;
        getPathInfo(obj: object, path: string): PathInfo;
        getPathValue(obj: object, path: string): object | undefined;
    }

    type ChaiPlugin = (chai: ChaiStatic, utils: ChaiUtils) => void;

    interface ChaiStatic {
        expect: ExpectStatic;
        should(): Should;
        /**
         * Provides a way to extend the internals of Chai
         */
        use(fn: ChaiPlugin): ChaiStatic;
        util: ChaiUtils;
        assert: AssertStatic;
        config: Config;
        Assertion: AssertionStatic;
        AssertionError: typeof AssertionError;
        version: string;
    }

    export interface ExpectStatic {
        (val: any, message?: string): Assertion;
        fail(message?: string): never;
        fail(actual: any, expected: any, message?: string, operator?: Operator): never;
    }

    export interface AssertStatic extends Assert {
    }

    // chai.Assertion.prototype.assert arguments
    type AssertionArgs = [
        // 'expression to be tested'
        // This parameter is unused and the docs list its type as
        // 'Philosophical', which is mentioned nowhere else in the source. Do
        // with that what you will!
        any,
        Message, // message if value fails
        Message, // message if negated value fails
        any, // expected value
        any?, // actual value
        boolean?, // showDiff
    ];

    export interface AssertionPrototype {
        assert(...args: AssertionArgs): void;
        _obj: any;
    }

    export interface AssertionStatic extends AssertionPrototype {
        prototype: AssertionPrototype;

        new(target: any, message?: string, ssfi?: Function, lockSsfi?: boolean): Assertion;

        // Deprecated properties:
        includeStack: boolean;
        showDiff: boolean;

        // Partials of functions on ChaiUtils:
        addProperty(name: string, getter: (this: AssertionStatic) => any): void;
        addMethod(name: string, method: (this: AssertionStatic, ...args: any[]) => any): void;
        addChainableMethod(
            name: string,
            method: (this: AssertionStatic, ...args: any[]) => void,
            chainingBehavior?: () => void,
        ): void;
        overwriteProperty(name: string, getter: (this: AssertionStatic, _super: any) => any): void;
        overwriteMethod(name: string, method: (this: AssertionStatic, ...args: any[]) => any): void;
        overwriteChainableMethod(
            name: string,
            method: (this: AssertionStatic, ...args: any[]) => void,
            chainingBehavior?: () => void,
        ): void;
    }

    export type Operator = string; // "==" | "===" | ">" | ">=" | "<" | "<=" | "!=" | "!==";

    export type OperatorComparable = boolean | null | number | string | undefined | Date;

    interface ShouldAssertion {
        equal(value1: any, value2: any, message?: string): void;
        Throw: ShouldThrow;
        throw: ShouldThrow;
        exist(value: any, message?: string): void;
    }

    interface Should extends ShouldAssertion {
        not: ShouldAssertion;
        fail(message?: string): never;
        fail(actual: any, expected: any, message?: string, operator?: Operator): never;
    }

    interface ShouldThrow {
        (actual: Function, expected?: string | RegExp, message?: string): void;
        (actual: Function, constructor: Error | Function, expected?: string | RegExp, message?: string): void;
    }

    interface Assertion extends LanguageChains, NumericComparison, TypeComparison {
        not: Assertion;
        deep: Deep;
        ordered: Ordered;
        nested: Nested;
        own: Own;
        any: KeyFilter;
        all: KeyFilter;
        a: Assertion;
        an: Assertion;
        include: Include;
        includes: Include;
        contain: Include;
        contains: Include;
        ok: Assertion;
        true: Assertion;
        false: Assertion;
        null: Assertion;
        undefined: Assertion;
        NaN: Assertion;
        exist: Assertion;
        empty: Assertion;
        arguments: Assertion;
        Arguments: Assertion;
        finite: Assertion;
        equal: Equal;
        equals: Equal;
        eq: Equal;
        eql: Equal;
        eqls: Equal;
        property: Property;
        ownProperty: Property;
        haveOwnProperty: Property;
        ownPropertyDescriptor: OwnPropertyDescriptor;
        haveOwnPropertyDescriptor: OwnPropertyDescriptor;
        length: Length;
        lengthOf: Length;
        match: Match;
        matches: Match;
        string(string: string, message?: string): Assertion;
        keys: Keys;
        key(string: string): Assertion;
        throw: Throw;
        throws: Throw;
        Throw: Throw;
        respondTo: RespondTo;
        respondsTo: RespondTo;
        itself: Assertion;
        satisfy: Satisfy;
        satisfies: Satisfy;
        closeTo: CloseTo;
        approximately: CloseTo;
        members: Members;
        increase: PropertyChange;
        increases: PropertyChange;
        decrease: PropertyChange;
        decreases: PropertyChange;
        change: PropertyChange;
        changes: PropertyChange;
        extensible: Assertion;
        sealed: Assertion;
        frozen: Assertion;
        oneOf: OneOf;
    }

    interface LanguageChains {
        to: Assertion;
        be: Assertion;
        been: Assertion;
        is: Assertion;
        that: Assertion;
        which: Assertion;
        and: Assertion;
        has: Assertion;
        have: Assertion;
        with: Assertion;
        at: Assertion;
        of: Assertion;
        same: Assertion;
        but: Assertion;
        does: Assertion;
    }

    interface NumericComparison {
        above: NumberComparer;
        gt: NumberComparer;
        greaterThan: NumberComparer;
        least: NumberComparer;
        gte: NumberComparer;
        greaterThanOrEqual: NumberComparer;
        below: NumberComparer;
        lt: NumberComparer;
        lessThan: NumberComparer;
        most: NumberComparer;
        lte: NumberComparer;
        lessThanOrEqual: NumberComparer;
        within(start: number, finish: number, message?: string): Assertion;
        within(start: Date, finish: Date, message?: string): Assertion;
    }

    interface NumberComparer {
        (value: number | Date, message?: string): Assertion;
    }

    interface TypeComparison {
        (type: string, message?: string): Assertion;
        instanceof: InstanceOf;
        instanceOf: InstanceOf;
    }

    interface InstanceOf {
        (constructor: any, message?: string): Assertion;
    }

    interface CloseTo {
        (expected: number, delta: number, message?: string): Assertion;
    }

    interface Nested {
        include: Include;
        includes: Include;
        contain: Include;
        contains: Include;
        property: Property;
        members: Members;
    }

    interface Own {
        include: Include;
        includes: Include;
        contain: Include;
        contains: Include;
        property: Property;
    }

    interface Deep extends KeyFilter {
        be: Assertion;
        equal: Equal;
        equals: Equal;
        eq: Equal;
        include: Include;
        includes: Include;
        contain: Include;
        contains: Include;
        property: Property;
        ordered: Ordered;
        nested: Nested;
        oneOf: OneOf;
        own: Own;
    }

    interface Ordered {
        members: Members;
    }

    interface KeyFilter {
        keys: Keys;
        members: Members;
    }

    interface Equal {
        (value: any, message?: string): Assertion;
    }

    interface Property {
        (name: string | symbol, value: any, message?: string): Assertion;
        (name: string | symbol, message?: string): Assertion;
    }

    interface OwnPropertyDescriptor {
        (name: string | symbol, descriptor: PropertyDescriptor, message?: string): Assertion;
        (name: string | symbol, message?: string): Assertion;
    }

    interface Length extends LanguageChains, NumericComparison {
        (length: number, message?: string): Assertion;
    }

    interface Include {
        (value: any, message?: string): Assertion;
        keys: Keys;
        deep: Deep;
        ordered: Ordered;
        members: Members;
        any: KeyFilter;
        all: KeyFilter;
        oneOf: OneOf;
    }

    interface OneOf {
        (list: readonly unknown[], message?: string): Assertion;
    }

    interface Match {
        (regexp: RegExp, message?: string): Assertion;
    }

    interface Keys {
        (...keys: string[]): Assertion;
        (keys: readonly any[] | Object): Assertion;
    }

    interface Throw {
        (expected?: string | RegExp, message?: string): Assertion;
        (constructor: Error | Function, expected?: string | RegExp, message?: string): Assertion;
    }

    interface RespondT