import { runAttempt as defaultRunAttempt } from "../agent/runner.js";
import { validateDispatch as defaultValidateDispatch } from "../config/validate.js";
import { resolveWorkflowKind } from "../drivers/kind.js";
import { getDriver } from "../drivers/registry.js";
import { ensureWorkspace as defaultEnsureWorkspace, removeWorkspace as defaultRemoveWorkspace } from "../workspace/manager.js";
import { advanceTaskToRunning } from "./advance.js";
import { reconcileRunning as defaultReconcileRunning } from "./reconcile.js";
import { backoffMs, shouldRetry } from "./retry.js";
import { cancelRetry, claim, markCompleted, markRunning, release, scheduleRetry } from "./state.js";
export async function tick(state, deps) {
    const now = deps.now?.() ?? Date.now();
    deps.onEvent?.({ type: "tick_started", running: state.running.size });
    const reconcileResults = await (deps.reconcileRunning ?? defaultReconcileRunning)(state, {
        tasks: deps.tasks
    });
    if (isTickAborted(deps)) {
        return;
    }
    for (const result of reconcileResults) {
        deps.onEvent?.({ type: "reconcile", task_id: result.taskId, action: result.action });
    }
    const retryReconcileResults = await reconcileRetryQueue(state, deps);
    if (isTickAborted(deps)) {
        return;
    }
    for (const result of retryReconcileResults) {
        deps.onEvent?.({ type: "reconcile", task_id: result.taskId, action: result.action });
    }
    const validateDispatch = deps.validateDispatch ?? defaultValidateDispatch;
    const validation = await validateDispatch(state.cfg, deps.tasks);
    if (isTickAborted(deps)) {
        return;
    }
    if (!validation.ok) {
        deps.onEvent?.({ type: "validation_failed", result: validation });
        return;
    }
    const capacity = Math.max(0, state.cfg.agent.maxConcurrentAgents - state.running.size);
    if (capacity === 0) {
        return;
    }
    const candidates = await activeCandidates(state, deps.tasks);
    if (isTickAborted(deps)) {
        return;
    }
    let dispatched = 0;
    for (const task of candidates) {
        if (isTickAborted(deps)) {
            return;
        }
        if (dispatched >= capacity) {
            return;
        }
        const kind = resolveWorkflowKind(task);
        if (getDriver(kind) === undefined) {
            deps.onEvent?.({
                type: "task_skipped",
                task_id: task.qualifiedId,
                reason: "unsupported_kind",
                kind
            });
            continue;
        }
        const attempt = acquireDispatchSlot(state, task, now);
        if (attempt === undefined) {
            continue;
        }
        dispatched += 1;
        startWorker(state, deps, task, attempt);
    }
}
async function activeCandidates(state, tasks) {
    const byId = new Map();
    for (const activeState of state.cfg.activeStateNames) {
        for (const task of await tasks.allTasks({ state: activeState })) {
            byId.set(task.qualifiedId, task);
        }
    }
    return [...byId.values()];
}
async function reconcileRetryQueue(state, deps) {
    const results = [];
    for (const retry of [...state.retry_attempts.values()]) {
        let task;
        try {
            task = await deps.tasks.get(retry.taskId);
        }
        catch {
            continue;
        }
        if (state.cfg.terminalStateNames.includes(task.state)) {
            await removeWorkspaceSafely(state, deps, retry.taskId, task.qualifiedId);
            markCompleted(state, retry.taskId);
            results.push({ taskId: retry.taskId, action: "stop_clean" });
            continue;
        }
        if (!state.cfg.activeStateNames.includes(task.state)) {
            release(state, retry.taskId);
            results.push({ taskId: retry.taskId, action: "stop_keep" });
            continue;
        }
        results.push({ taskId: retry.taskId, action: "update" });
    }
    return results;
}
function acquireDispatchSlot(state, task, now) {
    if (state.completed.has(task.qualifiedId)) {
        return undefined;
    }
    const retry = state.retry_attempts.get(task.qualifiedId);
    if (retry !== undefined) {
        if (retry.dueAt > now) {
            return undefined;
        }
        cancelRetry(state, task.qualifiedId);
        markRunning(state, { taskId: task.qualifiedId, attempt: retry.attempt, task });
        return retry.attempt;
    }
    if (!claim(state, task.qualifiedId)) {
        return undefined;
    }
    markRunning(state, { taskId: task.qualifiedId, attempt: 1, task });
    return 1;
}
function startWorker(state, deps, task, attempt) {
    const controller = new AbortController();
    const promise = (async () => {
        let outcome;
        let workspace;
        let activeTask = task;
        try {
            workspace = await (deps.ensureWorkspace ?? defaultEnsureWorkspace)(state.cfg.workspace.root, task.qualifiedId);
        }
        catch (error) {
            outcome = {
                reason: "abnormal",
                failure: "workspace_error",
                error: errorMessage(error)
            };
        }
        if (workspace !== undefined) {
            try {
                deps.onEvent?.({
                    type: "dispatch",
                    task_id: task.qualifiedId,
                    attempt,
                    workspace: workspace.path
                });
                activeTask = await advanceTaskToRunning(deps.tasks, task, {
                    events: ["start"],
                    requireSupported: true
                });
                outcome = await (deps.runAttempt ?? defaultRunAttempt)({
                    task: activeTask,
                    attempt,
                    cfg: state.cfg,
                    workspaceDir: workspace.path,
                    deps: {
                        spawn: deps.spawn,
                        taskPromptTemplate: deps.taskPromptTemplate,
                        refreshTask: (qualifiedId) => deps.tasks.get(qualifiedId),
                        onEvent: deps.onEvent,
                        reconcile: async ({ task: runningTask }) => {
                            const refreshed = await deps.tasks.get(runningTask.qualifiedId);
                            if (state.cfg.terminalStateNames.includes(refreshed.state)) {
                                return refreshed.state === "done" ? "continue" : "canceled";
                            }
                            if (!state.cfg.activeStateNames.includes(refreshed.state)) {
                                return "canceled";
                            }
                            return "continue";
                        },
                        logger: deps.logger?.warn === undefined
                            ? undefined
                            : {
                                warn: deps.logger.warn.bind(deps.logger)
                            }
                    },
                    abort: controller.signal
                });
            }
            catch (error) {
                const message = errorMessage(error);
                deps.logger?.error?.("maestro worker rejected", {
                    taskId: activeTask.qualifiedId,
                    attempt,
                    error: message
                });
                outcome = {
                    reason: "abnormal",
                    failure: "agent_crashed",
                    error: message
                };
            }
        }
        if (outcome === undefined) {
            outcome = {
                reason: "abnormal",
                failure: "workspace_error",
                error: "workspace was not created"
            };
        }
        if (controller.signal.aborted && outcome.reason !== "skip") {
            outcome = { reason: "abnormal", failure: "canceled" };
        }
        const phase = outcomePhase(outcome);
        deps.onEvent?.({
            type: "worker_exit",
            task_id: activeTask.qualifiedId,
            attempt,
            phase,
            outcome
        });
        if (workspace !== undefined &&
            (outcome.reason === "normal" || outcome.failure === "canceled") &&
            (await cleanupTerminalWorkspace(state, deps, activeTask.qualifiedId))) {
            return;
        }
        const retryScheduled = scheduleWorkerRetry(state, deps, activeTask.qualifiedId, attempt, phase, outcome);
        if (!retryScheduled && outcome.failure === "canceled" && workspace !== undefined) {
            await cleanupTerminalWorkspace(state, deps, activeTask.qualifiedId);
        }
    })();
    deps.trackWorker?.({ taskId: task.qualifiedId, controller, promise });
}
function scheduleWorkerRetry(state, deps, taskId, attempt, phase, outcome) {
    if (phase === "skipped") {
        release(state, taskId);
        return false;
    }
    const decision = shouldRetry(phase, outcome.failure, { attempt });
    if (!decision.retry) {
        release(state, taskId);
        return false;
    }
    const nextAttempt = decision.attempt ?? attempt + 1;
    const delayMs = decision.kind === "continuation"
        ? decision.delayMs
        : backoffMs(attempt, state.cfg.agent.maxRetryBackoffMs);
    const dueAt = (deps.now?.() ?? Date.now()) + delayMs;
    scheduleRetry(state, { taskId, attempt: nextAttempt, dueAt });
    deps.onEvent?.({ type: "retry_scheduled", task_id: taskId, attempt: nextAttempt, due_at: dueAt });
    return true;
}
async function cleanupTerminalWorkspace(state, deps, taskId) {
    let task;
    try {
        task = await deps.tasks.get(taskId);
    }
    catch {
        return false;
    }
    if (!state.cfg.terminalStateNames.includes(task.state)) {
        return false;
    }
    await removeWorkspaceSafely(state, deps, taskId, task.qualifiedId);
    markCompleted(state, taskId);
    deps.onEvent?.({ type: "reconcile", task_id: taskId, action: "stop_clean" });
    return true;
}
async function removeWorkspaceSafely(state, deps, taskId, qualifiedId) {
    try {
        await (deps.removeWorkspace ?? defaultRemoveWorkspace)(state.cfg.workspace.root, qualifiedId);
    }
    catch (error) {
        deps.logger?.warn?.("maestro workspace cleanup failed", {
            taskId,
            error: errorMessage(error)
        });
    }
}
function errorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
function isTickAborted(deps) {
    return deps.abort?.aborted ?? false;
}
function outcomePhase(outcome) {
    if (outcome.reason === "normal") {
        return "succeeded";
    }
    if (outcome.reason === "skip") {
        return "skipped";
    }
    return outcome.failure === "canceled" ? "canceled" : "failed";
}
