/**
 * Session Lanes Module
 *
 * Provides queue-based message processing to prevent message interleaving
 * when multiple requests are in-flight for the same session.
 */
import { EventEmitter } from 'events';
/**
 * Default lane configuration
 */
export const DEFAULT_LANE_CONFIG = {
    maxQueueSize: 0,
    processingTimeoutMs: 0,
    fifo: true,
    autoStart: true,
};
// ============================================================================
// Session Lane
// ============================================================================
/**
 * A lane for processing messages in order
 */
export class SessionLane extends EventEmitter {
    config;
    queue = [];
    status = 'idle';
    currentItem = null;
    processor = null;
    processingPromise = null;
    idCounter = 0;
    constructor(config = {}) {
        super();
        this.config = { ...DEFAULT_LANE_CONFIG, ...config };
    }
    /**
     * Set the message processor
     */
    setProcessor(processor) {
        this.processor = processor;
    }
    /**
     * Enqueue a message for processing
     */
    enqueue(payload, options = {}) {
        // Check queue size limit
        if (this.config.maxQueueSize > 0 && this.queue.length >= this.config.maxQueueSize) {
            throw new Error(`Lane queue full (max: ${this.config.maxQueueSize})`);
        }
        const item = {
            id: `lane-${++this.idCounter}-${Date.now()}`,
            payload,
            enqueuedAt: Date.now(),
            priority: options.priority ?? 0,
            metadata: options.metadata,
        };
        // Insert based on priority and FIFO setting
        if (this.config.fifo) {
            // Simple FIFO - append to end
            this.queue.push(item);
        }
        else {
            // Priority queue - insert at correct position
            const insertIndex = this.queue.findIndex(q => q.priority > item.priority);
            if (insertIndex === -1) {
                this.queue.push(item);
            }
            else {
                this.queue.splice(insertIndex, 0, item);
            }
        }
        this.emit('enqueue', item);
        // Auto-start processing if configured
        if (this.config.autoStart && this.status === 'idle') {
            this.startProcessing();
        }
        return item;
    }
    /**
     * Start processing the queue
     */
    startProcessing() {
        if (this.status === 'processing' || this.status === 'draining') {
            return;
        }
        if (!this.processor) {
            throw new Error('No processor set for lane');
        }
        this.status = 'processing';
        this.processingPromise = this.processQueue();
    }
    /**
     * Process the queue
     */
    async processQueue() {
        while (this.queue.length > 0 && this.status !== 'paused') {
            const item = this.queue.shift();
            this.currentItem = item;
            item.startedAt = Date.now();
            this.emit('start', item);
            try {
                let result;
                if (this.config.processingTimeoutMs > 0) {
                    // Process with timeout
                    result = await Promise.race([
                        this.processor(item),
                        new Promise((_, reject) => setTimeout(() => reject(new Error('Processing timeout')), this.config.processingTimeoutMs)),
                    ]);
                }
                else {
                    result = await this.processor(item);
                }
                item.completedAt = Date.now();
                this.emit('complete', item, result);
            }
            catch (error) {
                item.completedAt = Date.now();
                this.emit('error', item, error);
            }
            this.currentItem = null;
        }
        if (this.status !== 'paused') {
            this.status = 'idle';
            this.emit('drain');
        }
        this.processingPromise = null;
    }
    /**
     * Pause processing
     */
    pause() {
        if (this.status === 'processing') {
            this.status = 'paused';
            this.emit('pause');
        }
    }
    /**
     * Resume processing
     */
    resume() {
        if (this.status === 'paused') {
            this.emit('resume');
            this.status = 'processing';
            this.processingPromise = this.processQueue();
        }
    }
    /**
     * Drain the queue (finish current and process remaining)
     */
    async drain() {
        if (this.status === 'idle' && this.queue.length === 0) {
            return;
        }
        this.status = 'draining';
        // Wait for current processing to complete
        if (this.processingPromise) {
            await this.processingPromise;
        }
    }
    /**
     * Clear the queue (cancel pending items)
     */
    clear() {
        const cleared = [...this.queue];
        this.queue = [];
        return cleared;
    }
    /**
     * Get current lane status
     */
    getStatus() {
        return this.status;
    }
    /**
     * Get queue length
     */
    getQueueLength() {
        return this.queue.length;
    }
    /**
     * Get current processing item
     */
    getCurrentItem() {
        return this.currentItem;
    }
    /**
     * Get all pending items
     */
    getPendingItems() {
        return [...this.queue];
    }
    /**
     * Get lane statistics
     */
    getStats() {
        return {
            status: this.status,
            queueLength: this.queue.length,
            isProcessing: this.currentItem !== null,
            currentItemId: this.currentItem?.id ?? null,
        };
    }
}
// ============================================================================
// Lane Manager
// ============================================================================
/**
 * Manages multiple session lanes
 */
export class LaneManager {
    lanes = new Map();
    config;
    defaultProcessor = null;
    constructor(config = {}) {
        this.config = { ...DEFAULT_LANE_CONFIG, ...config };
    }
    /**
     * Set default processor for new lanes
     */
    setDefaultProcessor(processor) {
        this.defaultProcessor = processor;
    }
    /**
     * Get or create a lane for a session
     */
    getLane(sessionId) {
        let lane = this.lanes.get(sessionId);
        if (!lane) {
            lane = new SessionLane(this.config);
            if (this.defaultProcessor) {
                lane.setProcessor(this.defaultProcessor);
            }
            this.lanes.set(sessionId, lane);
        }
        return lane;
    }
    /**
     * Check if a lane exists
     */
    hasLane(sessionId) {
        return this.lanes.has(sessionId);
    }
    /**
     * Remove a lane
     */
    removeLane(sessionId) {
        const lane = this.lanes.get(sessionId);
        if (lane) {
            lane.clear();
            this.lanes.delete(sessionId);
            return true;
        }
        return false;
    }
    /**
     * Enqueue a message to a session's lane
     */
    enqueue(sessionId, payload, options) {
        const lane = this.getLane(sessionId);
        return lane.enqueue(payload, options);
    }
    /**
     * Get all active session IDs
     */
    getActiveSessions() {
        return Array.from(this.lanes.keys());
    }
    /**
     * Get total pending items across all lanes
     */
    getTotalPending() {
        let total = 0;
        for (const lane of this.lanes.values()) {
            total += lane.getQueueLength();
            if (lane.getCurrentItem())
                total++;
        }
        return total;
    }
    /**
     * Pause all lanes
     */
    pauseAll() {
        for (const lane of this.lanes.values()) {
            lane.pause();
        }
    }
    /**
     * Resume all lanes
     */
    resumeAll() {
        for (const lane of this.lanes.values()) {
            lane.resume();
        }
    }
    /**
     * Drain all lanes
     */
    async drainAll() {
        const drainPromises = Array.from(this.lanes.values()).map(lane => lane.drain());
        await Promise.all(drainPromises);
    }
    /**
     * Clear all lanes
     */
    clearAll() {
        for (const lane of this.lanes.values()) {
            lane.clear();
        }
        this.lanes.clear();
    }
    /**
     * Get statistics for all lanes
     */
    getStats() {
        const laneStats = {};
        let processingCount = 0;
        for (const [sessionId, lane] of this.lanes) {
            const stats = lane.getStats();
            laneStats[sessionId] = stats;
            if (stats.isProcessing)
                processingCount++;
        }
        return {
            totalLanes: this.lanes.size,
            totalPending: this.getTotalPending(),
            processingCount,
            lanes: laneStats,
        };
    }
}
// ============================================================================
// Singleton
// ============================================================================
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let laneManagerInstance = null;
/**
 * Get the singleton lane manager
 */
export function getLaneManager(config) {
    if (!laneManagerInstance) {
        laneManagerInstance = new LaneManager(config);
    }
    return laneManagerInstance;
}
/**
 * Reset the singleton lane manager
 */
export function resetLaneManager() {
    if (laneManagerInstance) {
        laneManagerInstance.clearAll();
        laneManagerInstance = null;
    }
}
// ============================================================================
// Convenience Functions
// ============================================================================
/**
 * Execute a function with lane ordering for a session
 */
export async function withLane(sessionId, payload, processor, config) {
    const manager = getLaneManager(config);
    return new Promise((resolve, reject) => {
        const lane = manager.getLane(sessionId);
        lane.setProcessor(async (item) => {
            try {
                const result = await processor(item.payload);
                resolve(result);
                return result;
            }
            catch (error) {
                reject(error);
                throw error;
            }
        });
        lane.enqueue(payload);
    });
}
/**
 * Create a lane-ordered async function
 */
export function createLanedFunction(getSessionId, processor, config) {
    const manager = getLaneManager(config);
    // Set the processor once
    manager.setDefaultProcessor(async (item) => {
        return await processor(item.payload);
    });
    return async (payload) => {
        const sessionId = getSessionId(payload);
        return new Promise((resolve, reject) => {
            const lane = manager.getLane(sessionId);
            const onComplete = (item, result) => {
                if (item.payload === payload) {
                    lane.off('complete', onComplete);
                    lane.off('error', onError);
                    resolve(result);
                }
            };
            const onError = (item, error) => {
                if (item.payload === payload) {
                    lane.off('complete', onComplete);
                    lane.off('error', onError);
                    reject(error);
                }
            };
            lane.on('complete', onComplete);
            lane.on('error', onError);
            lane.enqueue(payload);
        });
    };
}
//# sourceMappingURL=lanes.js.map