import { BrowserAgent, LLMClient, BrowserOptions } from 'magnitude-core';
import EventEmitter$1 from 'node:events';

/**
 * Minimal `EventEmitter` interface that is molded against the Node.js
 * `EventEmitter` interface.
 */
declare class EventEmitter<
  EventTypes extends EventEmitter.ValidEventTypes = string | symbol,
  Context extends any = any
> {
  static prefixed: string | boolean;

  /**
   * Return an array listing the events for which the emitter has registered
   * listeners.
   */
  eventNames(): Array<EventEmitter.EventNames<EventTypes>>;

  /**
   * Return the listeners registered for a given event.
   */
  listeners<T extends EventEmitter.EventNames<EventTypes>>(
    event: T
  ): Array<EventEmitter.EventListener<EventTypes, T>>;

  /**
   * Return the number of listeners listening to a given event.
   */
  listenerCount(event: EventEmitter.EventNames<EventTypes>): number;

  /**
   * Calls each of the listeners registered for a given event.
   */
  emit<T extends EventEmitter.EventNames<EventTypes>>(
    event: T,
    ...args: EventEmitter.EventArgs<EventTypes, T>
  ): boolean;

  /**
   * Add a listener for a given event.
   */
  on<T extends EventEmitter.EventNames<EventTypes>>(
    event: T,
    fn: EventEmitter.EventListener<EventTypes, T>,
    context?: Context
  ): this;
  addListener<T extends EventEmitter.EventNames<EventTypes>>(
    event: T,
    fn: EventEmitter.EventListener<EventTypes, T>,
    context?: Context
  ): this;

  /**
   * Add a one-time listener for a given event.
   */
  once<T extends EventEmitter.EventNames<EventTypes>>(
    event: T,
    fn: EventEmitter.EventListener<EventTypes, T>,
    context?: Context
  ): this;

  /**
   * Remove the listeners of a given event.
   */
  removeListener<T extends EventEmitter.EventNames<EventTypes>>(
    event: T,
    fn?: EventEmitter.EventListener<EventTypes, T>,
    context?: Context,
    once?: boolean
  ): this;
  off<T extends EventEmitter.EventNames<EventTypes>>(
    event: T,
    fn?: EventEmitter.EventListener<EventTypes, T>,
    context?: Context,
    once?: boolean
  ): this;

  /**
   * Remove all listeners, or those of the specified event.
   */
  removeAllListeners(event?: EventEmitter.EventNames<EventTypes>): this;
}

declare namespace EventEmitter {
  export interface ListenerFn<Args extends any[] = any[]> {
    (...args: Args): void;
  }

  export interface EventEmitterStatic {
    new <
      EventTypes extends ValidEventTypes = string | symbol,
      Context = any
    >(): EventEmitter<EventTypes, Context>;
  }

  /**
   * `object` should be in either of the following forms:
   * ```
   * interface EventTypes {
   *   'event-with-parameters': any[]
   *   'event-with-example-handler': (...args: any[]) => void
   * }
   * ```
   */
  export type ValidEventTypes = string | symbol | object;

  export type EventNames<T extends ValidEventTypes> = T extends string | symbol
    ? T
    : keyof T;

  export type ArgumentMap<T extends object> = {
    [K in keyof T]: T[K] extends (...args: any[]) => void
      ? Parameters<T[K]>
      : T[K] extends any[]
      ? T[K]
      : any[];
  };

  export type EventListener<
    T extends ValidEventTypes,
    K extends EventNames<T>
  > = T extends string | symbol
    ? (...args: any[]) => void
    : (
        ...args: ArgumentMap<Exclude<T, string | symbol>>[Extract<K, keyof T>]
      ) => void;

  export type EventArgs<
    T extends ValidEventTypes,
    K extends EventNames<T>
  > = Parameters<EventListener<T, K>>;

  export const EventEmitter: EventEmitterStatic;
}

interface CheckEvents {
    'checkStarted': (check: string) => void;
    'checkDone': (check: string, passed: boolean) => void;
}
declare class TestCaseAgent extends BrowserAgent {
    readonly checkEvents: EventEmitter<CheckEvents>;
    check(description: string): Promise<void>;
}

interface TestOptions {
    url?: string;
    prompt?: string;
}
interface WebServerConfig {
    command: string;
    url: string;
    timeout?: number;
    reuseExistingServer?: boolean;
}
type MagnitudeConfig = {
    url: string;
    llm?: LLMClient | LLMClient[];
    webServer?: WebServerConfig | WebServerConfig[];
    browser?: BrowserOptions;
    telemetry?: boolean;
    continueAfterFailure?: boolean;
    display?: {
        showActions?: boolean;
        showThoughts?: boolean;
    };
};
type TestFunction = (agent: TestCaseAgent) => Promise<void>;
type TestGroupFunction = () => void;
interface TestGroupDeclaration {
    (id: string, options: TestOptions, groupFn: TestGroupFunction): void;
    (id: string, groupFn: TestGroupFunction): void;
}
interface TestDeclaration {
    (title: string, options: TestOptions, testFn: TestFunction): void;
    (title: string, testFn: TestFunction): void;
    group: TestGroupDeclaration;
}

declare global {
    var __magnitudeTestFunctions: Map<string, TestFunction> | undefined;
    var __magnitudeMessageEmitter: EventEmitter$1 | undefined;
    var __magnitudeTestHooks: TestHooks | undefined;
    var __magnitudeTestPromptStack: Record<string, string[]> | undefined;
    var __magnitudeTestRegistry: Map<string, TestMetadata> | undefined;
}
type TestHooks = Record<'beforeAll' | 'afterAll' | 'beforeEach' | 'afterEach', (() => void | Promise<void>)[]>;
type TestMetadata = {
    title: string;
    url: string;
    filepath: string;
    group?: string;
};

declare const test: TestDeclaration;
declare const beforeAll: (fn: TestHooks["beforeAll" | "afterAll" | "beforeEach" | "afterEach"][number]) => void;
declare const afterAll: (fn: TestHooks["beforeAll" | "afterAll" | "beforeEach" | "afterEach"][number]) => void;
declare const beforeEach: (fn: TestHooks["beforeAll" | "afterAll" | "beforeEach" | "afterEach"][number]) => void;
declare const afterEach: (fn: TestHooks["beforeAll" | "afterAll" | "beforeEach" | "afterEach"][number]) => void;

export { type MagnitudeConfig, type WebServerConfig, afterAll, afterEach, beforeAll, beforeEach, test };
