/**
 * Triage-Self-Feedback agent persona — vars loader and report applier.
 *
 * loadTriageSelfFeedbackVars: builds the vars object for loadPrompt("triage-self-feedback", vars).
 * applyTriageReport: applies a parsed triage report: writes new REQUIREMENTS rows,
 *                    resolves entries via markResolved. Idempotent.
 */
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { sfRoot } from "./paths.js";
import { loadPrompt } from "./prompt-loader.js";
import {
	markResolved,
	readAllSelfFeedback,
	readUpstreamSelfFeedback,
} from "./self-feedback.js";
import {
	getActiveRequirements,
	getAllMilestones,
	getMilestoneSlices,
	isDbAvailable,
} from "./sf-db.js";

/**
 * Read all open (unresolved) feedback entries from the feedback channel.
 */
function readOpenEntries(basePath) {
	return [
		...readAllSelfFeedback(basePath),
		...readUpstreamSelfFeedback(),
	].filter((e) => !e.resolvedAt);
}
/**
 * Read requirements content — DB primary, .md file fallback for unmigrated projects.
 */
function readRequirementsContent(basePath) {
	if (isDbAvailable()) {
		const rows = getActiveRequirements();
		if (rows.length > 0) {
			return rows
				.map((r) => `- [${r.id}] ${r.title}: ${r.description ?? ""}`)
				.join("\n");
		}
	}
	const sfDir = sfRoot(basePath);
	for (const p of [
		join(sfDir, "REQUIREMENTS.md"),
		join(sfDir, "requirements.md"),
	]) {
		if (existsSync(p)) return readFileSync(p, "utf-8");
	}
	return "(no requirements found)";
}
/**
 * Build a brief roadmap summary — DB primary, filesystem fallback.
 */
function buildRoadmapSummary(basePath) {
	if (isDbAvailable()) {
		const milestones = getAllMilestones();
		if (milestones.length === 0) return "(no milestones found)";
		const lines = [];
		for (const m of milestones) {
			lines.push(`- ${m.id}: ${m.title || m.id} [${m.status}]`);
			const slices = getMilestoneSlices(m.id);
			for (const s of slices) {
				lines.push(`  - ${s.id}: ${s.title || s.id} [${s.status}]`);
			}
		}
		return lines.join("\n");
	}
	// Fallback: scan .sf/milestones/ for unmigrated projects
	const sfDir = sfRoot(basePath);
	const milestonesDir = join(sfDir, "milestones");
	if (!existsSync(milestonesDir)) return "(no milestones directory found)";
	let milestoneEntries;
	try {
		milestoneEntries = readdirSync(milestonesDir);
	} catch {
		return "(could not read milestones directory)";
	}
	const lines = [];
	for (const mName of milestoneEntries.sort()) {
		lines.push(`- ${mName}: [unknown]`);
	}
	return lines.length > 0 ? lines.join("\n") : "(no milestones found)";
}
/**
 * Build the vars object for loadPrompt("triage-self-feedback", vars).
 *
 * @param basePath - project root (the directory containing package.json and .sf/)
 */
export function loadTriageSelfFeedbackVars(basePath) {
	const allOpen = readOpenEntries(basePath);
	const forgeEntries = allOpen.filter((e) => e.repoIdentity === "forge");
	const upstreamEntries = allOpen.filter((e) => e.repoIdentity === "external");
	return {
		forgeSelfFeedbackJson: JSON.stringify(forgeEntries, null, 2),
		upstreamRollups: JSON.stringify(upstreamEntries, null, 2),
		existingRequirementsTable: readRequirementsContent(basePath),
		existingRoadmapSummary: buildRoadmapSummary(basePath),
	};
}
/**
 * Build the full prompt string for a triage-self-feedback agent run.
 * Convenience wrapper around loadPrompt + loadTriageSelfFeedbackVars.
 */
export function buildTriageSelfFeedbackPrompt(basePath) {
	const vars = loadTriageSelfFeedbackVars(basePath);
	return loadPrompt("triage-self-feedback", { ...vars });
}
// ─── Report parser ────────────────────────────────────────────────────────────
/**
 * Extract and parse the triage report JSON from agent output.
 *
 * Accepts:
 *  - A fenced ```json ... ``` block anywhere in the text.
 *  - Raw JSON (the entire string is the report).
 * Throws on malformed JSON or schema violations.
 */
export function parseTriageReport(agentOutput) {
	// Try to find a fenced ```json block
	const fenceMatch = agentOutput.match(/```json\s*([\s\S]*?)```/);
	const jsonStr = fenceMatch ? fenceMatch[1].trim() : agentOutput.trim();
	let parsed;
	try {
		parsed = JSON.parse(jsonStr);
	} catch (err) {
		throw new Error(
			`parseTriageReport: could not parse JSON from agent output: ${err.message}`,
		);
	}
	if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
		throw new Error("parseTriageReport: expected a JSON object at top level");
	}
	const obj = parsed;
	if (!Array.isArray(obj.clusterDecisions)) {
		throw new Error(
			"parseTriageReport: missing or non-array 'clusterDecisions'",
		);
	}
	if (!Array.isArray(obj.promotedRequirements)) {
		throw new Error(
			"parseTriageReport: missing or non-array 'promotedRequirements'",
		);
	}
	if (!Array.isArray(obj.resolutions)) {
		throw new Error("parseTriageReport: missing or non-array 'resolutions'");
	}
	return obj;
}
/**
 * Apply a parsed triage report:
 *   1. Append new REQUIREMENTS.md rows for each promotedRequirement.
 *      Idempotent: skips rows whose ID already appears in the file.
 *   2. Call markResolved for each resolution in the report.
 *      Idempotent: markResolved itself skips already-resolved entries.
 *   3. Quick Win #1: Auto-fix high-confidence self-reports where confidence > 0.85.
 *
 * @param basePath - project root (directory containing .sf/)
 * @param report   - parsed TriageReport from parseTriageReport()
 */
export async function applyTriageReport(basePath, report) {
	let requirementsAdded = 0;
	let entriesResolved = 0;
	let reportsAutoFixed = 0;
	let reportsPromotedToBacklog = 0;

	// ── 1. Write promoted requirements ────────────────────────────────────────
	if (report.promotedRequirements.length > 0) {
		const sfDir = sfRoot(basePath);
		const reqPath = (() => {
			const canonical = join(sfDir, "REQUIREMENTS.md");
			const legacy = join(sfDir, "requirements.md");
			if (existsSync(canonical)) return canonical;
			if (existsSync(legacy)) return legacy;
			return canonical; // will create
		})();
		let content = existsSync(reqPath) ? readFileSync(reqPath, "utf-8") : "";
		for (const req of report.promotedRequirements) {
			// Idempotency: skip if this requirement ID already present
			const idPattern = new RegExp(`###\\s+${req.id}\\s+`);
			if (idPattern.test(content)) continue;
			const row = [
				``,
				`### ${req.id} — ${req.title}`,
				`- Class: ${req.class}`,
				`- Status: active`,
				`- Description: ${req.description}`,
				`- Why it matters: ${req.whyItMatters}`,
				`- Source: ${req.source}`,
				`- Primary owning slice: none yet`,
				`- Supporting slices: none`,
				`- Validation: ${req.validation}`,
				`- Notes: ${req.notes}`,
			].join("\n");
			// Insert under the ## Active section if present; otherwise append
			if (/^## Active/m.test(content)) {
				// Append the new row just before the next ## heading (or end of Active section)
				const activeHeadingIdx = content.search(/^## Active/m);
				const nextHeadingIdx = content
					.slice(activeHeadingIdx + 1)
					.search(/^## /m);
				const insertAt =
					nextHeadingIdx === -1
						? content.length
						: activeHeadingIdx + 1 + nextHeadingIdx;
				content =
					content.slice(0, insertAt).trimEnd() +
					"\n" +
					row +
					"\n\n" +
					content.slice(insertAt).trimStart();
			} else {
				// No Active section — append at end
				content = content.trimEnd() + `\n\n## Active\n${row}\n`;
			}
			requirementsAdded++;
		}
		writeFileSync(reqPath, content, "utf-8");
	}

	// ── 2. Resolve entries ────────────────────────────────────────────────────
	for (const resolution of report.resolutions) {
		const evidenceKind = resolution.evidenceKind;
		const evidence =
			evidenceKind === "agent-fix"
				? { kind: "agent-fix", summaryNarrative: resolution.reason }
				: { kind: "human-clear" };
		const mutated = markResolved(
			resolution.entryId,
			{ reason: resolution.reason, evidence },
			basePath,
		);
		if (mutated) entriesResolved++;
	}

	// ── 3. Quick Win #1: Auto-fix high-confidence self-reports ────────────────
	// Integration point for self-report-fixer: read open reports and auto-apply
	// fixes where confidence > 0.85.
	try {
		const { autoFixHighConfidenceReports, promoteSelfReportsToBacklog } =
			await import("./self-report-fixer.js");
		const allOpen = [
			...readAllSelfFeedback(basePath),
			...readUpstreamSelfFeedback(),
		].filter((e) => !e.resolvedAt);

		if (allOpen.length > 0) {
			const result = await autoFixHighConfidenceReports(basePath, allOpen);
			reportsAutoFixed = result.applied.length;
			const promoted = promoteSelfReportsToBacklog(basePath, allOpen, {
				triageRunId: report.triageRunId ?? null,
			});
			reportsPromotedToBacklog =
				promoted.promoted.length + promoted.updated.length;
		}
	} catch {
		/* self-report fixer is optional; never block triage report application */
	}

	return {
		requirementsAdded,
		entriesResolved,
		reportsAutoFixed,
		reportsPromotedToBacklog,
	};
}
