import { RecordableHistogram } from 'node:perf_hooks';

/**
 * @public
 */
export declare namespace Benchmark {
    /**
     * A `Map` where keys are the {@link (Suite:namespace).Name | suite names} and values are the {@link (Suite:namespace).Results | suite results}.
     *
     * @public
     */
    export type Results = Map<Suite.Name, Suite.Results>;
    /**
     * Options for running a {@link (Benchmark:class)}.
     *
     * @public
     */
    export type RunOptions = {
        /**
         * Whether to run multithreaded suites sequentially or in parallel.
         *
         * @public
         */
        sequential?: boolean | undefined;
    };
}

/**
 * A benchmark which has many {@link SuiteLike}s.
 *
 * @example
 * ```js
 * import { Benchmark, Suite } from '@jonahsnider/benchmark';
 *
 * const benchmark = new Benchmark();
 *
 * const suite = new Suite('concatenation', { warmup: { durationMs: 10_000 }, run: { durationMs: 10_000 } })
 *   .addTest('+', () => 'a' + 'b')
 *   .addTest('templates', () => `${'a'}${'b'}`)
 *   .addTest('.concat()', () => 'a'.concat('b'));
 *
 * benchmark.addSuite(suite);
 *
 * const results = await benchmark.run();
 *
 * console.log(results);
 * ```
 *
 * @public
 */
export declare class Benchmark {
    #private;
    /**
     * The {@link SuiteLike}s in this {@link (Benchmark:class)}.
     */
    readonly suites: ReadonlyMap<Suite.Name, SuiteLike>;
    /**
     * Add a {@link SuiteLike} to this {@link (Benchmark:class)}.
     *
     * @example
     * ```js
     * benchmark.addSuite(suite);
     * ```
     *
     * @param suite - The {@link SuiteLike} to add
     *
     * @returns `this`
     */
    addSuite(suite: SuiteLike, options?: {
        threaded: false;
    }): this;
    /**
     * Add a {@link SuiteLike} to this {@link (Benchmark:class)} by loading it in a separate thread via its filepath.
     *
     * @example
     * ```js
     * await benchmark.addSuite(suite);
     * ```
     *
     * @param suite - A {@link (Suite:class)} with a filepath provided
     *
     * @returns `this`
     */
    addSuite(suite: SuiteLike, options: {
        threaded: true;
    }): Promise<this>;
    /**
     * Run all {@link (Suite:class)}s for this {@link (Benchmark:class)}.
     *
     * @example
     * ```js
     * const results = await benchmark.runSuites();
     * ```
     *
     * @example
     * Using an `AbortSignal` to cancel the benchmark:
     * ```js
     * const ac = new AbortController();
     * const signal = ac.signal;
     *
     * benchmark
     *   .runSuites(signal)
     *   .then(console.log)
     *   .catch(error => {
     *   	if (error.name === 'AbortError') {
     *   		console.log('The benchmark was aborted');
     *   	}
     *   });
     *
     * ac.abort();
     * ```
     *
     * @param abortSignal - An optional `AbortSignal` that can be used to cancel the running suites
     *
     * @returns A {@link (Benchmark:namespace).Results} `Map`
     */
    runSuites(abortSignal?: AbortSignal, options?: Benchmark.RunOptions): Promise<Benchmark.Results>;
}

/**
 * @public
 */
export declare namespace Suite {
    /**
     * The name of a {@link (Suite:class)}.
     *
     * @public
     */
    export type Name = string;
    /**
     * Results from running a {@link (Suite:class)}.
     *
     * @public
     */
    export type Results = Map<Test.Name, RecordableHistogram>;
    /**
     * Options for running {@link (Test:class)}s.
     *
     * @public
     */
    export type RunOptions = {
        trials: number;
        durationMs?: undefined;
    } | {
        trials?: undefined;
        durationMs: number;
    };
    /**
     * Options for running a {@link (Suite:class)}.
     *
     * @public
     */
    export type Options = {
        run: RunOptions;
        warmup: RunOptions;
        filepath?: string | undefined;
    };
}

/**
 * A collection of {@link (Test:class)}s that are different implementations of the same thing (ex. different ways of sorting an array).
 *
 * @example
 * ```js
 * import { Suite } from '@jonahsnider/benchmark';
 *
 * const suite = new Suite('concatenation', { warmup: { durationMs: 10_000 }, run: { durationMs: 10_000 } })
 *   .addTest('+', () => 'a' + 'b')
 *   .addTest('templates', () => `${'a'}${'b'}`)
 *   .addTest('.concat()', () => 'a'.concat('b'));
 *
 * const results = await suite.run();
 *
 * console.log(results);
 * ```
 *
 * @public
 */
export declare class Suite implements SuiteLike {
    #private;
    readonly name: Suite.Name;
    /**
     * Options for running this {@link (Suite:class)} and its warmup.
     */
    readonly options: Suite.Options;
    /**
     * The tests in this {@link (Suite:class)}.
     */
    tests: ReadonlyMap<Test.Name, Test>;
    /**
     * This {@link (Suite:class)}'s filepath, if it was provided.
     * Used for running the {@link (Suite:class)} in a separate thread.
     */
    get filepath(): string | undefined;
    /**
     * Creates a new {@link (Suite:class)}.
     *
     * @example
     * ```js
     * import { Suite } from '@jonahsnider/benchmark';
     *
     * const suite = new Suite('concatenation', { warmup: { durationMs: 10_000 }, run: { durationMs: 10_000 } });
     * ```
     *
     * @example
     * Suites that specify a filepath can be run in a separate thread in a {@link (Benchmark:class)}.
     * ```js
     * import { Suite } from '@jonahsnider/benchmark';
     *
     * const suite = new Suite('concatenation', {
     *   warmup: { durationMs: 10_000 },
     *   run: { durationMs: 10_000 },
     *   filepath: import.meta.url
     * });
     * ```
     *
     * @param name - The name of the {@link (Suite:class)}
     * @param options - Options for the {@link (Suite:class)}
     */
    constructor(name: Suite.Name, 
    /**
     * Options for running this {@link (Suite:class)} and its warmup.
     */
    options: Suite.Options);
    /**
     * Adds a test to this {@link (Suite:class)}.
     *
     * @example
     * ```js
     * const test = new Test(() => 'a' + 'b');
     *
     * suite.addTest('+', test);
     * ```
     *
     * @param testName - The name of the test
     * @param test - The test to add
     *
     * @returns `this`
     */
    addTest(testName: string, test: Test): this;
    /**
     * Creates and adds a test to this {@link (Suite:class)}.
     *
     * @example
     * ```js
     * suite.addTest('+', () => 'a' + 'b');
     * ```
     *
     * @param testName - The name of the test
     * @param fn - The function to run
     *
     * @returns `this`
     */
    addTest(testName: string, fn: () => unknown): this;
    /**
     * Runs this {@link (Suite:class)} using {@link (Suite:class).options}.
     *
     * @example
     * ```js
     * const results = await suite.run();
     * ```
     *
     * @example
     * Using an `AbortSignal` to cancel the suite:
     * ```js
     * const ac = new AbortController();
     * const signal = ac.signal;
     *
     * suite
     *   .run(signal)
     *   .then(console.log)
     *   .catch(error => {
     *   	if (error.name === 'AbortError') {
     *   		console.log('The suite was aborted');
     *   	}
     *   });
     *
     * ac.abort();
     * ```
     *
     * @returns The results of running this {@link (Suite:class)}
     */
    run(abortSignal?: AbortSignal): Promise<Suite.Results>;
}

/**
 * A suite of related tests that can be run together.
 *
 * @public
 */
export declare type SuiteLike = {
    /**
     * The name of this {@link SuiteLike}.
     */
    readonly name: Suite.Name;
    /**
     * The filepath to this {@link SuiteLike}, when available.
     */
    readonly filepath?: string | undefined;
    /**
     * Runs this {@link SuiteLike}.
     *
     * @example
     * A synchronous implementation:
     * ```js
     * const results = suite.run();
     * ```
     *
     * @example
     * An asynchronous implementation:
     * ```js
     * const results = await suite.run();
     * ```
     *
     * @example
     * Using an `AbortSignal` to cancel the suite:
     * ```js
     * const ac = new AbortController();
     * const signal = ac.signal;
     *
     * suite
     *   .run(signal)
     *   .then(console.log)
     *   .catch(error => {
     *   	if (error.name === 'AbortError') {
     *   		console.log('The suite was aborted');
     *   	}
     *   });
     *
     * ac.abort();
     * ```
     *
     * @returns The results of running this {@link SuiteLike}
     */
    run(abortSignal?: AbortSignal): Suite.Results | PromiseLike<Suite.Results>;
};

/**
 * @public
 */
export declare namespace Test {
    /**
     * The name of a {@link (Test:class)}.
     *
     * @public
     */
    export type Name = string;
}

/**
 * Tracks a function's execution in {@link (Test:class).histogram}.
 *
 * @example
 * ```ts
 * import { Test } from '@jonahsnider/benchmark';
 *
 * const test = new Test(() => 'a' + 'b');
 * ```
 *
 * @public
 */
export declare class Test<T = unknown> {
    #private;
    /** Execution times for this {@link (Test:class)}'s implementation. */
    readonly histogram: RecordableHistogram;
    /**
     * Create a new {@link (Test:class)} with a given implementation.
     *
     * You probably don't want to instantiate this class directly, instead you can register tests with {@link (Suite:class).(addTest:2)}.
     * You can also register {@link (Test:class)} instances with {@link (Suite:class).(addTest:1)}.
     *
     * @example
     * ```ts
     * const test = new Test(() => 'a' + 'b');
     * ```
     *
     * @param implementation - The implementation function of the test
     */
    constructor(implementation: () => T | PromiseLike<T>);
    /**
     * Runs this {@link (Test:class)}'s implementation once and records the execution time in {@link (Test:class).histogram}.
     *
     * @returns The return value of this {@link (Test:class)}'s implementation
     */
    run(): Promise<T>;
}

export { }
