import type { AnyDualModeContractDefinition, AnySSEContractDefinition, HttpStatusCode } from '@lokalise/api-contracts';
import type { ApiContractMetadataToRouteMapper } from '@lokalise/fastify-api-contracts';
import type { FastifyReply, FastifyRequest } from 'fastify';
import type { z } from 'zod';
import type { DualModeType } from '../dualmode/dualModeTypes.ts';
import type { SSERoomOperations } from '../sse/rooms/types.ts';
import type { SSEEventSchemas, SSEEventSender, SSELogger, SSEMessage } from '../sse/sseTypes.ts';
import type { SSECloseReason } from './fastifyRouteUtils.ts';
/**
 * Infer the union of all response body types from responseBodySchemasByStatusCode.
 * This allows handlers to return responses for specific status codes with proper typing.
 * Typically used for error responses, but can define schemas for any HTTP status code.
 *
 * @example
 * ```typescript
 * // Given responseBodySchemasByStatusCode: { 400: z.object({ error: string }), 404: z.object({ notFound: true }) }
 * // InferErrorResponses<typeof contract> = { error: string } | { notFound: true }
 * ```
 */
export type InferErrorResponses<ResponseSchemas extends Partial<Record<HttpStatusCode, z.ZodTypeAny>> | undefined> = ResponseSchemas extends Partial<Record<HttpStatusCode, z.ZodTypeAny>> ? {
    [K in keyof ResponseSchemas]: ResponseSchemas[K] extends z.ZodTypeAny ? z.infer<ResponseSchemas[K]> : never;
}[keyof ResponseSchemas] : never;
/**
 * Type for responseSchemasByStatusCode - maps HTTP status codes to Zod schemas.
 */
export type ResponseSchemasByStatusCode = Partial<Record<HttpStatusCode, z.ZodTypeAny>>;
/**
 * Strictly typed respond function for sse.respond().
 *
 * When responseBodySchemasByStatusCode is defined, this provides strict typing:
 * - For defined status codes: body must match the corresponding schema
 * - For undefined status codes: use respondRaw() or cast to bypass strict typing
 *
 * @example
 * ```typescript
 * // With responseBodySchemasByStatusCode: { 400: z.object({ error: string, details: string[] }), 404: z.object({ error: string, resourceId: string }) }
 * sse.respond(404, { error: 'Not Found', resourceId: '123' })  // ✓ OK - matches 404 schema
 * sse.respond(404, { error: 'Not Found' })                     // ✗ Error - missing resourceId
 * sse.respond(400, { error: 'Bad', resourceId: '123' })        // ✗ Error - wrong schema for 400
 * ```
 */
export type StrictRespondFunction<ResponseSchemas extends ResponseSchemasByStatusCode | undefined> = ResponseSchemas extends ResponseSchemasByStatusCode ? keyof ResponseSchemas & number extends never ? (code: number, body: unknown) => SSERespondResult : <Code extends keyof ResponseSchemas & number>(code: Code, body: ResponseSchemas[Code] extends z.ZodTypeAny ? z.infer<ResponseSchemas[Code]> : unknown) => SSERespondResult : (code: number, body: unknown) => SSERespondResult;
/**
 * Result indicating the handler returned an HTTP response before streaming started.
 * Created via `sse.respond(code, body)`.
 * Use this for early returns (validation errors, not found, etc.) before starting SSE.
 */
export type SSERespondResult = {
    _type: 'respond';
    code: number;
    body: unknown;
};
/**
 * Session lifetime mode, specified when calling `sse.start()`.
 * - `'autoClose'`: Close session automatically after handler completes (request-response streaming)
 * - `'keepAlive'`: Keep session open after handler completes (long-lived connections)
 */
export type SSESessionMode = 'autoClose' | 'keepAlive';
/**
 * Possible results from an SSE handler.
 * - `SSERespondResult`: Send HTTP response before streaming (via sse.respond())
 * - `void`: Streaming was started via sse.start(), mode determines what happens next
 */
export type SSEHandlerResult = SSERespondResult | void;
/**
 * Message format for use with SSESession.sendStream().
 * Allows sending typed events through an async iterable.
 *
 * @template Events - Event schemas for type-safe event names and data
 *
 * @example
 * ```typescript
 * async function* generateMessages(): AsyncIterable<SSEStreamMessage<typeof contract.serverSentEventSchemas>> {
 *   yield { event: 'chunk', data: { delta: 'Hello' } }
 *   yield { event: 'chunk', data: { delta: ' world' } }
 *   yield { event: 'done', data: { usage: { total: 2 } } }
 * }
 *
 * await connection.sendStream(generateMessages())
 * ```
 */
export type SSEStreamMessage<Events extends SSEEventSchemas = SSEEventSchemas> = {
    [K in keyof Events & string]: {
        event: K;
        data: z.input<Events[K]>;
        id?: string;
        retry?: number;
    };
}[keyof Events & string];
/**
 * Represents an active SSE connection with typed event sending.
 *
 * @template Events - Event schemas for type-safe sending
 * @template Context - Custom context data stored per connection
 */
export type SSESession<Events extends SSEEventSchemas = SSEEventSchemas, Context = unknown> = {
    /** Unique identifier for this connection */
    id: string;
    /** The original Fastify request */
    request: FastifyRequest;
    /** The Fastify reply with SSE capabilities from @fastify/sse */
    reply: FastifyReply;
    /** Custom context data for this connection */
    context: Context;
    /** Timestamp when the connection was established */
    connectedAt: Date;
    /**
     * Type-safe event sender for this connection.
     * Event names and data are validated against the contract's event schemas.
     */
    send: SSEEventSender<Events>;
    /**
     * Check if the SSE connection is still open.
     * Queries the underlying @fastify/sse connection state.
     *
     * @returns true if the connection is still open, false if closed
     */
    isConnected: () => boolean;
    /**
     * Get the underlying writable stream for advanced streaming operations.
     * Useful for piping data directly or using Node.js stream utilities.
     *
     * @returns The underlying NodeJS.WritableStream from @fastify/sse
     *
     * @example
     * ```typescript
     * import { pipeline } from 'node:stream/promises'
     *
     * // Pipe data from a readable stream to SSE
     * const readable = createReadableStream()
     * await pipeline(readable, connection.getStream())
     * ```
     */
    getStream: () => NodeJS.WritableStream;
    /**
     * Send multiple SSE messages from an async iterable with validation.
     * Each message is validated against the contract's event schemas before sending.
     *
     * @param messages - Async iterable of SSE messages to send
     * @returns Promise that resolves when all messages have been sent
     *
     * @example
     * ```typescript
     * async function* generateMessages() {
     *   yield { event: 'chunk', data: { delta: 'Hello' } }
     *   yield { event: 'chunk', data: { delta: ' world' } }
     *   yield { event: 'done', data: { usage: { total: 2 } } }
     * }
     *
     * await connection.sendStream(generateMessages())
     * ```
     */
    sendStream: (messages: AsyncIterable<SSEStreamMessage<Events>>) => Promise<void>;
    /**
     * Room operations for this connection.
     * Only available when rooms are enabled in the controller config.
     *
     * @example
     * ```typescript
     * // Join rooms based on route parameters or business logic
     * session.rooms.join(`dashboard:${request.params.dashboardId}`)
     * session.rooms.join(['project:123', 'team:engineering'])
     *
     * // Leave rooms when context changes
     * session.rooms.leave('project:123')
     * ```
     */
    rooms: SSERoomOperations;
    /**
     * Zod schemas for validating event data.
     * Map of event name to Zod schema. Used by sendEvent for runtime validation.
     * @internal
     */
    eventSchemas?: SSEEventSchemas;
};
/**
 * Options for starting an SSE connection.
 */
export type SSEStartOptions<Context = unknown> = {
    /** Initial context data for the connection */
    context?: Context;
};
/**
 * Context object passed to SSE handlers for deferred header sending.
 *
 * This abstraction allows handlers to:
 * 1. Perform validation before any headers are sent
 * 2. Return early with HTTP responses (errors, redirects, etc.) before streaming
 * 3. Explicitly start streaming when ready
 *
 * @template Events - Event schemas for type-safe sending
 * @template ResponseBody - Response body type for early returns
 *
 * @example Basic usage in handler
 * ```typescript
 * sse: async (request, sse) => {
 *   // Phase 1: Validation (headers NOT sent yet)
 *   const entity = await db.find(request.params.id)
 *   if (!entity) {
 *     return sse.respond(404, { error: 'Entity not found' })
 *   }
 *
 *   // Phase 2: Start streaming (sends 200 + SSE headers)
 *   // 'autoClose' = close after handler, 'keepAlive' = keep open for external events
 *   const session = sse.start('autoClose', { context: { entity } })
 *
 *   // Phase 3: Stream events
 *   await session.send('data', { item: entity })
 * }
 * ```
 *
 * @example Error handling with try/catch (no return needed)
 * ```typescript
 * sse: async (request, sse) => {
 *   try {
 *     const entity = await db.getById(request.params.id)
 *     const session = sse.start('autoClose')
 *     await session.send('data', { item: entity })
 *   } catch (e) {
 *     // sse.respond() can be called without returning - both patterns work
 *     sse.respond(404, { error: 'Entity not found' })
 *   }
 * }
 * ```
 *
 * @example Passing to internal helper methods
 * ```typescript
 * import { SSEContext } from 'opinionated-machine'
 * import { chatStreamContract } from './contracts'
 *
 * class ChatController extends AbstractSSEController<{ chat: typeof chatStreamContract }> {
 *   buildSSERoutes() {
 *     return {
 *       chat: buildHandler(chatStreamContract, {
 *         sse: async (request, sse) => {
 *           // Delegate to internal method with full type safety
 *           await this.handleChatStream(request.body.message, sse)
 *         },
 *       }),
 *     }
 *   }
 *
 *   // Type the sse parameter using SSEContext with the contract's event schemas
 *   private async handleChatStream(
 *     message: string,
 *     sse: SSEContext<typeof chatStreamContract.serverSentEventSchemas>,
 *   ) {
 *     const session = sse.start('autoClose')
 *     // session.send() is fully typed based on contract's serverSentEventSchemas
 *     await session.send('chunk', { delta: 'Hello' })
 *     await session.send('done', { usage: { total: 5 } })
 *   }
 * }
 * ```
 */
export type SSEContext<Events extends SSEEventSchemas = SSEEventSchemas, ResponseSchemas extends ResponseSchemasByStatusCode | undefined = undefined> = {
    /**
     * Start streaming - sends HTTP 200 + SSE headers, returns typed session.
     *
     * After calling this method, you can no longer send HTTP responses.
     * Use `respond()` before `start()` for early returns.
     *
     * @param mode - Session lifetime mode:
     *   - `'autoClose'`: Close session automatically after handler completes (request-response streaming)
     *   - `'keepAlive'`: Keep session open after handler completes (long-lived connections)
     * @param options - Optional configuration for the session
     * @returns SSESession for sending events
     */
    start: <Context = unknown>(mode: SSESessionMode, options?: SSEStartOptions<Context>) => SSESession<Events, Context>;
    /**
     * Send an HTTP response before streaming starts (early return).
     *
     * Use this for any case where you want to return a regular HTTP response
     * instead of starting an SSE stream: validation errors, not found, redirects, etc.
     *
     * Must be called BEFORE `start()`. You can either return the result or just call it
     * (useful in try/catch blocks). Both patterns work:
     *
     * When `responseBodySchemasByStatusCode` is defined in the contract, this method provides
     * strict typing - the body must match the schema for the given status code.
     *
     * @param code - HTTP status code (e.g., 200, 404, 422)
     * @param body - Response body (strictly typed when status code has a defined schema)
     * @returns SSERespondResult (can be returned or ignored)
     *
     * @example Return pattern (simple cases)
     * ```typescript
     * if (!entity) {
     *   return sse.respond(404, { error: 'Entity not found', resourceId: id })
     * }
     * ```
     *
     * @example Strict typing with responseBodySchemasByStatusCode
     * ```typescript
     * // Contract defines: responseBodySchemasByStatusCode: { 404: z.object({ error: string, resourceId: string }) }
     * sse.respond(404, { error: 'Not Found', resourceId: '123' })  // OK
     * sse.respond(404, { error: 'Not Found' })                     // TypeScript error - missing resourceId
     * ```
     */
    respond: StrictRespondFunction<ResponseSchemas>;
    /**
     * Advanced: send headers without creating a full connection.
     *
     * Use this only for advanced streaming scenarios where you need headers
     * sent early but will manage streaming manually via `sse.reply.sse`.
     *
     * Most handlers should use `start()` instead.
     */
    sendHeaders: () => void;
    /**
     * Escape hatch to raw Fastify reply if needed.
     * Use with caution - prefer the typed methods above.
     */
    reply: FastifyReply;
};
/**
 * Async preHandler hook for SSE routes.
 *
 * IMPORTANT: SSE route preHandlers MUST return a Promise. This is required
 * for proper integration with @fastify/sse. Synchronous handlers will cause
 * connection issues.
 *
 * For rejection (auth failure), return the reply after sending:
 * ```typescript
 * preHandler: (request, reply) => {
 *   if (!validAuth) {
 *     return reply.code(401).send({ error: 'Unauthorized' })
 *   }
 *   return Promise.resolve()
 * }
 * ```
 */
export type FastifySSEPreHandler = (request: FastifyRequest, reply: FastifyReply) => Promise<unknown>;
/**
 * Options for configuring an SSE route.
 */
export type FastifySSERouteOptions = {
    /**
     * Async preHandler hook for authentication/authorization.
     * Runs BEFORE the SSE connection is established.
     *
     * MUST return a Promise - synchronous handlers will cause connection issues.
     * Return `reply.code(401).send(...)` for rejection, or `Promise.resolve()` for success.
     *
     * @see FastifySSEPreHandler for usage examples
     */
    preHandler?: FastifySSEPreHandler;
    /**
     * Called when client connects (after SSE handshake).
     */
    onConnect?: (connection: SSESession) => void | Promise<void>;
    /**
     * Called when the SSE connection closes for any reason (client disconnect,
     * network failure, or server explicitly closing via closeConnection()).
     *
     * @param connection - The connection that was closed
     * @param reason - Why the connection was closed:
     *   - 'server': Server explicitly closed (closeConnection() or success('disconnect'))
     *   - 'client': Client closed (EventSource.close(), navigation, network failure)
     *
     * Use this for cleanup like unsubscribing from events or removing from tracking.
     */
    onClose?: (connection: SSESession, reason: SSECloseReason) => void | Promise<void>;
    /**
     * Handler for Last-Event-ID reconnection.
     * Return an iterable of events to replay, or handle replay manually.
     * Supports both sync iterables (arrays, generators) and async iterables.
     */
    onReconnect?: (connection: SSESession, lastEventId: string) => Iterable<SSEMessage> | AsyncIterable<SSEMessage> | void | Promise<void>;
    /**
     * Optional logger for SSE route errors.
     * If not provided, errors will be logged to console.error.
     * Compatible with CommonLogger from @lokalise/node-core and pino loggers.
     */
    logger?: SSELogger;
    /**
     * Custom serializer for SSE message data on this route.
     * Overrides the global serializer if set.
     * @default JSON.stringify
     */
    serializer?: (data: unknown) => string;
    /**
     * Heartbeat interval in milliseconds for this route.
     * Overrides the global heartbeat interval if set.
     * Set to 0 to disable heartbeats.
     * @default 30000
     */
    heartbeatInterval?: number;
    /**
     * Maps contract metadata to additional Fastify route options.
     *
     * Called with the contract's `metadata` field and its return value is merged into the
     * Fastify route options as a base. It is useful for attaching cross-cutting concerns
     * (auth, rate limiting, tracing, etc.) driven by metadata defined in the contract rather
     * than in the route handler.
     *
     * @example
     * ```typescript
     * buildHandler(myContract, { sse: handler }, {
     *   contractMetadataToRouteMapper: (metadata) => ({
     *     config: { rateLimit: metadata.rateLimit },
     *   }),
     * })
     * ```
     */
    contractMetadataToRouteMapper?: ApiContractMetadataToRouteMapper;
};
/**
 * Route configuration returned by buildSSERoutes().
 *
 * @template Contract - The SSE route definition
 */
export type FastifySSEHandlerConfig<Contract extends AnySSEContractDefinition> = {
    /** The SSE route contract */
    contract: Contract;
    /** Handlers object containing the SSE handler */
    handlers: SSEOnlyHandlers<Contract['serverSentEventSchemas'], InferOptionalSchema<Contract['requestPathParamsSchema']>, InferOptionalSchema<Contract['requestQuerySchema']>, InferOptionalSchema<Contract['requestHeaderSchema']>, InferOptionalSchema<Contract['requestBodySchema'], undefined>, Contract['responseBodySchemasByStatusCode']>;
    /** Optional route configuration */
    options?: FastifySSERouteOptions;
};
/**
 * Maps SSE contracts to route handler containers for type checking.
 */
export type BuildFastifySSERoutesReturnType<APIContracts extends Record<string, AnySSEContractDefinition>> = {
    [K in keyof APIContracts]: SSERouteHandler<APIContracts[K]>;
};
/**
 * Safely infer the output type of an optional Zod schema property.
 * Since contract schema properties are optional (`Schema | undefined`),
 * a bare `Contract['schema'] extends z.ZodTypeAny` check always fails
 * because `ZodObject | undefined` does not extend `ZodTypeAny`.
 * NonNullable strips the `undefined` before the check.
 */
type InferOptionalSchema<T, Fallback = unknown> = NonNullable<T> extends z.ZodTypeAny ? z.infer<NonNullable<T>> : Fallback;
/**
 * Infer the FastifyRequest type from an SSE contract.
 *
 * Use this to get properly typed request parameters in handlers without
 * manually spelling out the types.
 *
 * @example
 * ```typescript
 * const handler = async (
 *   request: InferSSERequest<typeof chatCompletionContract>,
 *   connection: SSESession,
 * ) => {
 *   // request.body is typed as { message: string; stream: true }
 *   const { message } = request.body
 * }
 * ```
 */
export type InferSSERequest<Contract extends AnySSEContractDefinition> = FastifyRequest<{
    Params: InferOptionalSchema<Contract['requestPathParamsSchema']>;
    Querystring: InferOptionalSchema<Contract['requestQuerySchema']>;
    Headers: InferOptionalSchema<Contract['requestHeaderSchema']>;
    Body: InferOptionalSchema<Contract['requestBodySchema'], undefined>;
}>;
/**
 * Reply object available to sync handlers.
 *
 * Unlike the full FastifyReply, this omits `send()` because the framework handles
 * sending the response after validation. Sync handlers should return the response body
 * directly instead of calling `reply.send()`.
 *
 * Fluent setters (code, status, header, etc.) are overridden to return SyncModeReply
 * so that chaining `.send()` after them is a compile-time error.
 *
 * Use `reply.code()` to set status codes and `reply.header()` to set response headers.
 */
type FastifyReplyFluentKeys = {
    [K in keyof FastifyReply]: FastifyReply[K] extends (...args: never[]) => infer R ? [R] extends [FastifyReply] ? K : never : never;
}[keyof FastifyReply];
type ReplaceReturn<F, NewReturn> = F extends (...args: infer A) => FastifyReply ? (...args: A) => NewReturn : F;
export type SyncModeReply = Omit<FastifyReply, 'send' | FastifyReplyFluentKeys> & {
    [K in Exclude<FastifyReplyFluentKeys, 'send'>]: ReplaceReturn<FastifyReply[K], SyncModeReply>;
};
/**
 * Handler function for sync (non-streaming) mode.
 *
 * The handler should return the response body directly. The framework will validate it
 * against the contract schema and send it. Use `reply.code()` / `reply.header()` to set
 * status codes and headers, but do not call `reply.send()`.
 *
 * @template Params - Path parameters type
 * @template Query - Query string parameters type
 * @template Headers - Request headers type
 * @template Body - Request body type
 * @template SyncResponse - Response type that must match contract's successResponseBodySchema
 */
export type SyncModeHandler<Params = unknown, Query = unknown, Headers = unknown, Body = unknown, SyncResponse = unknown> = (request: FastifyRequest<{
    Params: Params;
    Querystring: Query;
    Headers: Headers;
    Body: Body;
}>, reply: SyncModeReply) => SyncResponse | Promise<SyncResponse>;
/**
 * Handler function for SSE mode with deferred headers.
 *
 * The handler receives an SSEContext object that allows:
 * 1. Early returns before headers are sent (validation errors, not found, etc.)
 * 2. Explicit streaming start via `sse.start(mode)`
 * 3. Type-safe event sending via the returned session
 *
 * Note: `sse.respond()` can be called with or without returning the result.
 * Both patterns work - useful for try/catch blocks where returning is awkward.
 *
 * @returns SSEHandlerResult (optional - void is fine if sse.respond() or sse.start() was called)
 *
 * @example Request-response streaming (autoClose mode)
 * ```typescript
 * sse: async (request, sse) => {
 *   const entity = await db.find(request.params.id)
 *   if (!entity) {
 *     return sse.respond(404, { error: 'Not found' })
 *   }
 *
 *   const session = sse.start('autoClose')
 *   await session.send('chunk', { delta: 'Hello' })
 *   await session.send('done', { usage: { total: 5 } })
 *   // Session closes automatically after handler returns
 * }
 * ```
 *
 * @example Error handling with try/catch (no return needed)
 * ```typescript
 * sse: async (request, sse) => {
 *   try {
 *     const entity = await db.getById(request.params.id)
 *     const session = sse.start('autoClose')
 *     await session.send('data', entity)
 *   } catch (e) {
 *     sse.respond(404, { error: 'Not found' })  // No return needed
 *   }
 * }
 * ```
 *
 * @example Long-lived session (keepAlive mode)
 * ```typescript
 * sse: async (request, sse) => {
 *   const session = sse.start('keepAlive')
 *   this.subscriptions.set(session.id, request.params.userId)
 *   // Session stays open after handler returns
 * }
 * ```
 *
 * @template Events - SSE event schemas for type-safe sending
 * @template Params - Path parameters type
 * @template Query - Query string parameters type
 * @template Headers - Request headers type
 * @template Body - Request body type
 */
export type SSEModeHandler<Events extends SSEEventSchemas = SSEEventSchemas, Params = unknown, Query = unknown, Headers = unknown, Body = unknown, ResponseSchemas extends ResponseSchemasByStatusCode | undefined = undefined> = (request: FastifyRequest<{
    Params: Params;
    Querystring: Query;
    Headers: Headers;
    Body: Body;
}>, sse: SSEContext<Events, ResponseSchemas>) => SSEHandlerResult | Promise<SSEHandlerResult>;
/**
 * Combined handlers for dual-mode routes.
 *
 * @template Params - Path parameters type
 * @template Query - Query string parameters type
 * @template Headers - Request headers type
 * @template Body - Request body type
 * @template SyncResponse - Sync response type
 * @template Events - SSE event schemas
 * @template ResponseSchemas - Response schemas by status code for strict sse.respond() typing
 */
export type DualModeHandlers<Params = unknown, Query = unknown, Headers = unknown, Body = unknown, SyncResponse = unknown, Events extends SSEEventSchemas = SSEEventSchemas, ResponseSchemas extends ResponseSchemasByStatusCode | undefined = undefined> = {
    sync: SyncModeHandler<Params, Query, Headers, Body, SyncResponse>;
    sse: SSEModeHandler<Events, Params, Query, Headers, Body, ResponseSchemas>;
};
/**
 * Options for configuring a dual-mode route.
 * Extends SSE route options with JSON-specific options.
 */
export type FastifyDualModeRouteOptions = FastifySSERouteOptions & {
    /**
     * Default mode when Accept header doesn't specify preference.
     * @default 'json'
     */
    defaultMode?: DualModeType;
};
/**
 * Infer handlers type based on contract type.
 * All dual-mode contracts use `{ sync: handler, sse: handler }` pattern.
 *
 * The sync handler return type includes both:
 * - The success response type (from successResponseBodySchema)
 * - Error response types (from responseBodySchemasByStatusCode)
 *
 * The SSE handler's `sse.respond()` method is strictly typed when `responseBodySchemasByStatusCode`
 * is defined - the body must match the schema for the given status code.
 *
 * This allows returning error responses without type casting:
 * ```typescript
 * sync: (request, reply) => {
 *   if (notFound) {
 *     reply.code(404)
 *     return { error: 'Not found', resourceId: '123' } // No cast needed!
 *   }
 *   return { success: true, data: 'OK' }
 * }
 *
 * sse: (request, sse) => {
 *   if (notFound) {
 *     return sse.respond(404, { error: 'Not found', resourceId: '123' }) // Strictly typed!
 *   }
 *   // ...
 * }
 * ```
 *
 * @template Contract - The dual-mode contract definition
 */
export type InferDualModeHandlers<Contract extends AnyDualModeContractDefinition> = DualModeHandlers<InferOptionalSchema<Contract['requestPathParamsSchema']>, InferOptionalSchema<Contract['requestQuerySchema']>, InferOptionalSchema<Contract['requestHeaderSchema']>, InferOptionalSchema<Contract['requestBodySchema'], undefined>, (Contract['successResponseBodySchema'] extends z.ZodTypeAny ? z.infer<Contract['successResponseBodySchema']> : unknown) | InferErrorResponses<Contract['responseBodySchemasByStatusCode']>, Contract['serverSentEventSchemas'], Contract['responseBodySchemasByStatusCode']>;
/**
 * Handler configuration returned by buildDualModeRoutes().
 *
 * @template Contract - The dual-mode route definition
 */
export type FastifyDualModeHandlerConfig<Contract extends AnyDualModeContractDefinition> = {
    /** The dual-mode route contract */
    contract: Contract;
    /** Handlers for sync and SSE modes - type depends on contract style */
    handlers: InferDualModeHandlers<Contract>;
    /** Optional route configuration */
    options?: FastifyDualModeRouteOptions;
};
/**
 * Maps dual-mode contracts to route handler containers for type checking.
 */
export type BuildFastifyDualModeRoutesReturnType<APIContracts extends Record<string, AnyDualModeContractDefinition>> = {
    [K in keyof APIContracts]: DualModeRouteHandler<APIContracts[K]>;
};
/**
 * SSE-only handler object - just the SSE handler.
 * Explicitly rejects `sync` property to distinguish from dual-mode handlers.
 */
export type SSEOnlyHandlers<Events extends SSEEventSchemas = SSEEventSchemas, Params = unknown, Query = unknown, Headers = unknown, Body = unknown, ResponseSchemas extends ResponseSchemasByStatusCode | undefined = undefined> = {
    sse: SSEModeHandler<Events, Params, Query, Headers, Body, ResponseSchemas>;
    /** SSE-only contracts do not support sync handlers */
    sync?: never;
};
/**
 * Infer the handler type based on contract type.
 * - SSE-only contracts: `{ sse: handler }`
 * - Dual-mode contracts: `{ sync: handler, sse: handler }`
 */
export type InferHandlers<Contract> = Contract extends AnyDualModeContractDefinition ? InferDualModeHandlers<Contract> : Contract extends AnySSEContractDefinition ? SSEOnlyHandlers<Contract['serverSentEventSchemas'], InferOptionalSchema<Contract['requestPathParamsSchema']>, InferOptionalSchema<Contract['requestQuerySchema']>, InferOptionalSchema<Contract['requestHeaderSchema']>, InferOptionalSchema<Contract['requestBodySchema'], undefined>, Contract['responseBodySchemasByStatusCode']> : never;
/**
 * Helper type to infer the correct handlers type based on contract.
 */
type HandlersForContract<Contract> = Contract extends AnyDualModeContractDefinition ? InferDualModeHandlers<Contract> : Contract extends AnySSEContractDefinition ? SSEOnlyHandlers<Contract['serverSentEventSchemas'], InferOptionalSchema<Contract['requestPathParamsSchema']>, InferOptionalSchema<Contract['requestQuerySchema']>, InferOptionalSchema<Contract['requestHeaderSchema']>, InferOptionalSchema<Contract['requestBodySchema'], undefined>, Contract['responseBodySchemasByStatusCode']> : never;
/**
 * Branded container for SSE route handlers.
 * Contains the contract, handlers, and optional route configuration.
 *
 * @template Contract - The SSE contract definition
 */
export type SSERouteHandler<Contract extends AnySSEContractDefinition> = {
    readonly __type: 'SSERouteHandler';
    readonly contract: Contract;
    readonly handlers: SSEOnlyHandlers<Contract['serverSentEventSchemas'], InferOptionalSchema<Contract['requestPathParamsSchema']>, InferOptionalSchema<Contract['requestQuerySchema']>, InferOptionalSchema<Contract['requestHeaderSchema']>, InferOptionalSchema<Contract['requestBodySchema'], undefined>, Contract['responseBodySchemasByStatusCode']>;
    readonly options?: FastifySSERouteOptions;
};
/**
 * Branded container for dual-mode route handlers.
 * Contains the contract, handlers, and optional route configuration.
 *
 * @template Contract - The dual-mode contract definition
 */
export type DualModeRouteHandler<Contract extends AnyDualModeContractDefinition> = {
    readonly __type: 'DualModeRouteHandler';
    readonly contract: Contract;
    readonly handlers: InferDualModeHandlers<Contract>;
    readonly options?: FastifyDualModeRouteOptions;
};
/**
 * Unified handler builder for both SSE-only and dual-mode contracts.
 *
 * Returns a branded container with the contract embedded, eliminating the need
 * to pass the contract separately when building routes.
 *
 * This function provides automatic type inference based on the contract type:
 * - **SSE-only contracts**: Provide `{ sse: handler }` only
 * - **Dual-mode contracts**: Provide both `{ sync: handler, sse: handler }`
 *
 * ## Handler Signatures
 *
 * **Sync handler** (dual-mode only):
 * ```typescript
 * sync: (request, reply) => SyncResponse | Promise<SyncResponse>
 * ```
 *
 * **SSE handler** (both SSE-only and dual-mode):
 * ```typescript
 * sse: (request, sse) => SSEHandlerResult | Promise<SSEHandlerResult>
 * ```
 *
 * The SSE handler receives an SSEContext that allows deferred header sending:
 * - `sse.respond(code, body)` - Return HTTP response before streaming (early return)
 * - `sse.start(mode)` - Start streaming (sends 200 + SSE headers), returns session
 *   - `'autoClose'` - Close session after handler completes
 *   - `'keepAlive'` - Keep session open for external events
 *
 * @see SSEContext for the sse parameter API
 * @see SSEHandlerResult for the possible return values
 *
 * @example
 * ```typescript
 * // SSE-only contract - request-response streaming with early return
 * const sseHandler = buildHandler(chatStreamContract, {
 *   sse: async (request, sse) => {
 *     const entity = await db.find(request.params.id)
 *     if (!entity) {
 *       return sse.respond(404, { error: 'Not found' })
 *     }
 *     const session = sse.start('autoClose')
 *     for (const word of request.body.message.split(' ')) {
 *       await session.send('chunk', { delta: word })
 *     }
 *     await session.send('done', { usage: { total: 5 } })
 *   },
 * })
 *
 * // SSE-only with options (3rd param)
 * const notificationHandler = buildHandler(notificationsContract, {
 *   sse: async (request, sse) => {
 *     const session = sse.start('keepAlive')
 *     this.subscriptions.set(session.id, request.params.userId)
 *   },
 * }, { onConnect: ..., onClose: ... })
 *
 * // Dual-mode contract - supports both sync and SSE responses
 * const dualModeHandler = buildHandler(chatCompletionContract, {
 *   sync: (request, reply) => {
 *     reply.header('x-custom', 'value')
 *     return { reply: 'Hello', usage: { tokens: 5 } }
 *   },
 *   sse: async (request, sse) => {
 *     const session = sse.start('autoClose')
 *     await session.send('chunk', { delta: 'Hello' })
 *     await session.send('done', { usage: { total: 5 } })
 *   },
 * }, { preHandler: authHandler })
 * ```
 */
export declare function buildHandler<Contract extends AnySSEContractDefinition>(contract: Contract, handlers: HandlersForContract<Contract>, options?: FastifySSERouteOptions): SSERouteHandler<Contract>;
export declare function buildHandler<Contract extends AnyDualModeContractDefinition>(contract: Contract, handlers: HandlersForContract<Contract>, options?: FastifyDualModeRouteOptions): DualModeRouteHandler<Contract>;
/**
 * Options for registering SSE routes globally.
 */
export type RegisterSSERoutesOptions = {
    /**
     * Heartbeat interval in milliseconds.
     * @default 30000
     */
    heartbeatInterval?: number;
    /**
     * Custom serializer for SSE message data.
     * @default JSON.stringify
     */
    serializer?: (data: unknown) => string;
    /**
     * Global preHandler hooks applied to all SSE routes.
     * Use for authentication that should apply to all SSE endpoints.
     *
     * IMPORTANT: Must return a Promise for SSE compatibility.
     * Synchronous handlers will cause connection issues.
     */
    preHandler?: FastifySSEPreHandler;
    /**
     * Rate limit configuration (requires @fastify/rate-limit to be registered).
     * If @fastify/rate-limit is not registered, this config is ignored.
     */
    rateLimit?: {
        /** Maximum number of connections */
        max: number;
        /** Time window for rate limiting */
        timeWindow: string | number;
        /** Custom key generator (e.g., for per-user limits) */
        keyGenerator?: (request: unknown) => string;
    };
};
/**
 * Options for registering dual-mode routes globally.
 */
export type RegisterDualModeRoutesOptions = {
    /**
     * Heartbeat interval in milliseconds for SSE mode.
     * @default 30000
     */
    heartbeatInterval?: number;
    /**
     * Custom serializer for SSE message data.
     * @default JSON.stringify
     */
    serializer?: (data: unknown) => string;
    /**
     * Global preHandler hooks applied to all dual-mode routes.
     * Use for authentication that should apply to all endpoints.
     *
     * IMPORTANT: Must return a Promise for SSE mode compatibility.
     * Synchronous handlers will cause connection issues in SSE mode.
     */
    preHandler?: FastifySSEPreHandler;
    /**
     * Rate limit configuration (requires @fastify/rate-limit to be registered).
     * If @fastify/rate-limit is not registered, this config is ignored.
     */
    rateLimit?: {
        /** Maximum number of requests */
        max: number;
        /** Time window for rate limiting */
        timeWindow: string | number;
        /** Custom key generator (e.g., for per-user limits) */
        keyGenerator?: (request: unknown) => string;
    };
};
export {};
