import { exec } from 'child_process';
import { promisify } from 'util';
import { EventEmitter } from 'events';
const execAsync = promisify(exec);
export class TaskOrchestrator extends EventEmitter {
    config;
    runningTasks;
    taskQueue;
    workflows;
    executions;
    constructor(config) {
        super();
        this.config = {
            maxConcurrentTasks: config?.maxConcurrentTasks || 5,
            defaultTimeout: config?.defaultTimeout || 300000, // 5 minutes
            retryPolicy: {
                maxRetries: config?.retryPolicy?.maxRetries || 3,
                backoffMultiplier: config?.retryPolicy?.backoffMultiplier || 2,
                initialDelay: config?.retryPolicy?.initialDelay || 1000,
                maxDelay: config?.retryPolicy?.maxDelay || 30000,
            },
            executionMode: config?.executionMode || 'local',
            persistence: {
                enabled: config?.persistence?.enabled || true,
                path: config?.persistence?.path || '.atlas/workflows',
            },
        };
        this.runningTasks = new Map();
        this.taskQueue = [];
        this.workflows = new Map();
        this.executions = new Map();
    }
    async createWorkflow(workflow) {
        const newWorkflow = {
            ...workflow,
            id: crypto.randomUUID(),
            createdAt: new Date().toISOString(),
            updatedAt: new Date().toISOString(),
        };
        // Validate workflow
        this.validateWorkflow(newWorkflow);
        this.workflows.set(newWorkflow.id, newWorkflow);
        this.emit('workflow:created', newWorkflow);
        return newWorkflow;
    }
    async executeWorkflow(workflowId, variables) {
        const workflow = this.workflows.get(workflowId);
        if (!workflow) {
            throw new Error(`Workflow ${workflowId} not found`);
        }
        const execution = {
            id: crypto.randomUUID(),
            workflowId: workflow.id,
            workflowVersion: workflow.version,
            status: 'pending',
            startTime: new Date().toISOString(),
            tasks: [],
            variables: { ...workflow.variables, ...variables },
            logs: [],
        };
        this.executions.set(execution.id, execution);
        this.emit('execution:started', execution);
        try {
            execution.status = 'running';
            await this.runWorkflow(workflow, execution);
            execution.status = 'completed';
            execution.endTime = new Date().toISOString();
            execution.duration = new Date(execution.endTime).getTime() - new Date(execution.startTime).getTime();
            this.emit('execution:completed', execution);
        }
        catch (error) {
            execution.status = 'failed';
            execution.error = error.message;
            execution.endTime = new Date().toISOString();
            execution.duration = new Date(execution.endTime).getTime() - new Date(execution.startTime).getTime();
            this.emit('execution:failed', execution);
            throw error;
        }
        return execution;
    }
    async executeTask(task, variables) {
        const execution = {
            taskId: task.id,
            executionId: crypto.randomUUID(),
            status: 'pending',
            retryCount: 0,
            logs: [],
        };
        this.emit('task:started', { task, execution });
        try {
            execution.status = 'running';
            execution.startTime = new Date().toISOString();
            let result;
            switch (task.type) {
                case 'command':
                    result = await this.executeCommand(task, variables);
                    break;
                case 'function':
                    result = await this.executeFunction(task, variables);
                    break;
                case 'manual':
                    result = await this.waitForManualCompletion(task);
                    break;
                case 'parallel':
                    result = await this.executeParallelTasks(task, variables);
                    break;
                case 'sequential':
                    result = await this.executeSequentialTasks(task, variables);
                    break;
                default:
                    throw new Error(`Unknown task type: ${task.type}`);
            }
            execution.output = result;
            execution.status = 'completed';
            execution.endTime = new Date().toISOString();
            execution.duration = new Date(execution.endTime).getTime() - new Date(execution.startTime).getTime();
            this.emit('task:completed', { task, execution });
        }
        catch (error) {
            execution.error = error.message;
            execution.status = 'failed';
            execution.endTime = new Date().toISOString();
            execution.duration = execution.startTime
                ? new Date(execution.endTime).getTime() - new Date(execution.startTime).getTime()
                : 0;
            // Retry logic
            if (execution.retryCount < task.maxRetries) {
                execution.retryCount++;
                const delay = this.calculateRetryDelay(execution.retryCount);
                this.emit('task:retrying', { task, execution, delay });
                await new Promise(resolve => setTimeout(resolve, delay));
                return this.executeTask(task, variables);
            }
            this.emit('task:failed', { task, execution });
            throw error;
        }
        return execution;
    }
    async cancelExecution(executionId) {
        const execution = this.executions.get(executionId);
        if (!execution) {
            throw new Error(`Execution ${executionId} not found`);
        }
        execution.status = 'cancelled';
        execution.endTime = new Date().toISOString();
        // Cancel all running tasks
        for (const task of execution.tasks) {
            if (task.status === 'running' || task.status === 'pending') {
                task.status = 'cancelled';
            }
        }
        this.emit('execution:cancelled', execution);
    }
    getWorkflowStatus(workflowId) {
        return this.workflows.get(workflowId);
    }
    getExecutionStatus(executionId) {
        return this.executions.get(executionId);
    }
    listWorkflows() {
        return Array.from(this.workflows.values());
    }
    listExecutions(workflowId) {
        const executions = Array.from(this.executions.values());
        if (workflowId) {
            return executions.filter(e => e.workflowId === workflowId);
        }
        return executions;
    }
    async runWorkflow(workflow, execution) {
        // Build dependency graph
        const dependencyGraph = this.buildDependencyGraph(workflow.tasks);
        // Execute tasks based on dependencies
        const completed = new Set();
        const failed = new Set();
        while (completed.size + failed.size < workflow.tasks.length) {
            // Find tasks ready to execute
            const readyTasks = workflow.tasks.filter(task => {
                if (completed.has(task.id) || failed.has(task.id))
                    return false;
                // Check if all dependencies are satisfied
                return task.dependencies.every(dep => {
                    if (failed.has(dep)) {
                        // If dependency failed, check if we should skip this task
                        return false;
                    }
                    return completed.has(dep);
                });
            });
            if (readyTasks.length === 0) {
                // No tasks ready, check for deadlock
                const remainingTasks = workflow.tasks.filter(t => !completed.has(t.id) && !failed.has(t.id));
                if (remainingTasks.length > 0) {
                    throw new Error('Workflow deadlock detected');
                }
                break;
            }
            // Execute ready tasks (respecting concurrency limit)
            const tasksToRun = readyTasks.slice(0, this.config.maxConcurrentTasks - this.runningTasks.size);
            await Promise.all(tasksToRun.map(async (task) => {
                try {
                    const taskExecution = await this.executeTask(task, execution.variables);
                    execution.tasks.push(taskExecution);
                    completed.add(task.id);
                }
                catch (error) {
                    failed.add(task.id);
                    // Continue or fail based on task configuration
                    if (!task.metadata?.continueOnError) {
                        throw error;
                    }
                }
            }));
        }
    }
    async executeCommand(task, variables) {
        const config = task.input;
        let command = this.interpolateVariables(config.command, variables);
        const options = {
            cwd: config.workingDirectory,
            env: { ...process.env, ...config.environment },
            shell: config.shell,
            timeout: task.timeout || this.config.defaultTimeout,
        };
        try {
            const { stdout, stderr } = await execAsync(command, options);
            if (config.captureOutput) {
                return { stdout, stderr };
            }
            return stdout;
        }
        catch (error) {
            throw new Error(`Command failed: ${error.message}`);
        }
    }
    async executeFunction(task, variables) {
        const config = task.input;
        // This is a placeholder - in a real implementation, you'd have a function registry
        throw new Error('Function execution not implemented');
    }
    async waitForManualCompletion(task) {
        // This would typically integrate with a UI or API
        return new Promise((resolve, reject) => {
            this.once(`task:manual:${task.id}`, (result) => {
                if (result.error) {
                    reject(new Error(result.error));
                }
                else {
                    resolve(result.output);
                }
            });
        });
    }
    async executeParallelTasks(task, variables) {
        const subtasks = task.input;
        const results = await Promise.all(subtasks.map(subtask => this.executeTask(subtask, variables)));
        return results.map(r => r.output);
    }
    async executeSequentialTasks(task, variables) {
        const subtasks = task.input;
        const results = [];
        for (const subtask of subtasks) {
            const result = await this.executeTask(subtask, variables);
            results.push(result.output);
        }
        return results;
    }
    validateWorkflow(workflow) {
        // Check for circular dependencies
        const visited = new Set();
        const recursionStack = new Set();
        const hasCycle = (taskId) => {
            visited.add(taskId);
            recursionStack.add(taskId);
            const task = workflow.tasks.find(t => t.id === taskId);
            if (!task)
                return false;
            for (const dep of task.dependencies) {
                if (!visited.has(dep)) {
                    if (hasCycle(dep))
                        return true;
                }
                else if (recursionStack.has(dep)) {
                    return true;
                }
            }
            recursionStack.delete(taskId);
            return false;
        };
        for (const task of workflow.tasks) {
            if (!visited.has(task.id) && hasCycle(task.id)) {
                throw new Error('Workflow contains circular dependencies');
            }
        }
        // Validate entry point exists
        const entryTask = workflow.tasks.find(t => t.id === workflow.entryPoint);
        const entryGroup = workflow.groups.find(g => g.id === workflow.entryPoint);
        if (!entryTask && !entryGroup) {
            throw new Error('Workflow entry point not found');
        }
    }
    buildDependencyGraph(tasks) {
        const graph = new Map();
        for (const task of tasks) {
            if (!graph.has(task.id)) {
                graph.set(task.id, new Set());
            }
            for (const dep of task.dependencies) {
                const depSet = graph.get(dep) || new Set();
                depSet.add(task.id);
                graph.set(dep, depSet);
            }
        }
        return graph;
    }
    calculateRetryDelay(retryCount) {
        const delay = Math.min(this.config.retryPolicy.initialDelay * Math.pow(this.config.retryPolicy.backoffMultiplier, retryCount - 1), this.config.retryPolicy.maxDelay);
        return delay;
    }
    interpolateVariables(template, variables) {
        if (!variables)
            return template;
        return template.replace(/\$\{([^}]+)\}/g, (match, key) => {
            const value = this.getNestedValue(variables, key);
            return value !== undefined ? String(value) : match;
        });
    }
    getNestedValue(obj, path) {
        const keys = path.split('.');
        let current = obj;
        for (const key of keys) {
            if (current && typeof current === 'object' && key in current) {
                current = current[key];
            }
            else {
                return undefined;
            }
        }
        return current;
    }
    completeManualTask(taskId, output, error) {
        this.emit(`task:manual:${taskId}`, { output, error });
    }
}
//# sourceMappingURL=orchestrator.js.map