/**
 * status-completion-drift.js — detect completion recorded outside status columns.
 *
 * Purpose: stop the autonomous loop from redispatching units whose narrative
 * evidence says complete while the canonical DB status remains pending.
 *
 * Consumer: M048/S03 Wiggums detector sweep and R072 remediation.
 */
export const DRIFT_DISPATCH_THRESHOLD = 2;
export const LOW_TOOL_CALL_THRESHOLD = 1;

/**
 * Detect status/completion drift in task-like rows.
 *
 * Purpose: convert the live M010/S05/T02 failure mode into a reusable detector
 * so SF pauses on the drift signature instead of spending more executor turns.
 *
 * Consumer: periodic-runner.js default detector list.
 */
export function detectStatusCompletionDrift(ctx = {}, options = {}) {
	const rows = Array.isArray(ctx.tasks)
		? ctx.tasks
		: Array.isArray(ctx.units)
			? ctx.units
			: [];
	const recentDispatches = Array.isArray(ctx.recentDispatches)
		? ctx.recentDispatches
		: [];
	const threshold = options.dispatchThreshold ?? DRIFT_DISPATCH_THRESHOLD;
	const maxToolCalls = options.lowToolCallThreshold ?? LOW_TOOL_CALL_THRESHOLD;
	const now = options.now ?? Date.now();
	const throttleMs = options.throttleMs ?? 60_000;
	const throttleState =
		options.throttleState instanceof Map ? options.throttleState : null;

	for (const row of rows) {
		const unitId = unitKey(row);
		if (!unitId || !hasCompletionProse(row) || isCompleteStatus(row)) continue;
		if (throttleState) {
			const last = throttleState.get(`status-completion-drift:${unitId}`);
			if (typeof last === "number" && now - last < throttleMs) {
				continue;
			}
		}
		const dispatches = recentDispatches.filter((dispatch) => {
			return (
				dispatch?.unitId === unitId &&
				dispatch?.outcome !== "complete" &&
				Number(dispatch?.toolCallCount ?? dispatch?.tool_calls ?? 0) <=
					maxToolCalls
			);
		});
		if (dispatches.length < threshold) continue;
		throttleState?.set(`status-completion-drift:${unitId}`, now);
		return {
			stuck: true,
			reason: "status-completion-drift",
			signature: {
				unitId,
				status: row.status ?? row.task_status ?? null,
				completedAt: row.completed_at ?? row.completedAt ?? null,
				dispatchCount: dispatches.length,
				proseFields: completionProseFields(row),
			},
		};
	}

	return { stuck: false, reason: "", signature: {} };
}

/**
 * Run the status-completion drift detector as a UOK verification gate.
 *
 * Purpose: make R072 visible through the common gate contract.
 *
 * Consumer: detector gate registry and periodicDetectorSweepGate.
 */
export const statusCompletionDriftGate = {
	id: "status-completion-drift",
	type: "verification",
	async execute(ctx = {}) {
		const result = detectStatusCompletionDrift(ctx, ctx.options);
		if (result.stuck) {
			return {
				outcome: "fail",
				failureClass: "verification",
				rationale: result.reason,
				findings: result.signature,
			};
		}
		return {
			outcome: "pass",
			failureClass: null,
			rationale: "no status-completion drift",
		};
	},
};

function unitKey(row) {
	if (typeof row?.unitId === "string") return row.unitId;
	if (typeof row?.unit_id === "string") return row.unit_id;
	const milestoneId = row?.milestoneId ?? row?.milestone_id;
	const sliceId = row?.sliceId ?? row?.slice_id;
	const taskId = row?.taskId ?? row?.id;
	if (milestoneId && sliceId && taskId)
		return `${milestoneId}/${sliceId}/${taskId}`;
	return typeof taskId === "string" ? taskId : null;
}

function isCompleteStatus(row) {
	const status = String(row?.status ?? row?.task_status ?? "").toLowerCase();
	const completedAt = row?.completed_at ?? row?.completedAt;
	return (
		status === "complete" ||
		status === "completed" ||
		status === "done" ||
		(completedAt !== null && completedAt !== undefined && completedAt !== "")
	);
}

function hasCompletionProse(row) {
	return completionProseFields(row).length > 0;
}

function completionProseFields(row) {
	const fields = [
		["narrative", row?.narrative],
		["full_summary_md", row?.full_summary_md ?? row?.fullSummaryMd],
		[
			"verification_result",
			row?.verification_result ?? row?.verificationResult,
		],
		["summary", row?.summary],
	];
	return fields
		.filter(([, value]) => completionText(value))
		.map(([name]) => name);
}

function completionText(value) {
	if (typeof value !== "string") return false;
	const text = value.toLowerCase();
	return (
		text.includes("complete") ||
		text.includes("completed") ||
		text.includes("done") ||
		text.includes("verification passed") ||
		text.includes("all tests pass")
	);
}
