import fs from 'fs';
import path from 'path';
import os from 'os';
import { EventEmitter } from 'events';
import { spawn } from 'child_process';
/**
 * Background Tasks Manager - Inspired by Codex CLI Cloud
 * Allows running AI tasks in the background
 */
export class BackgroundTaskManager extends EventEmitter {
    tasks = new Map();
    runningProcesses = new Map();
    tasksDir;
    maxConcurrent;
    constructor(maxConcurrent = 3) {
        super();
        this.maxConcurrent = maxConcurrent;
        this.tasksDir = path.join(os.homedir(), '.codebuddy', 'tasks');
        this.ensureTasksDir();
        this.loadTasks();
        // Wire up notification delivery for task completion/failure
        this.on('task-completed', (task) => this.notifyTaskEvent(task, 'completed'));
        this.on('task-failed', (task) => this.notifyTaskEvent(task, 'failed'));
    }
    /**
     * Send notification for task events via configured channel
     */
    async notifyTaskEvent(task, event) {
        if (!task.notifyOn?.includes(event) || !task.notifyChannel)
            return;
        try {
            const { getChannelManager } = await import('../channels/index.js');
            const manager = getChannelManager();
            const [channelType, channelId] = task.notifyChannel.includes(':')
                ? task.notifyChannel.split(':', 2)
                : [task.notifyChannel, 'default'];
            const icon = event === 'completed' ? '✅' : '❌';
            const content = `${icon} Task **${task.id}**: ${event}\n${task.result?.output?.slice(0, 500) || task.result?.error || ''}`;
            await manager.sendToUser(channelType, channelId, content, event === 'failed' ? 'high' : 'normal');
        }
        catch {
            // Notification delivery is best-effort
        }
    }
    /**
     * Ensure tasks directory exists
     */
    ensureTasksDir() {
        if (!fs.existsSync(this.tasksDir)) {
            fs.mkdirSync(this.tasksDir, { recursive: true });
        }
    }
    /**
     * Load existing tasks from disk
     */
    loadTasks() {
        try {
            const files = fs.readdirSync(this.tasksDir);
            for (const file of files) {
                if (!file.endsWith('.json'))
                    continue;
                try {
                    const taskPath = path.join(this.tasksDir, file);
                    const task = JSON.parse(fs.readFileSync(taskPath, 'utf-8'));
                    task.createdAt = new Date(task.createdAt);
                    if (task.startedAt)
                        task.startedAt = new Date(task.startedAt);
                    if (task.completedAt)
                        task.completedAt = new Date(task.completedAt);
                    // Reset running tasks to pending (they were interrupted)
                    if (task.status === 'running') {
                        task.status = 'pending';
                    }
                    this.tasks.set(task.id, task);
                }
                catch (_error) {
                    // Skip invalid task files
                }
            }
        }
        catch (_error) {
            // Directory doesn't exist or can't be read
        }
    }
    /**
     * Save task to disk
     */
    saveTask(task) {
        const taskPath = path.join(this.tasksDir, `${task.id}.json`);
        fs.writeFileSync(taskPath, JSON.stringify(task, null, 2));
    }
    /**
     * Delete task from disk
     */
    deleteTaskFile(taskId) {
        const taskPath = path.join(this.tasksDir, `${taskId}.json`);
        if (fs.existsSync(taskPath)) {
            fs.unlinkSync(taskPath);
        }
    }
    /**
     * Generate unique task ID
     */
    generateId() {
        const timestamp = Date.now().toString(36);
        const random = Math.random().toString(36).substring(2, 8);
        return `task_${timestamp}_${random}`;
    }
    /**
     * Create a new background task
     */
    createTask(prompt, options = {}) {
        const task = {
            id: this.generateId(),
            prompt,
            status: 'pending',
            priority: options.priority || 'normal',
            createdAt: new Date(),
            workingDirectory: options.workingDirectory || process.cwd(),
            model: options.model,
            maxToolRounds: options.maxToolRounds,
            tags: options.tags
        };
        this.tasks.set(task.id, task);
        this.saveTask(task);
        this.emit('task-created', task);
        if (options.runImmediately) {
            this.runTask(task.id);
        }
        return task;
    }
    /**
     * Run a task in background
     */
    async runTask(taskId) {
        const task = this.tasks.get(taskId);
        if (!task) {
            throw new Error(`Task not found: ${taskId}`);
        }
        if (task.status === 'running') {
            throw new Error(`Task is already running: ${taskId}`);
        }
        // Check concurrent limit
        const runningCount = Array.from(this.tasks.values())
            .filter(t => t.status === 'running').length;
        if (runningCount >= this.maxConcurrent) {
            throw new Error(`Maximum concurrent tasks (${this.maxConcurrent}) reached. Wait for a task to complete.`);
        }
        task.status = 'running';
        task.startedAt = new Date();
        this.saveTask(task);
        this.emit('task-started', task);
        // Run grok in headless mode as a child process
        const args = [
            '--prompt', task.prompt,
            '--directory', task.workingDirectory
        ];
        if (task.model) {
            args.push('--model', task.model);
        }
        if (task.maxToolRounds) {
            args.push('--max-tool-rounds', task.maxToolRounds.toString());
        }
        // Find grok executable
        const codebuddyPath = process.argv[1]; // Current script path
        const child = spawn('node', [codebuddyPath, ...args], {
            cwd: task.workingDirectory,
            env: process.env,
            stdio: ['pipe', 'pipe', 'pipe']
        });
        this.runningProcesses.set(taskId, child);
        let stdout = '';
        let stderr = '';
        child.stdout?.on('data', (data) => {
            stdout += data.toString();
            this.emit('task-output', { taskId, data: data.toString() });
        });
        child.stderr?.on('data', (data) => {
            stderr += data.toString();
        });
        child.on('close', (code) => {
            this.runningProcesses.delete(taskId);
            const updatedTask = this.tasks.get(taskId);
            if (updatedTask) {
                updatedTask.completedAt = new Date();
                updatedTask.status = code === 0 ? 'completed' : 'failed';
                updatedTask.result = {
                    success: code === 0,
                    output: stdout,
                    error: stderr || undefined,
                    duration: updatedTask.startedAt
                        ? Date.now() - updatedTask.startedAt.getTime()
                        : undefined
                };
                this.saveTask(updatedTask);
                this.emit('task-completed', updatedTask);
            }
        });
        child.on('error', (error) => {
            this.runningProcesses.delete(taskId);
            const updatedTask = this.tasks.get(taskId);
            if (updatedTask) {
                updatedTask.completedAt = new Date();
                updatedTask.status = 'failed';
                updatedTask.result = {
                    success: false,
                    error: error.message
                };
                this.saveTask(updatedTask);
                this.emit('task-failed', updatedTask, error);
            }
        });
    }
    /**
     * Cancel a running task
     */
    cancelTask(taskId) {
        const task = this.tasks.get(taskId);
        if (!task) {
            return false;
        }
        const process = this.runningProcesses.get(taskId);
        if (process) {
            process.kill('SIGTERM');
            this.runningProcesses.delete(taskId);
        }
        task.status = 'cancelled';
        task.completedAt = new Date();
        this.saveTask(task);
        this.emit('task-cancelled', task);
        return true;
    }
    /**
     * Get a task by ID
     */
    getTask(taskId) {
        return this.tasks.get(taskId);
    }
    /**
     * Get all tasks
     */
    getTasks(options = {}) {
        let tasks = Array.from(this.tasks.values());
        // Filter by status
        if (options.status) {
            tasks = tasks.filter(t => t.status === options.status);
        }
        // Filter completed tasks if not explicitly included
        if (!options.includeCompleted) {
            tasks = tasks.filter(t => !['completed', 'cancelled'].includes(t.status));
        }
        // Sort by priority and creation time
        tasks.sort((a, b) => {
            const priorityOrder = { high: 0, normal: 1, low: 2 };
            const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
            if (priorityDiff !== 0)
                return priorityDiff;
            return b.createdAt.getTime() - a.createdAt.getTime();
        });
        // Apply limit
        if (options.limit) {
            tasks = tasks.slice(0, options.limit);
        }
        return tasks;
    }
    /**
     * Delete a task
     */
    deleteTask(taskId) {
        const task = this.tasks.get(taskId);
        if (!task) {
            return false;
        }
        // Cancel if running
        if (task.status === 'running') {
            this.cancelTask(taskId);
        }
        this.tasks.delete(taskId);
        this.deleteTaskFile(taskId);
        this.emit('task-deleted', taskId);
        return true;
    }
    /**
     * Clear completed tasks
     */
    clearCompleted() {
        const completed = Array.from(this.tasks.values())
            .filter(t => ['completed', 'cancelled', 'failed'].includes(t.status));
        for (const task of completed) {
            this.tasks.delete(task.id);
            this.deleteTaskFile(task.id);
        }
        return completed.length;
    }
    /**
     * Retry a failed task
     */
    retryTask(taskId) {
        const originalTask = this.tasks.get(taskId);
        if (!originalTask || originalTask.status !== 'failed') {
            return null;
        }
        const newTask = this.createTask(originalTask.prompt, {
            priority: originalTask.priority,
            workingDirectory: originalTask.workingDirectory,
            model: originalTask.model,
            maxToolRounds: originalTask.maxToolRounds,
            tags: [...(originalTask.tags || []), 'retry'],
            runImmediately: true
        });
        return newTask;
    }
    /**
     * Get task statistics
     */
    getStats() {
        const tasks = Array.from(this.tasks.values());
        return {
            total: tasks.length,
            pending: tasks.filter(t => t.status === 'pending').length,
            running: tasks.filter(t => t.status === 'running').length,
            completed: tasks.filter(t => t.status === 'completed').length,
            failed: tasks.filter(t => t.status === 'failed').length,
            cancelled: tasks.filter(t => t.status === 'cancelled').length
        };
    }
    /**
     * Format task for display
     */
    formatTask(task) {
        const statusEmojis = {
            pending: '⏳',
            running: '🔄',
            completed: '✅',
            failed: '❌',
            cancelled: '🚫'
        };
        const priorityEmojis = {
            high: '🔴',
            normal: '🟡',
            low: '🟢'
        };
        let output = `${statusEmojis[task.status]} [${task.id.slice(0, 8)}] ${priorityEmojis[task.priority]}\n`;
        output += `   ${task.prompt.substring(0, 60)}${task.prompt.length > 60 ? '...' : ''}\n`;
        output += `   Created: ${task.createdAt.toLocaleString()}`;
        if (task.result?.duration) {
            output += ` | Duration: ${Math.round(task.result.duration / 1000)}s`;
        }
        return output;
    }
    /**
     * Format tasks list for display
     */
    formatTasksList(options = { includeCompleted: true, limit: 20 }) {
        const tasks = this.getTasks(options);
        if (tasks.length === 0) {
            return 'No background tasks.\n\n💡 Create one with: grok task "your prompt" --background';
        }
        let output = '📋 Background Tasks\n' + '═'.repeat(60) + '\n\n';
        for (const task of tasks) {
            output += this.formatTask(task) + '\n\n';
        }
        const stats = this.getStats();
        output += '─'.repeat(60) + '\n';
        output += `Total: ${stats.total} | Running: ${stats.running} | Pending: ${stats.pending} | Completed: ${stats.completed}\n`;
        output += '\n💡 Commands: /tasks, /task run <id>, /task cancel <id>, /task clear';
        return output;
    }
    /**
     * Clean up resources
     */
    dispose() {
        // Cancel all running tasks
        for (const [taskId] of this.runningProcesses) {
            this.cancelTask(taskId);
        }
        this.tasks.clear();
        this.removeAllListeners();
    }
}
// Singleton instance
let backgroundTaskManagerInstance = null;
export function getBackgroundTaskManager(maxConcurrent) {
    if (!backgroundTaskManagerInstance) {
        backgroundTaskManagerInstance = new BackgroundTaskManager(maxConcurrent);
    }
    return backgroundTaskManagerInstance;
}
export function resetBackgroundTaskManager() {
    if (backgroundTaskManagerInstance) {
        backgroundTaskManagerInstance.dispose();
    }
    backgroundTaskManagerInstance = null;
}
//# sourceMappingURL=background-tasks.js.map