/**
 * loop-signals.js — normalized loop/stuck supervision signals.
 *
 * Purpose: give extensions one contract for loop detectors, watchdogs, retry
 * guards, and tool churn signals so supervisors can decide from one shape
 * instead of bespoke metadata branches.
 *
 * Consumer: SF autonomy supervision and cross-extension loop guards.
 */

const VALID_SCOPES = new Set([
	"process",
	"session",
	"unit",
	"tool",
	"provider",
	"artifact",
]);
const VALID_SEVERITIES = new Set(["info", "warning", "fail", "pause"]);
const VALID_ACTIONS = new Set([
	"continue",
	"retry",
	"switch-model",
	"pause",
	"block",
	"recover",
]);

function pick(value, allowed, fallback) {
	return allowed.has(value) ? value : fallback;
}

/**
 * Build a normalized loop supervision signal.
 *
 * Purpose: preserve producer-specific evidence while making action, severity,
 * scope, and unit identity machine-readable for shared supervision.
 *
 * Consumer: detector adapters, tool guards, provider guards, and watchdogs.
 */
export function createLoopSignal(input = {}) {
	const unit =
		input.unit && typeof input.unit === "object"
			? {
					type: String(input.unit.type ?? ""),
					id: String(input.unit.id ?? ""),
				}
			: undefined;
	return {
		scope: pick(input.scope, VALID_SCOPES, "unit"),
		kind: String(input.kind ?? "unknown-loop-signal"),
		severity: pick(input.severity, VALID_SEVERITIES, "warning"),
		...(unit?.type && unit?.id ? { unit } : {}),
		evidence:
			input.evidence && typeof input.evidence === "object"
				? input.evidence
				: { value: input.evidence ?? null },
		recommendedAction: pick(input.recommendedAction, VALID_ACTIONS, "pause"),
		...(input.message ? { message: String(input.message) } : {}),
		...(input.source ? { source: String(input.source) } : {}),
	};
}

/**
 * Convert a Wiggums detector sweep hit into a normalized loop signal.
 *
 * Purpose: let the periodic detector family feed the same supervisor contract
 * as direct runaway and zero-progress checks.
 *
 * Consumer: auto-runaway-guard when consuming buffered detector sweep results.
 */
export function detectorSweepSignal(fired, unitType, unitId, now = Date.now()) {
	return createLoopSignal({
		scope: "unit",
		kind: `wiggums:${fired?.name ?? "unknown"}`,
		severity: "fail",
		unit: { type: unitType, id: unitId },
		evidence: {
			detector: fired?.name ?? "unknown",
			signature: fired?.signature ?? null,
			failedAt: now,
		},
		recommendedAction: "pause",
		source: "periodic-detector-sweep",
	});
}

/**
 * Convert a zero-progress detector hit into a normalized loop signal.
 *
 * Purpose: make the strongest unit-churn signal available through the same
 * contract as Wiggums detectors and future tool/provider loop producers.
 *
 * Consumer: auto-runaway-guard.
 */
export function zeroProgressSignal(input) {
	return createLoopSignal({
		scope: "unit",
		kind: "zero-progress",
		severity: "fail",
		unit: { type: input.unitType, id: input.unitId },
		evidence: input.evidence,
		recommendedAction: "pause",
		source: "zero-progress-detector",
	});
}

/**
 * Convert a repeated identical tool call into a normalized loop signal.
 *
 * Purpose: expose pre-tool blocking as the same signal class as unit-level
 * stuck detectors instead of hiding it inside a string reason.
 *
 * Consumer: SF tool-call loop guard.
 */
export function repeatedToolCallSignal(input) {
	return createLoopSignal({
		scope: "tool",
		kind: "tool-call:duplicate",
		severity: "fail",
		evidence: {
			toolName: input.toolName,
			count: input.count,
			threshold: input.threshold,
			signature: input.signature,
		},
		recommendedAction: "block",
		source: "tool-call-loop-guard",
	});
}

/**
 * Convert a repeated failing shell command into a normalized loop signal.
 *
 * Purpose: make tool-result watchdog steering machine-readable while preserving
 * the existing human steering message.
 *
 * Consumer: SF tool watchdog.
 */
export function repeatedToolFailureSignal(input) {
	return createLoopSignal({
		scope: "tool",
		kind: "tool-result:repeated-failure",
		severity: "warning",
		evidence: {
			toolName: input.toolName,
			command: input.command,
			count: input.count,
			windowMs: input.windowMs,
			excerpt: input.excerpt,
		},
		recommendedAction: "continue",
		source: "tool-watchdog",
	});
}

/**
 * Convert a duplicate web-search query into a normalized loop signal.
 *
 * Purpose: let non-SF search tools report query loops through the shared
 * extension signal contract instead of only returning an opaque tool error.
 *
 * Consumer: search-the-web tool loop guard.
 */
export function duplicateSearchSignal(input) {
	return createLoopSignal({
		scope: "tool",
		kind: "search:duplicate-query",
		severity: "fail",
		evidence: {
			query: input.query,
			provider: input.provider,
			domain: input.domain ?? "",
			consecutiveDupeCount: input.consecutiveDupeCount,
		},
		recommendedAction: "block",
		source: "search-the-web",
	});
}
