import { FastifyReply, FastifyRequest } from 'fastify';
import { CookieSerializeOptions } from '@fastify/cookie';

interface PageMetadata {
    title: string;
    description: string;
    keywords?: string;
    canonical?: string;
    og?: {
        title?: string;
        description?: string;
        image?: string;
    };
}
/**
 * Base meta structure with required page metadata
 */
interface BaseMeta {
    page?: PageMetadata;
}
interface ErrorDetails {
    [key: string]: unknown;
}
/**
 * Error details value - supports both object and array formats
 *
 * @example Object format (structured errors)
 * { field: 'email', reason: 'invalid format', code: 'VALIDATION_ERROR' }
 *
 * @example Array format (multiple validation errors with type field)
 * [
 *   { field: 'email', type: 'invalid_email', message: 'Must be a valid email address' },
 *   { field: 'password', type: 'invalid_length', message: 'Must be at least 8 characters long' }
 * ]
 *
 * @example Array format (error trace)
 * ['Step 1 failed', 'Rollback initiated', 'Cleanup completed']
 */
type ErrorDetailsValue = ErrorDetails | unknown[];
/**
 * Error object structure for API error responses
 */
interface ErrorObject {
    code: string;
    message: string;
    details?: ErrorDetailsValue;
}
interface RedirectInfo {
    target: string;
    permanent: boolean;
    preserve_query?: boolean;
}
/**
 * API Success Response with extensible meta
 *
 * @template T - The data type
 * @template M - Additional meta properties (extends BaseMeta)
 *
 * @example
 * // Basic usage (no extra meta)
 * type BasicResponse = APISuccessResponse<User>;
 *
 * @example
 * // With required extra meta fields
 * interface CustomMeta extends BaseMeta {
 *   pagination: { page: number; total: number };
 *   cache: { expires: string };
 * }
 * type PaginatedResponse = APISuccessResponse<User[], CustomMeta>;
 */
interface APISuccessResponse<T, M extends BaseMeta = BaseMeta> {
    status: 'success';
    status_code: number;
    request_id: string;
    request_timestamp?: string;
    type: 'api';
    data: T;
    meta: M;
    error?: null;
}
/**
 * API Error Response with extensible meta
 *
 * @template M - Additional meta properties (extends BaseMeta)
 */
interface APIErrorResponse<M extends BaseMeta = BaseMeta> {
    status: 'error';
    status_code: number;
    request_id: string;
    request_timestamp?: string;
    type: 'api';
    data: null;
    meta: M;
    error: ErrorObject;
}
/**
 * API response envelope as a discriminated union
 *
 * @template T - The data type for success responses
 * @template M - Additional meta properties (extends BaseMeta)
 */
type APIResponseEnvelope<T = unknown, M extends BaseMeta = BaseMeta> = APISuccessResponse<T, M> | APIErrorResponse<M>;
/**
 * Page Success Response with extensible meta
 *
 * @template T - The data type
 * @template M - Additional meta properties (extends BaseMeta)
 */
interface PageSuccessResponse<T, M extends BaseMeta = BaseMeta> {
    status: 'success';
    status_code: number;
    request_id: string;
    request_timestamp?: string;
    type: 'page';
    data: T;
    meta: M;
    error?: null;
    ssr_request_context?: Record<string, unknown>;
}
/**
 * Page Error Response with extensible meta
 *
 * @template M - Additional meta properties (extends BaseMeta)
 */
interface PageErrorResponse<M extends BaseMeta = BaseMeta> {
    status: 'error';
    status_code: number;
    request_id: string;
    request_timestamp?: string;
    type: 'page';
    data: null;
    meta: M;
    error: ErrorObject;
    ssr_request_context?: Record<string, unknown>;
}
/**
 * Page Redirect Response with extensible meta
 *
 * @template M - Additional meta properties (extends BaseMeta)
 */
interface PageRedirectResponse<M extends BaseMeta = BaseMeta> {
    status: 'redirect';
    status_code: 200;
    request_id: string;
    request_timestamp?: string;
    type: 'page';
    data: null;
    meta: M;
    error?: null;
    redirect: RedirectInfo;
    ssr_request_context?: Record<string, unknown>;
}
/**
 * Page response envelope as a discriminated union
 *
 * @template T - The data type for success responses
 * @template M - Additional meta properties (extends BaseMeta)
 */
type PageResponseEnvelope<T = unknown, M extends BaseMeta = BaseMeta> = PageSuccessResponse<T, M> | PageErrorResponse<M> | PageRedirectResponse<M>;

/**
 * Parsed domain information derived from a request hostname.
 *
 * Used both on the Fastify request object (`request.domainInfo`) and in the
 * Unirend React context (`useDomainInfo()`).
 */
interface DomainInfo {
    /** Bare hostname with port stripped (IPv6-safe, e.g. `'app.example.com'` or `'::1'`). */
    hostname: string;
    /**
     * Apex domain without a leading dot (e.g. `'example.com'`).
     * Empty string for localhost and raw IP addresses where no root domain can be resolved.
     *
     * When empty, omit the `domain` attribute entirely — `domain=.localhost` is invalid
     * per RFC 6265 and browsers reject it. A cookie without a `domain` attribute becomes
     * a host-only cookie scoped to the exact hostname, which is correct for localhost and IPs.
     *
     * Prepend `.` when using as a cookie `domain` attribute to span subdomains:
     * ```ts
     * document.cookie = [
     *   'theme=dark',
     *   'path=/',
     *   'max-age=31536000',
     *   domainInfo.rootDomain ? `domain=.${domainInfo.rootDomain}` : null,
     * ].filter(Boolean).join('; ');
     * ```
     */
    rootDomain: string;
}

/**
 * Controlled reply surface available to handlers.
 * Allows setting headers and cookies without giving full reply control.
 *
 * Used by page data loader handlers, API route handlers, and processFileUpload().
 * Provides limited access to prevent handlers from prematurely sending responses
 * or bypassing the framework's envelope pattern.
 */
interface ControlledReply {
    /** Set a response header (content-type may be enforced by framework) */
    header: (name: string, value: string) => void;
    /** Set a cookie if @fastify/cookie is registered */
    setCookie?: (name: string, value: string, options?: CookieSerializeOptions) => void;
    /** Alias for setCookie if @fastify/cookie is registered */
    cookie?: (name: string, value: string, options?: CookieSerializeOptions) => void;
    /** Clear a cookie if @fastify/cookie is registered */
    clearCookie?: (name: string, options?: CookieSerializeOptions) => void;
    /** Verify and unsign a cookie value if @fastify/cookie is registered */
    unsignCookie?: (value: string) => {
        valid: true;
        renew: boolean;
        value: string;
    } | {
        valid: false;
        renew: false;
        value: null;
    };
    /** Sign a cookie value if @fastify/cookie is registered */
    signCookie?: (value: string) => string;
    /** Read a response header value (if available) */
    getHeader: (name: string) => string | number | string[] | undefined;
    /** Read all response headers as a plain object */
    getHeaders: () => Record<string, unknown>;
    /** Remove a response header by name */
    removeHeader: (name: string) => void;
    /** Check if a response header has been set */
    hasHeader: (name: string) => boolean;
    /** Whether the reply has already been sent */
    sent: boolean;
    /**
     * Access to the underlying response stream (for connection monitoring)
     *
     * Limited scope: Only used internally by processFileUpload() for detecting
     * broken connections during file uploads. Most handlers won't need this
     */
    raw: {
        /** Whether the underlying connection has been destroyed */
        destroyed: boolean;
    };
    /**
     * Internal: send an error envelope and finish the response immediately.
     *
     * This is installed by the framework's controlled-reply wrapper so helpers
     * can terminate with the same raw/hijacked path while still reusing shared
     * header logic such as CORS application.
     *
     * ControlledReply intentionally does not expose general-purpose send/write
     * methods to user handlers. This internal escape hatch exists only so
     * framework-owned helpers like APIResponseHelpers can terminate early in a
     * controlled way without reopening arbitrary reply access.
     */
    _sendErrorEnvelope: (statusCode: number, errorEnvelope: APIErrorResponse<BaseMeta> | PageErrorResponse<BaseMeta>) => Promise<void>;
}
declare module 'fastify' {
    interface FastifyRequest {
        /**
         * Active SSR app key for multi-app routing.
         *
         * Read-only request value. Defaults to `'__default__'`.
         * Use `request.setActiveSSRApp(appKey)` in SSR middleware to select a
         * registered app and refresh app-derived request values.
         */
        readonly activeSSRApp: string;
        /**
         * Select the active SSR app for this request.
         *
         * Validates that the app exists, updates `request.activeSSRApp`, refreshes
         * `request.publicAppConfig`, and updates the app-level CDN default unless
         * middleware already overrode `request.CDNBaseURL`.
         */
        setActiveSSRApp: (appKey: string) => void;
        /**
         * Resolved client IP address.
         *
         * Set once per request by the framework using `getClientIP` (if provided)
         * or falling back to `request.ip` (which reflects Fastify proxy handling
         * when `fastifyOptions.trustProxy` is configured).
         *
         * Available throughout the entire request lifecycle, including plugins,
         * hooks, page data loader handlers, API route handlers, and access log
         * templates/hooks.
         */
        clientIP: string;
        /**
         * Server label for this instance (e.g. `'SSR'`, `'API'`).
         *
         * Set once per request by the framework from the `serverLabel` server option.
         * Available in access log templates as `{{serverLabel}}` and throughout
         * the request lifecycle via `request.serverLabel`.
         */
        serverLabel: string;
        /**
         * Safe-to-share app configuration cloned and frozen for this request.
         *
         * Available to plugins, handlers, and helpers on SSR and API servers.
         * SSR also exposes it to React and injects it into HTML. API servers only
         * send it to clients if your response includes selected values.
         */
        publicAppConfig?: Record<string, unknown>;
        /**
         * Effective CDN base URL for this SSR request.
         *
         * SSR middleware can set this as a per-request override. If it remains
         * unset, the SSR server populates it from the active app's `CDNBaseURL`
         * before preHandler hooks, route handlers, SSR render, and custom 500
         * pages run.
         *
         * API servers do not set this field.
         */
        CDNBaseURL?: string;
        /**
         * Optional request-scoped helper installed by the built-in CORS plugin.
         *
         * Raw/hijacked response paths can call this before `writeHead(...)` to
         * apply the same actual-response CORS/security headers that normal
         * Fastify-managed responses receive.
         */
        applyCORSHeaders?: (reply: FastifyReply) => void | Promise<void>;
        /**
         * Internal request-start timestamp captured by the framework.
         *
         * Used by framework features that need a stable "request received" time,
         * including API/page envelope timestamps and fallback response-time
         * calculation when Fastify's built-in elapsedTime is unavailable.
         */
        receivedAt?: number;
        /**
         * Per-request mutable key-value store initialized by both SSRServer and APIServer
         * before any plugins or hooks run.
         *
         * Use this to pass data between middleware, hooks, and handlers — for example,
         * seeding user session info, theme preferences, or CSRF tokens from an onRequest
         * hook so that page data loader handlers and API handlers can read them.
         *
         * During SSR, the server injects the final context into the rendered HTML so
         * client-side React can hydrate with the same values via `useRequestContext()`.
         *
         * In separated SSR/API deployments the SSR layer forwards this context to trusted
         * API page data requests and merges returned context back automatically.
         */
        requestContext: Record<string, unknown>;
        /**
         * Parsed domain information for this request, computed once per request from
         * `request.hostname` by both SSRServer and APIServer.
         *
         * - `hostname`: bare hostname with port stripped (IPv6-safe)
         * - `rootDomain`: apex domain without a leading dot (e.g. `'example.com'`);
         *   empty string for localhost and raw IP addresses
         *
         * Use `rootDomain` to set subdomain-spanning cookies by prepending a dot:
         * `domain=.${request.domainInfo.rootDomain}` — the same value that the
         * client-side `cycleTheme()` helper uses.
         */
        domainInfo: DomainInfo;
        /**
         * Set to `true` when the request is handled by the built-in static content
         * handler (fingerprinted assets, public files, etc.), regardless of which
         * server type is serving the file.
         *
         * Initialized to `false` for every request. The static content handler sets
         * it to `true` before calling `reply.hijack()`, so it is observable in
         * `onResponse` hooks and access log templates even though `onSend` is bypassed.
         *
         * Use this to skip work that is inappropriate for static asset responses —
         * for example, cookie renewal should only happen on document/API responses,
         * not on every `.js` or `.css` file request.
         */
        isStaticAsset: boolean;
    }
}

/**
 * Helper utilities for constructing API/Page response envelopes.
 *
 * These are static so the class can be easily subclassed or the methods can be
 * re-exported. Users may extend this class to inject their own default meta or
 * wrap additional logic (e.g., account metadata, logging, etc.).
 */
declare class APIResponseHelpers {
    /**
     * Creates a standardized API success response envelope for API (AJAX/JSON) endpoints.
     *
     * @typeParam T - The type of the response data payload.
     * @typeParam M - Meta type that extends BaseMeta.
     *   Allows consumers to add application specific meta keys
     *   (e.g. `account`, `pagination`, etc.).
     * @param params - Object containing request, data, statusCode (default 200), and optional meta.
     * @returns An APISuccessResponse envelope with merged meta and a request_id.
     */
    static createAPISuccessResponse<T, M extends BaseMeta = BaseMeta>(params: {
        request: FastifyRequest;
        data: T;
        statusCode?: number;
        meta?: Partial<M>;
    }): APISuccessResponse<T, M>;
    /**
     * Creates a standardized API error response envelope for API (AJAX/JSON) endpoints.
     *
     * @typeParam M - Meta type that extends BaseMeta.
     *   Allows consumers to add application specific meta keys
     *   (e.g. `account`, `pagination`, etc.).
     * @param params - Object containing request, statusCode, errorCode, errorMessage, optional errorDetails, and optional meta.
     * @returns An APIErrorResponse envelope with merged meta and a request_id.
     */
    static createAPIErrorResponse<M extends BaseMeta = BaseMeta>(params: {
        request: FastifyRequest;
        statusCode: number;
        errorCode: string;
        errorMessage: string;
        errorDetails?: ErrorDetailsValue;
        meta?: Partial<M>;
    }): APIErrorResponse<M>;
    /**
     * Creates a standardized Page success response envelope for SSR/data loaders.
     *
     * @typeParam T - The type of the response data payload.
     * @typeParam M - Meta type that extends BaseMeta.
     *   Allows consumers to add application specific meta keys
     *   (e.g. `account`, `pagination`, etc.).
     * @param params - Object containing request, data, pageMetadata, statusCode (default 200), and optional meta.
     * @returns A PageSuccessResponse envelope with merged meta and a request_id.
     */
    static createPageSuccessResponse<T, M extends BaseMeta = BaseMeta>(params: {
        request: FastifyRequest;
        data: T;
        pageMetadata: PageMetadata;
        statusCode?: number;
        meta?: Partial<M>;
    }): PageSuccessResponse<T, M>;
    /**
     * Creates a standardized Page redirect response envelope for SSR/data loaders.
     * Always uses status code 200 to avoid confusion with HTTP redirects.
     *
     * @typeParam M - Meta type that extends BaseMeta.
     *   Allows consumers to add application specific meta keys.
     * @param params - Object containing request, redirectInfo, pageMetadata, and optional meta.
     * @returns A PageRedirectResponse envelope with merged meta and a request_id.
     */
    static createPageRedirectResponse<M extends BaseMeta = BaseMeta>(params: {
        request: FastifyRequest;
        redirectInfo: RedirectInfo;
        pageMetadata: PageMetadata;
        meta?: Partial<M>;
    }): PageRedirectResponse<M>;
    /**
     * Creates a standardized Page error response envelope for SSR/data loaders.
     *
     * @typeParam M - Meta type that extends BaseMeta.
     *   Allows consumers to add application specific meta keys
     *   (e.g. `account`, `pagination`, etc.).
     * @param params - Object containing request, statusCode, errorCode, errorMessage,
     *   optional errorDetails, pageMetadata, and optional meta.
     * @returns A PageErrorResponse envelope with merged meta and a request_id.
     */
    static createPageErrorResponse<M extends BaseMeta = BaseMeta>(params: {
        request: FastifyRequest;
        statusCode: number;
        errorCode: string;
        errorMessage: string;
        errorDetails?: ErrorDetailsValue;
        pageMetadata: PageMetadata;
        meta?: Partial<M>;
    }): PageErrorResponse<M>;
    /**
     * Send an error envelope response with the appropriate method
     * Works with both FastifyReply and ControlledReply
     *
     * This is a public utility for sending error responses in a way that works
     * with both standard Fastify handlers and controlled reply handlers.
     *
     * @param reply - Fastify reply object or ControlledReply
     * @param statusCode - HTTP status code to send
     * @param errorResponse - Error envelope to send
     *
     * @example
     * ```typescript
     * const errorResponse = APIResponseHelpers.createAPIErrorResponse({
     *   request,
     *   statusCode: 400,
     *   errorCode: 'invalid_input',
     *   errorMessage: 'Invalid input provided',
     * });
     *
     * await APIResponseHelpers.sendErrorEnvelope(
     *   request,
     *   reply,
     *   400,
     *   errorResponse,
     * );
     * ```
     *
     * This helper is usable directly, but it is also part of the framework's
     * controlled early-termination path. Unlike the envelope creation helpers,
     * it has transport semantics (shared headers, hijack/raw write, immediate
     * response finalization), so overriding it in a custom helpers subclass is
     * discouraged unless you intend to preserve that contract.
     */
    static sendErrorEnvelope(request: FastifyRequest, reply: FastifyReply | ControlledReply, statusCode: number, errorResponse: APIErrorResponse<BaseMeta> | PageErrorResponse<BaseMeta>): Promise<void>;
    /**
     * Ensures an incoming Fastify request has a valid JSON body.
     * If invalid, sends a standardized error response and returns false.
     *
     * Use this helper for POST, PUT, PATCH, and DELETE endpoints that expect JSON payloads.
     * This is a pre-validation convenience before using schema validators like Zod.
     *
     * @param request - Fastify request object
     * @param reply - Fastify reply object or ControlledReply
     * @returns true if body is valid JSON, otherwise false (error envelope already sent)
     *
     * @example
     * ```typescript
     * server.api.post('users', async (request, reply) => {
     *   if (!(await APIResponseHelpers.ensureJSONBody(request, reply))) {
     *     return false; // Error envelope already sent
     *   }
     *
     *   // Now safe to validate using a schema validator (e.g. Zod) or process the body
     *   const validated = userSchema.parse(request.body);
     *   // ...
     * });
     * ```
     */
    static ensureJSONBody(request: FastifyRequest, reply: FastifyReply | ControlledReply): Promise<boolean>;
    /**
     * Ensures an incoming Fastify request has a valid URL-encoded form body.
     * If invalid, sends a standardized error response and returns false.
     *
     * Use this helper for POST, PUT, or PATCH endpoints that expect URL-encoded form data.
     * This is a pre-validation convenience before processing form fields.
     *
     * Note: For file uploads with multipart/form-data, use ensureMultipartBody instead.
     *
     * @param request - Fastify request object
     * @param reply - Fastify reply object or ControlledReply
     * @returns true if form body is valid, otherwise false (error envelope already sent)
     *
     * @example
     * ```typescript
     * server.api.post('contact', async (request, reply) => {
     *   if (!(await APIResponseHelpers.ensureURLEncodedBody(request, reply))) {
     *     return false; // Error envelope already sent
     *   }
     *
     *   // Now safe to process form fields
     *   const formData = request.body as Record<string, unknown>;
     *   // ...
     * });
     * ```
     */
    static ensureURLEncodedBody(request: FastifyRequest, reply: FastifyReply | ControlledReply): Promise<boolean>;
    /**
     * Ensures an incoming Fastify request has multipart/form-data Content-Type.
     * If invalid, sends a standardized error response and returns false.
     *
     * **Note:** `processFileUpload()` automatically validates Content-Type,
     * so you typically don't need this helper when using `processFileUpload()`.
     *
     * **Advanced use case:** Use this for early validation in middleware (e.g., auth/rate-limiting)
     * before multipart parsing begins:
     *
     * ```typescript
     * // Block uploads for non-premium users before parsing
     * pluginHost.addHook('preHandler', async (request, reply) => {
     *   if (request.headers['content-type']?.includes('multipart/form-data')) {
     *     if (!user.isPremium) {
     *       return reply.code(403).send({ error: 'Premium feature' });
     *     }
     *   }
     * });
     * ```
     *
     * For standard file uploads, use `processFileUpload()` instead:
     * ```typescript
     * import { processFileUpload } from 'unirend/server';
     *
     * const results = await processFileUpload({
     *   request,
     *   reply,
     *   maxSizePerFile: 5 * 1024 * 1024,
     *   allowedMimeTypes: ['image/jpeg', 'image/png'],
     *   processor: async (stream, metadata, context) => {
     *     // ... handle upload
     *   },
     * });
     * ```
     *
     * @param request - Fastify request object
     * @param reply - Fastify reply object or ControlledReply
     * @returns true if Content-Type is multipart/form-data, otherwise false (error envelope already sent)
     *
     */
    static ensureMultipartBody(request: FastifyRequest, reply: FastifyReply | ControlledReply): Promise<boolean>;
    /** Determines if envelope is a success response */
    static isSuccessResponse<T, M extends BaseMeta = BaseMeta>(response: APIResponseEnvelope<T, M> | PageResponseEnvelope<T, M>): response is APISuccessResponse<T, M> | PageSuccessResponse<T, M>;
    /** Determines if envelope is an error response */
    static isErrorResponse<M extends BaseMeta = BaseMeta>(response: APIResponseEnvelope<unknown, M> | PageResponseEnvelope<unknown, M>): response is APIErrorResponse<M> | PageErrorResponse<M>;
    /** Determines if envelope is a redirect response */
    static isRedirectResponse<M extends BaseMeta = BaseMeta>(response: APIResponseEnvelope<unknown, M> | PageResponseEnvelope<unknown, M>): response is PageRedirectResponse<M>;
    /** Determines if envelope is a page (SSR) response */
    static isPageResponse<T, M extends BaseMeta = BaseMeta>(response: APIResponseEnvelope<T, M> | PageResponseEnvelope<T, M>): response is PageResponseEnvelope<T, M>;
    /**
     * Validates that an unknown value is a proper envelope object
     * This is a catch-all validation function that checks for proper envelope structure
     * without requiring specific typing - useful for runtime validation of handler responses
     */
    static isValidEnvelope(result: unknown): result is PageResponseEnvelope | APIResponseEnvelope;
}

export { type APIErrorResponse, type APIResponseEnvelope, APIResponseHelpers, type APISuccessResponse, type BaseMeta, type ErrorDetails, type ErrorDetailsValue, type ErrorObject, type PageErrorResponse, type PageMetadata, type PageRedirectResponse, type PageResponseEnvelope, type PageSuccessResponse, type RedirectInfo };
