import type { WaitingOptions } from './types.js'

/**
 * A plugin the adds support for various event-related functions in tests.
 */
export class EventPlugin {
  /**
   * Waits for an event to be fired on a target.
   * @param target The target to listen for the event on.
   * @param type The type of event to listen for.
   * @returns A promise that resolves when the event is fired with the captured event.
   */
  async until(target: EventTarget, type: string, options?: AddEventListenerOptions & WaitingOptions): Promise<Event> {
    const timeout = options?.timeout
    return new Promise((resolve, reject) => {
      const timer =
        typeof timeout === 'number' &&
        setTimeout(() => {
          target.removeEventListener(type, handler)
          reject(new Error(`Timeout waiting for event: ${type}`, { cause: 'E_TIMEOUT' }))
        }, timeout)
      const handler = (event: Event) => {
        if (timer) {
          clearTimeout(timer)
        }
        target.removeEventListener(type, handler)
        resolve(event)
      }
      target.addEventListener(type, handler, options)
    })
  }

  /**
   * Captures all events of a specific type dispatched on a target within a given timeout.
   * @param target The target to listen for events on.
   * @param type The type of event to capture.
   * @param options Optional settings, including timeout.
   * @returns A promise that resolves with an array of captured events.
   */
  async capture<T extends Event = Event>(
    target: EventTarget,
    type: string,
    options?: AddEventListenerOptions & WaitingOptions
  ): Promise<T[]> {
    const timeout = options?.timeout
    const capturedEvents: T[] = []

    return new Promise((resolve) => {
      setTimeout(() => {
        target.removeEventListener(type, handler)
        resolve(capturedEvents)
      }, timeout)

      const handler = (event: Event) => {
        capturedEvents.push(event as T)
      }

      target.addEventListener(type, handler, options)
    })
  }
}
