/**
 * @fileoverview Node.js HTTP server bootstrap for the MCP transport.
 * Splits the Node-only `@hono/node-server` `serve()` start path and `node:http`
 * port-probe out of `httpTransport.ts` so the Worker bundle (which only needs
 * `createHttpApp`) can drop both via tree-shaking.
 * @module src/mcp-server/transports/http/httpServer
 */
import http from 'node:http';
import { serve } from '@hono/node-server';
import { config } from '../../../config/index.js';
import { createHttpApp } from '../../../mcp-server/transports/http/httpTransport.js';
import { logger } from '../../../utils/internal/logger.js';
import { logStartupBanner } from '../../../utils/internal/startupBanner.js';
function isPortInUse(port, host, parentContext) {
    const context = { ...parentContext, operation: 'isPortInUse', port, host };
    logger.debug(`Checking if port ${port} is in use...`, context);
    return new Promise((resolve) => {
        const tempServer = http.createServer();
        tempServer
            .once('error', (err) => resolve(err.code === 'EADDRINUSE'))
            .once('listening', () => tempServer.close(() => resolve(false)))
            .listen(port, host);
    });
}
function startHttpServerWithRetry(app, initialPort, host, maxRetries, parentContext) {
    const startContext = {
        ...parentContext,
        operation: 'startHttpServerWithRetry',
    };
    logger.info(`Attempting to start HTTP server on port ${initialPort} with ${maxRetries} retries.`, startContext);
    const { promise, resolve, reject } = Promise.withResolvers();
    const tryBind = (port, attempt) => {
        if (attempt > maxRetries + 1) {
            const error = new Error(`Failed to bind to any port after ${maxRetries} retries.`);
            logger.fatal(error.message, { ...startContext, port, attempt });
            return reject(error);
        }
        isPortInUse(port, host, { ...startContext, port, attempt })
            .then((inUse) => {
            if (inUse) {
                logger.warning(`Port ${port} is in use, retrying...`, {
                    ...startContext,
                    port,
                    attempt,
                });
                setTimeout(() => tryBind(port + 1, attempt + 1), config.mcpHttpPortRetryDelayMs);
                return;
            }
            try {
                const serverInstance = serve({ fetch: app.fetch, port, hostname: host }, (info) => {
                    const serverAddress = `http://${info.address}:${info.port}${config.mcpHttpEndpointPath}`;
                    logger.info(`HTTP transport listening at ${serverAddress}`, {
                        ...startContext,
                        port,
                        address: serverAddress,
                    });
                    logStartupBanner(`\n🚀 MCP Server running at: ${serverAddress}`, 'http');
                });
                resolve(serverInstance);
            }
            catch (err) {
                logger.warning(`Binding attempt failed for port ${port}, retrying...`, {
                    ...startContext,
                    port,
                    attempt,
                    error: String(err),
                });
                setTimeout(() => tryBind(port + 1, attempt + 1), config.mcpHttpPortRetryDelayMs);
            }
        })
            .catch((err) => reject(err instanceof Error ? err : new Error(String(err))));
    };
    tryBind(initialPort, 1);
    return promise;
}
export async function startHttpTransport(serverFactory, parentContext, manifest) {
    const transportContext = {
        ...parentContext,
        component: 'HttpTransportStart',
    };
    logger.info('Starting HTTP transport.', transportContext);
    const { app, sessionStore } = await createHttpApp(serverFactory, transportContext, manifest);
    const server = await startHttpServerWithRetry(app, config.mcpHttpPort, config.mcpHttpHost, config.mcpHttpMaxPortRetries, transportContext);
    logger.info('HTTP transport started successfully.', transportContext);
    return {
        server,
        stop: (ctx) => stopHttpTransport(server, sessionStore, ctx),
    };
}
/** Max time (ms) to wait for in-flight connections (e.g. SSE streams) to drain. */
const DRAIN_TIMEOUT_MS = 5_000;
function stopHttpTransport(server, sessionStore, parentContext) {
    const operationContext = {
        ...parentContext,
        operation: 'stopHttpTransport',
        transportType: 'Http',
    };
    logger.info('Attempting to stop http transport...', operationContext);
    sessionStore?.destroy();
    return new Promise((resolve, reject) => {
        // Force-close all connections (including pre-existing SSE streams) after a
        // grace period. server.closeAllConnections() (Node 18.2+) covers sockets
        // that were already alive before server.close() — unlike the `connection`
        // event which only fires for new arrivals.
        const drainTimer = setTimeout(() => {
            logger.warning('Drain timeout reached — force-closing all connections.', operationContext);
            server.closeAllConnections();
        }, DRAIN_TIMEOUT_MS);
        drainTimer.unref();
        server.close((err) => {
            clearTimeout(drainTimer);
            if (err) {
                logger.error('Error closing HTTP server.', err, operationContext);
                return reject(err);
            }
            logger.info('HTTP server closed successfully.', operationContext);
            resolve();
        });
    });
}
//# sourceMappingURL=httpServer.js.map