/**
 * Graceful Shutdown Manager
 *
 * Provides clean application shutdown with:
 * - Session state preservation
 * - Pending operation completion (waits for in-progress operations)
 * - Connection cleanup (MCP, Database)
 * - Log flushing
 * - Configurable timeout (default 30s) before force exit
 *
 * Intercepts signals: SIGINT (Ctrl+C), SIGTERM (kill), SIGHUP (terminal closed)
 */
import { logger, getLogger } from './logger.js';
import { DisposableManager, registerDisposable } from './disposable.js';
const DEFAULT_OPTIONS = {
    timeoutMs: 30000, // 30 seconds max before force exit
    forceExitOnTimeout: true,
    showProgress: true,
};
/**
 * Graceful Shutdown Manager
 *
 * Coordinates clean shutdown of all application components
 */
export class GracefulShutdownManager {
    static instance = null;
    handlers = [];
    isShuttingDown = false;
    shutdownPromise = null;
    options;
    signalsRegistered = false;
    pendingOperations = new Map();
    completedHandlers = new Set();
    constructor(options = {}) {
        this.options = { ...DEFAULT_OPTIONS, ...options };
    }
    /**
     * Get singleton instance
     */
    static getInstance(options) {
        if (!GracefulShutdownManager.instance) {
            GracefulShutdownManager.instance = new GracefulShutdownManager(options);
            // Register self as disposable for cleanup
            registerDisposable(GracefulShutdownManager.instance);
        }
        return GracefulShutdownManager.instance;
    }
    /**
     * Register process signal handlers
     */
    registerSignalHandlers() {
        if (this.signalsRegistered) {
            return;
        }
        // SIGINT: Ctrl+C
        process.on('SIGINT', () => this.handleSignal('SIGINT'));
        // SIGTERM: Kill command
        process.on('SIGTERM', () => this.handleSignal('SIGTERM'));
        // SIGHUP: Terminal closed
        process.on('SIGHUP', () => this.handleSignal('SIGHUP'));
        this.signalsRegistered = true;
        logger.debug('Signal handlers registered for graceful shutdown');
    }
    /**
     * Handle incoming signal
     */
    async handleSignal(signal) {
        logger.info(`Received ${signal}, initiating graceful shutdown...`);
        // Show user-friendly message
        logger.info(`Received ${signal}. Shutting down gracefully...`);
        try {
            await this.shutdown({ exitCode: 0 });
        }
        catch (error) {
            logger.error('Error during shutdown', error instanceof Error ? error : new Error(String(error)));
            process.exit(1);
        }
    }
    /**
     * Register a shutdown handler
     */
    registerHandler(handler) {
        this.handlers.push(handler);
        // Sort by priority (higher first)
        this.handlers.sort((a, b) => b.priority - a.priority);
        logger.debug(`Registered shutdown handler: ${handler.name} (priority: ${handler.priority})`);
    }
    /**
     * Unregister a shutdown handler by name
     */
    unregisterHandler(name) {
        const index = this.handlers.findIndex(h => h.name === name);
        if (index !== -1) {
            this.handlers.splice(index, 1);
            return true;
        }
        return false;
    }
    /**
     * Track a pending operation (blocks shutdown until complete)
     * Returns a function to mark the operation as complete
     */
    trackOperation(id, description) {
        this.pendingOperations.set(id, {
            description,
            startTime: Date.now(),
        });
        logger.debug(`Tracking operation: ${id} - ${description}`);
        return () => {
            this.pendingOperations.delete(id);
            logger.debug(`Operation completed: ${id}`);
        };
    }
    /**
     * Check if there are pending operations
     */
    hasPendingOperations() {
        return this.pendingOperations.size > 0;
    }
    /**
     * Get list of pending operations
     */
    getPendingOperations() {
        const now = Date.now();
        return Array.from(this.pendingOperations.entries()).map(([id, op]) => ({
            id,
            description: op.description,
            duration: now - op.startTime,
        }));
    }
    /**
     * Execute shutdown with timeout
     */
    async shutdown(overrideOptions) {
        // Prevent multiple concurrent shutdowns
        if (this.isShuttingDown) {
            logger.debug('Shutdown already in progress, waiting...');
            return this.shutdownPromise;
        }
        this.isShuttingDown = true;
        const options = { ...this.options, ...overrideOptions };
        this.shutdownPromise = this.executeShutdown(options);
        return this.shutdownPromise;
    }
    /**
     * Execute the actual shutdown sequence
     */
    async executeShutdown(options) {
        const startTime = Date.now();
        this.completedHandlers.clear();
        logger.info('Starting graceful shutdown sequence...', {
            handlerCount: this.handlers.length,
            pendingOperations: this.pendingOperations.size,
            timeoutMs: options.timeoutMs,
        });
        // Wait for pending operations first (with partial timeout)
        const operationTimeout = Math.min(options.timeoutMs / 3, 10000); // Max 10s for operations
        if (this.pendingOperations.size > 0) {
            if (options.showProgress) {
                logger.info(`Waiting for ${this.pendingOperations.size} pending operation(s)...`);
                this.getPendingOperations().forEach(op => {
                    logger.info(`  - ${op.description} (${Math.round(op.duration / 1000)}s)`);
                });
            }
            await this.waitForPendingOperations(operationTimeout);
        }
        // Create timeout promise for handlers
        const remainingTime = options.timeoutMs - (Date.now() - startTime);
        const timeoutPromise = new Promise((resolve) => {
            setTimeout(() => resolve('timeout'), Math.max(remainingTime, 1000));
        });
        // Create shutdown work promise
        const shutdownWork = this.runShutdownHandlers(options.showProgress);
        // Race between shutdown and timeout
        const result = await Promise.race([
            shutdownWork.then(() => 'completed'),
            timeoutPromise,
        ]);
        const elapsed = Date.now() - startTime;
        if (result === 'timeout') {
            const pendingOps = this.getPendingOperations();
            const pendingHandlers = this.handlers.filter(h => !this.completedHandlers.has(h.name));
            logger.warn(`Shutdown timed out after ${options.timeoutMs}ms`, {
                completedHandlers: this.completedHandlers.size,
                totalHandlers: this.handlers.length,
                pendingOperations: pendingOps.length,
            });
            logger.warn(`Shutdown timed out after ${options.timeoutMs / 1000}s. Forcing exit...`);
            if (pendingOps.length > 0) {
                logger.warn('Pending operations that were interrupted:');
                pendingOps.forEach(op => logger.warn(`  - ${op.description}`));
            }
            if (pendingHandlers.length > 0) {
                logger.warn('Handlers that did not complete:');
                pendingHandlers.forEach(h => logger.warn(`  - ${h.name}`));
            }
            if (options.forceExitOnTimeout) {
                process.exit(options.exitCode ?? 1);
            }
        }
        else {
            logger.info(`Graceful shutdown completed in ${elapsed}ms`);
            if (options.showProgress) {
                logger.info(`Shutdown completed in ${elapsed}ms.`);
            }
            // Flush logs before exit
            await this.flushLogs();
            process.exit(options.exitCode ?? 0);
        }
    }
    /**
     * Wait for pending operations to complete
     */
    async waitForPendingOperations(timeoutMs) {
        const startTime = Date.now();
        while (this.pendingOperations.size > 0) {
            if (Date.now() - startTime > timeoutMs) {
                logger.warn('Timeout waiting for pending operations', {
                    remaining: this.pendingOperations.size,
                });
                break;
            }
            // Wait a bit and check again
            await new Promise(resolve => setTimeout(resolve, 100));
        }
    }
    /**
     * Run all shutdown handlers in priority order
     */
    async runShutdownHandlers(showProgress = false) {
        // First, run custom handlers
        for (const handler of this.handlers) {
            try {
                if (showProgress) {
                    process.stdout.write(`  [shutdown] ${handler.name}...`);
                }
                logger.debug(`Running shutdown handler: ${handler.name}`);
                const result = handler.handler();
                if (result instanceof Promise) {
                    await result;
                }
                this.completedHandlers.add(handler.name);
                if (showProgress) {
                    process.stdout.write(' done\n');
                }
                logger.debug(`Completed shutdown handler: ${handler.name}`);
            }
            catch (error) {
                this.completedHandlers.add(handler.name); // Mark as completed even on error
                if (showProgress) {
                    process.stdout.write(' failed\n');
                }
                logger.error(`Error in shutdown handler ${handler.name}:`, error instanceof Error ? error : new Error(String(error)));
                // Continue with other handlers even if one fails
            }
        }
        // Then, dispose all registered disposables
        try {
            if (showProgress) {
                process.stdout.write('  [shutdown] disposing resources...');
            }
            logger.debug('Disposing all registered resources...');
            await DisposableManager.getInstance().disposeAll();
            if (showProgress) {
                process.stdout.write(' done\n');
            }
            logger.debug('All resources disposed');
        }
        catch (error) {
            if (showProgress) {
                process.stdout.write(' failed\n');
            }
            logger.error('Error disposing resources:', error instanceof Error ? error : new Error(String(error)));
        }
    }
    /**
     * Flush all pending logs
     */
    async flushLogs() {
        try {
            const loggerInstance = getLogger();
            loggerInstance.close();
        }
        catch {
            // Ignore errors during log flushing
        }
    }
    /**
     * Check if shutdown is in progress
     */
    isInShutdown() {
        return this.isShuttingDown;
    }
    /**
     * Implement Disposable interface
     */
    dispose() {
        // Cleanup if needed
        this.handlers = [];
        GracefulShutdownManager.instance = null;
    }
    /**
     * Reset the singleton (for testing)
     */
    static reset() {
        if (GracefulShutdownManager.instance) {
            GracefulShutdownManager.instance.handlers = [];
            GracefulShutdownManager.instance.isShuttingDown = false;
            GracefulShutdownManager.instance.shutdownPromise = null;
        }
        GracefulShutdownManager.instance = null;
    }
}
/**
 * Get the global shutdown manager instance
 */
export function getShutdownManager(options) {
    return GracefulShutdownManager.getInstance(options);
}
/**
 * Register a shutdown handler with the global manager
 */
export function onShutdown(name, handler, priority = 0) {
    getShutdownManager().registerHandler({ name, handler, priority });
}
/**
 * Initiate graceful shutdown
 */
export async function initiateShutdown(options) {
    return getShutdownManager().shutdown(options);
}
/**
 * Track a pending operation that should complete before shutdown
 * Returns a function to mark the operation as complete
 *
 * @example
 * const done = trackOperation('file-save', 'Saving file...');
 * try {
 *   await saveFile();
 * } finally {
 *   done();
 * }
 */
export function trackOperation(id, description) {
    return getShutdownManager().trackOperation(id, description);
}
/**
 * Check if shutdown is in progress
 */
export function isShuttingDown() {
    return getShutdownManager().isInShutdown();
}
/**
 * Register default handlers for common resources
 */
export function registerDefaultShutdownHandlers() {
    const manager = getShutdownManager();
    // Session save handler (highest priority)
    manager.registerHandler({
        name: 'session-save',
        priority: 100,
        handler: async () => {
            try {
                // Dynamically import to avoid circular dependencies
                const { getSessionStore } = await import('../persistence/session-store.js');
                const sessionStore = getSessionStore();
                const currentSession = await sessionStore.getCurrentSession();
                if (currentSession) {
                    await sessionStore.saveSession(currentSession);
                    logger.info(`Session saved: ${currentSession.id}`);
                }
            }
            catch (error) {
                logger.warn('Failed to save session during shutdown', { error: String(error) });
            }
        },
    });
    // Crash handler terminal restore (high priority)
    manager.registerHandler({
        name: 'terminal-restore',
        priority: 90,
        handler: async () => {
            try {
                const { getCrashHandler } = await import('../errors/crash-handler.js');
                getCrashHandler().restoreTerminal();
            }
            catch {
                // Ignore errors
            }
        },
    });
    // MCP connection cleanup (medium priority)
    manager.registerHandler({
        name: 'mcp-cleanup',
        priority: 50,
        handler: async () => {
            try {
                const { getMCPClient } = await import('../mcp/mcp-client.js');
                const client = getMCPClient();
                if (client) {
                    await client.dispose();
                    logger.debug('MCP connections closed');
                }
            }
            catch {
                // MCP client may not be initialized
            }
        },
    });
    // Database cleanup (medium priority)
    manager.registerHandler({
        name: 'database-cleanup',
        priority: 40,
        handler: async () => {
            try {
                const { getDatabaseManager } = await import('../database/database-manager.js');
                const db = getDatabaseManager();
                if (db) {
                    db.close();
                    logger.debug('Database connections closed');
                }
            }
            catch {
                // Database may not be initialized
            }
        },
    });
    // Log flush (lowest priority - should be last)
    manager.registerHandler({
        name: 'log-flush',
        priority: -100,
        handler: () => {
            const loggerInstance = getLogger();
            loggerInstance.close();
        },
    });
    logger.debug('Default shutdown handlers registered');
}
/**
 * Initialize graceful shutdown system
 * Call this early in application startup
 */
export function initializeGracefulShutdown(options) {
    const manager = getShutdownManager(options);
    manager.registerSignalHandlers();
    registerDefaultShutdownHandlers();
    return manager;
}
//# sourceMappingURL=graceful-shutdown.js.map