import type { AllContractEventNames, AnySSEContractDefinition, ExtractEventSchema } from '@lokalise/api-contracts';
import type { z } from 'zod';
import type { BuildFastifySSERoutesReturnType, SSESession } from '../routes/fastifyRouteTypes.ts';
import type { SSERoomBroadcaster } from './rooms/SSERoomBroadcaster.ts';
import type { SSERoomManager } from './rooms/SSERoomManager.ts';
import type { RoomBroadcastOptions } from './rooms/types.ts';
import { SSESessionSpy } from './SSESessionSpy.ts';
import type { SSEControllerConfig, SSEMessage } from './sseTypes.ts';
export type { BuildFastifySSERoutesReturnType, FastifySSEHandlerConfig, FastifySSEPreHandler, FastifySSERouteOptions, InferSSERequest, SSEContext, SSEHandlerResult, SSERespondResult, SSESession, SSESessionMode, SSEStartOptions, } from '../routes/fastifyRouteTypes.ts';
export type { SSESessionEvent } from './SSESessionSpy.ts';
export { SSESessionSpy } from './SSESessionSpy.ts';
export type { AllContractEventNames, AllContractEvents, ExtractEventSchema, SSEControllerConfig, SSEEventSchemas, SSEEventSender, SSELogger, SSEMessage, } from './sseTypes.ts';
/**
 * Abstract base class for SSE controllers.
 *
 * Provides connection management, broadcasting, and lifecycle hooks.
 * Extend this class to create SSE controllers that handle real-time
 * streaming connections.
 *
 * @template APIContracts - Map of route names to SSE route definitions
 *
 * @example
 * ```typescript
 * class NotificationsSSEController extends AbstractSSEController<typeof contracts> {
 *   public static contracts = {
 *     notifications: buildSseContract({ ... }),
 *   } as const
 *
 *   public buildSSERoutes() {
 *     return {
 *       notifications: this.handleNotifications,
 *     }
 *   }
 *
 *   private handleNotifications = buildHandler(
 *     NotificationsSSEController.contracts.notifications,
 *     {
 *       sse: async (request, sse) => {
 *         const session = sse.start('autoClose')
 *         await session.send('notification', { message: 'Hello!' })
 *         // Connection closes automatically when handler returns
 *       },
 *     },
 *   )
 * }
 * ```
 */
export declare abstract class AbstractSSEController<APIContracts extends Record<string, AnySSEContractDefinition>> {
    /** Map of connection ID to connection object */
    protected connections: Map<string, SSESession>;
    /** Private storage for connection spy */
    private readonly _connectionSpy?;
    /** Room manager for room-based broadcasting (optional) */
    private readonly _roomManager?;
    /** Room broadcaster for decoupled room broadcasting (optional) */
    private readonly _roomBroadcaster?;
    /**
     * SSE controllers must override this constructor and call super with their
     * dependencies object and the SSE config.
     *
     * @param _dependencies - The dependencies object (cradle proxy in awilix)
     * @param sseConfig - Optional SSE controller configuration
     *
     * @example
     * ```typescript
     * class MySSEController extends AbstractSSEController<MyContracts> {
     *   private myService: MyService
     *
     *   constructor(deps: { myService: MyService }, sseConfig?: SSEControllerConfig) {
     *     super(deps, sseConfig)
     *     this.myService = deps.myService
     *   }
     * }
     * ```
     */
    constructor(_dependencies: object, sseConfig?: SSEControllerConfig);
    /**
     * Get the connection spy for testing.
     * Throws an error if spies are not enabled.
     * Enable spies by passing `{ enableConnectionSpy: true }` to the constructor.
     *
     * @example
     * ```typescript
     * // In test, create controller with spy enabled
     * // Pass dependencies first, then config with enableConnectionSpy
     * const controller = new MySSEController({}, { enableConnectionSpy: true })
     *
     * // Start connection (async)
     * connectSSE(baseUrl, '/api/stream')
     *
     * // Wait for connection - handles race condition
     * const connection = await controller.connectionSpy.waitForConnection()
     * ```
     *
     * @throws Error if connection spy is not enabled
     */
    get connectionSpy(): SSESessionSpy;
    /**
     * Get the shared room broadcaster instance.
     *
     * The broadcaster is the same singleton that's registered in DI as `sseRoomBroadcaster`.
     * Domain services should resolve it directly from DI rather than going through the controller.
     *
     * @throws Error if rooms are not enabled in the controller config
     *
     * **Required DI registrations** (must be registered before the controller):
     * - `sseRoomManager` — `asValue(new SSERoomManager())` or `asSingletonFunction((): SSERoomManager => new SSERoomManager(config))`
     * - `sseRoomBroadcaster` — `asSingletonClass(SSERoomBroadcaster)`
     *
     * @example
     * ```typescript
     * // In a domain service — resolve broadcaster directly from DI
     * class MetricsService {
     *   private readonly broadcaster: SSERoomBroadcaster
     *   constructor(deps: { sseRoomBroadcaster: SSERoomBroadcaster }) {
     *     this.broadcaster = deps.sseRoomBroadcaster
     *   }
     * }
     * ```
     */
    get roomBroadcaster(): SSERoomBroadcaster;
    /**
     * Build and return SSE route configurations.
     * Similar pattern to AbstractController.buildRoutes().
     */
    abstract buildSSERoutes(): BuildFastifySSERoutesReturnType<APIContracts>;
    /**
     * Controller-level hook called when any connection is established.
     * Override this method to add global connection handling logic.
     * This is called AFTER the connection is registered and route-level onConnect.
     *
     * @param connection - The newly established connection
     */
    protected onConnectionEstablished?(connection: SSESession): void;
    /**
     * Controller-level hook called when any connection is closed.
     * Override this method to add global disconnect handling logic.
     * This is called BEFORE the connection is unregistered and route-level onClose.
     *
     * @param connection - The connection being closed
     */
    protected onConnectionClosed?(connection: SSESession): void;
    /**
     * Send an event to a specific connection.
     *
     * This is a private method used internally by broadcast methods and sendEventInternal.
     * Handlers should use the type-safe `connection.send` method instead of calling
     * this method directly.
     *
     * Event data is validated against the Zod schema defined in the contract's `events` field
     * if the connection has event schemas attached (which happens automatically when routes
     * are built using buildFastifyRoute).
     *
     * @param connectionId - The connection to send to
     * @param message - The SSE message to send
     * @returns true if sent successfully, false if connection not found or closed
     * @throws Error if event data fails validation against the contract schema
     */
    private sendEvent;
    /**
     * Raw internal method for the route builder to send events.
     * This is used by the route builder to create the typed `send` function.
     * External code should use `sendEventInternal` instead for type safety.
     * @internal
     */
    _sendEventRaw<T>(connectionId: string, message: SSEMessage<T>): Promise<boolean>;
    /**
     * Get the room manager for use by route utilities.
     * Returns undefined if rooms are not enabled.
     * @internal
     */
    get _internalRoomManager(): SSERoomManager | undefined;
    /**
     * Send an event to a connection with type-safe event names and data.
     *
     * This method provides autocomplete and type checking for event names and data
     * that match any event defined in the controller's contracts. Use this for
     * external event sources (subscriptions, timers, message queues) when you
     * don't have access to the handler's `send` function.
     *
     * For best type safety in handlers, use the `send` parameter instead.
     * For external sources, you can also store the `send` function for per-route typing.
     *
     * @example
     * ```typescript
     * // External event source (subscription callback)
     * this.messageQueue.onMessage((msg) => {
     *   this.sendEventInternal(connectionId, {
     *     event: 'notification',  // autocomplete shows all valid events
     *     data: { id: msg.id, message: msg.text }  // typed based on event
     *   })
     * })
     * ```
     *
     * @param connectionId - The connection to send to
     * @param message - The event message with typed event name and data
     * @returns true if sent successfully, false if connection not found
     */
    sendEventInternal<EventName extends AllContractEventNames<APIContracts>>(connectionId: string, message: {
        event: EventName;
        data: z.input<ExtractEventSchema<APIContracts, EventName>>;
        id?: string;
        retry?: number;
    }): Promise<boolean>;
    /**
     * Broadcast an event to all connected clients.
     *
     * @param message - The SSE message to broadcast
     * @returns Number of clients the message was sent to
     */
    protected broadcast<T>(message: SSEMessage<T>): Promise<number>;
    /**
     * Broadcast an event to connections matching a predicate.
     *
     * @param message - The SSE message to broadcast
     * @param predicate - Function to filter connections
     * @returns Number of clients the message was sent to
     */
    protected broadcastIf<T>(message: SSEMessage<T>, predicate: (connection: SSESession) => boolean): Promise<number>;
    /**
     * Get all active connections.
     */
    protected getConnections(): SSESession[];
    /**
     * Get the number of active connections.
     */
    protected getConnectionCount(): number;
    /**
     * Close a specific connection.
     *
     * This gracefully ends the SSE stream by calling the underlying `reply.sse.close()`.
     * All previously sent data is flushed to the client before the connection terminates.
     *
     * Called automatically by the route builder when handler returns `success('disconnect')`.
     * Can also be called manually for scenarios like external triggers or timeouts.
     *
     * @param connectionId - The connection to close
     * @returns true if connection was found and closed
     */
    closeConnection(connectionId: string): boolean;
    /**
     * Close all active connections.
     * Called during graceful shutdown via asyncDispose.
     */
    closeAllConnections(): void;
    /**
     * Register a connection (called internally by route builder).
     * Triggers the onConnectionEstablished hook and spy if defined.
     * @internal
     */
    registerConnection(connection: SSESession): void;
    /**
     * Unregister a connection (called internally by route builder).
     * Triggers the onConnectionClosed hook and spy if defined.
     * This method is idempotent - calling it multiple times for the same
     * connection ID has no effect after the first call.
     * @internal
     */
    unregisterConnection(connectionId: string): void;
    /**
     * Get the room manager for this controller.
     * Throws an error if rooms are not enabled.
     *
     * @throws Error if rooms are not enabled in the controller config
     */
    protected get roomManager(): SSERoomManager;
    /**
     * Check if rooms are enabled for this controller.
     */
    protected get roomsEnabled(): boolean;
    /**
     * Broadcast a type-safe event to all connections in one or more rooms.
     *
     * Event names and data are validated against the controller's contract schemas
     * at compile time, ensuring only valid events can be broadcast.
     *
     * When broadcasting to multiple rooms, connections in multiple rooms
     * only receive the message once (de-duplicated).
     *
     * @param room - Room name or array of room names
     * @param eventName - Event name (must be defined in one of the controller's contracts)
     * @param data - Event data (must match the schema for the event)
     * @param options - Broadcast options (local, id, retry)
     * @returns Number of local connections the message was sent to
     *
     * @example
     * ```typescript
     * // Broadcast to a single room (type-safe)
     * await this.broadcastToRoom('dashboard:123', 'metricsUpdate', {
     *   cpu: 45.2, memory: 72.1
     * })
     *
     * // Broadcast to multiple rooms (no duplicates)
     * await this.broadcastToRoom(['premium', 'beta-testers'], 'featureFlag', {
     *   flag: 'new-ui', enabled: true
     * })
     * ```
     */
    protected broadcastToRoom<EventName extends AllContractEventNames<APIContracts>>(room: string | string[], eventName: EventName, data: ExtractEventSchema<APIContracts, EventName> extends z.ZodTypeAny ? z.input<ExtractEventSchema<APIContracts, EventName>> : never, options?: RoomBroadcastOptions & {
        id?: string;
        retry?: number;
    }): Promise<number>;
    /**
     * Join a connection to one or more rooms.
     * Prefer using `session.rooms.join()` in handlers instead.
     *
     * @param connectionId - The connection to add to rooms
     * @param room - Room name or array of room names
     */
    protected joinRoom(connectionId: string, room: string | string[]): void;
    /**
     * Remove a connection from one or more rooms.
     * Prefer using `session.rooms.leave()` in handlers instead.
     *
     * @param connectionId - The connection to remove from rooms
     * @param room - Room name or array of room names
     */
    protected leaveRoom(connectionId: string, room: string | string[]): void;
    /**
     * Get all connection IDs in a room.
     *
     * @param room - The room to query
     * @returns Array of connection IDs
     */
    protected getConnectionsInRoom(room: string): string[];
    /**
     * Get the number of connections in a room.
     *
     * @param room - The room to query
     * @returns Number of connections
     */
    protected getConnectionCountInRoom(room: string): number;
}
