/**
 * Lightweight content validator for autonomous mode safety harness.
 * Validates that high-value unit outputs contain minimum expected content.
 *
 * Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
 */
import { existsSync, readFileSync } from "node:fs";
import { lint as lintMarkdown } from "markdownlint/sync";
import { inspectMilestoneRoadmapMarkdown } from "../milestone-quality.js";
import { inspectSlicePlanMarkdown } from "../plan-quality.js";
import { parseRoadmapSlices } from "../roadmap-slices.js";
import { logWarning } from "../workflow-logger.js";
// ─── Public API ─────────────────────────────────────────────────────────────
/**
 * Validate content quality for a completed unit.
 * Returns an array of violations. Empty array = content looks acceptable.
 *
 * @param unitType - The type of unit that completed (e.g. "plan-slice")
 * @param artifactPath - Absolute path to the primary artifact file
 */
export function validateContent(unitType, artifactPath) {
	if (!artifactPath || !existsSync(artifactPath)) return [];
	const validator = VALIDATORS[unitType];
	if (!validator) return [];
	try {
		const content = readFileSync(artifactPath, "utf-8");
		return validator(content);
	} catch (e) {
		logWarning("safety", `content validation read failed: ${e.message}`);
		return [];
	}
}
const LEAKED_JSON_FIELD_RE =
	/(^|[,{]\s*)"(?:successCriteria|proofLevel|integrationClosure|observabilityImpact|planningMeeting|visionMeeting|productResearch|tasks|updatedTasks|removedTaskIds|verify|expectedOutput)"\s*:|",\s*"[A-Za-z][A-Za-z0-9_]*"\s*:/m;
const MARKDOWNLINT_CONFIG = {
	default: true,
	// Generated plans often contain long commands/tables and repeated headings
	// across role sections; keep markdownlint focused on structural hazards.
	MD013: false, // line length
	MD024: false, // duplicate heading text
	MD033: false, // inline HTML
	// These rules flag cosmetic issues in documentation examples and template
	// placeholders that don't affect readability or correctness.
	MD031: false, // blank lines around fenced code blocks
	MD032: false, // blank lines around lists
	MD040: false, // fenced code blocks need language
	MD060: false, // table column style
};
const VALIDATORS = {
	"plan-slice": validatePlanSlice,
	"plan-milestone": validatePlanMilestone,
	"plan-task": validateGenericMarkdownArtifact,
	"replan-slice": validatePlanSlice,
	"execute-task": validateGenericMarkdownArtifact,
	"complete-slice": validateGenericMarkdownArtifact,
	"validate-milestone": validateGenericMarkdownArtifact,
};
function validateCommonArtifactText(content) {
	const violations = [];
	if (content.includes("\\n")) {
		violations.push({
			severity: "warning",
			reason:
				"Artifact contains literal escaped newline text (`\\n`) instead of rendered newlines",
		});
	}
	const leakedJson = content.match(LEAKED_JSON_FIELD_RE);
	if (leakedJson) {
		violations.push({
			severity: "warning",
			reason: `Artifact appears to contain leaked JSON field syntax near '${leakedJson[0].slice(0, 80)}'`,
		});
	}
	return violations;
}
function validateMarkdownHygiene(content) {
	const results = lintMarkdown({
		strings: { artifact: content },
		config: MARKDOWNLINT_CONFIG,
	});
	return (results.artifact ?? []).slice(0, 10).map((issue) => ({
		severity: "warning",
		reason: `Markdown lint ${issue.ruleNames.join("/")} at line ${issue.lineNumber}: ${issue.ruleDescription}`,
	}));
}
function validateGenericMarkdownArtifact(content) {
	return [
		...validateCommonArtifactText(content),
		...validateMarkdownHygiene(content),
	];
}
function validatePlanSlice(content) {
	const violations = validateGenericMarkdownArtifact(content);
	// Must have at least 1 task entry — single-task slices are valid (#3649)
	const taskCount = (content.match(/- \[[ x]\] \*\*T\d+/g) || []).length;
	if (taskCount < 1) {
		violations.push({
			severity: "warning",
			reason: `Slice plan has ${taskCount} task(s) — expected at least 1`,
		});
	}
	// Should have a Files Likely Touched section
	if (
		!content.includes("## Files Likely Touched") &&
		!content.includes("## Files")
	) {
		violations.push({
			severity: "warning",
			reason: "Slice plan missing 'Files Likely Touched' section",
		});
	}
	// Should have a verification section
	if (!content.includes("Verify") && !content.includes("verify")) {
		violations.push({
			severity: "warning",
			reason: "Slice plan has no verification instructions",
		});
	}
	for (const issue of inspectSlicePlanMarkdown(content).issues) {
		violations.push({
			severity: "warning",
			reason: `Slice plan ${issue}`,
		});
	}
	return violations;
}
function validatePlanMilestone(content) {
	const violations = validateGenericMarkdownArtifact(content);
	// Must have at least 1 slice entry. Roadmaps are normally rendered as a
	// Slice Overview table, so use the canonical parser instead of only looking
	// for prose headings.
	const sliceCount = parseRoadmapSlices(content).length;
	if (sliceCount < 1) {
		violations.push({
			severity: "warning",
			reason: `Milestone roadmap has ${sliceCount} slice(s) — expected at least 1`,
		});
	}
	for (const issue of inspectMilestoneRoadmapMarkdown(content).issues) {
		violations.push({
			severity: "warning",
			reason: `Milestone roadmap ${issue}`,
		});
	}
	return violations;
}
