/**
 * Standard Server Implementation
 * A simplified, consistent server pattern for all SuperDangerous applications
 * Combines the best of StartupOrchestrator with the simplicity apps need
 */
import express from "express";
import { createServer } from "http";
import cors from "cors";
import { createWebSocketServer } from "../services/websocketServer.js";
import { displayStartupBanner } from "../utils/startupBanner.js";
import { getProcessOnPort } from "./portUtils.js";
import { createLogger, getLogger } from "./index.js";
import { aiErrorHandler } from "../middleware/aiErrorHandler.js";
import { apiErrorHandler } from "./apiResponse.js";
import { createRequestLoggingMiddleware } from "../middleware/requestLogging.js";
import { getAppDataPath, getLogsPath, isDesktopApp, } from "../utils/appPaths.js";
let logger; // Will be initialized when needed
function ensureLogger() {
    if (!logger) {
        logger = createLogger("Server");
    }
    return logger;
}
/**
 * Standard server implementation that all apps should use
 * Handles startup, error handling, and banner display consistently
 */
export class StandardServer {
    app;
    httpServer;
    config;
    wsServer;
    startTime;
    isInitialized = false;
    shuttingDown = false;
    signalsBound = false;
    connections = new Set();
    constructor(config) {
        const environment = process.env.NODE_ENV || "development";
        // Default to localhost for development/test, 0.0.0.0 for production/containerized environments
        const defaultHost = environment === "development" || environment === "test"
            ? "localhost"
            : "0.0.0.0";
        // Auto-enable desktop integration if running in Electron or explicitly enabled
        const enableDesktopIntegration = config.enableDesktopIntegration ?? isDesktopApp();
        this.config = {
            port: 8080,
            host: process.env.HOST || config.host || defaultHost,
            environment,
            enableWebSocket: true,
            bodyLimit: "10mb",
            requestTimeoutMs: 120_000,
            headersTimeoutMs: 65_000,
            keepAliveTimeoutMs: 60_000,
            gracefulShutdownSignals: ["SIGTERM", "SIGINT"],
            enableRequestLogging: true,
            enableDesktopIntegration,
            appId: config.appId ||
                `com.company.${config.appName.toLowerCase().replace(/[^a-z0-9]/g, "-")}`,
            exitOnStartupError: config.exitOnStartupError ?? process.env.NODE_ENV !== "test",
            ...config,
        };
        this.app = express();
        this.httpServer = createServer(this.app);
        this.httpServer.on("connection", (socket) => {
            this.connections.add(socket);
            socket.on("close", () => this.connections.delete(socket));
        });
        this.startTime = Date.now();
        this.bindShutdownSignals();
    }
    /**
     * Get the Express app instance for middleware setup
     */
    getApp() {
        return this.app;
    }
    /**
     * Get the HTTP server instance
     */
    getServer() {
        return this.httpServer;
    }
    /**
     * Initialize the server (setup middleware, routes, etc.)
     */
    async initialize() {
        if (this.isInitialized)
            return;
        this.isInitialized = true;
        try {
            // Setup desktop-specific paths, CORS, and logging if running as desktop app
            if (this.config.enableDesktopIntegration) {
                await this.setupDesktopIntegration();
            }
            // Initialize the logger first to ensure file output works
            const logger = getLogger;
            if (logger &&
                typeof logger.isInitialized === "function" &&
                !logger.isInitialized()) {
                // Use proper logs directory for desktop apps
                const logsDir = this.config.enableDesktopIntegration
                    ? getLogsPath(this.config.appId, this.config.appName)
                    : "./data/logs";
                await logger.initialize({
                    appName: this.config.appName,
                    logLevel: this.config.logging?.level || process.env.LOG_LEVEL || "info",
                    consoleOutput: true,
                    fileOutput: true,
                    logsDir,
                });
            }
            // Setup default middleware
            this.setupDefaultMiddleware();
            // Initialize WebSocket if enabled (before onInitialize so it can be passed)
            if (this.config.enableWebSocket) {
                this.wsServer = await Promise.resolve(createWebSocketServer(this.httpServer));
            }
            // Call custom initialization if provided, passing the WebSocket server
            if (this.config.onInitialize) {
                const wantsWebSocket = this.config.onInitialize.length >= 2;
                if (wantsWebSocket) {
                    await this.config.onInitialize(this.app, this.wsServer);
                }
                else {
                    await this.config.onInitialize(this.app);
                }
            }
            // Setup error handlers (should be last)
            this.setupErrorHandlers();
        }
        catch (_error) {
            ensureLogger().error("Server initialization failed:", _error);
            this.isInitialized = false;
            throw _error;
        }
    }
    /**
     * Setup desktop app integration (CORS, data paths, logging, etc.)
     */
    async setupDesktopIntegration() {
        ensureLogger().info("Setting up desktop app integration", {
            appId: this.config.appId,
            isDesktopApp: isDesktopApp(),
            dataPath: this.config.desktopDataPath ||
                getAppDataPath(this.config.appId, this.config.appName),
        });
        // Initialize logging for desktop apps
        const logger = getLogger;
        if (!logger.isInitialized()) {
            const logsDir = getLogsPath(this.config.appId, this.config.appName);
            await logger.initialize({
                appName: this.config.appName,
                logLevel: process.env.LOG_LEVEL || "info",
                consoleOutput: true,
                fileOutput: true,
                logsDir,
            });
            ensureLogger().info("Logging initialized for desktop app", { logsDir });
        }
        // Setup CORS for desktop apps
        const corsOrigins = [...(this.config.corsOrigins || [])];
        // Add localhost origins based on webPort if specified
        if (this.config.webPort) {
            corsOrigins.push(`http://localhost:${this.config.webPort}`, `http://localhost:${this.config.webPort + 1}`);
        }
        // Add Electron origins when running in desktop mode
        if (isDesktopApp()) {
            // Electron file:// protocol sends null or file:// origin
            corsOrigins.push("file://", "null");
        }
        this.app.use(cors({
            origin: corsOrigins,
            credentials: true,
        }));
    }
    /**
     * Setup default middleware for all applications
     */
    setupDefaultMiddleware() {
        // Basic middleware that should be present in all apps
        this.app.use(express.json({ limit: this.config.bodyLimit }));
        this.app.use(express.urlencoded({ extended: true, limit: this.config.bodyLimit }));
        // CORS setup for non-desktop apps (desktop setup happens in setupDesktopIntegration)
        if (!this.config.enableDesktopIntegration) {
            const corsOrigins = [...(this.config.corsOrigins || [])];
            // Add localhost origins based on webPort if specified
            if (this.config.webPort) {
                corsOrigins.push(`http://localhost:${this.config.webPort}`, `http://localhost:${this.config.webPort + 1}`);
            }
            if (corsOrigins.length > 0) {
                this.app.use(cors({
                    origin: corsOrigins,
                    credentials: true,
                }));
            }
            else {
                this.app.use(cors());
            }
        }
        // Request logging (can be disabled)
        if (this.config.enableRequestLogging) {
            this.app.use(createRequestLoggingMiddleware({
                logger: ensureLogger(),
                ...this.config.requestLogging,
            }));
        }
        // Trust proxy to respect X-Forwarded-* headers in containerized/proxy envs
        const trustProxy = this.config.trustProxy ??
            (this.config.environment !== "development" ? true : false);
        if (typeof this.app.set === "function") {
            this.app.set("trust proxy", trustProxy);
        }
    }
    /**
     * Setup error handlers (should be last in middleware chain)
     */
    setupErrorHandlers() {
        // AI error handler for AI service errors
        this.app.use(aiErrorHandler);
        // Standardized API error handler
        this.app.use(apiErrorHandler);
    }
    /**
     * Start the server and listen on the configured port
     */
    async start() {
        if (!this.isInitialized) {
            throw new Error("Server not initialized. Call initialize() first.");
        }
        const port = this.config.port;
        const host = this.config.host;
        const exitOnError = this.config.exitOnStartupError !== false;
        // Check if API port is available (skip for port 0 which means "pick any available")
        if (port !== 0) {
            const processOnPort = await getProcessOnPort(port);
            if (processOnPort) {
                const message = `Port ${port} is already in use`;
                if (exitOnError) {
                    ensureLogger().error(message);
                    process.exit(1);
                }
                else {
                    ensureLogger().warn(`${message} (PID ${processOnPort.pid} - ${processOnPort.command})`);
                    return;
                }
            }
        }
        return new Promise((resolve, reject) => {
            const fail = (error) => {
                ensureLogger().error("Server error:", error);
                if (exitOnError) {
                    process.exit(1);
                }
                reject(error);
            };
            // Handle server errors
            this.httpServer.on("error", (error) => {
                if (error.code === "EADDRINUSE") {
                    const message = `Port ${port} is already in use`;
                    ensureLogger().error(message);
                    if (exitOnError) {
                        process.exit(1);
                    }
                    reject(new Error(message));
                    return;
                }
                else if (error.code === "EACCES") {
                    const message = `Port ${port} requires elevated privileges`;
                    ensureLogger().error(message);
                    if (exitOnError) {
                        process.exit(1);
                    }
                    reject(new Error(message));
                    return;
                }
                else {
                    fail(error instanceof Error ? error : new Error(String(error)));
                    return;
                }
            });
            // Start listening
            // Apply safer timeouts for production workloads
            this.httpServer.requestTimeout = this.config.requestTimeoutMs;
            this.httpServer.headersTimeout = this.config.headersTimeoutMs;
            this.httpServer.keepAliveTimeout = this.config.keepAliveTimeoutMs;
            try {
                this.httpServer.listen(port, host, async () => {
                    // Display banner only after successful binding
                    // Note: In production, webPort is typically not used because the API server
                    // serves the UI assets directly from the main port. However, if webPort
                    // is explicitly configured, respect it.
                    // Only display banner if not suppressed
                    if (process.env.SUPPRESS_STARTUP_BANNER !== "true") {
                        displayStartupBanner({
                            appName: this.config.appName,
                            appVersion: this.config.appVersion,
                            description: this.config.description,
                            port: this.config.port,
                            webPort: this.config.webPort, // Pass the actual configured webPort
                            environment: this.config.environment,
                            startTime: this.startTime,
                        });
                    }
                    // Call custom start handler if provided
                    if (this.config.onStart) {
                        try {
                            await this.config.onStart();
                        }
                        catch (_error) {
                            const error = _error instanceof Error ? _error : new Error(String(_error));
                            ensureLogger().error("Custom start handler failed:", error);
                            if (exitOnError) {
                                process.exit(1);
                            }
                            reject(error);
                            return;
                        }
                    }
                    if (this.config.webPort) {
                        ensureLogger().info(`Web UI Port: ${this.config.webPort}`);
                    }
                    // Bind graceful shutdown signals once per instance
                    this.bindShutdownSignals();
                    resolve();
                });
            }
            catch (err) {
                const error = err instanceof Error ? err : new Error(String(err));
                ensureLogger().error("Failed to start server:", error);
                if (exitOnError) {
                    process.exit(1);
                }
                reject(error);
            }
        });
    }
    /**
     * Get the data path for desktop apps (platform-specific)
     */
    getDataPath() {
        if (this.config.enableDesktopIntegration) {
            return (this.config.desktopDataPath ||
                getAppDataPath(this.config.appId, this.config.appName));
        }
        return "./data";
    }
    /**
     * Check if running as desktop app
     */
    isDesktopApp() {
        return this.config.enableDesktopIntegration || false;
    }
    /**
     * Attach signal listeners for graceful shutdown
     */
    bindShutdownSignals() {
        if (this.signalsBound)
            return;
        if (!this.config.gracefulShutdownSignals?.length)
            return;
        const signals = this.config.gracefulShutdownSignals;
        if (process.setMaxListeners) {
            const current = process.getMaxListeners ? process.getMaxListeners() : 10;
            process.setMaxListeners(Math.max(current, signals.length + 20));
        }
        signals.forEach((signal) => {
            process.on(signal, async () => {
                ensureLogger().info(`Received ${signal}, shutting down gracefully...`);
                try {
                    await this.stop();
                    process.exit(0);
                }
                catch (error) {
                    ensureLogger().error("Graceful shutdown failed", error);
                    process.exit(1);
                }
            });
        });
        this.signalsBound = true;
    }
    /**
     * Stop the server gracefully
     */
    async stop() {
        if (this.shuttingDown)
            return Promise.resolve();
        this.shuttingDown = true;
        return new Promise((resolve, reject) => {
            const timeout = setTimeout(() => {
                ensureLogger().warn("Force closing server after timeout");
                this.forceCloseConnections();
                resolve();
            }, 10_000);
            const close = () => {
                clearTimeout(timeout);
                ensureLogger().info("Server stopped gracefully");
                resolve();
            };
            try {
                if (this.wsServer?.shutdown) {
                    this.wsServer.shutdown();
                }
                if (this.wsServer?.close) {
                    this.wsServer.close();
                }
                this.httpServer.close((err) => {
                    if (err) {
                        ensureLogger().error("Error while stopping server", err);
                        reject(err);
                        return;
                    }
                    this.forceCloseConnections();
                    close();
                });
            }
            catch (error) {
                clearTimeout(timeout);
                reject(error);
            }
        });
    }
    forceCloseConnections() {
        // Prefer native closeAllConnections if available
        if (typeof this.httpServer.closeAllConnections === "function") {
            try {
                this.httpServer.closeAllConnections();
            }
            catch (err) {
                ensureLogger().warn("closeAllConnections failed", err);
            }
        }
        this.connections.forEach((socket) => {
            try {
                socket.destroy();
            }
            catch {
                // ignore
            }
        });
        this.connections.clear();
    }
}
/**
 * Convenience function to create and start a standard server
 */
export async function createStandardServer(config) {
    const server = new StandardServer(config);
    await server.initialize();
    await server.start();
    return server;
}
//# sourceMappingURL=StandardServer.js.map