"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.startServer = startServer;
const runtime_1 = require("./apps/runtime");
const dialog_display_state_1 = require("./dialog-display-state");
const kernel_driver_1 = require("./llm/kernel-driver");
const log_1 = require("./log");
const supervisor_1 = require("./mcp/supervisor");
const open_generation_recovery_1 = require("./recovery/open-generation-recovery");
const reply_delivery_recovery_1 = require("./recovery/reply-delivery-recovery");
const rtws_1 = require("./rtws");
const work_language_1 = require("./runtime/work-language");
const auth_1 = require("./server/auth");
const certificates_1 = require("./server/certificates");
const dominds_self_update_1 = require("./server/dominds-self-update");
const network_hosts_1 = require("./server/network-hosts");
const port_selection_1 = require("./server/port-selection");
const server_core_1 = require("./server/server-core");
const websocket_handler_1 = require("./server/websocket-handler");
require("./tools/builtins");
const log = (0, log_1.createLogger)('server');
// Setup unhandled rejection handler to capture crashes
process.on('unhandledRejection', (reason, promise) => {
    log.error('Unhandled Rejection at:', undefined, { promise });
    log.error('Reason:', reason instanceof Error ? reason : new Error(String(reason)));
    log.error('Stack trace:', undefined, (reason instanceof Error && reason.stack) || 'No stack trace available');
    // Optionally, we could exit the process here with code 1
    // But in development, we might want to keep running
});
// Setup uncaught exception handler
process.on('uncaughtException', (error) => {
    log.error('Uncaught Exception:', error);
    log.error('Stack trace:', undefined, error.stack);
    // Optionally, exit with code 1
    // process.exit(1);
});
function attachPostListenStartupCancellation(httpServer, token) {
    const originalStop = httpServer.stop.bind(httpServer);
    httpServer.stop = async () => {
        token.canceled = true;
        await originalStop();
    };
    return httpServer;
}
function getErrnoCode(error) {
    if (!(error instanceof Error))
        return undefined;
    const withCode = error;
    return typeof withCode.code === 'string' ? withCode.code : undefined;
}
function buildAdjacentHttpsPortCandidates(params) {
    if (params.preferredPort < 1 || params.preferredPort > 65535)
        return [];
    return (0, port_selection_1.buildWebuiPortCandidates)({
        preferredPort: params.preferredPort,
        strictPort: false,
        direction: params.direction,
    });
}
async function runPostListenStartup(params) {
    await new Promise((resolve) => {
        setImmediate(resolve);
    });
    if (params.token.canceled)
        return;
    // Apps host is optional for server boot: app failures must stay loud, but they must not block WebUI startup.
    try {
        await (0, runtime_1.initAppsRuntime)({ rtwsRootAbs: params.rtwsRootAbs, kernel: params.kernel });
    }
    catch (error) {
        if (params.token.canceled)
            return;
        log.warn('Apps runtime initialization failed during server startup; continuing without app runtime capabilities until the app issue is fixed', error instanceof Error ? error : new Error(String(error)));
    }
    if (params.token.canceled)
        return;
    // Crash recovery: persisted in-flight generations are re-queued; stale non-generating queues
    // are surfaced as blocked/resumable from durable facts.
    await (0, dialog_display_state_1.reconcileDisplayStatesAfterRestart)();
    if (params.token.canceled)
        return;
    await (0, reply_delivery_recovery_1.recoverPendingReplyDeliveryAfterRestart)();
    if (params.token.canceled)
        return;
    await (0, open_generation_recovery_1.recoverOpenGenerationAfterRestart)();
    if (params.token.canceled)
        return;
    // Tests may opt out so the process can shut down cleanly without a driver stop API.
    if (params.startBackendDriver) {
        void (0, kernel_driver_1.runBackendDriver)();
    }
}
async function startServer(opts = {}) {
    const { language: resolvedLanguage, source } = (0, work_language_1.resolveWorkLanguage)({ env: process.env });
    (0, work_language_1.setWorkLanguage)(resolvedLanguage);
    // Get port and host from options
    const mode = opts.mode || 'prod';
    const preferredPort = opts.port ?? port_selection_1.DEFAULT_WEBUI_PORT;
    const strictPort = opts.strictPort ?? opts.port !== undefined;
    const portAutoDirection = opts.portAutoDirection ?? 'down';
    const host = opts.host || '127.0.0.1';
    const startBackendDriver = opts.startBackendDriver ?? true;
    const returnAfterListen = opts.returnAfterListen === true;
    const portCandidates = (0, port_selection_1.buildWebuiPortCandidates)({
        preferredPort,
        strictPort,
        direction: portAutoDirection,
    });
    // WebSocket clients set
    const clients = new Set();
    const serverMode = mode === 'dev' ? 'development' : 'production';
    const auth = (0, auth_1.computeAuthConfig)({
        mode: serverMode,
        env: process.env,
    });
    const certificateLookup = await (0, certificates_1.findAutoHttpsCertificateForHost)({
        host,
        certsDirAbs: opts.certsDirAbs,
    });
    for (const diagnostic of certificateLookup.diagnostics) {
        log.warn(diagnostic.message);
    }
    const tls = certificateLookup.kind === 'found' && !strictPort ? certificateLookup.certificate : undefined;
    const urlHost = (0, network_hosts_1.resolveHttpUrlHostForBindHost)(host);
    log.debug(`Starting server in ${mode} mode on ${(0, auth_1.formatServerOrigin)({
        scheme: 'http',
        host: urlHost,
        port: preferredPort,
    })} (bind ${host}; ${strictPort ? 'strict port' : `auto port ${portAutoDirection}`}; working language: ${(0, work_language_1.getWorkLanguage)()} from ${source})`);
    if (certificateLookup.kind === 'found' && strictPort) {
        log.debug(`HTTPS certificate found but Dominds HTTPS is disabled for strict --port; assuming HTTPS, if needed, is handled by a front proxy (${certificateLookup.certificate.certPath})`);
    }
    else if (tls !== undefined) {
        log.debug(`HTTPS certificate available: ${tls.certPath} (matched host ${tls.matchedHost})`);
    }
    let startedCore = null;
    let boundPort = null;
    for (const candidatePort of portCandidates) {
        const config = {
            mode: serverMode,
            staticRoot: 'webapp/dist',
            host,
            port: candidatePort,
            clients,
            auth,
        };
        const candidateServer = (0, server_core_1.createHttpServer)(config);
        try {
            boundPort = await candidateServer.start();
            startedCore = candidateServer;
            break;
        }
        catch (error) {
            if (!strictPort && getErrnoCode(error) === 'EADDRINUSE') {
                const nextDirection = portAutoDirection === 'down' ? 'lower' : 'higher';
                log.warn(`WebUI port ${candidatePort} is already in use; trying the next ${nextDirection} port`);
                continue;
            }
            throw error;
        }
    }
    if (startedCore === null || boundPort === null) {
        const boundText = (0, port_selection_1.formatWebuiPortScanBound)({
            preferredPort,
            direction: portAutoDirection,
        });
        throw new Error(`Failed to start WebUI: no available port found from ${preferredPort} ${boundText}`);
    }
    if (!strictPort && boundPort !== preferredPort) {
        log.warn(`WebUI preferred port ${preferredPort} was unavailable; listening on ${boundPort}`);
    }
    let httpsServer;
    let httpsPort;
    let httpsUrlHost;
    if (tls !== undefined) {
        const preferredHttpsPort = portAutoDirection === 'up' ? boundPort + 1 : boundPort - 1;
        const httpsPortCandidates = buildAdjacentHttpsPortCandidates({
            preferredPort: preferredHttpsPort,
            direction: portAutoDirection,
        });
        for (const candidatePort of httpsPortCandidates) {
            const config = {
                mode: serverMode,
                staticRoot: 'webapp/dist',
                host,
                port: candidatePort,
                tls,
                clients,
                auth,
            };
            const candidateServer = (0, server_core_1.createHttpServer)(config);
            try {
                httpsPort = await candidateServer.start();
                httpsServer = candidateServer;
                httpsUrlHost = tls.matchedHost;
                break;
            }
            catch (error) {
                if (getErrnoCode(error) === 'EADDRINUSE') {
                    const nextDirection = portAutoDirection === 'down' ? 'lower' : 'higher';
                    log.warn(`WebUI HTTPS port ${candidatePort} is already in use; trying the next ${nextDirection} port`);
                    continue;
                }
                await startedCore.stop();
                throw error;
            }
        }
        if (httpsServer === undefined || httpsPort === undefined || httpsUrlHost === undefined) {
            await startedCore.stop();
            throw new Error(`Failed to start WebUI HTTPS: no available adjacent port found from ${preferredHttpsPort}`);
        }
        log.debug(`HTTPS WebUI ready: ${(0, auth_1.formatServerOrigin)({
            scheme: 'https',
            host: httpsUrlHost,
            port: httpsPort,
        })}`);
    }
    (0, websocket_handler_1.setupWebSocketServer)(httpsServer === undefined
        ? startedCore.getHttpServer()
        : [startedCore.getHttpServer(), httpsServer.getHttpServer()], clients, auth, (0, work_language_1.getWorkLanguage)(), serverMode);
    const postListenStartupToken = { canceled: false };
    const httpServer = attachPostListenStartupCancellation(startedCore, postListenStartupToken);
    if (httpsServer !== undefined) {
        const originalStop = httpServer.stop.bind(httpServer);
        const httpsServerToStop = httpsServer;
        httpServer.stop = async () => {
            postListenStartupToken.canceled = true;
            await httpsServerToStop.stop();
            await originalStop();
        };
    }
    try {
        const rtwsRootAbs = (0, rtws_1.domindsRtwsRootAbs)();
        (0, dominds_self_update_1.configureDomindsSelfUpdate)({
            host,
            port: boundPort,
            mode: serverMode,
            closeWebSocketClients: () => {
                if (clients.size === 0)
                    return;
                log.info(`Closing ${clients.size} WebSocket client(s) for Dominds restart`);
                for (const ws of clients) {
                    try {
                        ws.close(1012, 'server_restart');
                    }
                    catch (error) {
                        log.warn('Failed to close WebSocket client for Dominds restart', error);
                    }
                }
            },
            stopServer: async () => {
                await httpServer.stop();
            },
        });
        // MCP is best-effort: startup must not be blocked by MCP config/server issues.
        try {
            (0, supervisor_1.startMcpSupervisor)();
        }
        catch (error) {
            log.warn('MCP supervisor startup failed during server startup; continuing without MCP runtime capabilities until the MCP issue is fixed', error instanceof Error ? error : new Error(String(error)));
        }
        const postListenStartup = runPostListenStartup({
            rtwsRootAbs,
            kernel: { scheme: 'http', host: urlHost, port: boundPort },
            startBackendDriver,
            token: postListenStartupToken,
        });
        if (returnAfterListen) {
            void postListenStartup.catch((error) => {
                log.error('Post-listen server startup failed; WebUI remains reachable, but runtime recovery/driver startup did not complete', error instanceof Error ? error : new Error(String(error)));
            });
        }
        else {
            await postListenStartup;
        }
    }
    catch (error) {
        if (!returnAfterListen) {
            await httpServer.stop();
            await (0, runtime_1.shutdownAppsRuntime)();
        }
        throw error;
    }
    return {
        httpServer,
        httpsServer,
        auth,
        host,
        urlHost,
        port: boundPort,
        httpsPort,
        httpsUrlHost,
        mode,
    };
}
