/**
 * @fileoverview Task queue for managing Claude prompts and operations
 *
 * Provides a priority queue for tasks with:
 * - Priority-based ordering (higher priority first)
 * - Dependency tracking between tasks
 * - State persistence via StateStore
 * - Session assignment for task execution
 *
 * @module task-queue
 */
import { EventEmitter } from 'node:events';
import { Task } from './task.js';
import { getStore } from './state-store.js';
/**
 * Events emitted by TaskQueue
 */
/**
 * Priority queue for managing tasks with dependency support.
 *
 * @description
 * Tasks are ordered by priority (descending) then creation time (ascending).
 * Dependencies can be specified to ensure tasks run in correct order.
 *
 * @extends EventEmitter
 */
export class TaskQueue extends EventEmitter {
    tasks = new Map();
    store = getStore();
    /** Creates a new TaskQueue and loads persisted tasks. */
    constructor() {
        super();
        this.loadFromStore();
    }
    loadFromStore() {
        const storedTasks = this.store.getTasks();
        for (const [id, state] of Object.entries(storedTasks)) {
            const task = Task.fromState(state);
            this.tasks.set(id, task);
        }
    }
    /**
     * Adds a new task to the queue.
     * @throws Error if the task's dependencies would create a circular dependency
     */
    addTask(options) {
        const task = new Task(options);
        // Validate dependencies before adding to prevent circular dependency deadlocks
        if (options.dependencies && options.dependencies.length > 0) {
            this.validateDependencies(task.id, options.dependencies);
        }
        this.tasks.set(task.id, task);
        this.store.setTask(task.id, task.toState());
        this.emit('taskAdded', task);
        return task;
    }
    /** Gets a task by ID. */
    getTask(id) {
        return this.tasks.get(id);
    }
    /** Removes a task by ID. Returns true if removed. */
    removeTask(id) {
        const removed = this.tasks.delete(id);
        if (removed) {
            this.store.removeTask(id);
            this.emit('taskRemoved', id);
        }
        return removed;
    }
    /** Updates a task and persists the change. */
    updateTask(task) {
        this.tasks.set(task.id, task);
        this.store.setTask(task.id, task.toState());
        this.emit('taskUpdated', task);
    }
    /** Gets all tasks in the queue. */
    getAllTasks() {
        return Array.from(this.tasks.values());
    }
    /** Gets pending tasks sorted by priority then creation time. */
    getPendingTasks() {
        return this.getAllTasks()
            .filter((t) => t.isPending())
            .sort((a, b) => {
            // Sort by priority (higher first), then by creation time (older first)
            if (a.priority !== b.priority) {
                return b.priority - a.priority;
            }
            return a.createdAt - b.createdAt;
        });
    }
    /** Gets tasks currently being executed. */
    getRunningTasks() {
        return this.getAllTasks().filter((t) => t.isRunning());
    }
    /** Gets successfully completed tasks. */
    getCompletedTasks() {
        return this.getAllTasks().filter((t) => t.isCompleted());
    }
    /** Gets tasks that failed execution. */
    getFailedTasks() {
        return this.getAllTasks().filter((t) => t.isFailed());
    }
    /** Returns true if there's a task ready for execution. */
    hasNext() {
        return this.getNextAvailable() !== null;
    }
    /** Gets the next task ready for execution (with satisfied dependencies). */
    getNextAvailable() {
        const pending = this.getPendingTasks();
        for (const task of pending) {
            if (this.areDependenciesSatisfied(task)) {
                return task;
            }
        }
        return null;
    }
    /** Alias for getNextAvailable(). */
    next() {
        return this.getNextAvailable();
    }
    areDependenciesSatisfied(task) {
        for (const depId of task.dependencies) {
            const dep = this.tasks.get(depId);
            if (!dep || !dep.isCompleted()) {
                return false;
            }
        }
        return true;
    }
    /**
     * Detect if adding a dependency would create a cycle.
     * Uses DFS to check if there's a path from depId back to taskId.
     *
     * @param taskId - The task that would have the new dependency
     * @param depId - The dependency being added
     * @param visited - Set of already visited nodes (for DFS)
     * @returns true if adding this dependency would create a cycle
     */
    wouldCreateCycle(taskId, depId, visited = new Set()) {
        // Direct self-reference
        if (depId === taskId)
            return true;
        // Already visited this node in current path
        if (visited.has(depId))
            return false;
        visited.add(depId);
        const depTask = this.tasks.get(depId);
        if (!depTask)
            return false;
        // Recursively check all dependencies of the dependency
        for (const nextDep of depTask.dependencies) {
            if (this.wouldCreateCycle(taskId, nextDep, visited)) {
                return true;
            }
        }
        return false;
    }
    /**
     * Validates that a set of dependencies won't create cycles for a given task.
     * Throws an error if a circular dependency is detected.
     *
     * @param taskId - The task ID that will have these dependencies
     * @param dependencies - Array of dependency task IDs to validate
     * @throws Error if a circular dependency would be created
     */
    validateDependencies(taskId, dependencies) {
        for (const depId of dependencies) {
            if (this.wouldCreateCycle(taskId, depId)) {
                throw new Error(`Circular dependency detected: adding dependency ${depId} to task ${taskId} would create a cycle`);
            }
        }
    }
    /** Gets all tasks assigned to a specific session. */
    getTasksBySession(sessionId) {
        return this.getAllTasks().filter((t) => t.assignedSessionId === sessionId);
    }
    /** Gets the currently running task for a session, if any. */
    getRunningTaskForSession(sessionId) {
        return this.getAllTasks().find((t) => t.isRunning() && t.assignedSessionId === sessionId) || null;
    }
    /** Gets counts of tasks by status (single-pass). */
    getCount() {
        let pending = 0, running = 0, completed = 0, failed = 0;
        for (const task of this.tasks.values()) {
            if (task.isPending())
                pending++;
            else if (task.isRunning())
                running++;
            else if (task.isCompleted())
                completed++;
            else if (task.isFailed())
                failed++;
        }
        return { total: this.tasks.size, pending, running, completed, failed };
    }
    /** Removes all completed tasks. Returns count removed. */
    clearCompleted() {
        let count = 0;
        for (const task of this.getAllTasks()) {
            if (task.isCompleted()) {
                this.removeTask(task.id);
                count++;
            }
        }
        return count;
    }
    /** Removes all failed tasks. Returns count removed. */
    clearFailed() {
        let count = 0;
        for (const task of this.getAllTasks()) {
            if (task.isFailed()) {
                this.removeTask(task.id);
                count++;
            }
        }
        return count;
    }
    /** Removes all tasks from the queue. Returns count removed. */
    clearAll() {
        const count = this.tasks.size;
        for (const id of this.tasks.keys()) {
            this.store.removeTask(id);
        }
        this.tasks.clear();
        return count;
    }
}
// Singleton instance
let queueInstance = null;
/** Gets or creates the singleton TaskQueue instance. */
export function getTaskQueue() {
    if (!queueInstance) {
        queueInstance = new TaskQueue();
    }
    return queueInstance;
}
//# sourceMappingURL=task-queue.js.map