import { DurableTaskQueue } from "../../tasks/queue.js";
import { runAgentTask } from "./agentRunner.js";
export class AgentRunService {
    state;
    config;
    constructor(state, config) {
        this.state = state;
        this.config = config;
    }
    start(input) {
        const runId = this.state.createRun({
            projectPath: this.config.projectPath,
            model: this.config.model,
            message: `agent:${input.agent} ${input.task}`,
            status: "running",
        });
        const taskId = this.state.createTask({
            runId,
            agent: input.agent,
            title: input.task,
            detail: "queued agent task",
        });
        this.state.appendEvent(runId, "agent_run_started", {
            agent: input.agent,
            task: input.task,
            task_id: taskId,
        });
        this.ensureRunnableAgentRunJob({
            runId,
            payload: { runId, firstTaskId: taskId },
            detail: "agent run queued",
            maxAttempts: 5,
        });
        return { runId, taskId };
    }
    addTask(input) {
        const run = this.state.getRun(input.runId);
        if (!run)
            throw new Error(`run not found: ${input.runId}`);
        if (run.status === "cancelled" || run.status === "failed") {
            throw new Error(`cannot add task to ${run.status} run: ${input.runId}`);
        }
        if (run.status === "succeeded") {
            this.state.updateRunStatus(input.runId, "running", "agent task added");
        }
        const taskId = this.state.createTask({
            runId: input.runId,
            agent: input.agent,
            title: input.task,
            detail: "queued agent task",
        });
        if (input.dependsOnTaskId)
            this.state.addTaskDependency(taskId, input.dependsOnTaskId);
        this.state.appendEvent(input.runId, "agent_task_added", {
            task_id: taskId,
            agent: input.agent,
            task: input.task,
            depends_on_task_id: input.dependsOnTaskId,
        });
        this.ensureRunnableAgentRunJob({
            runId: input.runId,
            payload: { runId: input.runId },
            detail: "agent task queued",
            maxAttempts: 5,
        });
        return taskId;
    }
    list(limit = 20) {
        const jobRunIds = new Set(this.state
            .listJobs({ kind: "agent_run", limit: Math.max(limit * 10, 100) })
            .map((job) => job.runId)
            .filter((runId) => Boolean(runId)));
        return this.state
            .listRuns(Math.max(limit * 3, limit))
            .filter((run) => run.message.startsWith("agent:") || jobRunIds.has(run.id))
            .slice(0, limit)
            .map((run) => ({
            run,
            tasks: this.state.listTasks(run.id),
        }));
    }
    detail(runId, eventLimit = 30) {
        const run = this.state.getRun(runId);
        if (!run)
            throw new Error(`run not found: ${runId}`);
        return {
            run,
            tasks: this.state.listTasks(runId),
            events: this.state.listEvents(runId, eventLimit).map((event) => ({
                kind: event.kind,
                payload: event.payload,
                createdAtMs: event.createdAtMs,
            })),
        };
    }
    async step(input) {
        const run = this.state.getRun(input.runId);
        if (!run)
            throw new Error(`run not found: ${input.runId}`);
        const queue = new DurableTaskQueue(this.state);
        const task = queue.claimNext(input.runId, "agent-runner");
        if (!task) {
            const finalStatus = this.finalizeIfComplete(input.runId);
            return {
                runId: input.runId,
                status: "idle",
                message: finalStatus ? `run ${input.runId} is ${finalStatus}` : "no runnable agent task",
            };
        }
        try {
            const result = await runAgentTask({
                name: task.agent,
                task: task.title,
                config: this.config,
                provider: input.provider,
                permissions: input.permissions,
            });
            for (const turn of result.turns) {
                this.state.saveCheckpoint(input.runId, `agent_${task.id}_turn_${turn.index}_envelope`, turn.envelope);
                this.state.appendEvent(input.runId, "agent_turn_completed", {
                    task_id: task.id,
                    agent: task.agent,
                    turn: turn.index,
                    status: turn.execution.status,
                    action_count: turn.envelope.actions.length,
                });
            }
            this.state.recordActionResults(input.runId, result.execution);
            if (result.execution.status === "succeeded") {
                queue.complete(task.id, result.execution.final_message || "agent task completed");
                this.finalizeIfComplete(input.runId);
                return {
                    runId: input.runId,
                    task,
                    status: "succeeded",
                    message: result.execution.final_message || "agent task completed",
                    result,
                };
            }
            queue.fail(task.id, result.execution.final_message || "agent task failed");
            this.state.updateRunStatus(input.runId, "failed", result.execution.final_message || "agent task failed");
            this.finishAgentRunJobs(input.runId, "failed", result.execution.final_message || "agent task failed");
            return {
                runId: input.runId,
                task,
                status: "failed",
                message: result.execution.final_message || "agent task failed",
                result,
            };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            queue.fail(task.id, message);
            this.state.updateRunStatus(input.runId, "failed", message);
            this.finishAgentRunJobs(input.runId, "failed", message);
            this.state.appendEvent(input.runId, "agent_run_failed", {
                task_id: task.id,
                agent: task.agent,
                message,
            });
            return { runId: input.runId, task, status: "failed", message };
        }
    }
    async runNow(input) {
        const started = this.start({ agent: input.agent, task: input.task });
        const stepped = await this.step({
            runId: started.runId,
            provider: input.provider,
            permissions: input.permissions,
        });
        return { ...started, ...stepped };
    }
    async drain(input) {
        const maxSteps = Math.min(50, Math.max(1, Math.trunc(input.maxSteps ?? 10)));
        const steps = [];
        for (let index = 0; index < maxSteps; index += 1) {
            const step = await this.step({
                runId: input.runId,
                provider: input.provider,
                permissions: input.permissions,
            });
            steps.push(step);
            if (step.status === "failed") {
                return { runId: input.runId, steps, status: "failed", message: step.message };
            }
            if (step.status === "idle") {
                const run = this.state.getRun(input.runId);
                return {
                    runId: input.runId,
                    steps,
                    status: run?.status === "succeeded" ? "succeeded" : "idle",
                    message: step.message,
                };
            }
        }
        return {
            runId: input.runId,
            steps,
            status: "max_steps",
            message: `stopped after ${maxSteps} agent steps`,
        };
    }
    finalizeIfComplete(runId) {
        const tasks = this.state.listTasks(runId);
        if (tasks.length === 0)
            return undefined;
        if (tasks.some((task) => task.status === "failed" || task.status === "cancelled")) {
            this.state.updateRunStatus(runId, "failed", "agent run failed");
            this.finishAgentRunJobs(runId, "failed", "agent run failed");
            return "failed";
        }
        if (tasks.every((task) => task.status === "succeeded")) {
            this.state.updateRunStatus(runId, "succeeded", "agent run completed");
            this.finishAgentRunJobs(runId, "succeeded", "agent run completed");
            return "succeeded";
        }
        return undefined;
    }
    ensureRunnableAgentRunJob(input) {
        const job = this.state.ensureRunJob({
            runId: input.runId,
            kind: "agent_run",
            payload: input.payload,
            detail: input.detail,
            maxAttempts: input.maxAttempts,
        });
        if (job.status === "succeeded" || job.status === "failed" || job.status === "cancelled") {
            this.state.retryJob(job.id, input.detail);
        }
    }
    finishAgentRunJobs(runId, status, detail) {
        for (const job of this.state.listJobs({ runId, kind: "agent_run", limit: 50 })) {
            if (job.status === "queued" || job.status === "running") {
                this.state.finishJob(job.id, status, detail);
            }
        }
    }
}
