/**
 * Post-Execution Checks — Validate task output after execution completes.
 *
 * Runs these checks against a completed task's output:
 *   1. Import resolution — verify relative imports in key_files resolve to existing files
 *   2. Cross-task signatures — detect hallucination cascades (function exists in task output
 *      but doesn't match prior tasks' actual code)
 *   3. Pattern consistency — warn on async style drift, naming convention inconsistencies
 *
 * Design principles:
 *   - Pure functions taking (taskRow, priorTasks, basePath) for testability
 *   - Import checks are blocking failures; pattern checks are warnings
 *   - No AST parsers — uses regex heuristics
 */
import { existsSync, readFileSync } from "node:fs";
import { dirname, extname, resolve } from "node:path";
import { parseRequirementsSections } from "./md-importer.js";
import { parseRoadmap } from "./parsers.js";
import {
	resolveMilestoneFile,
	resolveSfRootFile,
	resolveSliceFile,
} from "./paths.js";
// ─── Import Resolution Check ─────────────────────────────────────────────────
/**
 * Extract relative import paths from TypeScript/JavaScript source code.
 * Returns array of { importPath, lineNum } for relative imports.
 */
export function extractRelativeImports(source) {
	const imports = [];
	const lines = source.split("\n");
	// Match:
	//   import ... from './path'
	//   import ... from "../path"
	//   import './path'
	//   require('./path')
	//   require("../path")
	const importPattern =
		/(?:import\s+(?:.*?\s+from\s+)?|require\s*\(\s*)(['"])(\.\.?\/[^'"]+)\1/g;
	// Track if we're inside a block comment
	let inBlockComment = false;
	for (let i = 0; i < lines.length; i++) {
		const line = lines[i];
		// Handle block comment boundaries
		if (inBlockComment) {
			if (line.includes("*/")) {
				inBlockComment = false;
			}
			continue;
		}
		// Check for block comment start (that doesn't end on same line)
		const blockStart = line.indexOf("/*");
		const blockEnd = line.indexOf("*/");
		if (blockStart !== -1 && (blockEnd === -1 || blockEnd < blockStart)) {
			inBlockComment = true;
			continue;
		}
		// Skip single-line comments (// at start or after whitespace)
		const trimmed = line.trimStart();
		if (trimmed.startsWith("//")) {
			continue;
		}
		// Skip JSDoc-style lines (e.g., " * import ...")
		if (trimmed.startsWith("*")) {
			continue;
		}
		let match;
		// Reset lastIndex for each line
		importPattern.lastIndex = 0;
		while ((match = importPattern.exec(line)) !== null) {
			// Check if this match is after a // comment marker on the same line
			const beforeMatch = line.substring(0, match.index);
			if (beforeMatch.includes("//")) {
				continue;
			}
			imports.push({
				importPath: match[2],
				lineNum: i + 1,
			});
		}
	}
	return imports;
}
/**
 * Check if a relative import resolves to an existing file.
 * Handles extensions and TypeScript ESM convention (.js imports resolve to .ts).
 */
export function resolveImportPath(importPath, sourceFile, basePath) {
	const sourceDir = dirname(resolve(basePath, sourceFile));
	const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
	// Handle TypeScript ESM convention: .js imports resolve to .ts files
	// e.g., import './types.js' -> ./types.ts
	let normalizedPath = importPath;
	if (importPath.endsWith(".js")) {
		normalizedPath = importPath.slice(0, -3);
	} else if (importPath.endsWith(".jsx")) {
		normalizedPath = importPath.slice(0, -4);
	} else if (importPath.endsWith(".mjs")) {
		normalizedPath = importPath.slice(0, -4);
	} else if (importPath.endsWith(".cjs")) {
		normalizedPath = importPath.slice(0, -4);
	}
	// Try the normalized path with common extensions first
	for (const ext of extensions) {
		const fullPath = resolve(sourceDir, normalizedPath + ext);
		if (existsSync(fullPath)) {
			return { exists: true, resolvedPath: fullPath };
		}
	}
	// Try as a directory with index file
	for (const ext of extensions) {
		const indexPath = resolve(sourceDir, normalizedPath, `index${ext}`);
		if (existsSync(indexPath)) {
			return { exists: true, resolvedPath: indexPath };
		}
	}
	// Check if path already has extension (for .json, etc.)
	const hasExt = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"].some(
		(ext) => importPath.endsWith(ext),
	);
	if (hasExt) {
		const fullPath = resolve(sourceDir, importPath);
		if (existsSync(fullPath)) {
			return { exists: true, resolvedPath: fullPath };
		}
	}
	return { exists: false, resolvedPath: null };
}
/**
 * Check that all relative imports in key_files resolve to existing files.
 * Returns blocking failures for unresolvable imports.
 */
export function checkImportResolution(taskRow, _priorTasks, basePath) {
	const results = [];
	// Get files from key_files
	const filesToCheck = taskRow.key_files.filter((f) => {
		const ext = extname(f);
		return [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext);
	});
	for (const file of filesToCheck) {
		const absolutePath = resolve(basePath, file);
		// Skip if file doesn't exist (might have been deleted or renamed)
		if (!existsSync(absolutePath)) {
			continue;
		}
		let source;
		try {
			source = readFileSync(absolutePath, "utf-8");
		} catch {
			continue;
		}
		const imports = extractRelativeImports(source);
		for (const { importPath, lineNum } of imports) {
			const resolution = resolveImportPath(importPath, file, basePath);
			if (!resolution.exists) {
				results.push({
					category: "import",
					target: `${file}:${lineNum}`,
					passed: false,
					message: `Import '${importPath}' in ${file}:${lineNum} does not resolve to an existing file`,
					blocking: true,
				});
			}
		}
	}
	return results;
}
/**
 * Extract function signatures from TypeScript/JavaScript source code.
 */
function extractFunctionSignatures(source, fileName) {
	const signatures = [];
	const lines = source.split("\n");
	// Match function declarations and exports
	// Patterns:
	//   function name(params): ReturnType
	//   export function name(params): ReturnType
	//   export async function name(params): Promise<ReturnType>
	//   const name = (params): ReturnType =>
	//   export const name = (params): ReturnType =>
	const funcPattern =
		/(?:export\s+)?(?:async\s+)?(?:function\s+|const\s+)(\w+)(?:\s*=\s*)?\s*\(([^)]*)\)(?:\s*:\s*([^{=>\n]+))?/g;
	for (let i = 0; i < lines.length; i++) {
		const line = lines[i];
		funcPattern.lastIndex = 0;
		let match;
		while ((match = funcPattern.exec(line)) !== null) {
			const [, name, params, returnType] = match;
			signatures.push({
				name,
				params: normalizeParams(params),
				returnType: normalizeType(returnType || "void"),
				file: fileName,
				lineNum: i + 1,
			});
		}
	}
	return signatures;
}
/**
 * Normalize parameter list for comparison.
 */
function normalizeParams(params) {
	return params
		.replace(/\/\*[\s\S]*?\*\//g, "") // Remove block comments
		.replace(/\/\/[^\n]*/g, "") // Remove line comments
		.replace(/\s*=\s*[^,)]+/g, "") // Remove default values
		.replace(/\s+/g, " ") // Normalize whitespace
		.trim();
}
/**
 * Normalize type for comparison.
 */
function normalizeType(type) {
	return type.replace(/\s+/g, " ").trim();
}
/**
 * Detect hallucination cascades: functions in current task with mismatched signatures.
 * Returns non-blocking warnings for parameter/return type drift.
 */
export function checkCrossTaskSignatures(taskRow, priorTasks, basePath) {
	const results = [];
	// Build map of functions from prior tasks' key_files
	const priorSignatures = new Map();
	for (const task of priorTasks) {
		for (const file of task.key_files) {
			const ext = extname(file);
			if (![".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext))
				continue;
			const absolutePath = resolve(basePath, file);
			if (!existsSync(absolutePath)) continue;
			try {
				const source = readFileSync(absolutePath, "utf-8");
				const sigs = extractFunctionSignatures(source, file);
				for (const sig of sigs) {
					const existing = priorSignatures.get(sig.name) || [];
					existing.push(sig);
					priorSignatures.set(sig.name, existing);
				}
			} catch {
				// Skip unreadable files
			}
		}
	}
	// Extract function calls/references from current task's key_files
	// and check they match prior definitions
	for (const file of taskRow.key_files) {
		const ext = extname(file);
		if (![".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext)) continue;
		const absolutePath = resolve(basePath, file);
		if (!existsSync(absolutePath)) continue;
		try {
			const source = readFileSync(absolutePath, "utf-8");
			const currentSigs = extractFunctionSignatures(source, file);
			// Check each function in current task against prior definitions
			for (const currentSig of currentSigs) {
				const priorDefs = priorSignatures.get(currentSig.name);
				// If this function was defined in a prior task, check for signature drift
				// against ALL prior definitions, not just the most recent one.
				if (priorDefs && priorDefs.length > 0) {
					for (const priorDef of priorDefs) {
						// Check parameter mismatch
						if (currentSig.params !== priorDef.params) {
							results.push({
								category: "signature",
								target: currentSig.name,
								passed: false,
								message: `Function '${currentSig.name}' in ${file}:${currentSig.lineNum} has parameters '${currentSig.params}' but prior definition in ${priorDef.file}:${priorDef.lineNum} has '${priorDef.params}'`,
								blocking: false, // Warn only — may be intentional override
							});
						}
						// Check return type mismatch
						if (currentSig.returnType !== priorDef.returnType) {
							results.push({
								category: "signature",
								target: currentSig.name,
								passed: false,
								message: `Function '${currentSig.name}' in ${file}:${currentSig.lineNum} returns '${currentSig.returnType}' but prior definition in ${priorDef.file}:${priorDef.lineNum} returns '${priorDef.returnType}'`,
								blocking: false, // Warn only — may be intentional override
							});
						}
					}
				}
			}
		} catch {
			// Skip unreadable files
		}
	}
	return results;
}
// ─── Pattern Consistency Check ───────────────────────────────────────────────
/**
 * Detect async style drift and naming convention inconsistencies.
 * Warns only (non-blocking) since these are style issues, not correctness.
 */
export function checkPatternConsistency(taskRow, _priorTasks, basePath) {
	const results = [];
	for (const file of taskRow.key_files) {
		const ext = extname(file);
		if (![".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext)) continue;
		const absolutePath = resolve(basePath, file);
		if (!existsSync(absolutePath)) continue;
		try {
			const source = readFileSync(absolutePath, "utf-8");
			// Check for async style drift
			const asyncStyleResult = checkAsyncStyleDrift(source, file);
			if (asyncStyleResult) {
				results.push(asyncStyleResult);
			}
			// Check for naming convention inconsistencies
			const namingResults = checkNamingConsistency(source, file);
			results.push(...namingResults);
		} catch {
			// Skip unreadable files
		}
	}
	return results;
}
/**
 * Detect async style drift within a single file.
 * Returns a warning if both async/await AND .then() promise chaining are used.
 */
function checkAsyncStyleDrift(source, fileName) {
	// Check for async/await usage
	const hasAsyncAwait = /\basync\b[\s\S]*?\bawait\b/.test(source);
	// Check for .then() promise chaining (excluding comments)
	// Filter out common false positives like Array.prototype.then doesn't exist
	const hasThenChaining = /\.\s*then\s*\(/.test(source);
	// If both patterns are present, flag as style drift
	if (hasAsyncAwait && hasThenChaining) {
		return {
			category: "pattern",
			target: fileName,
			passed: false,
			message: `File ${fileName} mixes async/await with .then() promise chaining — consider using consistent async style`,
			blocking: false,
		};
	}
	return null;
}
/**
 * Check for naming convention inconsistencies within a file.
 * Detects mixing of camelCase and snake_case for similar identifier types.
 */
function checkNamingConsistency(source, fileName) {
	const results = [];
	// Extract function names
	const functionNames = [];
	const funcPattern =
		/(?:function\s+|const\s+|let\s+|var\s+)(\w+)(?:\s*=\s*(?:async\s*)?\(|\s*\()/g;
	let match;
	while ((match = funcPattern.exec(source)) !== null) {
		functionNames.push(match[1]);
	}
	// Check for mixed naming conventions in functions
	const camelCaseFuncs = functionNames.filter(
		(n) => /^[a-z][a-zA-Z0-9]*$/.test(n) && /[A-Z]/.test(n),
	);
	const snakeCaseFuncs = functionNames.filter((n) =>
		/^[a-z][a-z0-9]*(_[a-z0-9]+)+$/.test(n),
	);
	if (camelCaseFuncs.length > 0 && snakeCaseFuncs.length > 0) {
		results.push({
			category: "pattern",
			target: fileName,
			passed: false,
			message: `File ${fileName} mixes camelCase (${camelCaseFuncs.slice(0, 2).join(", ")}) and snake_case (${snakeCaseFuncs.slice(0, 2).join(", ")}) function names`,
			blocking: false,
		});
	}
	return results;
}
// ─── Cross-Slice Consistency Check ───────────────────────────────────────────
/**
 * Check whether any key_file in the current task is also referenced by tasks
 * in other slices. Returns non-blocking warnings for potential conflicts.
 */
export function checkCrossSliceConsistency(taskRow, allSliceTasks, _basePath) {
	const results = [];
	if (!taskRow.key_files || taskRow.key_files.length === 0) {
		return [
			{
				passed: true,
				blocking: false,
				category: "cross-slice",
				target: taskRow.id,
				message: "No key files to check for cross-slice consistency",
			},
		];
	}
	if (!allSliceTasks || allSliceTasks.length === 0) {
		return [
			{
				passed: true,
				blocking: false,
				category: "cross-slice",
				target: taskRow.id,
				message: "No other slice tasks to check against",
			},
		];
	}
	const otherSliceTasks = allSliceTasks.filter(
		(t) =>
			t.slice_id !== taskRow.slice_id && t.key_files && t.key_files.length > 0,
	);
	const conflictingSlices = new Map(); // file → Set(sliceIds)
	for (const file of taskRow.key_files) {
		for (const other of otherSliceTasks) {
			if (other.key_files.includes(file)) {
				if (!conflictingSlices.has(file)) {
					conflictingSlices.set(file, new Set());
				}
				conflictingSlices.get(file).add(other.slice_id);
			}
		}
	}
	if (conflictingSlices.size === 0) {
		return [
			{
				passed: true,
				blocking: false,
				category: "cross-slice",
				target: taskRow.id,
				message: "No cross-slice conflicts detected",
			},
		];
	}
	for (const [file, sliceIds] of conflictingSlices) {
		results.push({
			passed: false,
			blocking: false,
			category: "cross-slice",
			target: file,
			message: `File '${file}' is also a key file in slice(s) ${Array.from(sliceIds).join(", ")} — verify this is intentional`,
		});
	}
	return results;
}
// ─── Milestone Integrity Check ───────────────────────────────────────────────
/**
 * Verify milestone-level integrity: completed slices have summaries,
 * roadmap is consistent, and no active requirements are orphaned.
 */
export function checkMilestoneIntegrity(milestoneId, basePath) {
	const results = [];
	// Check ROADMAP exists and parse it
	const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
	if (!roadmapPath || !existsSync(roadmapPath)) {
		results.push({
			passed: false,
			blocking: false,
			category: "milestone",
			target: milestoneId,
			message: `Milestone ROADMAP.md not found for ${milestoneId}`,
		});
		return results;
	}
	let roadmap;
	try {
		const content = readFileSync(roadmapPath, "utf-8");
		roadmap = parseRoadmap(content);
	} catch (err) {
		results.push({
			passed: false,
			blocking: false,
			category: "milestone",
			target: milestoneId,
			message: `Failed to parse ROADMAP.md for ${milestoneId}: ${err.message}`,
		});
		return results;
	}
	// Check that slices marked done in ROADMAP have SUMMARY.md files
	for (const slice of roadmap.slices) {
		if (slice.done) {
			const summaryPath = resolveSliceFile(
				basePath,
				milestoneId,
				slice.id,
				"SUMMARY",
			);
			if (!summaryPath || !existsSync(summaryPath)) {
				results.push({
					passed: false,
					blocking: false,
					category: "milestone",
					target: `${milestoneId}/${slice.id}`,
					message: `Slice ${slice.id} is marked done in ROADMAP but missing SUMMARY.md`,
				});
			}
		}
	}
	// Check REQUIREMENTS.md for active requirements belonging to this milestone
	const reqPath = resolveSfRootFile(basePath, "REQUIREMENTS");
	if (reqPath && existsSync(reqPath)) {
		try {
			const reqContent = readFileSync(reqPath, "utf-8");
			const requirements = parseRequirementsSections(reqContent);
			const activeForMilestone = requirements.filter(
				(r) => r.status === "active" && r.primary_owner === milestoneId,
			);
			for (const req of activeForMilestone) {
				results.push({
					passed: false,
					blocking: false,
					category: "milestone",
					target: req.id,
					message: `Active requirement ${req.id} (${req.description || "no description"}) is owned by ${milestoneId} but not yet validated`,
				});
			}
		} catch (err) {
			results.push({
				passed: false,
				blocking: false,
				category: "milestone",
				target: milestoneId,
				message: `Failed to parse REQUIREMENTS.md: ${err.message}`,
			});
		}
	}
	// If no issues found, return a passing check
	if (results.length === 0) {
		results.push({
			passed: true,
			blocking: false,
			category: "milestone",
			target: milestoneId,
			message: `Milestone ${milestoneId} integrity checks passed`,
		});
	}
	return results;
}
// ─── Main Entry Point ────────────────────────────────────────────────────────
/**
 * Run all post-execution checks against a completed task.
 *
 * @param taskRow - The completed task row
 * @param priorTasks - Array of TaskRow from prior completed tasks in the slice
 * @param basePath - Base path for resolving file references
 * @param milestoneId - Optional milestone ID for milestone-level checks
 * @param allSliceTasks - Optional array of all tasks across all slices for cross-slice checks
 * @returns PostExecutionResult with status, checks, and duration
 */
export function runPostExecutionChecks(
	taskRow,
	priorTasks,
	basePath,
	milestoneId,
	allSliceTasks,
) {
	const startTime = Date.now();
	const allChecks = [];
	// Run all checks
	const importChecks = checkImportResolution(taskRow, priorTasks, basePath);
	const signatureChecks = checkCrossTaskSignatures(
		taskRow,
		priorTasks,
		basePath,
	);
	const patternChecks = checkPatternConsistency(taskRow, priorTasks, basePath);
	allChecks.push(...importChecks, ...signatureChecks, ...patternChecks);
	// New checks: cross-slice consistency and milestone integrity
	if (allSliceTasks && allSliceTasks.length > 0) {
		const crossSliceChecks = checkCrossSliceConsistency(
			taskRow,
			allSliceTasks,
			basePath,
		);
		allChecks.push(...crossSliceChecks);
	}
	if (milestoneId) {
		const milestoneChecks = checkMilestoneIntegrity(milestoneId, basePath);
		allChecks.push(...milestoneChecks);
	}
	const durationMs = Date.now() - startTime;
	// Determine overall status
	const hasBlockingFailure = allChecks.some((c) => !c.passed && c.blocking);
	const hasNonBlockingIssue = allChecks.some(
		(c) => (!c.passed && !c.blocking) || (c.passed && c.category === "pattern"),
	);
	let status;
	if (hasBlockingFailure) {
		status = "fail";
	} else if (hasNonBlockingIssue) {
		status = "warn";
	} else {
		status = "pass";
	}
	return {
		status,
		checks: allChecks,
		durationMs,
	};
}
