import { promises as fs } from "fs";
import { join } from "path";
import { homedir } from "os";
import { EventEmitter } from "events";
import { logger } from "../utils/globalLogger.js";
export class TaskManager extends EventEmitter {
    constructor(container, taskListId) {
        super();
        this.container = container;
        this.taskListId = taskListId;
        this.baseDir = join(homedir(), ".wave", "tasks");
    }
    getTaskListId() {
        return this.taskListId;
    }
    setTaskListId(taskListId) {
        this.taskListId = taskListId;
    }
    /**
     * Syncs the task list ID with the current session's root session ID.
     * This is typically called when the session is cleared or compressed.
     */
    async syncWithSession() {
        const messageManager = this.container.get("MessageManager");
        if (!messageManager)
            return;
        const rootSessionId = messageManager.getRootSessionId();
        if (this.taskListId !== rootSessionId && !process.env.WAVE_TASK_LIST_ID) {
            this.setTaskListId(rootSessionId);
            await this.refreshTasks();
        }
    }
    getSessionDir() {
        return join(this.baseDir, this.taskListId);
    }
    getTaskPath(taskId) {
        return join(this.getSessionDir(), `${taskId}.json`);
    }
    getLockPath() {
        return join(this.getSessionDir(), `.lock`);
    }
    async ensureSessionDir() {
        await fs.mkdir(this.getSessionDir(), { recursive: true });
    }
    async withLock(operation) {
        const lockPath = this.getLockPath();
        let lockHandle;
        const maxRetries = 100;
        const retryDelay = process.env.NODE_ENV === "test" ? 1 : 100;
        await this.ensureSessionDir();
        for (let i = 0; i < maxRetries; i++) {
            try {
                lockHandle = await fs.open(lockPath, "wx");
                break;
            }
            catch (error) {
                if (error.code === "EEXIST") {
                    if (i === maxRetries - 1) {
                        throw new Error(`Could not acquire lock for task list ${this.taskListId} after ${maxRetries} retries`);
                    }
                    await new Promise((resolve) => setTimeout(resolve, retryDelay));
                    continue;
                }
                throw error;
            }
        }
        try {
            return await operation();
        }
        finally {
            if (lockHandle) {
                await lockHandle.close();
            }
            try {
                await fs.unlink(lockPath);
            }
            catch (error) {
                logger.error(`Failed to release lock for task list ${this.taskListId}:`, error);
            }
        }
    }
    validateTask(task) {
        if (!task.id || typeof task.id !== "string")
            throw new Error("Invalid task ID");
        if (!task.subject || typeof task.subject !== "string")
            throw new Error("Invalid task subject");
        if (!task.description || typeof task.description !== "string")
            throw new Error("Invalid task description");
        if (!task.status ||
            !["pending", "in_progress", "completed", "deleted"].includes(task.status)) {
            throw new Error(`Invalid task status: ${task.status}`);
        }
    }
    async createTask(task) {
        return await this.withLock(async () => {
            const taskId = await this.getNextTaskId();
            const fullTask = { ...task, id: taskId };
            this.validateTask(fullTask);
            const taskPath = this.getTaskPath(taskId);
            const content = JSON.stringify(fullTask, null, 2);
            await fs.writeFile(taskPath, content, "utf8");
            this.emit("tasksChange", this.taskListId);
            logger.debug(`Task ${taskId} created in task list ${this.taskListId}`);
            return taskId;
        });
    }
    async getTask(taskId) {
        const taskPath = this.getTaskPath(taskId);
        try {
            const content = await fs.readFile(taskPath, "utf8");
            try {
                return JSON.parse(content.trim());
            }
            catch (parseError) {
                logger.error(`Failed to parse task file ${taskPath}:`, parseError);
                logger.error(`Corrupted content: ${content}`);
                throw parseError;
            }
        }
        catch (error) {
            if (error.code === "ENOENT") {
                return null;
            }
            throw error;
        }
    }
    async updateTask(task) {
        this.validateTask(task);
        await this.withLock(async () => {
            const taskPath = this.getTaskPath(task.id);
            const content = JSON.stringify(task, null, 2);
            await fs.writeFile(taskPath, content, "utf8");
            this.emit("tasksChange", this.taskListId);
            logger.debug(`Task ${task.id} updated in task list ${this.taskListId}`);
        });
    }
    async deleteTask(taskId) {
        await this.withLock(async () => {
            const taskPath = this.getTaskPath(taskId);
            try {
                await fs.unlink(taskPath);
                this.emit("tasksChange", this.taskListId);
                logger.debug(`Task ${taskId} deleted from task list ${this.taskListId}`);
            }
            catch (error) {
                if (error.code !== "ENOENT") {
                    throw error;
                }
            }
        });
    }
    async cleanupOldTaskLists(days = 30) {
        const threshold = Date.now() - days * 24 * 60 * 60 * 1000;
        try {
            const dirs = await fs.readdir(this.baseDir);
            for (const dir of dirs) {
                if (dir === this.taskListId)
                    continue;
                const dirPath = join(this.baseDir, dir);
                const stats = await fs.stat(dirPath);
                if (!stats.isDirectory())
                    continue;
                // Check mtime of the directory and its contents
                let latestMtime = stats.mtimeMs;
                const files = await fs.readdir(dirPath);
                for (const file of files) {
                    const filePath = join(dirPath, file);
                    const fileStats = await fs.stat(filePath);
                    if (fileStats.mtimeMs > latestMtime) {
                        latestMtime = fileStats.mtimeMs;
                    }
                }
                if (latestMtime < threshold) {
                    logger.info(`Cleaning up old task list directory: ${dirPath}`);
                    await fs.rm(dirPath, { recursive: true, force: true });
                }
            }
        }
        catch (error) {
            if (error.code !== "ENOENT") {
                logger.error("Failed to cleanup old task lists:", error);
            }
        }
    }
    async listTasks() {
        const sessionDir = this.getSessionDir();
        try {
            const files = await fs.readdir(sessionDir);
            const taskFiles = files.filter((f) => f.endsWith(".json"));
            const tasks = await Promise.all(taskFiles.map(async (file) => {
                const taskPath = join(sessionDir, file);
                try {
                    const content = await fs.readFile(taskPath, "utf8");
                    return JSON.parse(content.trim());
                }
                catch (error) {
                    logger.error(`Failed to read or parse task file ${taskPath}:`, error);
                    return null;
                }
            }));
            return tasks.filter((t) => t !== null);
        }
        catch (error) {
            if (error.code === "ENOENT") {
                return [];
            }
            throw error;
        }
    }
    async getNextTaskId() {
        const tasks = await this.listTasks();
        if (tasks.length === 0) {
            return "1";
        }
        const ids = tasks.map((t) => parseInt(t.id, 10)).filter((id) => !isNaN(id));
        if (ids.length === 0) {
            return (tasks.length + 1).toString();
        }
        return (Math.max(...ids) + 1).toString();
    }
    /**
     * Refreshes the task list by re-reading tasks from disk and emitting a change event.
     */
    async refreshTasks() {
        this.emit("tasksChange", this.taskListId);
    }
}
