import type {ErrorEvent} from './errors.ts'

/**
 * Stripped down version of `fetch()`, only defining the parts we care about.
 * This ensures it should work with "most" fetch implementations.
 *
 * @public
 */
export type FetchLike = (
  url: string | URL,
  init: EventSourceFetchInit,
) => Promise<FetchLikeResponse>

/**
 * Subset of `RequestInit` used for `fetch()` calls made by the `EventSource` class.
 * As we know that we will be passing certain values, we can be more specific and have
 * users not have to do optional chaining and similar for things that will always be there.
 *
 * @public
 */
export interface EventSourceFetchInit {
  /** An AbortSignal to set request's signal. Typed as `any` because of polyfill inconsistencies. */
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  signal: {aborted: boolean} | any

  /** A Headers object, an object literal, or an array of two-item arrays to set request's headers. */
  headers: {
    [key: string]: string
    Accept: 'text/event-stream'
  }

  /** A string to indicate whether the request will use CORS, or will be restricted to same-origin URLs. Sets request's mode. */
  mode: 'cors' | 'no-cors' | 'same-origin'

  /** A string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. Sets request's credentials. */
  credentials?: 'include' | 'omit' | 'same-origin'

  /** Controls how the request is cached. */
  cache: 'no-store'

  /** A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */
  redirect: 'error' | 'follow' | 'manual'
}

/**
 * A chunk read off a response body.
 *
 * Chunks are handed to a `TextDecoder`, which only accepts buffers, so this is what a reader has
 * to yield to be usable. The union is spelled out because the node (`TypedArray | DataView`) and
 * DOM (`ArrayBufferView`) typings describe "any buffer view" differently, and these three members
 * are accepted by both.
 */
type BodyChunk = Uint8Array | DataView | ArrayBuffer

/**
 * Stripped down version of `ReadableStreamDefaultReader`, only defining the parts we care about.
 *
 * Note that the `done: true` result allows `value` to be missing _or_ explicitly `undefined`:
 * TypeScript 5.9's DOM lib types `ReadableStreamDefaultReader.read()`'s done-result as
 * `{done: true, value: T | undefined}`, so under `exactOptionalPropertyTypes` an optional
 * property alone would reject standard readers.
 *
 * @public
 */
export interface ReaderLike {
  read(): Promise<{done: false; value: BodyChunk} | {done: true; value?: BodyChunk | undefined}>
  cancel(): Promise<void>
}

/**
 * Minimal version of the `Response` type returned by `fetch()`.
 *
 * @public
 */
export interface FetchLikeResponse {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  readonly body: {getReader(): ReaderLike} | Response['body'] | null
  readonly url: string
  readonly status: number
  readonly redirected: boolean
  readonly headers: {get(name: string): string | null}
}

/**
 * Mirrors the official DOM typings, with the exception of the extended ErrorEvent.
 *
 * @public
 */
export interface EventSourceEventMap {
  error: ErrorEvent
  message: MessageEvent
  open: Event
}

/**
 * Mirrors the official DOM typings (for the most part)
 *
 * @public
 */
export interface EventSourceInit {
  /**
   * A boolean value, defaulting to `false`, indicating if CORS should be set to `include` credentials.
   */
  withCredentials?: boolean

  /**
   * Optional fetch implementation to use. Defaults to `globalThis.fetch`.
   * Can also be used for advanced use cases like mocking, proxying, custom certs etc.
   */
  fetch?: FetchLike

  /**
   * Maximum number of characters to buffer while parsing an incomplete EventSource line.
   * Defaults to 100 MB.
   */
  maxBufferSize?: number
}

/**
 * Mirrors the official DOM typings (sorta).
 *
 * @public
 */
export interface EventListenerOptions {
  /** Not directly used by Node.js. Added for API completeness. Default: `false`. */
  capture?: boolean
}

/**
 * Mirrors the official DOM typings (sorta).
 *
 * @public
 */
export interface AddEventListenerOptions extends EventListenerOptions {
  /** When `true`, the listener is automatically removed when it is first invoked. Default: `false`. */
  once?: boolean
  /** When `true`, serves as a hint that the listener will not call the `Event` object's `preventDefault()` method. Default: false. */
  passive?: boolean
  /** The listener will be removed when the given AbortSignal object's `abort()` method is called. */
  signal?: AbortSignal
}

/**
 * Mirrors the official DOM typings.
 *
 * @public
 */
export type EventListenerOrEventListenerObject = EventListener | EventListenerObject

/**
 * Mirrors the official DOM typings.
 *
 * @public
 */
export interface EventListener {
  (evt: Event | MessageEvent): void
}

/**
 * Mirrors the official DOM typings.
 *
 * @public
 */
export interface EventListenerObject {
  handleEvent(object: Event): void
}
