import * as _fastify_cookie from '@fastify/cookie';
import { FastifyCookieOptions, Signer } from '@fastify/cookie';
export { CookieSerializeOptions, UnsignResult as CookieUnsignResult } from '@fastify/cookie';
import { FastifyReply, FastifyPluginAsync, FastifyPluginCallback, FastifyInstance, FastifyRequest, preHandlerHookHandler, FastifySchema, FastifyBaseLogger } from 'fastify';
import { APIResponseHelpers } from 'unirend/api-envelope';
import fs from 'fs';

/**
 * 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;
}

type APIResponseHelpersClass = typeof APIResponseHelpers;
/**
 * Plugin metadata returned by plugins for dependency tracking and cleanup
 */
interface PluginMetadata {
    /** Unique name for this plugin */
    name: string;
    /** Plugin dependencies - other plugin names that must be registered first */
    dependsOn?: string | string[];
}
/**
 * Plugin registration function type
 * Plugins get access to a controlled subset of Fastify functionality
 * Can optionally return metadata for dependency tracking
 */
type ServerPlugin = (pluginHost: PluginHostInstance, options: PluginOptions) => Promise<PluginMetadata | void> | PluginMetadata | void;
/**
 * Fastify hook names that plugins can register
 * Includes common lifecycle hooks plus string for custom hooks
 */
type FastifyHookName = Parameters<FastifyInstance['addHook']>[0];
/**
 * Controlled Fastify instance interface for plugins
 * Exposes safe methods while preventing access to destructive operations
 */
interface PluginHostInstance {
    /** Register plugins and middleware */
    register: <Options extends Record<string, unknown> = Record<string, never>>(plugin: FastifyPluginAsync<Options> | FastifyPluginCallback<Options>, opts?: Options) => Promise<void>;
    /** Add custom hooks */
    addHook: (hookName: FastifyHookName, handler: (request: FastifyRequest, reply: FastifyReply, ...args: unknown[]) => unknown) => void;
    /** Add decorators to request/reply objects */
    decorate: (property: string, value: unknown) => void;
    decorateRequest: (property: string, value: unknown) => void;
    decorateReply: (property: string, value: unknown) => void;
    /** Read-only accessors for server-level decorations */
    hasDecoration: (property: string) => boolean;
    getDecoration: <T = unknown>(property: string) => T | undefined;
    /** Access to route registration with constraints */
    route: (opts: SafeRouteOptions) => void;
    get: (path: string, handler: RouteHandler) => void;
    post: (path: string, handler: RouteHandler) => void;
    put: (path: string, handler: RouteHandler) => void;
    delete: (path: string, handler: RouteHandler) => void;
    patch: (path: string, handler: RouteHandler) => void;
    /** Server-level logger (pino). Use `(obj, msg)` argument order. Useful for logging during plugin setup, before any request exists. */
    log: FastifyBaseLogger;
    /** API route registration shortcuts method for versioned endpoints */
    api?: unknown;
    /** Page data loader handler registration method for page data endpoints */
    pageDataHandler?: unknown;
    /** The APIResponseHelpers class configured on this server — use this to build envelopes so custom subclasses are respected */
    APIResponseHelpers: APIResponseHelpersClass;
}
/**
 * Safe route options that prevent catch-all conflicts
 */
interface SafeRouteOptions {
    method: string | string[];
    url: string;
    handler: RouteHandler;
    preHandler?: preHandlerHookHandler | preHandlerHookHandler[];
    schema?: FastifySchema;
    config?: unknown;
    constraints?: {
        /** Only allow specific hosts, no wildcards that could conflict with SSR */
        host?: string;
        /** Only allow specific versions */
        version?: string;
    };
}
type RouteHandler = (request: FastifyRequest, reply: FastifyReply) => void | Promise<unknown>;
/**
 * Shared configuration for versioned API endpoint groups
 * Used by helpers that register versioned endpoints (page data, generic API routes, etc.)
 */
interface APIEndpointConfig {
    /**
     * Endpoint prefix that comes before version/endpoint (default: "/api")
     * Set to `false` to disable API handling (server becomes a plain web server)
     */
    apiEndpointPrefix?: string | false;
    /** Whether to enable versioning (default: true) */
    versioned?: boolean;
    /** Base endpoint name for page data loader handlers (default: "page_data"). Used by SSR/APIServer's page-data registration only. */
    pageDataEndpoint?: string;
}
/**
 * Plugin options passed to each plugin
 *
 * Environment information available at plugin registration time.
 *
 * Notes:
 * - Use these fields inside your plugin setup to decide what to REGISTER
 *   (e.g., which routes, which hooks). This is registration-time context.
 * - For per-request branching inside handlers/middleware, read
 *   `request.isDevelopment` (decorated by the servers). Both reflect the same
 *   underlying mode; they serve different scopes.
 */
interface PluginOptions {
    /** Type of server the plugin is running on */
    serverType: 'ssr' | 'api';
    /** Server mode (development or production) */
    mode: 'development' | 'production';
    /** Whether running in development mode */
    isDevelopment: boolean;
    /** API endpoints configuration from the server */
    apiEndpoints?: APIEndpointConfig;
}
/**
 * Configuration for a folder in the static router folderMap
 */
interface FolderConfig {
    /** Path to the directory */
    path: string;
    /** Whether to detect and use immutable caching for fingerprinted assets */
    detectImmutableAssets?: boolean;
}
/**
 * Options for the static router middleware
 * Used to serve static files in production SSR mode
 */
interface StaticContentRouterOptions {
    /** Exact URL → absolute file path (optional) */
    singleAssetMap?: Record<string, string>;
    /** URL prefix → absolute directory path (as string) or folder config object */
    folderMap?: Record<string, string | FolderConfig>;
    /** Response compression for buffered static responses; default true */
    compression?: boolean | ResponseCompressionOptions;
    /** Maximum size (in bytes) for hashing & in-memory caching; default 5 MB */
    smallFileMaxSize?: number;
    /** Maximum number of entries in ETag/content caches; default 100 */
    cacheEntries?: number;
    /** Maximum total memory size (in bytes) for content cache; default 50 MB */
    contentCacheMaxSize?: number;
    /** Maximum number of entries in the stat cache; default 250 */
    statCacheEntries?: number;
    /** TTL in milliseconds for negative stat cache entries; default 30 seconds */
    negativeCacheTtl?: number;
    /** TTL in milliseconds for positive stat cache entries; default 1 hour */
    positiveCacheTtl?: number;
    /** Custom Cache-Control header; default 'public, max-age=0, must-revalidate' */
    cacheControl?: string;
    /** Cache-Control header for immutable fingerprinted assets; default 'public, max-age=31536000, immutable' */
    immutableCacheControl?: string;
}
interface ResponseCompressionOptions {
    /** Whether compression is enabled when using the object form */
    enabled?: boolean;
    /** Minimum response size in bytes before compression is attempted */
    threshold?: number;
    /** Prefer Brotli over gzip when the client gives both equal q-values */
    preferBrotli?: boolean;
    /** Brotli quality level passed to Node.js zlib */
    brotliQuality?: number;
    /** gzip compression level passed to Node.js zlib */
    gzipLevel?: number;
}
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;
    }
}

/**
 * CORS origin configuration - can be a string, array, or function
 */
type CORSOrigin = string | string[] | ((origin: string | undefined, request: FastifyRequest) => boolean | Promise<boolean>);
/**
 * Configuration for dynamic CORS handling
 */
interface CORSConfig {
    /**
     * Allowed origins for CORS requests
     * - string: Single origin (e.g., "https://example.com")
     * - string[]: Multiple origins with wildcard support
     * - function: Dynamic origin validation
     * - "*": Allow all origins (not recommended with credentials)
     *
     * Wildcard patterns supported:
     * - "*.example.com": Direct subdomains only (api.example.com ✅, app.api.example.com ❌)
     * - "**.example.com": All subdomains including nested (api.example.com ✅, app.api.example.com ✅)
     * - "https://*": Any domain with HTTPS protocol
     * - "http://*": Any domain with HTTP protocol
     * - "https://*.example.com": HTTPS subdomains only
     * - "http://**.example.com": HTTP subdomains including nested
     *
     * Note: "null" origins (from sandboxed documents, file:// URLs) are treated as regular string values.
     * Include "null" in your origin array or handle it in your validation function if needed.
     *
     * @default "*"
     */
    origin?: CORSOrigin;
    /**
     * Origins that are allowed to send credentials (cookies, auth headers)
     * This enables more granular control than standard CORS libraries
     *
     * - string[]: List of trusted origins that can send credentials
     * - function: Dynamic credential validation based on origin
     * - true: Allow credentials for all allowed origins (same as @fastify/cors)
     * - false: Never allow credentials
     *
     * @default false
     */
    credentials?: boolean | string[] | ((origin: string | undefined, request: FastifyRequest) => boolean | Promise<boolean>);
    /**
     * Allowed HTTP methods
     * @default ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"]
     */
    methods?: string[];
    /**
     * Allowed request headers
     * - string[]: List of specific headers (e.g., ["Content-Type", "Authorization"])
     * - ["*"]: Reflect exactly what the browser requests (useful for public APIs)
     * @default ["Content-Type", "Authorization", "X-Requested-With"]
     */
    allowedHeaders?: string[];
    /**
     * Headers exposed to the client
     * @default []
     */
    exposedHeaders?: string[];
    /**
     * Max age for preflight cache (in seconds)
     * @default 86400 (24 hours)
     */
    maxAge?: number;
    /**
     * Whether to pass control to next handler on preflight OPTIONS requests
     * @default false
     */
    preflightContinue?: boolean;
    /**
     * Status code for successful preflight responses
     * @default 204
     */
    optionsSuccessStatus?: number;
    /**
     * Whether to allow private network requests (Chrome feature)
     * When true, responds to Access-Control-Request-Private-Network with Access-Control-Allow-Private-Network
     * @default false
     */
    allowPrivateNetwork?: boolean;
    /**
     * Opt-in: allow wildcard subdomain patterns (e.g., "*.example.com") in `credentials` array
     * When true, patterns like "*.example.com", "**.example.com", "*.*.example.com" are permitted.
     * Apex domains are NOT matched by wildcard patterns; include the apex explicitly if needed.
     * Invalid patterns (bare "*", protocol wildcards like "https://*") are rejected.
     *
     * @default false
     */
    credentialsAllowWildcardSubdomains?: boolean;
    /**
     * Opt-in: allow credentials: true when origin includes a protocol wildcard (e.g., "https://*")
     * By default this is disallowed for safety because it enables credentials for any origin
     * on that protocol.
     *
     * @default false
     */
    allowCredentialsWithProtocolWildcard?: boolean;
    /**
     * Controls the X-Frame-Options response header.
     * - false: do not send the header (default)
     * - "DENY" | "SAMEORIGIN": header value to send
     *
     * @default false
     */
    xFrameOptions?: false | 'DENY' | 'SAMEORIGIN';
    /**
     * Controls the Strict-Transport-Security (HSTS) response header.
     * - false: do not send the header (default)
     * - { maxAge, includeSubDomains?, preload? }: header parameters
     *
     * Note: HSTS is typically only appropriate over HTTPS in production.
     * This plugin does not inspect the connection security; enable with care.
     *
     * @default false
     */
    hsts?: false | {
        maxAge: number;
        includeSubDomains?: boolean;
        preload?: boolean;
    };
}
/**
 * Dynamic CORS plugin for Unirend
 *
 * Provides more flexible CORS handling than @fastify/cors, specifically:
 * - Dynamic credentials based on origin
 * - Function-based origin validation
 * - Separate credential and origin policies
 *
 * @example
 * ```typescript
 * // Allow public API access but only credentials for trusted origins
 * cors({
 *   origin: "*", // Allow any origin for public API
 *   credentials: ["https://myapp.com", "https://admin.myapp.com"], // Only these can send cookies
 *   methods: ["GET", "POST"],
 * })
 *
 * // Handle "null" origins from sandboxed documents or file:// URLs
 * cors({
 *   origin: ["https://app.com", "null"], // Explicitly allow null origins
 *   credentials: ["https://app.com"], // Credentials not allowed for null origins
 * })
 *
 * // Dynamic validation based on request
 * cors({
 *   origin: (origin, request) => {
 *     // Allow any origin for public endpoints
 *     if (request.url?.startsWith('/api/public/')) return true;
 *     // Restrict private endpoints
 *     return origin === 'https://myapp.com';
 *   },
 *   credentials: (origin, request) => {
 *     // Only allow credentials for authenticated endpoints from trusted origins
 *     return request.url?.startsWith('/api/auth/') && origin === 'https://myapp.com';
 *   }
 * })
 * ```
 */
declare function cors(config?: CORSConfig): ServerPlugin;

/**
 * Response configuration for invalid domain handler
 */
interface InvalidDomainResponse {
    contentType: 'json' | 'text' | 'html';
    content: string | object;
}
/**
 * Domain validation configuration - can be a string, array, or function
 */
type ValidProductionDomains = string | string[] | ((domain: string, request: FastifyRequest) => boolean | Promise<boolean>);
/**
 * Configuration options for the domainValidation plugin
 */
interface DomainValidationConfig {
    /**
     * Valid production domains that are allowed to access this server
     *
     * Can be a single domain string, array of domain strings (without protocol),
     * or a function for request-aware domain validation.
     * Wildcard patterns supported:
     * - "example.com" - allows exact match only
     * - "*.example.com" - allows direct subdomains only (api.example.com ✅, app.api.example.com ❌)
     * - "**.example.com" - allows all subdomains including nested (api.example.com ✅, app.api.example.com ✅)
     *
     * Examples:
     * - ["example.com", "www.example.com", "api.example.com"] - specific domains
     * - ["**.example.com", "example.com"] - apex + all subdomains (including nested)
     * - ["*.example.com", "example.com"] - apex + direct subdomains only
     *
     * Note: Domain validation is protocol-agnostic (ignores http/https)
     * If not specified, domain validation is skipped
     */
    validProductionDomains?: ValidProductionDomains;
    /**
     * Optional canonical domain to redirect to if the request domain doesn't match
     * Should be defined without www prefix or protocol (use wwwHandling to control www)
     * If specified, requests to valid domains will be redirected to this canonical domain
     * If not specified, valid domains are allowed without redirection
     * Example: "example.com"
     */
    canonicalDomain?: string;
    /**
     * Whether to enforce HTTPS by redirecting HTTP requests
     * @default true
     */
    enforceHTTPS?: boolean;
    /**
     * How to handle www prefix normalization for apex domains only
     * - "remove": Strip www prefix (www.example.com → example.com)
     * - "add": Add www prefix (example.com → www.example.com)
     * - "preserve": Don't modify www, only validate canonical domain matches
     * Note: Only applies to apex domains, not subdomains (api.example.com stays unchanged)
     * @default "preserve"
     */
    wwwHandling?: 'remove' | 'add' | 'preserve';
    /**
     * HTTP status code to use for redirects
     * @default 301 (permanent redirect)
     */
    redirectStatusCode?: 301 | 302 | 307 | 308;
    /**
     * Whether to preserve port numbers in canonical domain redirects
     * - true: example.com:3000 → canonical.com:3000
     * - false: example.com:3000 → canonical.com (strip port)
     * @default false
     */
    preservePort?: boolean;
    /**
     * Whether to skip all checks in development mode
     * @default true
     */
    skipInDevelopment?: boolean;
    /**
     * Whether to trust proxy headers (x-forwarded-host/proto) when determining
     * the original host and protocol. Only enable this when running behind a
     * trusted proxy/load balancer that sets these headers.
     * @default false
     */
    trustProxyHeaders?: boolean;
    /**
     * Optional custom handler for invalid domain responses
     * If not provided, returns a default 403 plain text or JSON error response
     * based on if detected as an API endpoint
     */
    invalidDomainHandler?: (request: FastifyRequest, domain: string, isDevelopment: boolean, isAPI: boolean) => InvalidDomainResponse;
}
/**
 * Domain security plugin that handles:
 * - Domain validation and canonical domain redirects
 * - HTTPS enforcement (HTTP to HTTPS redirects)
 * - WWW prefix normalization (add or remove www)
 *
 * This plugin is a no-op in development mode by default.
 */
declare function domainValidation(config: DomainValidationConfig): ServerPlugin;

interface ClientInfo {
    requestID: string;
    correlationID: string | null;
    /** True when request came from SSR layer with trusted forwarded headers */
    isFromSSRServerAPICall: boolean;
    IPAddress: string;
    userAgent: string;
    isIPFromHeader: boolean;
    isUserAgentFromHeader: boolean;
}
declare module 'fastify' {
    interface FastifyRequest {
        /** Optional unique request ID used by response helpers */
        requestID?: string;
        clientInfo?: ClientInfo;
        /** Unix timestamp (ms) when the request was received — set by onRequest hook */
        receivedAt?: number;
    }
}
/**
 * Configuration options for the clientInfo plugin
 */
interface ClientInfoLoggingOptions {
    /** Log each request with its generated request ID. Default: false */
    requestReceived?: boolean;
    /** Log decision/details when trusting forwarded client info. Default: false */
    forwardedClientInfo?: boolean;
    /** Warn when SSR/forwarded headers are present from untrusted source. Default: false */
    rejectedForwardedHeaders?: boolean;
}
interface ClientInfoConfig {
    /** Custom function to generate request IDs. Defaults to ulid() */
    requestIDGenerator?: () => string;
    /** Custom validator for request/correlation IDs. Defaults to ULID validation */
    requestIDValidator?: (id: string) => boolean;
    /** If true, set X-Request-ID and X-Correlation-ID response headers. Default: true */
    setResponseHeaders?: boolean;
    /**
     * Callback that determines whether to accept forwarded client-info headers.
     * Default: returns true when request.clientIP is private. Otherwise forwarded
     * headers are ignored and direct request values are used.
     */
    trustForwardedHeaders?: (request: FastifyRequest) => boolean;
    /** Optional logging configuration */
    logging?: boolean | ClientInfoLoggingOptions;
}
/**
 * Client Info plugin to extract and normalize client information and handle request IDs
 *
 * This middleware:
 * 1. Generates or forwards request IDs
 * 2. Handles client info from both direct requests and SSR-forwarded requests
 * 3. Validates SSR requests come from private IP ranges
 */
declare function clientInfo(config?: ClientInfoConfig): ServerPlugin;

type CookiesConfig = FastifyCookieOptions;
/**
 * Built-in cookies plugin that registers @fastify/cookie and exposes dependency metadata.
 *
 * Usage:
 *   plugins: [cookies({ secret: "your-secret" })]
 *
 * Other plugins can declare a dependency on "cookies" in their PluginMetadata.dependsOn
 * to ensure this plugin is registered first.
 */
declare function cookies(config?: CookiesConfig): ServerPlugin;

/**
 * Minimal stat info interface with only the properties we actually use
 */
interface MinimalStatInfo {
    isFile: boolean;
    size: number;
    mtime: Date;
    mtimeMs: number;
}
/**
 * Options for getFile() method
 */
interface GetFileOptions {
    /** Whether to detect immutable assets for cache control decisions */
    shouldDetectImmutable?: boolean;
    /** Optional ETag from client's If-None-Match header (for 304 optimization) */
    clientETag?: string;
    /** Accepted content encodings from the request for representation selection */
    acceptEncoding?: string | string[];
}
/**
 * Options for creating a read stream (for range requests)
 */
interface CreateStreamOptions {
    /** Start byte position (inclusive) */
    start?: number;
    /** End byte position (inclusive) */
    end?: number;
}
/**
 * Result from serveFile() indicating what action was taken
 */
type ServeFileResult = {
    served: false;
    reason: 'not-found';
} | {
    served: false;
    reason: 'error';
    error: Error;
} | {
    served: true;
    statusCode: 200 | 206 | 304 | 400 | 416;
};
/**
 * File content discriminated union - either buffered in memory or needs streaming
 */
type FileContent = {
    /** Content is buffered in memory (small files) */
    shouldStream: false;
    /** The file content buffer */
    data: Buffer;
} | {
    /** Content needs to be streamed from disk (large files) */
    shouldStream: true;
    /** Factory function to create a read stream with optional range support */
    createStream: (options?: CreateStreamOptions) => fs.ReadStream;
};
/**
 * Internal logger object used by static content helpers.
 */
type StaticContentWarnLoggerObject = {
    warn: (obj: object, msg: string) => void;
};
/**
 * Result when file is not found (404)
 */
interface FileNotFoundResult {
    status: 'not-found';
}
/**
 * Result when an unexpected error occurs (500)
 */
interface FileErrorResult {
    status: 'error';
    error: Error;
}
/**
 * Result when client's ETag matches (304 Not Modified)
 */
interface FileNotModifiedResult {
    status: 'not-modified';
    /** Generated ETag for the selected response representation */
    etag: string;
    /** Last-Modified date as HTTP header string */
    lastModified: string;
    /** Selected content encoding, if a compressed representation was chosen */
    contentEncoding?: 'br' | 'gzip';
    /** Whether the response should include `Vary: Accept-Encoding` */
    varyByAcceptEncoding: boolean;
}
/**
 * Result when file is found and should be served (200)
 */
interface FileFoundResult {
    status: 'ok';
    /** File stats (size, modification time, etc.) */
    stat: MinimalStatInfo;
    /** Generated ETag for the selected response representation */
    etag: string;
    /** Base file ETag before representation-specific encoding suffixes */
    baseETag: string;
    /** Last-Modified date as HTTP header string */
    lastModified: string;
    /** MIME type based on file extension */
    mimeType: string;
    /** File content - either buffered or needs streaming */
    content: FileContent;
    /** Selected content encoding, if a compressed representation was chosen */
    contentEncoding?: 'br' | 'gzip';
    /** Whether the response should include `Vary: Accept-Encoding` */
    varyByAcceptEncoding: boolean;
    /** Whether this file appears to be fingerprinted/immutable (for aggressive caching) */
    isImmutableAsset: boolean;
}
/**
 * Union type for all possible getFile() results
 */
type FileResult = FileNotFoundResult | FileErrorResult | FileNotModifiedResult | FileFoundResult;
/**
 * Encapsulates caching and serving of static content files.
 *
 * This class manages:
 * - Multiple LRU caches (ETag, file content, and file stats)
 * - Configuration for single asset and folder mappings
 * - Optimized file serving with HTTP caching headers
 * - Content-based ETags for small files, weak ETags for large files
 * - Automatic detection of immutable assets (fingerprinted files)
 *
 * Each instance maintains its own independent caches, allowing
 * multiple instances with different configurations.
 */
declare class StaticContentCache {
    private singleAssetMap;
    private folderMap;
    private readonly smallFileMaxSize;
    private readonly cacheControl;
    private readonly immutableCacheControl;
    private readonly negativeCacheTtl;
    private readonly positiveCacheTtl;
    private readonly compression;
    private readonly etagCache;
    private readonly contentCache;
    private readonly compressedVariantCache;
    private readonly statCache;
    private readonly compressedContentIndex;
    private readonly logger?;
    /**
     * Creates a new StaticContentCache instance
     *
     * @param options Static content configuration (file mappings, cache settings, etc.)
     * @param logger Optional logger (e.g., fastify.log) for error logging
     */
    constructor(options: StaticContentRouterOptions, logger?: StaticContentWarnLoggerObject);
    /**
     * Gets file metadata and content with optimized caching
     *
     * This method handles all the core file operations and caching:
     * - File stats caching to avoid repeated filesystem operations
     * - ETag generation and caching (content-based for small files, weak for large files)
     * - Small file content caching in memory for performance
     * - Proper MIME type detection
     * - Immutable asset detection for cache control decisions
     * - Optional short-circuit if client ETag matches (for 304 responses)
     *
     * Useful for both HTTP serving (via serveFile) and programmatic access
     *
     * @param resolvedPath The absolute path to the file
     * @param options Optional configuration for file retrieval
     * @returns Result with status: 'not-found', 'error', 'not-modified', or 'ok'
     */
    getFile(resolvedPath: string, options?: GetFileOptions): Promise<FileResult>;
    /**
     * Serves a static file via HTTP with conditional responses
     *
     * This is a thin HTTP wrapper around getFile() that handles:
     * - HTTP 304 Not Modified responses when client cache is valid (If-None-Match)
     * - HTTP 206 Partial Content responses for range requests
     * - Proper HTTP headers (Cache-Control, ETag, Content-Type, Last-Modified, etc.)
     * - Streaming large files vs sending cached buffers for small files
     *
     * The heavy lifting (file I/O, caching, ETag generation) is done by getFile()
     *
     * @param req The Fastify request object
     * @param reply The Fastify reply object
     * @param resolvedPath The absolute path to the file to be served
     * @param options Optional configuration for file serving
     * @returns Information about whether the file was served and what status code
     */
    serveFile(req: FastifyRequest, reply: FastifyReply, resolvedPath: string, options?: GetFileOptions): Promise<ServeFileResult>;
    /**
     * Replaces routing maps and clears all file caches in one shot.
     *
     * Use this after a full build has completed. Unlike `updateConfig`, this method
     * makes no attempt at smart per-path invalidation — it simply replaces
     * whichever maps you provide and wipes the content, stat, and ETag caches
     * unconditionally, guaranteeing fresh reads for the next request.
     *
     * You may pass `singleAssetMap`, `folderMap`, or both. Omitted sections retain
     * their current routing configuration. Pass an empty object (`{}`) for a
     * section to clear all mappings in that section. All file caches are always
     * cleared, regardless of which sections are provided — even when only
     * `singleAssetMap` is passed, the rebuilt HTML pages likely reference JS/CSS
     * bundles served from `folderMap` directories that were also regenerated in
     * the same build step, so preserving folder caches would risk serving stale
     * assets alongside fresh pages.
     *
     * For targeted cache invalidation (when URL-to-path mappings changed but
     * file contents at those paths are unchanged), use `updateConfig` instead.
     * Note: `updateConfig` does not detect in-place file content changes — it
     * only tracks which filesystem paths entered or left the map.
     *
     * @param newConfig Sections to replace (at least one should be provided)
     *
     * @example
     * ```typescript
     * // After an SSG build completes (page map only):
     * cache.replaceConfig({ singleAssetMap: await loadPageMap() });
     *
     * // After a build that changes both pages and asset folders:
     * cache.replaceConfig({
     *   singleAssetMap: await loadPageMap(),
     *   folderMap: { '/assets/': { path: './dist/assets', detectImmutableAssets: true } },
     * });
     * ```
     */
    replaceConfig(newConfig: {
        singleAssetMap?: Record<string, string>;
        folderMap?: Record<string, string | FolderConfig>;
    }): void;
    /**
     * Evicts a single file's cached content, stat, and ETag without touching
     * any URL-to-path mappings.
     *
     * Use this when you know a specific file changed on disk and want to force
     * a fresh read on the next request — without flushing the entire cache.
     * Works for files served via `singleAssetMap` or `folderMap`.
     *
     * The parameter is the **filesystem path** (as it appears in the cache key),
     * not a URL.
     *
     * For `singleAssetMap` entries these are the absolute paths you
     * provided.
     *
     * For folder-served files the cache key is the absolute path
     * resolved at request time.
     *
     * @param fsPath Absolute filesystem path of the file to evict
     *
     * @example
     * ```typescript
     * // A file watcher detected /dist/about.html was rewritten:
     * cache.invalidateFile('/dist/about.html');
     * ```
     */
    invalidateFile(fsPath: string): void;
    /**
     * Clears all caches (useful for testing or cache invalidation)
     */
    clearCaches(): void;
    /**
     * Gets statistics about cache usage
     */
    getCacheStats(): {
        etag: {
            items: number;
            byteSize: number;
        };
        content: {
            items: number;
            byteSize: number;
        };
        compressedVariants: {
            items: number;
            byteSize: number;
        };
        stat: {
            items: number;
            byteSize: number;
        };
    };
    /**
     * Updates the static content configuration at runtime with targeted cache
     * invalidation — only evicting entries whose URL-to-path mapping changed.
     *
     * Use this when URL routing is changing but file contents at existing paths
     * are unchanged (e.g., adding or removing pages without rebuilding assets).
     * For post-build reloads where file contents may have changed, use
     * `replaceConfig` instead.
     *
     * **Important:** When providing a section, you must provide the COMPLETE mapping for that section.
     * - If you provide `singleAssetMap`, it replaces the entire single asset map
     * - If you provide `folderMap`, it replaces the entire folder map
     * - You can update one section, the other, or both
     * - Omitted sections remain unchanged
     *
     * **Cache invalidation strategy:**
     * - `singleAssetMap` changes: Only invalidates filesystem paths whose URL-to-path
     *   *mapping* changed (added, removed, or pointed to a different file). Paths whose
     *   mapping is unchanged are not evicted — this method has no visibility into whether
     *   the file content on disk changed. If files were rebuilt in-place, use
     *   `replaceConfig` instead.
     * - `folderMap` changes: Clears all caches (folder changes are structural)
     *
     * @param newConfig Complete mapping(s) for the section(s) you want to update
     *
     * @example Update only single asset mappings
     * ```typescript
     * cache.updateConfig({
     *   singleAssetMap: {
     *     '/': './dist/index.html',
     *     '/blog/new-post': './dist/blog/new-post.html'
     *   }
     * });
     * ```
     *
     * @example Update only folder mappings
     * ```typescript
     * cache.updateConfig({
     *   folderMap: {
     *     '/assets': { path: './dist/assets', detectImmutableAssets: true }
     *   }
     * });
     * ```
     *
     * @example Update both sections
     * ```typescript
     * cache.updateConfig({
     *   singleAssetMap: { '/': './dist/index.html' },
     *   folderMap: { '/assets': './dist/assets' }
     * });
     * ```
     */
    updateConfig(newConfig: {
        singleAssetMap?: Record<string, string>;
        folderMap?: Record<string, string | FolderConfig>;
    }): void;
    /**
     * Handles an HTTP request by resolving the URL to a file path and serving it
     *
     * This is a convenience method that combines URL resolution with file serving.
     * If no file matches the URL, it returns without sending a response (lets the hook fall through).
     *
     * @param rawURL The raw request URL (may include query string or hash)
     * @param req The Fastify request object
     * @param reply The Fastify reply object
     * @returns Information about whether a file was served
     */
    handleRequest(rawURL: string, req: FastifyRequest, reply: FastifyReply): Promise<ServeFileResult>;
    /**
     * Normalizes single asset map keys to ensure leading slash
     * Also validates against null bytes to prevent path injection
     */
    private normalizeSingleAssetMap;
    /**
     * Normalizes folder map with proper prefix formatting
     * Also validates against null bytes to prevent path injection
     *
     * Handles two config formats:
     * 1. String shorthand: { "/assets/": "/path/to/assets" }
     * 2. Full config object: { "/assets/": { path: "/path/to/assets", detectImmutableAssets: true } }
     */
    private normalizeFolderMap;
    /**
     * Normalizes URL prefix: ensures leading and trailing slash, collapses multiple slashes
     */
    private normalizePrefix;
    /**
     * Gets the MIME type for a file based on its extension
     */
    private getMimeType;
    /**
     * Compares two FolderConfig objects for equality
     * Dynamically checks all properties so we don't need to update this if FolderConfig changes
     */
    private isSameFolderConfig;
    /**
     * Checks if a file appears to be fingerprinted/immutable based on filename
     *
     * Detects common build tool fingerprinting patterns:
     * - .{hash}.{ext} format (e.g., main.a1b2c3d4.js, styles.CTpDmzGw.css)
     * - -{hash}.{ext} format (e.g., chunk-a1b2c3d4.js, vendor-5f8e9a2b.js)
     *
     * Hash must be at least 6 alphanumeric characters
     *
     * @param filePath The file path to check
     * @returns True if the file appears to be fingerprinted
     */
    private isImmutableAsset;
    private getCompressedCacheKey;
    private invalidateCompressedVariants;
    private handleCompressedVariantCacheChange;
    private untrackCompressedVariantByKey;
    private untrackCompressedVariant;
}

/**
 * Creates a static content serving plugin that can be used with any Unirend server.
 *
 * This plugin serves static files from configured paths with:
 * - Efficient file caching and ETag support for conditional requests
 * - Content-based strong ETags for small files (SHA-256)
 * - Weak ETags for large files (size + mtime based)
 * - LRU caching for stats, content, and ETags
 * - Range request support for large files
 * - Immutable asset detection for fingerprinted files (optional)
 *
 * Multiple instances can be registered with different configurations,
 * allowing you to serve files from different directories with different settings.
 *
 * @example Basic usage - serve uploads folder
 * ```typescript
 * import { staticContent } from 'unirend/plugins';
 *
 * const server = serveSSRDev(paths, {
 *   plugins: [
 *     staticContent({
 *       folderMap: {
 *         '/uploads': './uploads',
 *         '/static': './public/static',
 *       },
 *     }),
 *   ],
 * });
 * ```
 *
 * @example Multiple folders with different settings
 * ```typescript
 * import { staticContent } from 'unirend/plugins';
 *
 * const server = serveSSRProd(buildDir, {
 *   plugins: [
 *     // User uploads - no immutable caching
 *     staticContent({
 *       folderMap: {
 *         '/uploads': { path: './uploads', detectImmutableAssets: false },
 *       },
 *     }),
 *     // Static assets with fingerprinted filenames - immutable caching
 *     staticContent({
 *       folderMap: {
 *         '/static': { path: './public/static', detectImmutableAssets: true },
 *       },
 *     }),
 *   ],
 * });
 * ```
 *
 * @example Custom plugin name for debugging and dependencies
 * ```typescript
 * const server = serveSSRProd(buildDir, {
 *   plugins: [
 *     staticContent({
 *       folderMap: { '/uploads': './uploads' },
 *     }, 'uploads-handler'),
 *   ],
 * });
 * ```
 *
 * @example Use on standalone API server
 * ```typescript
 * import { createAPIServer } from 'unirend/server';
 * import { staticContent } from 'unirend/plugins';
 *
 * const server = createAPIServer({
 *   plugins: [
 *     staticContent({
 *       folderMap: {
 *         '/files': './data/files',
 *       },
 *       singleAssetMap: {
 *         '/favicon.ico': './public/favicon.ico',
 *       },
 *     }),
 *   ],
 * });
 * ```
 *
 * @example Fine-tuned caching settings
 * ```typescript
 * staticContent({
 *   folderMap: { '/assets': './dist/assets' },
 *   smallFileMaxSize: 1024 * 1024, // 1MB - files below this get content-based ETags
 *   cacheEntries: 200, // Max LRU cache entries
 *   contentCacheMaxSize: 100 * 1024 * 1024, // 100MB total content cache
 *   positiveCacheTtl: 3600 * 1000, // 1 hour for found files
 *   negativeCacheTtl: 60 * 1000, // 1 minute for 404s
 *   cacheControl: 'public, max-age=3600', // Custom Cache-Control
 * })
 * ```
 *
 * @example Provide external cache for runtime updates
 * ```typescript
 * import { staticContent, StaticContentCache } from 'unirend/plugins';
 *
 * // Create cache externally for runtime control
 * const cache = new StaticContentCache({
 *   folderMap: { '/pages': './dist/pages' }
 * });
 *
 * const server = serveSSRDev(paths, {
 *   plugins: [
 *     staticContent(cache, 'pages-handler'),
 *   ],
 * });
 *
 * await server.listen(3000);
 *
 * // Later: update mappings dynamically
 * cache.updateConfig({
 *   singleAssetMap: {
 *     '/blog/new-post': './dist/blog/new-post.html'
 *   }
 * });
 * ```
 *
 * @param configOrCache Static content router configuration OR an existing StaticContentCache instance
 * @param name Optional custom name for this plugin instance (useful for debugging and plugin dependencies)
 * @returns A ServerPlugin that can be added to the plugins array
 */
declare function staticContent(configOrCache: StaticContentRouterOptions | StaticContentCache, name?: string): ServerPlugin;

declare const cookieUtils: {
    readonly parse: (cookieHeader: string, opts?: _fastify_cookie.ParseOptions) => {
        [key: string]: string;
    };
    readonly serialize: (name: string, value: string, opts?: _fastify_cookie.SerializeOptions) => string;
    readonly signerFactory: _fastify_cookie.SignerFactory;
    readonly Signer: typeof Signer;
    readonly sign: _fastify_cookie.Sign;
    readonly unsign: _fastify_cookie.Unsign;
};

export { type CORSConfig, type CORSOrigin, type ClientInfoConfig, type CookiesConfig, type DomainValidationConfig, type FolderConfig, type InvalidDomainResponse, type StaticContentRouterOptions, type ValidProductionDomains, clientInfo, cookieUtils, cookies, cors, domainValidation, staticContent };
