/**
 * Workflow Helpers — state checking and section building for dispatch workflows.
 *
 * Purpose: Consolidate functions that check workflow state (reassess, UAT)
 * and build context sections (resume, carry-forward). Separates workflow logic
 * from prompt building.
 *
 * Consumer: auto-prompts.js for buildReplan* and buildExecute* functions.
 */

import { existsSync } from "node:fs";
import { join } from "node:path";
import { loadFile, parseContinue, parseSummary } from "./files.js";
import { resolveSliceFile } from "./paths.js";
import { isDbAvailable } from "./sf-db.js";

/**
 * Escape regex special characters for safe use in RegExp.
 * Purpose: shared utility for markdown section extraction.
 */
export function escapeRegExp(value) {
	return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

/**
 * Convert multi-line text to single line.
 * @internal Helper
 */
function oneLine(text) {
	return text.replace(/\s+/g, " ").trim();
}

/**
 * Extract a markdown section by heading.
 * Purpose: shared utility for markdown section extraction.
 */
export function extractMarkdownSection(content, heading) {
	const match = new RegExp(`^## ${escapeRegExp(heading)}\\s*$`, "m").exec(
		content,
	);
	if (!match) return null;
	const start = match.index + match[0].length;
	const rest = content.slice(start);
	const nextHeading = rest.match(/^##\s+/m);
	const end = nextHeading?.index ?? rest.length;
	return rest.slice(0, end).trim();
}

/**
 * Check if the most recently completed slice needs reassessment.
 * Returns { sliceId } if reassessment is needed, null otherwise.
 *
 * Skips reassessment when:
 * - No roadmap exists yet
 * - No slices are completed
 * - The last completed slice already has an assessment file
 * - All slices are complete (milestone done — no point reassessing)
 */
export async function checkNeedsReassessment(base, mid, _state, _prefs) {
	// DB primary path
	try {
		const { getMilestoneSlices } = await import("./sf-db.js");
		if (isDbAvailable()) {
			const slices = getMilestoneSlices(mid);
			if (slices.length > 0) {
				const completedSliceIds = slices
					.filter((s) => s.status === "complete")
					.map((s) => s.id);
				const hasIncomplete = slices.some((s) => s.status !== "complete");
				if (completedSliceIds.length === 0 || !hasIncomplete) return null;
				const lastCompleted = completedSliceIds[completedSliceIds.length - 1];
				// Try both "ASSESSMENT" (canonical, e.g. S03-ASSESSMENT.md) and
				// the legacy "ASSESS" suffix. Without the ASSESSMENT branch the
				// dispatcher re-dispatched reassess-roadmap indefinitely because
				// resolveFile's pattern (`${idPrefix}-.*-${suffix}.md`) requires
				// at least one char before `-ASSESS`, so `S03-ASSESSMENT.md`
				// never matched suffix "ASSESS". Witnessed 2026-05-17 on
				// M010/S03 — 70+ degenerate iterations.
				const assessmentFile =
					resolveSliceFile(base, mid, lastCompleted, "ASSESSMENT") ??
					resolveSliceFile(base, mid, lastCompleted, "ASSESS");
				if (assessmentFile && existsSync(assessmentFile)) return null;
				return { sliceId: lastCompleted };
			}
		}
	} catch {
		// Fall through to file-based
	}
	return null;
}

/**
 * Check if the most recently completed slice needs a UAT run.
 * Returns { sliceId, uatType } if UAT should be dispatched, null otherwise.
 *
 * Skips when:
 * - No roadmap or no completed slices
 * - All slices are done (milestone complete path)
 * - uat_dispatch preference is not enabled
 * - No UAT file exists for the slice
 * - uat_verdict already recorded in DB (backfilled on open from any existing ASSESSMENT/UAT_RESULT)
 */
export async function checkNeedsRunUat(base, mid, _state, prefs) {
	// Check if UAT dispatch is enabled
	if (!prefs?.uat_dispatch) return null;

	try {
		const { getMilestoneSlices, getSliceUatVerdict } = await import(
			"./sf-db.js"
		);
		if (isDbAvailable()) {
			const slices = getMilestoneSlices(mid);
			if (slices.length > 0) {
				const completedSlices = slices.filter((s) => s.status === "complete");
				const hasIncomplete = slices.some((s) => s.status !== "complete");
				if (completedSlices.length === 0 || !hasIncomplete) return null;
				const lastCompleted = completedSlices[completedSlices.length - 1];
				const uatFile = resolveSliceFile(base, mid, lastCompleted.id, "UAT");
				if (!uatFile || !existsSync(uatFile)) return null;
				// DB-only: verdict backfilled on open from any existing ASSESSMENT/UAT_RESULT file
				if (getSliceUatVerdict(mid, lastCompleted.id)) return null;
				const uatContent = await loadFile(uatFile);
				const { hasVerdict } = await import("./verdict-parser.js");
				const uatType = hasVerdict(uatContent) ? "verdict" : "narrative";
				return { sliceId: lastCompleted.id, uatType };
			}
		}
	} catch {
		// Fall through
	}
	return null;
}

/**
 * Build the resume section for continuing a task.
 * Used to replay the continue file and provide context for resuming work.
 */
export function buildResumeSection(
	continueContent,
	legacyContinueContent,
	continueRelPath,
	legacyContinueRelPath,
) {
	const resolvedContent = continueContent ?? legacyContinueContent;
	const resolvedRelPath = continueContent
		? continueRelPath
		: legacyContinueRelPath;
	if (!resolvedContent || !resolvedRelPath) {
		return [
			"## Resume State",
			"- No continue file present. Start from the top of the task plan.",
		].join("\n");
	}
	const cont = parseContinue(resolvedContent);
	const lines = [
		"## Resume State",
		`Source: \`${resolvedRelPath}\``,
		`- Status: ${cont.frontmatter.status || "in_progress"}`,
	];
	if (cont.frontmatter.step && cont.frontmatter.totalSteps) {
		lines.push(
			`- Progress: step ${cont.frontmatter.step} of ${cont.frontmatter.totalSteps}`,
		);
	}
	if (cont.completedWork)
		lines.push(`- Completed: ${oneLine(cont.completedWork)}`);
	if (cont.remainingWork)
		lines.push(`- Remaining: ${oneLine(cont.remainingWork)}`);
	if (cont.decisions) lines.push(`- Decisions: ${oneLine(cont.decisions)}`);
	if (cont.nextAction) lines.push(`- Next action: ${oneLine(cont.nextAction)}`);
	return lines.join("\n");
}

/**
 * Build the carry-forward section for the next task.
 * Inlines prior task summaries with key info and diagnostics.
 */
export async function buildCarryForwardSection(priorSummaryPaths, base) {
	if (priorSummaryPaths.length === 0) {
		return [
			"## Carry-Forward Context",
			"- No prior task summaries in this slice.",
		].join("\n");
	}
	const items = await Promise.all(
		priorSummaryPaths.map(async (relPath) => {
			const absPath = join(base, relPath);
			const content = await loadFile(absPath);
			if (!content) return `- \`${relPath}\``;
			const summary = parseSummary(content);
			const provided = summary.frontmatter.provides.slice(0, 2).join("; ");
			const decisions = summary.frontmatter.key_decisions
				.slice(0, 2)
				.join("; ");
			const patterns = summary.frontmatter.patterns_established
				.slice(0, 2)
				.join("; ");
			const keyFiles = summary.frontmatter.key_files.slice(0, 3).join("; ");
			const diagnostics = extractMarkdownSection(content, "Diagnostics");
			const parts = [summary.title || relPath];
			if (summary.oneLiner) parts.push(summary.oneLiner);
			if (provided) parts.push(`provides: ${provided}`);
			if (decisions) parts.push(`decisions: ${decisions}`);
			if (patterns) parts.push(`patterns: ${patterns}`);
			if (keyFiles) parts.push(`key_files: ${keyFiles}`);
			if (diagnostics) parts.push(`diagnostics: ${oneLine(diagnostics)}`);
			return `- \`${relPath}\` — ${parts.join(" | ")}`;
		}),
	);
	return ["## Carry-Forward Context", ...items].join("\n");
}
