import { IncomingMessage, ServerResponse } from 'node:http';
import { Http2ServerRequest, Http2ServerResponse } from 'node:http2';
import { Readable } from 'node:stream';
import { EventEmitter } from 'node:events';

interface IterateOptions {
    /**
     * Event name/type to be emitted when iterable data is sent to the client.
     *
     * Defaults to `"iteration"`.
     */
    eventName?: string;
}
type PushFromIterable = (iterable: Iterable<unknown> | AsyncIterable<unknown>, options?: IterateOptions) => Promise<void>;

type WebReadableStream = ReadableStream;
interface StreamOptions {
    /**
     * Event name/type to be emitted when stream data is sent to the client.
     *
     * Defaults to `"stream"`.
     */
    eventName?: string;
}
type PushFromStream = (stream: Readable | WebReadableStream, options?: StreamOptions) => Promise<boolean>;

type SanitizerFunction = (text: string) => string;

/**
 * Serialize arbitrary data to a string that can be sent over the wire to the client.
 */
type SerializerFunction = (data: unknown) => string;

interface EventBufferOptions {
    /**
     * Serialize data to a string that can be written.
     *
     * Defaults to `JSON.stringify`.
     */
    serializer?: SerializerFunction;
    /**
     * Sanitize values so as to not prematurely dispatch events when writing fields whose text inadvertently contains newlines.
     *
     * By default, CR, LF and CRLF characters are replaced with a single LF character (`\n`) and then any trailing LF characters are stripped so as to prevent a blank line being written and accidentally dispatching the event before `.dispatch()` is called.
     */
    sanitizer?: SanitizerFunction;
}
/**
 * An `EventBuffer` allows you to write raw spec-compliant SSE fields into a text buffer that can be sent directly over the wire.
 */
declare class EventBuffer {
    private buffer;
    private serialize;
    private sanitize;
    constructor(options?: EventBufferOptions);
    /**
     * Write a line with a field key and value appended with a newline character.
     */
    private writeField;
    /**
     * Write an event name field (also referred to as the event "type" in the specification).
     *
     * @param type - Event name/type.
     */
    event(type: string): this;
    /**
     * Write arbitrary data into a data field.
     *
     * Data is serialized to a string using the given `serializer` function option or JSON stringification by default.
     *
     * @param data - Data to serialize and write.
     */
    data: (data: unknown) => this;
    /**
     * Write an event ID field.
     *
     * Defaults to an empty string if no argument is given.
     *
     * @param id - Identification string to write.
     */
    id: (id?: string) => this;
    /**
     * Write a retry field that suggests a reconnection time with the given milliseconds.
     *
     * @param time - Time in milliseconds to retry.
     */
    retry: (time: number) => this;
    /**
     * Write a comment (an ignored field).
     *
     * This will not fire an event but is often used to keep the connection alive.
     *
     * @param text - Text of the comment. Otherwise writes an empty field value.
     */
    comment: (text?: string) => this;
    /**
     * Indicate that the event has finished being created by writing an additional newline character.
     */
    dispatch: () => this;
    /**
     * Create, write and dispatch an event with the given data all at once.
     *
     * This is equivalent to calling the methods `event`, `id`, `data` and `dispatch` in that order.
     *
     * If no event name is given, the event name is set to `"message"`.
     *
     * If no event ID is given, the event ID is set to a unique string generated using a cryptographic pseudorandom number generator.
     *
     * @param data - Data to write.
     * @param eventName - Event name to write.
     * @param eventId - Event ID to write.
     */
    push: (data: unknown, eventName?: string, eventId?: string) => this;
    /**
     * Pipe readable stream data as a series of events into the buffer.
     *
     * This uses the `push` method under the hood.
     *
     * If no event name is given in the `options` object, the event name is set to `"stream"`.
     *
     * @param stream - Readable stream to consume data from.
     * @param options - Event name to use for each event created.
     *
     * @returns A promise that resolves with `true` or rejects based on the success of the stream write finishing.
     */
    stream: PushFromStream;
    /**
     * Iterate over an iterable and write yielded values as events into the buffer.
     *
     * This uses the `push` method under the hood.
     *
     * If no event name is given in the `options` object, the event name is set to `"iteration"`.
     *
     * @param iterable - Iterable to consume data from.
     *
     * @returns A promise that resolves once all data has been successfully yielded from the iterable.
     */
    iterate: PushFromIterable;
    /**
     * Clear the contents of the buffer.
     */
    clear: () => this;
    /**
     * Get a copy of the buffer contents.
     */
    read: () => string;
}

interface EventMap {
    [name: string | symbol]: (...args: any[]) => void;
}
/**
 * Get event names type
 */
type EventNames<T> = keyof {
    [K in keyof T as string extends K ? never : number extends K ? never : K]: never;
} | (string & {});
/**
 * Wraps the EventEmitter class to add types that map event names
 * to types of arguments in the event handler callback.
 */
declare class TypedEmitter<Events extends EventMap> extends EventEmitter {
    addListener<EventName extends EventNames<Events>>(event: EventName, listener: Events[EventName]): this;
    prependListener<EventName extends EventNames<Events>>(event: EventName, listener: Events[EventName]): this;
    prependOnceListener<EventName extends EventNames<Events>>(event: EventName, listener: Events[EventName]): this;
    on<EventName extends EventNames<Events>>(event: EventName, listener: Events[EventName]): this;
    once<EventName extends EventNames<Events>>(event: EventName, listener: Events[EventName]): this;
    emit<EventName extends EventNames<Events>>(event: EventName, ...args: Parameters<Events[EventName]>): boolean;
    off<EventName extends EventNames<Events>>(event: EventName, listener: Events[EventName]): this;
    removeListener<EventName extends EventNames<Events>>(event: EventName, listener: Events[EventName]): this;
}

interface SessionOptions<State = DefaultSessionState> extends Pick<EventBufferOptions, "serializer" | "sanitizer"> {
    /**
     * Whether to trust or ignore the last event ID given by the client in the `Last-Event-ID` request header.
     *
     * When set to `false`, the `lastId` property will always be initialized to an empty string.
     *
     * Defaults to `true`.
     */
    trustClientEventId?: boolean;
    /**
     * Time in milliseconds for the client to wait before attempting to reconnect if the connection is closed.
     *
     * This is a request to the client browser, and does not guarantee that the client will actually respect the given time.
     *
     * Equivalent to immediately calling `.retry().dispatch().flush()` after a connection is made.
     *
     * Give as `null` to avoid sending an explicit reconnection time and allow the client browser to decide itself.
     *
     * Defaults to `2000` milliseconds.
     *
     * @see https://html.spec.whatwg.org/multipage/server-sent-events.html#concept-event-stream-reconnection-time
     */
    retry?: number | null;
    /**
     * Time in milliseconds interval for the session to send a comment to keep the connection alive.
     *
     * Give as `null` to disable the connection keep-alive mechanism.
     *
     * Defaults to `10000` milliseconds (`10` seconds).
     */
    keepAlive?: number | null;
    /**
     * Status code to be sent to the client.
     *
     * Event stream requests can be redirected using HTTP 301 and 307 status codes.
     * Make sure to set `Location` header when using these status codes using the `headers` property.
     *
     * A client can be asked to stop reconnecting by using 204 status code.
     *
     * Defaults to `200`.
     */
    statusCode?: number;
    /**
     * Additional headers to be sent along with the response.
     */
    headers?: Record<string, string | string[] | undefined>;
    /**
     * Custom state for this session.
     *
     * Use this object to safely store information related to the session and user.
     */
    state?: State;
}
interface DefaultSessionState {
    [key: string]: unknown;
}
interface SessionEvents extends EventMap {
    connected: () => void;
    disconnected: () => void;
    push: (data: unknown, eventName: string, eventId: string) => void;
}
/**
 * A `Session` represents an open connection between the server and a client.
 *
 * It extends from the {@link https://nodejs.org/api/events.html#events_class_eventemitter | EventEmitter} class.
 *
 * It emits the `connected` event after it has connected and sent the response head to the client.
 * It emits the `disconnected` event after the connection has been closed.
 *
 * When using the Fetch API, the session is considered connected only once the `ReadableStream` contained in the body
 * of the `Response` returned by `getResponse` has began being consumed.
 *
 * When using the Node HTTP APIs, the session will send the response with status code, headers and other preamble data ahead of time,
 * allowing the session to connect and start pushing events immediately. As such, keep in mind that attempting
 * to write additional headers after the session has been created will result in an error being thrown.
 *
 * @param req - The Node HTTP/1 {@link https://nodejs.org/api/http.html#http_class_http_incomingmessage | ServerResponse}, HTTP/2 {@link https://nodejs.org/api/http2.html#class-http2http2serverrequest | Http2ServerRequest} or the Fetch API {@link https://developer.mozilla.org/en-US/docs/Web/API/Request | Request} object.
 * @param res - The Node HTTP {@link https://nodejs.org/api/http.html#http_class_http_serverresponse | IncomingMessage}, HTTP/2 {@link https://nodejs.org/api/http2.html#class-http2http2serverresponse | Http2ServerResponse} or the Fetch API {@link https://developer.mozilla.org/en-US/docs/Web/API/Response | Response} object. Optional if using the Fetch API.
 * @param options - Optional additional configuration for the session.
 */
declare class Session<State = DefaultSessionState> extends TypedEmitter<SessionEvents> {
    /**
     * The last event ID sent to the client.
     *
     * This is initialized to the last event ID given by the user, and otherwise is equal to the last number given to the `.id` method.
     *
     * For security reasons, keep in mind that the client can provide *any* initial ID here. Use the `trustClientEventId` constructor option to ignore the client-given initial ID.
     *
     * @readonly
     */
    lastId: string;
    /**
     * Indicates whether the session and underlying connection is open or not.
     *
     * @readonly
     */
    isConnected: boolean;
    /**
     * Custom state for this session.
     *
     * Use this object to safely store information related to the session and user.
     *
     * Use [module augmentation and declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation)
     * to safely add new properties to the `DefaultSessionState` interface.
     */
    state: State;
    private buffer;
    private connection;
    private sanitize;
    private serialize;
    private initialRetry;
    private keepAliveInterval;
    private keepAliveTimer?;
    constructor(req: IncomingMessage, res: ServerResponse, options?: SessionOptions<State>);
    constructor(req: Http2ServerRequest, res: Http2ServerResponse, options?: SessionOptions<State>);
    constructor(req: Request, res?: Response, options?: SessionOptions<State>);
    constructor(req: Request, options?: SessionOptions<State>);
    private initialize;
    private onDisconnected;
    /**
     * Write an empty comment and flush it to the client.
     */
    private keepAlive;
    /**
     * Flush the contents of the internal buffer to the client and clear the buffer.
     */
    private flush;
    /**
     * Get a Request object representing the request of the underlying connection this session manages.
     *
     * When using the Fetch API, this will be the original Request object passed to the session constructor.
     *
     * When using the Node HTTP APIs, this will be a new Request object with status code and headers copied from the original request.
     * When the originally given request or response is closed, the abort signal attached to this Request will be triggered.
     */
    getRequest: () => Request;
    /**
     * Get a Response object representing the response of the underlying connection this session manages.
     *
     * When using the Fetch API, this will be a new Response object with status code and headers copied from the original response if given.
     * Its body will be a ReadableStream that should begin being consumed for the session to consider itself connected.
     *
     * When using the Node HTTP APIs, this will be a new Response object with status code and headers copied from the original response.
     * Its body will be `null`, as data is instead written to the stream of the originally given response object.
     */
    getResponse: () => Response;
    /**
     * Push an event to the client.
     *
     * If no event name is given, the event name is set to `"message"`.
     *
     * If no event ID is given, the event ID (and thus the `lastId` property) is set to a unique string generated using a cryptographic pseudorandom number generator.
     *
     * If the session has disconnected, an `SseError` will be thrown.
     *
     * Emits the `push` event with the given data, event name and event ID in that order.
     *
     * @param data - Data to write.
     * @param eventName - Event name to write.
     * @param eventId - Event ID to write.
     */
    push: (data: unknown, eventName?: string, eventId?: string) => this;
    /**
     * Pipe readable stream data as a series of events to the client.
     *
     * This uses the `push` method under the hood.
     *
     * If no event name is given in the `options` object, the event name is set to `"stream"`.
     *
     * @param stream - Readable stream to consume data from.
     * @param options - Options to alter how the stream is flushed to the client.
     *
     * @returns A promise that resolves with `true` or rejects based on the success of the stream write finishing.
     */
    stream: PushFromStream;
    /**
     * Iterate over an iterable and send yielded values as events to the client.
     *
     * This uses the `push` method under the hood.
     *
     * If no event name is given in the `options` object, the event name is set to `"iteration"`.
     *
     * @param iterable - Iterable to consume data from.
     *
     * @returns A promise that resolves once all data has been successfully yielded from the iterable.
     */
    iterate: PushFromIterable;
    /**
     * Batch and send multiple events at once.
     *
     * If given an `EventBuffer` instance, its contents will be sent to the client.
     *
     * If given a callback, it will be passed an instance of `EventBuffer` which uses the same serializer and sanitizer as the session.
     * Once its execution completes - or once it resolves if it returns a promise - the contents of the passed `EventBuffer` will be sent to the client.
     *
     * @param batcher - Event buffer to get contents from, or callback that takes an event buffer to write to.
     *
     * @returns A promise that resolves once all data from the event buffer has been successfully sent to the client.
     *
     * @see EventBuffer
     */
    batch: (batcher: EventBuffer | ((buffer: EventBuffer) => void | Promise<void>)) => Promise<void>;
}

/**
 * Create a new session.
 *
 * When using the Fetch API, resolves immediately with a session instance before it has connected.
 * You can listen for the `connected` event on the session to know when it has connected, or
 * otherwise use the shorthand `createResponse` function that does so for you instead.
 *
 * When using the Node HTTP APIs, waits for the session to connect before resolving with its instance.
 */
declare function createSession<State = DefaultSessionState>(req: IncomingMessage, res: ServerResponse, options?: SessionOptions<State>): Promise<Session<State>>;
declare function createSession<State = DefaultSessionState>(req: Http2ServerRequest, res: Http2ServerResponse, options?: SessionOptions<State>): Promise<Session<State>>;
declare function createSession<State = DefaultSessionState>(req: Request, res?: Response, options?: SessionOptions<State>): Promise<Session<State>>;
declare function createSession<State = DefaultSessionState>(req: Request, options?: SessionOptions<State>): Promise<Session<State>>;

type CreateResponseCallback<State> = (session: Session<State>) => void;
/**
 * Create a new session using the Fetch API and return its corresponding `Response` object.
 *
 * The last argument should be a callback function that will be invoked with
 * the session instance once it has connected.
 */
declare function createResponse<State = DefaultSessionState>(request: Request, callback: CreateResponseCallback<State>): Response;
declare function createResponse<State = DefaultSessionState>(request: Request, response: Response, callback: CreateResponseCallback<State>): Response;
declare function createResponse<State = DefaultSessionState>(request: Request, options: SessionOptions<State>, callback: CreateResponseCallback<State>): Response;
declare function createResponse<State = DefaultSessionState>(request: Request, response: Response, options: SessionOptions<State>, callback: CreateResponseCallback<State>): Response;

interface ChannelOptions<State = DefaultChannelState> {
    /**
     * Custom state for this channel.
     *
     * Use this object to safely store information related to the channel.
     */
    state?: State;
}
interface BroadcastOptions<SessionState = DefaultSessionState> {
    /**
     * Unique ID for the event being broadcast.
     *
     * If no event ID is given, the event ID is set to a unique string generated using a cryptographic pseudorandom number generator.
     */
    eventId?: string;
    /**
     * Filter sessions that should receive the event.
     *
     * Called with each session and should return `true` to allow the event to be sent and otherwise return `false` to prevent the session from receiving the event.
     */
    filter?: (session: Session<SessionState>) => boolean;
}
interface ChannelEvents<SessionState = DefaultSessionState> extends EventMap {
    "session-registered": (session: Session<SessionState>) => void;
    "session-deregistered": (session: Session<SessionState>) => void;
    "session-disconnected": (session: Session<SessionState>) => void;
    broadcast: (data: unknown, eventName: string, eventId: string) => void;
}
interface DefaultChannelState {
    [key: string]: unknown;
}
/**
 * A `Channel` is used to broadcast events to many sessions at once.
 *
 * It extends from the {@link https://nodejs.org/api/events.html#events_class_eventemitter | EventEmitter} class.
 *
 * You may use the second generic argument `SessionState` to enforce that only sessions with the same state type may be registered with this channel.
 */
declare class Channel<State = DefaultChannelState, SessionState = DefaultSessionState> extends TypedEmitter<ChannelEvents<SessionState>> {
    /**
     * Custom state for this channel.
     *
     * Use this object to safely store information related to the channel.
     */
    state: State;
    private sessions;
    constructor(options?: ChannelOptions<State>);
    /**
     * List of the currently active sessions subscribed to this channel.
     */
    get activeSessions(): ReadonlyArray<Session<SessionState>>;
    /**
     * Number of sessions subscribed to this channel.
     */
    get sessionCount(): number;
    /**
     * Register a session so that it can start receiving events from this channel.
     *
     * If the session was already registered to begin with this method does nothing.
     *
     * @param session - Session to register.
     */
    register(session: Session<SessionState>): this;
    /**
     * Deregister a session so that it no longer receives events from this channel.
     *
     * If the session was not registered to begin with this method does nothing.
     *
     * @param session - Session to deregister.
     */
    deregister(session: Session<SessionState>): this;
    /**
     * Broadcast an event to every active session registered with this channel.
     *
     * Under the hood this calls the `push` method on every active session.
     *
     * If no event name is given, the event name is set to `"message"`.
     *
     * Note that the broadcasted event will have the same ID across all receiving sessions instead of generating a unique ID for each.
     *
     * @param data - Data to write.
     * @param eventName - Event name to write.
     */
    broadcast: (data: unknown, eventName?: string, options?: BroadcastOptions<SessionState>) => this;
}

declare const createChannel: <State = DefaultChannelState, SessionState = DefaultSessionState>(options?: ChannelOptions<State> | undefined) => Channel<State, SessionState>;

declare const createEventBuffer: (options?: EventBufferOptions | undefined) => EventBuffer;

declare class SseError extends Error {
    constructor(message: string);
}

export { type BroadcastOptions, Channel, type ChannelEvents, type ChannelOptions, type CreateResponseCallback, type DefaultChannelState, type DefaultSessionState, EventBuffer, type EventBufferOptions, type IterateOptions, Session, type SessionEvents, type SessionOptions, SseError, type StreamOptions, createChannel, createEventBuffer, createResponse, createSession };
