/**
 * periodic-runner.js — run Wiggums detectors on a bounded cadence.
 *
 * Purpose: centralize periodic stuck-state detection so autonomous surfaces can
 * share detector throttling, error isolation, and signatures.
 *
 * Consumer: SF autonomous mode at run-control boundaries.
 */
import { detectArtifactFlap } from "./artifact-flap.js";
import { detectCrashLoop } from "./crash-loop-classifier.js";
import { detectProductionPlateau } from "./production-plateau.js";
import { detectRepeatedFeedbackKind } from "./repeated-feedback-kind.js";
import { detectSameUnitLoop } from "./same-unit-loop.js";
import { detectStaleLock } from "./stale-lock.js";
import { detectStatusCompletionDrift } from "./status-completion-drift.js";
import { detectZeroProgress } from "./zero-progress.js";

export const SWEEP_CADENCE_MS = 60 * 1000;

async function optionalDetector(modulePath, exportName) {
	try {
		const module = await import(modulePath);
		return module?.[exportName] ?? null;
	} catch {
		return null;
	}
}

function defaultDetectors(ctx, options) {
	return [
		{
			name: "same-unit-loop",
			run: () =>
				detectSameUnitLoop(ctx?.unitId, ctx?.recentDispatches, options),
		},
		{
			name: "zero-progress",
			run: () =>
				detectZeroProgress(
					ctx?.unitMetrics,
					{
						tool_calls: 0,
						elapsedMs: 0,
						fingerprint: ctx?.sessionFingerprint,
					},
					{
						...options,
						collectWorktreeFingerprint: () => ctx?.sessionFingerprint ?? null,
					},
				),
		},
		{
			name: "repeated-feedback-kind",
			run: () => detectRepeatedFeedbackKind(ctx?.recentFeedback, options),
		},
		{
			name: "artifact-flap",
			run: () => detectArtifactFlap(ctx?.artifactHistory, options),
		},
		{
			name: "stale-lock",
			run: () => detectStaleLock(ctx?.lockPaths, options),
		},
		{
			name: "crash-loop",
			run: () =>
				detectCrashLoop(ctx?.basePath ?? ctx?.cwd, ctx?.currentResult, options),
		},
		{
			name: "status-completion-drift",
			run: () => detectStatusCompletionDrift(ctx, options),
		},
		{
			name: "production-plateau",
			run: () => detectProductionPlateau(ctx?.unitMetrics, ctx, options),
		},
	];
}

/**
 * Run all available periodic stuck-state detectors once.
 *
 * Purpose: return a compact sweep result while keeping individual detector
 * failures from blocking the rest of the autonomy health check.
 *
 * Consumer: machine-surface autonomous controllers.
 */
export async function runDetectorSweep(ctx = {}, options = {}) {
	const startedAt = Date.now();
	const throttleMs = options.throttleMs ?? 60_000;
	const throttleState =
		options.throttleState instanceof Map ? options.throttleState : new Map();
	const optionalDetectors = (
		await Promise.all([
			optionalDetector("./model-route-flap.js", "detectModelRouteFlap"),
			optionalDetector("./prompt-drift.js", "detectPromptDrift"),
		])
	)
		.filter(Boolean)
		.map((detector, index) => ({
			name: index === 0 ? "model-route-flap" : "prompt-drift",
			run: () => detector(ctx, options),
		}));
	const detectors =
		options.detectors ??
		defaultDetectors(ctx, options).concat(optionalDetectors);
	const detectorsFired = [];
	let totalChecked = 0;

	for (const detector of detectors) {
		const lastFiredAt = throttleState.get(detector.name);
		if (
			typeof lastFiredAt === "number" &&
			startedAt - lastFiredAt < throttleMs
		) {
			continue;
		}
		totalChecked += 1;
		try {
			const result = await detector.run();
			if (result?.stuck) {
				throttleState.set(detector.name, startedAt);
				detectorsFired.push({
					name: detector.name,
					signature: result.signature,
				});
			}
		} catch (error) {
			options.onError?.(error, detector.name);
		}
	}

	return {
		detectorsFired,
		totalChecked,
		durationMs: Date.now() - startedAt,
	};
}

/**
 * Run the periodic detector sweep as a UOK verification gate.
 *
 * Purpose: expose the aggregate detector sweep to the gate runner as a single
 * manual-attention signal when any detector fires.
 *
 * Consumer: UOK gate registry alignment for detector-based verification gates.
 */
export const periodicDetectorSweepGate = {
	id: "periodic-detector-sweep",
	type: "verification",
	async execute(ctx = {}) {
		const result = await runDetectorSweep(ctx);
		if (result.detectorsFired.length === 0) {
			return {
				outcome: "pass",
				failureClass: null,
				rationale: "no detectors fired",
			};
		}
		return {
			outcome: "manual-attention",
			failureClass: "verification",
			rationale: "detectors fired",
			findings: result.detectorsFired,
		};
	},
};
