/**
 * Continuation Event Emitter Service
 *
 * Centralized event emission for continuation detection.
 * Collects events from PTY sessions, activity monitoring, and heartbeat services,
 * normalizes them, and emits them for processing.
 *
 * @module services/continuation/continuation-events.service
 */
import { EventEmitter } from 'events';
import { LoggerService } from '../core/logger.service.js';
import { CONTINUATION_CONSTANTS } from '../../constants.js';
/**
 * Service that emits continuation events from various sources
 *
 * Features:
 * - Event debouncing to prevent rapid-fire events
 * - Event deduplication within a time window
 * - Integration with PTY sessions, activity monitor, and heartbeat service
 *
 * @example
 * ```typescript
 * const emitter = ContinuationEventEmitter.getInstance();
 *
 * emitter.on('continuation', (event) => {
 *   console.log(`Continuation event: ${event.trigger} for ${event.sessionName}`);
 * });
 *
 * // Register a PTY session
 * emitter.registerPtySession(ptySession, 'agent-001', '/path/to/project');
 * ```
 */
export class ContinuationEventEmitter extends EventEmitter {
    static instance = null;
    logger;
    pendingEvents = new Map();
    recentEvents = new Map();
    registeredSessions = new Set();
    DEBOUNCE_MS;
    DEDUP_WINDOW_MS = 10000; // 10 seconds deduplication window
    /**
     * Creates a new ContinuationEventEmitter
     */
    constructor() {
        super();
        this.logger = LoggerService.getInstance().createComponentLogger('ContinuationEventEmitter');
        this.DEBOUNCE_MS = CONTINUATION_CONSTANTS.EVENTS.DEBOUNCE_MS;
        // Clean up recent events periodically
        setInterval(() => this.cleanupRecentEvents(), 60000);
    }
    /**
     * Gets the singleton instance
     *
     * @returns The ContinuationEventEmitter instance
     */
    static getInstance() {
        if (!ContinuationEventEmitter.instance) {
            ContinuationEventEmitter.instance = new ContinuationEventEmitter();
        }
        return ContinuationEventEmitter.instance;
    }
    /**
     * Clears the singleton instance (for testing)
     */
    static clearInstance() {
        if (ContinuationEventEmitter.instance) {
            ContinuationEventEmitter.instance.removeAllListeners();
            ContinuationEventEmitter.instance.pendingEvents.forEach(timer => clearTimeout(timer));
            ContinuationEventEmitter.instance.pendingEvents.clear();
        }
        ContinuationEventEmitter.instance = null;
    }
    /**
     * Register a PTY session for exit event tracking
     *
     * @param session - PTY session to monitor
     * @param agentId - Agent identifier
     * @param projectPath - Project path
     */
    registerPtySession(session, agentId, projectPath) {
        if (this.registeredSessions.has(session.name)) {
            this.logger.debug('Session already registered', { sessionName: session.name });
            return;
        }
        this.registeredSessions.add(session.name);
        session.onExit((exitCode) => {
            this.logger.info('PTY session exited', { sessionName: session.name, exitCode });
            this.emitDebounced({
                trigger: 'pty_exit',
                sessionName: session.name,
                agentId,
                projectPath,
                timestamp: new Date().toISOString(),
                metadata: { exitCode },
            });
        });
        this.logger.debug('Registered PTY session for continuation events', {
            sessionName: session.name,
            agentId,
        });
    }
    /**
     * Unregister a session (call when session is destroyed)
     *
     * @param sessionName - Session name to unregister
     */
    unregisterSession(sessionName) {
        this.registeredSessions.delete(sessionName);
        // Clear any pending events for this session
        for (const key of this.pendingEvents.keys()) {
            if (key.startsWith(`${sessionName}-`)) {
                clearTimeout(this.pendingEvents.get(key));
                this.pendingEvents.delete(key);
            }
        }
        this.logger.debug('Unregistered session', { sessionName });
    }
    /**
     * Emit an activity idle event
     *
     * @param sessionName - Session that is idle
     * @param agentId - Agent identifier
     * @param projectPath - Project path
     * @param metadata - Additional metadata
     */
    emitActivityIdle(sessionName, agentId, projectPath, metadata) {
        this.emitDebounced({
            trigger: 'activity_idle',
            sessionName,
            agentId,
            projectPath,
            timestamp: new Date().toISOString(),
            metadata,
        });
    }
    /**
     * Emit a heartbeat stale event
     *
     * @param sessionName - Session with stale heartbeat
     * @param agentId - Agent identifier
     * @param projectPath - Project path
     * @param lastHeartbeat - ISO timestamp of last heartbeat
     */
    emitHeartbeatStale(sessionName, agentId, projectPath, lastHeartbeat) {
        this.emitDebounced({
            trigger: 'heartbeat_stale',
            sessionName,
            agentId,
            projectPath,
            timestamp: new Date().toISOString(),
            metadata: { lastHeartbeat },
        });
    }
    /**
     * Emit an explicit continuation request event
     *
     * @param sessionName - Session requesting continuation
     * @param agentId - Agent identifier
     * @param projectPath - Project path
     * @param reason - Reason for the request
     */
    emitExplicitRequest(sessionName, agentId, projectPath, reason) {
        this.emitDebounced({
            trigger: 'explicit_request',
            sessionName,
            agentId,
            projectPath,
            timestamp: new Date().toISOString(),
            metadata: { requestReason: reason },
        });
    }
    /**
     * Manually trigger a continuation event (bypasses debouncing)
     *
     * @param event - The continuation event to emit
     */
    trigger(event) {
        if (this.isDuplicate(event)) {
            this.logger.debug('Skipping duplicate event', {
                sessionName: event.sessionName,
                trigger: event.trigger,
            });
            return;
        }
        this.markEventSent(event);
        this.logger.info('Emitting continuation event', {
            sessionName: event.sessionName,
            trigger: event.trigger,
        });
        this.emit('continuation', event);
    }
    /**
     * Emit an event with debouncing
     *
     * @param event - The continuation event to emit
     */
    emitDebounced(event) {
        const key = this.getEventKey(event);
        // Check for duplicate
        if (this.isDuplicate(event)) {
            this.logger.debug('Skipping duplicate event (debounced)', {
                sessionName: event.sessionName,
                trigger: event.trigger,
            });
            return;
        }
        // Clear existing debounce timer
        if (this.pendingEvents.has(key)) {
            clearTimeout(this.pendingEvents.get(key));
        }
        // Set new debounce timer
        const timer = setTimeout(() => {
            this.pendingEvents.delete(key);
            this.markEventSent(event);
            this.logger.info('Emitting debounced continuation event', {
                sessionName: event.sessionName,
                trigger: event.trigger,
            });
            this.emit('continuation', event);
        }, this.DEBOUNCE_MS);
        this.pendingEvents.set(key, timer);
        this.logger.debug('Scheduled continuation event', {
            sessionName: event.sessionName,
            trigger: event.trigger,
            debounceMs: this.DEBOUNCE_MS,
        });
    }
    /**
     * Get a unique key for an event (for debouncing)
     *
     * @param event - The continuation event
     * @returns Unique key string
     */
    getEventKey(event) {
        return `${event.sessionName}-${event.trigger}`;
    }
    /**
     * Check if an event is a duplicate (sent recently)
     *
     * @param event - The continuation event
     * @returns True if duplicate
     */
    isDuplicate(event) {
        const key = this.getEventKey(event);
        const lastSent = this.recentEvents.get(key);
        if (!lastSent) {
            return false;
        }
        return Date.now() - lastSent < this.DEDUP_WINDOW_MS;
    }
    /**
     * Mark an event as sent (for deduplication)
     *
     * @param event - The continuation event
     */
    markEventSent(event) {
        const key = this.getEventKey(event);
        this.recentEvents.set(key, Date.now());
    }
    /**
     * Clean up old entries from recent events map
     */
    cleanupRecentEvents() {
        const now = Date.now();
        let cleaned = 0;
        for (const [key, timestamp] of this.recentEvents.entries()) {
            if (now - timestamp > this.DEDUP_WINDOW_MS * 2) {
                this.recentEvents.delete(key);
                cleaned++;
            }
        }
        if (cleaned > 0) {
            this.logger.debug('Cleaned up recent events', { count: cleaned });
        }
    }
    /**
     * Get number of pending events (for testing)
     *
     * @returns Number of pending events
     */
    getPendingEventCount() {
        return this.pendingEvents.size;
    }
    /**
     * Get registered session names (for testing)
     *
     * @returns Set of registered session names
     */
    getRegisteredSessions() {
        return new Set(this.registeredSessions);
    }
}
//# sourceMappingURL=continuation-events.service.js.map