/**
 * Concurrency Control Utilities
 *
 * Provides a lightweight promise queue for controlling concurrent operations.
 * Similar to p-queue but without external dependencies.
 *
 * Use VASPERA_SCAN_CONCURRENCY env var to configure default concurrency.
 *
 * @module util/concurrency
 */
import { logger } from "../logger.js";
/**
 * Default concurrency level for scanner operations
 */
export const DEFAULT_CONCURRENCY = 3;
/**
 * Get the configured concurrency level from environment
 */
export function getConcurrencyLevel() {
    const envValue = process.env.VASPERA_SCAN_CONCURRENCY;
    if (envValue) {
        const parsed = parseInt(envValue, 10);
        if (!isNaN(parsed) && parsed > 0 && parsed <= 20) {
            return parsed;
        }
    }
    return DEFAULT_CONCURRENCY;
}
/**
 * Promise Queue for controlled concurrency
 *
 * Limits the number of concurrent async operations to prevent
 * resource exhaustion and improve reliability.
 *
 * @example
 * ```typescript
 * const queue = new PromiseQueue({ concurrency: 3 });
 *
 * // Add tasks
 * queue.add(() => scanFile("file1.ts"), { priority: 1 });
 * queue.add(() => scanFile("file2.ts"), { priority: 2 }); // runs first
 *
 * // Wait for all tasks
 * const results = await queue.onIdle();
 * ```
 */
export class PromiseQueue {
    concurrency;
    autoStart;
    defaultTimeout;
    onEmpty;
    onError;
    queue = [];
    running = new Set();
    results = [];
    taskIdCounter = 0;
    isPaused = false;
    idlePromise = null;
    idleResolve = null;
    constructor(options = {}) {
        this.concurrency = options.concurrency ?? getConcurrencyLevel();
        this.autoStart = options.autoStart ?? true;
        this.defaultTimeout = options.timeout;
        this.onEmpty = options.onEmpty;
        this.onError = options.onError;
        logger.debug("concurrency.queue_created", {
            concurrency: this.concurrency,
            autoStart: this.autoStart,
        });
    }
    /**
     * Add a task to the queue
     */
    add(fn, options) {
        const task = {
            id: options?.id ?? `task-${++this.taskIdCounter}`,
            fn,
            priority: options?.priority ?? 0,
            addedAt: new Date(),
            timeout: options?.timeout ?? this.defaultTimeout,
        };
        // Insert in priority order (higher priority first)
        let inserted = false;
        for (let i = 0; i < this.queue.length; i++) {
            if (task.priority > this.queue[i].priority) {
                this.queue.splice(i, 0, task);
                inserted = true;
                break;
            }
        }
        if (!inserted) {
            this.queue.push(task);
        }
        logger.debug("concurrency.task_added", {
            id: task.id,
            priority: task.priority,
            queueSize: this.queue.length,
        });
        // Create promise that resolves when this task completes
        const taskPromise = new Promise((resolve) => {
            const checkResult = setInterval(() => {
                const result = this.results.find((r) => r.id === task.id);
                if (result) {
                    clearInterval(checkResult);
                    resolve(result);
                }
            }, 10);
        });
        // Start processing if autoStart is enabled
        if (this.autoStart && !this.isPaused) {
            this.process();
        }
        return taskPromise;
    }
    /**
     * Add multiple tasks at once
     */
    addAll(tasks) {
        const promises = tasks.map((t) => this.add(t.fn, { priority: t.priority, id: t.id, timeout: t.timeout }));
        return Promise.all(promises);
    }
    /**
     * Process tasks from the queue
     */
    async process() {
        while (this.queue.length > 0 &&
            this.running.size < this.concurrency &&
            !this.isPaused) {
            const task = this.queue.shift();
            if (!task)
                break;
            this.running.add(task.id);
            this.runTask(task);
        }
        // Check if idle
        if (this.queue.length === 0 && this.running.size === 0) {
            this.onEmpty?.();
            if (this.idleResolve) {
                this.idleResolve([...this.results]);
                this.idleResolve = null;
                this.idlePromise = null;
            }
        }
    }
    /**
     * Run a single task
     */
    async runTask(task) {
        const startTime = Date.now();
        try {
            // Wrap with timeout if specified
            let value;
            if (task.timeout) {
                value = await Promise.race([
                    task.fn(),
                    new Promise((_, reject) => setTimeout(() => reject(new Error(`Task ${task.id} timed out after ${task.timeout}ms`)), task.timeout)),
                ]);
            }
            else {
                value = await task.fn();
            }
            const result = {
                id: task.id,
                success: true,
                value,
                durationMs: Date.now() - startTime,
            };
            this.results.push(result);
            logger.debug("concurrency.task_completed", {
                id: task.id,
                durationMs: result.durationMs,
            });
        }
        catch (error) {
            const result = {
                id: task.id,
                success: false,
                error: error,
                durationMs: Date.now() - startTime,
            };
            this.results.push(result);
            this.onError?.(error, task);
            logger.debug("concurrency.task_failed", {
                id: task.id,
                error: String(error),
                durationMs: result.durationMs,
            });
        }
        finally {
            this.running.delete(task.id);
            // Process next tasks
            this.process();
        }
    }
    /**
     * Start processing (if paused)
     */
    start() {
        this.isPaused = false;
        this.process();
    }
    /**
     * Pause processing (current tasks continue, new tasks wait)
     */
    pause() {
        this.isPaused = true;
    }
    /**
     * Clear pending tasks (running tasks continue)
     */
    clear() {
        this.queue = [];
    }
    /**
     * Get queue statistics
     */
    getStats() {
        const completed = this.results.filter((r) => r.success).length;
        const failed = this.results.filter((r) => !r.success).length;
        const totalDuration = this.results.reduce((sum, r) => sum + r.durationMs, 0);
        return {
            pending: this.queue.length,
            running: this.running.size,
            completed,
            failed,
            avgDurationMs: this.results.length > 0 ? Math.round(totalDuration / this.results.length) : 0,
            totalDurationMs: totalDuration,
        };
    }
    /**
     * Get current queue size
     */
    get size() {
        return this.queue.length;
    }
    /**
     * Get number of running tasks
     */
    get pending() {
        return this.running.size;
    }
    /**
     * Check if queue is idle (no pending or running tasks)
     */
    get isIdle() {
        return this.queue.length === 0 && this.running.size === 0;
    }
    /**
     * Wait for queue to become idle
     */
    onIdle() {
        if (this.isIdle) {
            return Promise.resolve([...this.results]);
        }
        if (!this.idlePromise) {
            this.idlePromise = new Promise((resolve) => {
                this.idleResolve = resolve;
            });
        }
        return this.idlePromise;
    }
}
/**
 * Run tasks with controlled concurrency
 *
 * @param tasks - Array of async functions to execute
 * @param concurrency - Maximum concurrent operations
 * @returns Array of results in order
 *
 * @example
 * ```typescript
 * const results = await runConcurrent(
 *   files.map(f => () => scanFile(f)),
 *   3
 * );
 * ```
 */
export async function runConcurrent(tasks, concurrency) {
    const queue = new PromiseQueue({
        concurrency: concurrency ?? getConcurrencyLevel(),
    });
    const promises = tasks.map((fn, i) => queue.add(fn, { id: `task-${i}` }));
    return Promise.all(promises);
}
/**
 * Run tasks with controlled concurrency, returning only successful values
 *
 * @param tasks - Array of async functions to execute
 * @param concurrency - Maximum concurrent operations
 * @returns Array of successful results in order (null for failed)
 */
export async function runConcurrentValues(tasks, concurrency) {
    const results = await runConcurrent(tasks, concurrency);
    return results.map((r) => (r.success ? r.value : null));
}
/**
 * Map over items with controlled concurrency
 *
 * @param items - Items to process
 * @param fn - Async function to apply to each item
 * @param concurrency - Maximum concurrent operations
 * @returns Array of results
 */
export async function mapConcurrent(items, fn, concurrency) {
    const tasks = items.map((item, index) => () => fn(item, index));
    return runConcurrent(tasks, concurrency);
}
/**
 * Create a throttled version of an async function
 *
 * @param fn - Async function to throttle
 * @param concurrency - Maximum concurrent calls
 * @returns Throttled function
 */
export function throttle(fn, concurrency) {
    const queue = new PromiseQueue({
        concurrency: concurrency ?? getConcurrencyLevel(),
    });
    return (...args) => queue.add(() => fn(...args));
}
/**
 * Batch items and process batches with controlled concurrency
 *
 * @param items - Items to process
 * @param batchSize - Size of each batch
 * @param fn - Async function to process a batch
 * @param concurrency - Maximum concurrent batches
 */
export async function batchConcurrent(items, batchSize, fn, concurrency) {
    // Create batches
    const batches = [];
    for (let i = 0; i < items.length; i += batchSize) {
        batches.push(items.slice(i, i + batchSize));
    }
    // Process batches concurrently
    const tasks = batches.map((batch, index) => () => fn(batch, index));
    return runConcurrent(tasks, concurrency);
}
//# sourceMappingURL=concurrency.js.map