import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { verifyExpectedArtifact } from "./auto-recovery.js";
import {
	formatCrashInfo,
	isLockProcessAlive,
	readCrashLock,
} from "./crash-recovery.js";
import { sfRoot } from "./paths.js";
import { synthesizeCrashRecovery } from "./session-forensics.js";
import { deriveState } from "./state.js";
export function readPausedSessionMetadata(basePath) {
	const pausedPath = join(sfRoot(basePath), "runtime", "paused-session.json");
	if (!existsSync(pausedPath)) return null;
	try {
		return JSON.parse(readFileSync(pausedPath, "utf-8"));
	} catch {
		return null;
	}
}
export function isBootstrapCrashLock(lock) {
	return !!(
		lock &&
		lock.unitType === "starting" &&
		lock.unitId === "bootstrap"
	);
}
export function hasResumableDerivedState(state) {
	if (!state?.activeMilestone) return false;
	if (state.phase === "complete" || state.phase === "blocked") return false;
	return true;
}
export async function assessInterruptedSession(basePath) {
	const pausedSession = readPausedSessionMetadata(basePath);
	const worktreeExists = pausedSession?.worktreePath
		? existsSync(pausedSession.worktreePath)
		: false;
	const assessmentBasePath = worktreeExists
		? pausedSession.worktreePath
		: basePath;
	const rawLock = readCrashLock(basePath);
	const lock = rawLock && rawLock.pid !== process.pid ? rawLock : null;
	if (!lock && !pausedSession) {
		return {
			classification: "none",
			lock: null,
			pausedSession: null,
			state: null,
			recovery: null,
			recoveryPrompt: null,
			recoveryToolCallCount: 0,
			artifactSatisfied: false,
			hasResumableDiskState: false,
			isBootstrapCrash: false,
		};
	}
	if (lock && isLockProcessAlive(lock)) {
		return {
			classification: "running",
			lock,
			pausedSession,
			state: null,
			recovery: null,
			recoveryPrompt: null,
			recoveryToolCallCount: 0,
			artifactSatisfied: false,
			hasResumableDiskState: false,
			isBootstrapCrash: false,
		};
	}
	const isBootstrapCrash = isBootstrapCrashLock(lock);
	const state = await deriveState(assessmentBasePath);
	const hasResumableDiskState = hasResumableDerivedState(state);
	const artifactSatisfied = !!(
		lock &&
		!isBootstrapCrash &&
		verifyExpectedArtifact(lock.unitType, lock.unitId, assessmentBasePath)
	);
	let recovery = null;
	if (lock && !isBootstrapCrash && !artifactSatisfied) {
		recovery = synthesizeCrashRecovery(
			assessmentBasePath,
			lock.unitType,
			lock.unitId,
			lock.sessionFile,
			join(sfRoot(assessmentBasePath), "activity"),
		);
	}
	const recoveryToolCallCount = recovery?.trace.toolCallCount ?? 0;
	const recoveryPrompt = recoveryToolCallCount > 0 ? recovery.prompt : null;
	if (isBootstrapCrash) {
		return {
			classification: pausedSession ? "recoverable" : "stale",
			lock,
			pausedSession,
			state,
			recovery,
			recoveryPrompt,
			recoveryToolCallCount,
			artifactSatisfied,
			hasResumableDiskState,
			isBootstrapCrash: true,
		};
	}
	if (
		!hasResumableDiskState &&
		pausedSession &&
		!lock &&
		recoveryToolCallCount === 0
	) {
		return {
			classification: "stale",
			lock,
			pausedSession,
			state,
			recovery,
			recoveryPrompt,
			recoveryToolCallCount,
			artifactSatisfied,
			hasResumableDiskState,
			isBootstrapCrash: false,
		};
	}
	if (
		lock &&
		artifactSatisfied &&
		!hasResumableDiskState &&
		recoveryToolCallCount === 0
	) {
		return {
			classification: "stale",
			lock,
			pausedSession,
			state,
			recovery,
			recoveryPrompt,
			recoveryToolCallCount,
			artifactSatisfied,
			hasResumableDiskState,
			isBootstrapCrash: false,
		};
	}
	const hasStrongRecoverySignal =
		hasResumableDiskState || recoveryToolCallCount > 0;
	return {
		classification: hasStrongRecoverySignal ? "recoverable" : "stale",
		lock,
		pausedSession,
		state,
		recovery,
		recoveryPrompt,
		recoveryToolCallCount,
		artifactSatisfied,
		hasResumableDiskState,
		isBootstrapCrash: false,
	};
}
export function formatInterruptedSessionSummary(assessment) {
	if (assessment.lock) return [formatCrashInfo(assessment.lock)];
	if (assessment.pausedSession?.milestoneId) {
		return [
			`Paused autonomous mode session detected for ${assessment.pausedSession.milestoneId}.`,
		];
	}
	return ["Paused autonomous mode session detected."];
}
// #wiggums: verify the PID is actually alive before telling the operator
// to kill it. Without this check, a crashed prior session leaves the lock
// pid behind and we tell the operator "kill <dead-PID>" which is
// confusing + makes the operator think SF is buggy. If the PID isn't
// alive, label the lock as stale rather than reporting a live session.
function isPidAlive(pid) {
	if (!Number.isInteger(pid) || pid <= 0) return false;
	if (pid === process.pid) return false;
	try {
		process.kill(pid, 0);
		return true;
	} catch (err) {
		if (err?.code === "EPERM") return true;
		return false;
	}
}

export function formatInterruptedSessionRunningMessage(assessment) {
	const pid = assessment.lock?.pid;
	if (pid && !isPidAlive(pid)) {
		return `Stale autonomous mode lock from dead PID ${pid}. The previous session crashed without releasing the lock — it will be auto-recovered on the next session-lock acquisition (#sf-stale-lock-auto-recover).`;
	}
	return pid
		? `Another autonomous mode session (PID ${pid}) appears to be running.\nStop it with \`kill ${pid}\` before starting a new session.`
		: "Another autonomous mode session appears to be running.";
}
