/**
 * Worker pool task execution strategy.
 *
 * Runs tasks using a fixed-size worker pool. Each worker picks up the next
 * available task as soon as it finishes the current one, maximizing slot
 * utilization. Works for both sequential (concurrency=1) and parallel
 * (concurrency>1) execution through the same code path.
 *
 * Polls for newly added tasks at a configurable interval so that tasks
 * added to .takt/tasks.yaml during execution are picked up without waiting
 * for an active task to complete.
 */
import { info, blankLine } from '../../../shared/ui/index.js';
import { TaskPrefixWriter } from '../../../shared/ui/TaskPrefixWriter.js';
import { EXIT_SIGINT } from '../../../shared/exitCodes.js';
import { createLogger } from '../../../shared/utils/index.js';
import { executeRunTaskAndComplete } from './runTaskExecution.js';
import { ShutdownManager } from './shutdownManager.js';
import { isInputWaiting } from './inputWait.js';
const log = createLogger('worker-pool');
function createPollTimer(intervalMs, signal) {
    let timeoutId;
    let onAbort;
    const promise = new Promise((resolve) => {
        if (signal.aborted) {
            resolve({ type: 'poll' });
            return;
        }
        onAbort = () => {
            if (timeoutId !== undefined)
                clearTimeout(timeoutId);
            resolve({ type: 'poll' });
        };
        timeoutId = setTimeout(() => {
            signal.removeEventListener('abort', onAbort);
            onAbort = undefined;
            resolve({ type: 'poll' });
        }, intervalMs);
        signal.addEventListener('abort', onAbort, { once: true });
    });
    return {
        promise,
        cancel: () => {
            if (timeoutId !== undefined) {
                clearTimeout(timeoutId);
                timeoutId = undefined;
            }
            if (onAbort) {
                signal.removeEventListener('abort', onAbort);
                onAbort = undefined;
            }
        },
    };
}
/**
 * Run tasks using a worker pool with the given concurrency.
 *
 * Algorithm:
 * 1. Create a shared AbortController
 * 2. Maintain a queue of pending tasks and a set of active promises
 * 3. Fill available slots from the queue
 * 4. Wait for any active task to complete OR a poll timer to fire (Promise.race)
 * 5. On task completion: record result
 * 6. On poll tick or completion: claim new tasks and fill freed slots
 * 7. Repeat until queue is empty and all active tasks complete
 */
export async function runWithWorkerPool(taskRunner, initialTasks, concurrency, cwd, taskExecutionOptions, runOptions, pollIntervalMs) {
    const abortController = new AbortController();
    const shutdownManager = new ShutdownManager({
        callbacks: {
            onGraceful: () => abortController.abort(),
            onForceKill: () => process.exit(EXIT_SIGINT),
        },
    });
    shutdownManager.install();
    const selfSigintOnce = process.env.TAKT_E2E_SELF_SIGINT_ONCE === '1';
    const selfSigintTwice = process.env.TAKT_E2E_SELF_SIGINT_TWICE === '1';
    let selfSigintInjected = false;
    let successCount = 0;
    let failCount = 0;
    const executedTaskNames = [];
    const queue = [...initialTasks];
    const active = new Map();
    const colorCounter = { value: 0 };
    try {
        while (queue.length > 0 || active.size > 0) {
            if (!abortController.signal.aborted) {
                fillSlots(queue, active, concurrency, taskRunner, cwd, taskExecutionOptions, runOptions, abortController, colorCounter);
                if ((selfSigintOnce || selfSigintTwice) && !selfSigintInjected && active.size > 0) {
                    selfSigintInjected = true;
                    process.emit('SIGINT');
                    if (selfSigintTwice) {
                        // E2E deterministic path: force-exit shortly after graceful SIGINT.
                        // Avoids intermittent hangs caused by listener ordering/races.
                        setTimeout(() => {
                            process.exit(EXIT_SIGINT);
                        }, 25);
                    }
                }
            }
            if (active.size === 0) {
                break;
            }
            const completionPromises = [...active.keys()].map((p) => p.then((result) => ({ type: 'completion', promise: p, result }), () => ({ type: 'completion', promise: p, result: false })));
            let settled;
            if (abortController.signal.aborted) {
                // Graceful shutdown: stop scheduling new work but wait for in-flight tasks to settle.
                settled = await Promise.race(completionPromises);
            }
            else {
                const pollTimer = createPollTimer(pollIntervalMs, abortController.signal);
                try {
                    settled = await Promise.race([...completionPromises, pollTimer.promise]);
                }
                finally {
                    pollTimer.cancel();
                }
            }
            if (settled.type === 'completion') {
                const task = active.get(settled.promise);
                active.delete(settled.promise);
                if (task) {
                    executedTaskNames.push(task.name);
                    if (settled.result) {
                        successCount++;
                    }
                    else {
                        failCount++;
                    }
                }
            }
            if (!abortController.signal.aborted && !isInputWaiting()) {
                const freeSlots = concurrency - active.size;
                if (freeSlots > 0) {
                    const newTasks = taskRunner.claimNextTasks(freeSlots);
                    log.trace('poll_tick', { active: active.size, queued: queue.length, freeSlots });
                    if (newTasks.length > 0) {
                        log.debug('poll_new_tasks', { count: newTasks.length });
                        queue.push(...newTasks);
                    }
                    else {
                        log.trace('no_new_tasks');
                    }
                }
            }
        }
    }
    finally {
        shutdownManager.cleanup();
    }
    return { success: successCount, fail: failCount, executedTaskNames };
}
function fillSlots(queue, active, concurrency, taskRunner, cwd, taskExecutionOptions, runOptions, abortController, colorCounter) {
    while (active.size < concurrency && queue.length > 0) {
        const task = queue.shift();
        const isParallel = concurrency > 1;
        const colorIndex = colorCounter.value++;
        const issueNumber = task.data?.issue;
        const taskPrefix = issueNumber === undefined ? task.name : `#${issueNumber}`;
        const taskDisplayLabel = issueNumber === undefined ? undefined : taskPrefix;
        if (isParallel) {
            const writer = new TaskPrefixWriter({
                taskName: task.name,
                colorIndex,
                issue: issueNumber,
                displayLabel: taskDisplayLabel,
            });
            writer.writeLine(`=== Task: ${task.name} ===`);
        }
        else {
            blankLine();
            info(`=== Task: ${task.name} ===`);
        }
        const promise = executeRunTaskAndComplete(task, taskRunner, cwd, taskExecutionOptions, {
            abortSignal: abortController.signal,
            taskPrefix: isParallel ? taskPrefix : undefined,
            taskColorIndex: isParallel ? colorIndex : undefined,
            taskDisplayLabel: isParallel ? taskDisplayLabel : undefined,
        }, runOptions);
        active.set(promise, task);
    }
}
//# sourceMappingURL=parallelExecution.js.map