/**
 * Timeout recovery logic for auto-mode units.
 * Handles idle and hard timeout recovery with escalation, steering messages,
 * and blocker placeholder generation.
 */
import { readUnitRuntimeRecord, writeUnitRuntimeRecord, formatExecuteTaskRecoveryStatus, inspectExecuteTaskDurability, } from "./unit-runtime.js";
import { resolveExpectedArtifactPath, diagnoseExpectedArtifact, writeBlockerPlaceholder, } from "./auto-recovery.js";
import { existsSync } from "node:fs";
import { resolveAgentEnd } from "./auto-loop.js";
export async function recoverTimedOutUnit(ctx, pi, unitType, unitId, reason, rctx) {
    const { basePath, verbose, currentUnitStartedAt, unitRecoveryCount } = rctx;
    const runtime = readUnitRuntimeRecord(basePath, unitType, unitId);
    const recoveryAttempts = runtime?.recoveryAttempts ?? 0;
    const maxRecoveryAttempts = reason === "idle" ? 2 : 1;
    const recoveryKey = `${unitType}/${unitId}`;
    const attemptNumber = (unitRecoveryCount.get(recoveryKey) ?? 0) + 1;
    unitRecoveryCount.set(recoveryKey, attemptNumber);
    if (attemptNumber > 1) {
        // Exponential backoff: 2^(n-1) seconds, capped at 30s
        const backoffMs = Math.min(1000 * Math.pow(2, attemptNumber - 2), 30000);
        ctx.ui.notify(`Recovery attempt ${attemptNumber} for ${unitType} ${unitId}. Waiting ${backoffMs / 1000}s before retry.`, "info");
        await new Promise(r => setTimeout(r, backoffMs));
    }
    if (unitType === "execute-task") {
        const status = await inspectExecuteTaskDurability(basePath, unitId);
        if (!status)
            return "paused";
        writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
            recovery: status,
        });
        const durableComplete = status.summaryExists && status.taskChecked && status.nextActionAdvanced;
        if (durableComplete) {
            writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
                phase: "finalized",
                recovery: status,
            });
            ctx.ui.notify(`${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed on disk. Continuing auto-mode. (attempt ${attemptNumber})`, "info");
            unitRecoveryCount.delete(recoveryKey);
            resolveAgentEnd({ messages: [], _synthetic: "timeout-recovery" });
            return "recovered";
        }
        if (recoveryAttempts < maxRecoveryAttempts) {
            const isEscalation = recoveryAttempts > 0;
            writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
                phase: "recovered",
                recovery: status,
                recoveryAttempts: recoveryAttempts + 1,
                lastRecoveryReason: reason,
                lastProgressAt: Date.now(),
                progressCount: (runtime?.progressCount ?? 0) + 1,
                lastProgressKind: reason === "idle" ? "idle-recovery-retry" : "hard-recovery-retry",
            });
            const steeringLines = isEscalation
                ? [
                    `**FINAL ${reason === "idle" ? "IDLE" : "HARD TIMEOUT"} RECOVERY — last chance before this task is skipped.**`,
                    `You are still executing ${unitType} ${unitId}.`,
                    `Recovery attempt ${recoveryAttempts + 1} of ${maxRecoveryAttempts}.`,
                    `Current durability status: ${formatExecuteTaskRecoveryStatus(status)}.`,
                    "You MUST finish the durable output NOW, even if incomplete.",
                    "Write the task summary with whatever you have accomplished so far.",
                    "Mark the task [x] in the plan. Commit your work.",
                    "A partial summary is infinitely better than no summary.",
                ]
                : [
                    `**${reason === "idle" ? "IDLE" : "HARD TIMEOUT"} RECOVERY — do not stop.**`,
                    `You are still executing ${unitType} ${unitId}.`,
                    `Recovery attempt ${recoveryAttempts + 1} of ${maxRecoveryAttempts}.`,
                    `Current durability status: ${formatExecuteTaskRecoveryStatus(status)}.`,
                    "Do not keep exploring.",
                    "Immediately finish the required durable output for this unit.",
                    "If full completion is impossible, write the partial artifact/state needed for recovery and make the blocker explicit.",
                ];
            pi.sendMessage({
                customType: "nerf-auto-timeout-recovery",
                display: verbose,
                content: steeringLines.join("\n"),
            }, { triggerTurn: true, deliverAs: "steer" });
            ctx.ui.notify(`${reason === "idle" ? "Idle" : "Timeout"} recovery: steering ${unitType} ${unitId} to finish durable output (attempt ${attemptNumber}, session ${recoveryAttempts + 1}/${maxRecoveryAttempts}).`, "warning");
            return "recovered";
        }
        // Retries exhausted — write a blocker placeholder and advance.
        const diagnostic = formatExecuteTaskRecoveryStatus(status);
        const placeholder = writeBlockerPlaceholder(unitType, unitId, basePath, `${reason} recovery exhausted ${maxRecoveryAttempts} attempts. Status: ${diagnostic}`);
        if (placeholder) {
            writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
                phase: "skipped",
                recovery: status,
                recoveryAttempts: recoveryAttempts + 1,
                lastRecoveryReason: reason,
            });
            ctx.ui.notify(`${unitType} ${unitId} skipped after ${maxRecoveryAttempts} recovery attempts (${diagnostic}). Blocker artifacts written. Advancing pipeline. (attempt ${attemptNumber})`, "warning");
            unitRecoveryCount.delete(recoveryKey);
            resolveAgentEnd({ messages: [], _synthetic: "timeout-recovery" });
            return "recovered";
        }
        // Fallback: couldn't write skip artifacts — pause as before.
        writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
            phase: "paused",
            recovery: status,
            recoveryAttempts: recoveryAttempts + 1,
            lastRecoveryReason: reason,
        });
        ctx.ui.notify(`${reason === "idle" ? "Idle" : "Timeout"} recovery check for ${unitType} ${unitId}: ${diagnostic}`, "warning");
        return "paused";
    }
    const expected = diagnoseExpectedArtifact(unitType, unitId, basePath) ?? "required durable artifact";
    // Check if the artifact already exists on disk — agent may have written it
    // without signaling completion.
    const artifactPath = resolveExpectedArtifactPath(unitType, unitId, basePath);
    if (artifactPath && existsSync(artifactPath)) {
        writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
            phase: "finalized",
            recoveryAttempts: recoveryAttempts + 1,
            lastRecoveryReason: reason,
        });
        ctx.ui.notify(`${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} artifact already exists on disk. Advancing. (attempt ${attemptNumber})`, "info");
        unitRecoveryCount.delete(recoveryKey);
        resolveAgentEnd({ messages: [], _synthetic: "timeout-recovery" });
        return "recovered";
    }
    if (recoveryAttempts < maxRecoveryAttempts) {
        const isEscalation = recoveryAttempts > 0;
        writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
            phase: "recovered",
            recoveryAttempts: recoveryAttempts + 1,
            lastRecoveryReason: reason,
            lastProgressAt: Date.now(),
            progressCount: (runtime?.progressCount ?? 0) + 1,
            lastProgressKind: reason === "idle" ? "idle-recovery-retry" : "hard-recovery-retry",
        });
        const steeringLines = isEscalation
            ? [
                `**FINAL ${reason === "idle" ? "IDLE" : "HARD TIMEOUT"} RECOVERY — last chance before skip.**`,
                `You are still executing ${unitType} ${unitId}.`,
                `Recovery attempt ${recoveryAttempts + 1} of ${maxRecoveryAttempts} — next failure skips this unit.`,
                `Expected durable output: ${expected}.`,
                "You MUST write the artifact file NOW, even if incomplete.",
                "Write whatever you have — partial research, preliminary findings, best-effort analysis.",
                "A partial artifact is infinitely better than no artifact.",
                "If you are truly blocked, write the file with a BLOCKER section explaining why.",
            ]
            : [
                `**${reason === "idle" ? "IDLE" : "HARD TIMEOUT"} RECOVERY — stay in auto-mode.**`,
                `You are still executing ${unitType} ${unitId}.`,
                `Recovery attempt ${recoveryAttempts + 1} of ${maxRecoveryAttempts}.`,
                `Expected durable output: ${expected}.`,
                "Stop broad exploration.",
                "Write the required artifact now.",
                "If blocked, write the partial artifact and explicitly record the blocker instead of going silent.",
            ];
        pi.sendMessage({
            customType: "nerf-auto-timeout-recovery",
            display: verbose,
            content: steeringLines.join("\n"),
        }, { triggerTurn: true, deliverAs: "steer" });
        ctx.ui.notify(`${reason === "idle" ? "Idle" : "Timeout"} recovery: steering ${unitType} ${unitId} to produce ${expected} (attempt ${attemptNumber}, session ${recoveryAttempts + 1}/${maxRecoveryAttempts}).`, "warning");
        return "recovered";
    }
    // Retries exhausted — write a blocker placeholder and advance the pipeline
    // instead of silently stalling.
    const placeholder = writeBlockerPlaceholder(unitType, unitId, basePath, `${reason} recovery exhausted ${maxRecoveryAttempts} attempts without producing the artifact.`);
    if (placeholder) {
        writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
            phase: "skipped",
            recoveryAttempts: recoveryAttempts + 1,
            lastRecoveryReason: reason,
        });
        ctx.ui.notify(`${unitType} ${unitId} skipped after ${maxRecoveryAttempts} recovery attempts. Blocker placeholder written to ${placeholder}. Advancing pipeline. (attempt ${attemptNumber})`, "warning");
        unitRecoveryCount.delete(recoveryKey);
        resolveAgentEnd({ messages: [], _synthetic: "timeout-recovery" });
        return "recovered";
    }
    // Fallback: couldn't resolve artifact path — pause as before.
    writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
        phase: "paused",
        recoveryAttempts: recoveryAttempts + 1,
        lastRecoveryReason: reason,
    });
    return "paused";
}
