import { spawn } from "child_process";
import * as os from "os";
import * as fs from "fs";
import * as path from "path";
import { stripAnsiColors } from "../utils/stringUtils.js";
import { logger } from "../utils/globalLogger.js";
export class BackgroundTaskManager {
    constructor(container, options) {
        this.container = container;
        this.tasks = new Map();
        this.nextId = 1;
        this.callbacks = options.callbacks || {};
        this.workdir = options.workdir;
    }
    get notificationQueue() {
        return this.container.get("NotificationQueue");
    }
    notifyTasksChange() {
        this.callbacks.onBackgroundTasksChange?.(Array.from(this.tasks.values()));
    }
    generateId() {
        return `task_${process.pid}_${this.nextId++}`;
    }
    addTask(task) {
        this.tasks.set(task.id, task);
        this.notifyTasksChange();
    }
    getTask(id) {
        return this.tasks.get(id);
    }
    getAllTasks() {
        return Array.from(this.tasks.values());
    }
    startShell(command, timeout) {
        const id = this.generateId();
        const startTime = Date.now();
        const child = spawn(command, {
            shell: true,
            stdio: "pipe",
            cwd: this.workdir,
            env: {
                ...process.env,
            },
        });
        // Create log file
        const logPath = path.join(os.tmpdir(), `wave-task-${id}.log`);
        const logStream = fs.createWriteStream(logPath, { flags: "w" });
        const shell = {
            id,
            type: "shell",
            process: child,
            command,
            startTime,
            status: "running",
            stdout: "",
            stderr: "",
            outputPath: logPath,
            onStop: () => {
                try {
                    logStream.end();
                    if (child.pid) {
                        process.kill(-child.pid, "SIGTERM");
                        const forceKillTimer = setTimeout(() => {
                            if (child.pid && !child.killed) {
                                try {
                                    process.kill(-child.pid, "SIGKILL");
                                }
                                catch (error) {
                                    logger.error("Failed to force kill process:", error);
                                }
                            }
                        }, 1000);
                        forceKillTimer.unref();
                    }
                    else {
                        child.kill("SIGTERM");
                    }
                }
                catch {
                    child.kill("SIGTERM");
                }
            },
        };
        this.tasks.set(id, shell);
        this.notifyTasksChange();
        // Set up timeout if specified
        let timeoutHandle;
        if (timeout && timeout > 0) {
            timeoutHandle = setTimeout(() => {
                if (shell.status === "running") {
                    const timeoutMsg = "\n\nCommand timed out";
                    shell.stderr += timeoutMsg;
                    if (logStream.writable) {
                        logStream.write(timeoutMsg);
                    }
                    this.stopTask(id);
                }
            }, timeout);
        }
        const onStdout = (data) => {
            const stripped = stripAnsiColors(data.toString());
            shell.stdout += stripped;
            if (logStream.writable) {
                logStream.write(stripped);
            }
            this.notifyTasksChange();
        };
        const onStderr = (data) => {
            const stripped = stripAnsiColors(data.toString());
            shell.stderr += stripped;
            if (logStream.writable) {
                logStream.write(stripped);
            }
            this.notifyTasksChange();
        };
        const onExit = (code) => {
            if (timeoutHandle) {
                clearTimeout(timeoutHandle);
            }
            if (logStream.writable) {
                logStream.end();
            }
            shell.status = code === 0 ? "completed" : "failed";
            shell.exitCode = code ?? 0;
            shell.endTime = Date.now();
            shell.runtime = shell.endTime - startTime;
            this.notifyTasksChange();
            // Enqueue completion notification
            const notificationQueue = this.container.has("NotificationQueue")
                ? this.container.get("NotificationQueue")
                : undefined;
            if (notificationQueue) {
                const statusStr = shell.status;
                const summary = `Command "${command}" ${statusStr} with exit code ${code ?? 0}`;
                notificationQueue.enqueue(`<task-notification>\n<task-id>${id}</task-id>\n<task-type>shell</task-type>\n<output-file>${logPath}</output-file>\n<status>${statusStr}</status>\n<summary>${summary}</summary>\n</task-notification>`);
            }
        };
        const onError = (error) => {
            if (timeoutHandle) {
                clearTimeout(timeoutHandle);
            }
            const stripped = `\nProcess error: ${stripAnsiColors(error.message)}`;
            shell.status = "failed";
            shell.stderr += stripped;
            if (logStream.writable) {
                logStream.write(stripped);
                logStream.end();
            }
            shell.exitCode = 1;
            shell.endTime = Date.now();
            shell.runtime = shell.endTime - startTime;
            this.notifyTasksChange();
            // Enqueue error notification
            const notificationQueue = this.container.has("NotificationQueue")
                ? this.container.get("NotificationQueue")
                : undefined;
            if (notificationQueue) {
                const summary = `Command "${command}" failed with error: ${stripAnsiColors(error.message)}`;
                notificationQueue.enqueue(`<task-notification>\n<task-id>${id}</task-id>\n<task-type>shell</task-type>\n<output-file>${logPath}</output-file>\n<status>failed</status>\n<summary>${summary}</summary>\n</task-notification>`);
            }
        };
        child.stdout?.on("data", onStdout);
        child.stderr?.on("data", onStderr);
        child.on("exit", onExit);
        child.on("error", onError);
        const detach = () => {
            child.stdout?.off("data", onStdout);
            child.stderr?.off("data", onStderr);
            child.off("exit", onExit);
            child.off("error", onError);
            if (timeoutHandle) {
                clearTimeout(timeoutHandle);
            }
            logStream.end();
            this.tasks.delete(id);
            this.notifyTasksChange();
        };
        return { id, child, detach };
    }
    adoptProcess(child, command, initialStdout = "", initialStderr = "") {
        const id = this.generateId();
        const startTime = Date.now();
        // Create log file
        const logPath = path.join(os.tmpdir(), `wave-task-${id}.log`);
        const logStream = fs.createWriteStream(logPath, { flags: "w" });
        // Write initial output to log file
        if (initialStdout) {
            logStream.write(stripAnsiColors(initialStdout));
        }
        if (initialStderr) {
            logStream.write(stripAnsiColors(initialStderr));
        }
        const shell = {
            id,
            type: "shell",
            process: child,
            command,
            startTime,
            status: "running",
            stdout: initialStdout,
            stderr: initialStderr,
            outputPath: logPath,
            onStop: () => {
                try {
                    logStream.end();
                    if (child.pid) {
                        process.kill(-child.pid, "SIGTERM");
                        const forceKillTimer = setTimeout(() => {
                            if (child.pid && !child.killed) {
                                try {
                                    process.kill(-child.pid, "SIGKILL");
                                }
                                catch (error) {
                                    logger.error("Failed to force kill process:", error);
                                }
                            }
                        }, 1000);
                        forceKillTimer.unref();
                    }
                    else {
                        child.kill("SIGTERM");
                    }
                }
                catch {
                    child.kill("SIGTERM");
                }
            },
        };
        this.tasks.set(id, shell);
        this.notifyTasksChange();
        child.stdout?.on("data", (data) => {
            const stripped = stripAnsiColors(data.toString());
            shell.stdout += stripped;
            if (logStream.writable) {
                logStream.write(stripped);
            }
            this.notifyTasksChange();
        });
        child.stderr?.on("data", (data) => {
            const stripped = stripAnsiColors(data.toString());
            shell.stderr += stripped;
            if (logStream.writable) {
                logStream.write(stripped);
            }
            this.notifyTasksChange();
        });
        child.on("exit", (code) => {
            if (logStream.writable) {
                logStream.end();
            }
            shell.status = code === 0 ? "completed" : "failed";
            shell.exitCode = code ?? 0;
            shell.endTime = Date.now();
            shell.runtime = shell.endTime - startTime;
            this.notifyTasksChange();
            // Enqueue completion notification
            const notificationQueue = this.container.has("NotificationQueue")
                ? this.container.get("NotificationQueue")
                : undefined;
            if (notificationQueue) {
                const statusStr = shell.status;
                const summary = `Command "${command}" ${statusStr} with exit code ${code ?? 0}`;
                notificationQueue.enqueue(`<task-notification>\n<task-id>${id}</task-id>\n<task-type>shell</task-type>\n<output-file>${logPath}</output-file>\n<status>${statusStr}</status>\n<summary>${summary}</summary>\n</task-notification>`);
            }
        });
        child.on("error", (error) => {
            const stripped = `\nProcess error: ${stripAnsiColors(error.message)}`;
            shell.status = "failed";
            shell.stderr += stripped;
            if (logStream.writable) {
                logStream.write(stripped);
                logStream.end();
            }
            shell.exitCode = 1;
            shell.endTime = Date.now();
            shell.runtime = shell.endTime - startTime;
            this.notifyTasksChange();
            // Enqueue error notification
            const notificationQueue = this.container.has("NotificationQueue")
                ? this.container.get("NotificationQueue")
                : undefined;
            if (notificationQueue) {
                const summary = `Command "${command}" failed with error: ${stripAnsiColors(error.message)}`;
                notificationQueue.enqueue(`<task-notification>\n<task-id>${id}</task-id>\n<task-type>shell</task-type>\n<output-file>${logPath}</output-file>\n<status>failed</status>\n<summary>${summary}</summary>\n</task-notification>`);
            }
        });
        return id;
    }
    getOutput(id, filter) {
        const task = this.tasks.get(id);
        if (!task) {
            return null;
        }
        let stdout = task.stdout;
        let stderr = task.stderr;
        // Apply regex filter if provided
        if (filter) {
            try {
                const regex = new RegExp(filter);
                stdout = stdout
                    .split("\n")
                    .filter((line) => regex.test(line))
                    .join("\n");
                stderr = stderr
                    .split("\n")
                    .filter((line) => regex.test(line))
                    .join("\n");
            }
            catch (error) {
                logger.warn(`Invalid filter regex: ${filter}`, error);
            }
        }
        return {
            stdout,
            stderr,
            status: task.status,
            outputPath: task.outputPath,
            type: task.type,
            exitCode: task.exitCode,
        };
    }
    stopTask(id) {
        const task = this.tasks.get(id);
        if (!task || task.status !== "running") {
            return false;
        }
        if (task.onStop) {
            try {
                const result = task.onStop();
                if (result instanceof Promise) {
                    result.catch((error) => {
                        logger.error("Error in background task onStop callback:", error);
                    });
                }
            }
            catch (error) {
                logger.error("Error in background task onStop callback:", error);
            }
        }
        // If it's a subagent task, we should also notify the subagent manager to cleanup
        // However, to avoid circular dependency, we rely on the onStop callback
        // which is already set to instance.aiManager.abortAIMessage()
        // The subagentManager.cleanupInstance will be called by the tool or by status change.
        task.status = "killed";
        task.endTime = Date.now();
        task.runtime = task.endTime - task.startTime;
        this.notifyTasksChange();
        // Enqueue killed notification
        const notificationQueue = this.container.has("NotificationQueue")
            ? this.container.get("NotificationQueue")
            : undefined;
        if (notificationQueue) {
            const description = task.description || "";
            const command = task.command || "";
            const summary = task.type === "subagent"
                ? `Agent task "${description}" was stopped`
                : `Command "${command}" was stopped`;
            notificationQueue.enqueue(`<task-notification>\n<task-id>${id}</task-id>\n<task-type>${task.type}</task-type>\n<status>killed</status>\n<summary>${summary}</summary>\n</task-notification>`);
        }
        return true;
    }
    cleanup() {
        // Kill all running tasks
        for (const [id, task] of this.tasks) {
            if (task.status === "running") {
                this.stopTask(id);
            }
        }
        this.tasks.clear();
        this.notifyTasksChange();
    }
}
