import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { getErrorMessage } from "./error-utils.js";
import { resolveTaskFile } from "./paths.js";
import { updateTaskStatus } from "./sf-db.js";
import { invalidateStateCache } from "./state.js";
import { appendEvent } from "./workflow-events.js";
import { logWarning } from "./workflow-logger.js";
import { writeManifest } from "./workflow-manifest.js";
import { renderAllProjections } from "./workflow-projections.js";

const REPO_INSTRUCTION_FILES = [
	"AGENTS.md",
	"CLAUDE.md",
	"CONTRIBUTING.md",
	"README.md",
];
function readIfExists(path) {
	try {
		return existsSync(path) ? readFileSync(path, "utf-8") : "";
	} catch {
		return "";
	}
}
function loadRepoInstructionText(basePath) {
	return REPO_INSTRUCTION_FILES.map((file) =>
		readIfExists(join(basePath, file)),
	)
		.filter(Boolean)
		.join("\n\n");
}
function hasLegacyStagingConstraint(instructions) {
	const text = instructions.toLowerCase();
	const marksStagingHistorical =
		text.includes("legacy staging artifacts") ||
		/deploy\/staging\/?.{0,160}historical/s.test(text) ||
		text.includes("there is no staging environment");
	const forbidsStagingAsTarget =
		text.includes("do not treat them as the deploy target") ||
		text.includes("do not treat them as deploy target") ||
		text.includes("do not assume docker-compose") ||
		/unless.{0,80}local compose validation/s.test(text);
	return marksStagingHistorical && forbidsStagingAsTarget;
}
function taskTargetsLocalComposeStaging(taskText) {
	const text = taskText.toLowerCase();
	const hasCompose = /\bdocker(?:\s+compose|-compose)\b/.test(text);
	const hasStagingTarget =
		text.includes("deploy/staging") ||
		text.includes("staging stack") ||
		text.includes("staging environment") ||
		text.includes("local-compose");
	const asksToRunCompose =
		/\b(validate|start|starts|smoke|poll|health|shut down|up|down)\b.{0,120}\bdocker(?:\s+compose|-compose)\b/s.test(
			text,
		) ||
		/\bdocker(?:\s+compose|-compose)\b.{0,120}\b(up|-d|start|starts|run|validate|health|down)\b/s.test(
			text,
		);
	return hasStagingTarget && (hasCompose || asksToRunCompose);
}
function taskRecordsExplicitLocalComposeRequest(taskText) {
	return /(?:user|human)\s+explicitly\s+(?:asked|requested).{0,120}(?:local compose|docker(?:\s+compose|-compose)|deploy\/staging)/is.test(
		taskText,
	);
}
/** Check for conflicts between repo instructions and a task's execution context. Returns conflict details if dispatch should be blocked, null otherwise. */
export function getExecuteTaskInstructionConflict(
	basePath,
	mid,
	sid,
	tid,
	taskTitle,
) {
	const instructions = loadRepoInstructionText(basePath);
	if (!hasLegacyStagingConstraint(instructions)) return null;
	const taskPlanPath = resolveTaskFile(basePath, mid, sid, tid, "PLAN");
	const taskPlanContent = taskPlanPath ? readIfExists(taskPlanPath) : "";
	const taskText = [taskTitle, taskPlanContent].filter(Boolean).join("\n\n");
	if (!taskTargetsLocalComposeStaging(taskText)) return null;
	if (taskRecordsExplicitLocalComposeRequest(taskText)) return null;
	return {
		reason:
			`Cannot dispatch execute-task ${mid}/${sid}/${tid}: task plan targets Docker Compose staging, ` +
			"but current repo instructions mark deploy/staging as historical and say not to treat it as the deploy target unless explicitly requested. " +
			"Replan or skip this stale task, and use repo-appropriate verification instead.",
	};
}
/**
 * Mark a task skipped due to instruction conflict and log the event.
 */
export async function skipExecuteTaskForInstructionConflict(
	basePath,
	mid,
	sid,
	tid,
	reason,
) {
	const ts = new Date().toISOString();
	updateTaskStatus(mid, sid, tid, "skipped", ts);
	try {
		await renderAllProjections(basePath, mid);
		writeManifest(basePath);
		appendEvent(basePath, {
			cmd: "skip-task",
			params: {
				milestoneId: mid,
				sliceId: sid,
				taskId: tid,
				reason,
			},
			ts,
			actor: "system",
			actor_name: "instruction-conflict-guard",
			trigger_reason: "repo instructions conflict with planned task",
		});
	} catch (err) {
		logWarning(
			"dispatch",
			`instruction-conflict skip post-mutation hook warning: ${getErrorMessage(err)}`,
		);
	}
	invalidateStateCache();
}
