/**
 * commands-harness.ts - repo-native harness evolution commands.
 *
 * Purpose: expose the read-only profiler so operators can seed harness
 * evolution state without changing prompts or claiming untracked files.
 * Also provides a promotion path for turning .sf runtime observations into
 * tracked docs artifacts (sf-moocr4rv-au7r3l).
 */
import { mkdirSync, writeFileSync } from "node:fs";
import { join, resolve } from "node:path";
import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
import { projectRoot } from "./commands/context.js";
import { profileRepository } from "./repo-profiler.js";
import { getLatestRepoProfile, recordRepoProfile } from "./sf-db.js";

const HARNESS_PROMOTION_REPO_DIR = "docs/exec-plans/active";
/**
 * Format a repo profile summary for user notification.
 */
function formatProfileSummary(profile) {
	const untracked = profile.git.changedFiles.filter(
		(file) => file.gitStatus === "untracked",
	).length;
	const modified = profile.git.changedFiles.filter(
		(file) => file.gitStatus === "modified",
	).length;
	const stacks =
		profile.stacks
			.map((stack) => stack.kind)
			.filter(Boolean)
			.join(", ") || "none detected";
	const risks =
		profile.riskHints
			.map((hint) => hint.family)
			.filter(Boolean)
			.join(", ") || "none detected";
	return [
		"Repo harness profile recorded",
		`Profile: ${profile.profileId}`,
		`State: ${join(profile.projectRoot, ".sf", "sf.db")}`,
		`Branch: ${profile.git.branch ?? "unknown"}`,
		`Changed files: ${profile.git.changedFiles.length} (${modified} modified, ${untracked} untracked)`,
		`Stacks: ${stacks}`,
		`Risk hints: ${risks}`,
		"",
		"Runtime observation boundary:",
		"- Profile state was stored only in .sf runtime state.",
		"- No repo-committable artifact was written by profiling.",
		"- Use /harness promote <finding-id> after review to create a tracked docs artifact.",
		"- Untracked files remain observed_only; SF did not stage or adopt them.",
	].join("\n");
}
/**
 * Convert a finding id into a stable filename segment.
 *
 * Purpose: keep promotion artifacts deterministic while preventing path
 * traversal through user-provided finding IDs.
 *
 * Consumer: `/harness promote <finding-id>`.
 */
function findingIdSlug(findingId) {
	const slug = findingId
		.trim()
		.toLowerCase()
		.replace(/[^a-z0-9._-]+/g, "-")
		.replace(/^-+|-+$/g, "")
		.slice(0, 120);
	return slug || "finding";
}
/**
 * Parse the persisted repo profile JSON from .sf runtime state.
 *
 * Purpose: promotion must be a writeback from recorded observations, not a new
 * profiler run that can observe its own artifact or introduce timestamps.
 *
 * Consumer: `/harness promote <finding-id>`.
 */
function parseRecordedProfile(profileJson) {
	try {
		const parsed = JSON.parse(profileJson);
		if (
			typeof parsed.profileId === "string" &&
			typeof parsed.createdAt === "string" &&
			parsed.git &&
			Array.isArray(parsed.git.changedFiles)
		) {
			return parsed;
		}
	} catch {
		// Fall back to row-level metadata below.
	}
	return null;
}
/**
 * Build the stable JSON payload embedded in a promotion artifact.
 *
 * Purpose: document the recorded observation facts without leaking absolute
 * runtime paths or adding promotion-time fields.
 *
 * Consumer: `/harness promote <finding-id>`.
 */
function profilePromotionPayload(profile, fallback) {
	return {
		profileId: profile?.profileId ?? fallback.profileId,
		profileCapturedAt: profile?.createdAt ?? fallback.createdAt,
		branch: profile?.git.branch ?? fallback.branch,
		dirty: profile?.git.dirty ?? fallback.dirty,
		changedFiles: profile?.git.changedFiles ?? [],
		stacks: profile?.stacks ?? [],
		entrypoints: profile?.entrypoints ?? [],
		tests: profile?.tests ?? [],
		ci: profile?.ci ?? [],
		docs: profile?.docs ?? [],
		dataStores: profile?.dataStores ?? [],
		networkSurfaces: profile?.networkSurfaces ?? [],
		riskHints: profile?.riskHints ?? [],
	};
}
/**
 * Promote a harness/profile finding from .sf runtime observations into a
 * tracked docs artifact. This is the writeback path that turns operational
 * state into reviewable, committable documentation.
 *
 * Purpose: satisfy AC1 of sf-moocr4rv-au7r3l — harness findings must be
 * promotable into tracked docs with deterministic path and content.
 *
 * Consumer: `/harness promote <finding-id>` command.
 */
export async function handleHarnessPromote(findingId, ctx) {
	const basePath = projectRoot();
	const opened = await ensureDbOpen(basePath);
	if (!opened) {
		ctx.ui.notify("No SF database available. Run /init first.", "warning");
		return;
	}
	if (!findingId || findingId.trim().length === 0) {
		ctx.ui.notify(
			"Usage: /harness promote <finding-id>\nPromotes a harness observation to a tracked docs artifact.",
			"warning",
		);
		return;
	}
	const displayFindingId = findingId.trim();
	const latestProfile = getLatestRepoProfile();
	if (!latestProfile) {
		ctx.ui.notify(
			"No recorded harness profile found. Run /harness profile first; promotion writes tracked docs only from .sf runtime observations.",
			"warning",
		);
		return;
	}
	const slug = findingIdSlug(displayFindingId);
	const relativePath = `${HARNESS_PROMOTION_REPO_DIR}/harness-promotion-${slug}.md`;
	const trackedDir = resolve(basePath, "docs", "exec-plans", "active");
	const targetPath = join(trackedDir, `harness-promotion-${slug}.md`);
	// Ensure the tracked directory exists (creates under the repo, not .sf)
	mkdirSync(trackedDir, { recursive: true });
	const recordedProfile = parseRecordedProfile(latestProfile.profileJson);
	const payload = profilePromotionPayload(recordedProfile, {
		profileId: latestProfile.profileId,
		branch: latestProfile.branch,
		dirty: latestProfile.dirty,
		createdAt: latestProfile.createdAt,
	});
	// Build the promoted artifact content
	const content = [
		`# Harness Promotion: ${displayFindingId}`,
		"",
		`Finding ID: ${displayFindingId}`,
		`Repo artifact: \`${relativePath}\``,
		"Source: `.sf` runtime observations",
		`Source profile: ${latestProfile.profileId}`,
		`Source profile captured at: ${latestProfile.createdAt}`,
		`Source branch: ${latestProfile.branch ?? "unknown"}`,
		"",
		"## Runtime Boundary",
		"",
		"- `.sf` remains operational runtime state and is not repo output.",
		"- Unpromoted .sf runtime observations remain `observed_only`.",
		"- This Markdown file is the repo-committable artifact created by promotion.",
		"- Promotion does not stage or claim untracked observed files.",
		"",
		"## Observed Profile",
		"",
		"```json",
		JSON.stringify(payload, null, 2),
		"```",
		"",
		"## Review Checklist",
		"",
		"- [ ] Reviewed by human",
		"- [ ] Adopted into milestone plan",
		"- [ ] Rejected (document reason below)",
		"",
		"## Notes",
		"",
		"_Add review notes here._",
		"",
	].join("\n");
	writeFileSync(targetPath, content, "utf8");
	ctx.ui.notify(
		[
			`Harness finding '${displayFindingId}' promoted to tracked docs.`,
			`Path: ${relativePath}`,
			"",
			"This Markdown file is now the repo-committable artifact for review.",
			"Unpromoted .sf runtime state remains observed_only.",
		].join("\n"),
		"info",
	);
}
/**
 * Run repo harness profiling and persist the resulting snapshot.
 *
 * Purpose: give users and future auto-flow slices an explicit entry point for
 * harness evolution's read-only observation phase.
 *
 * Consumer: `/harness profile` command.
 */
export async function handleHarness(args, ctx) {
	const subcommand = args.trim() || "profile";
	if (subcommand.startsWith("promote ")) {
		const findingId = subcommand.slice("promote ".length).trim();
		await handleHarnessPromote(findingId, ctx);
		return;
	}
	if (!["profile", "snapshot", "status"].includes(subcommand)) {
		ctx.ui.notify(
			"Usage: /harness profile | /harness promote <finding-id>\nRecords a read-only .sf runtime profile or promotes a reviewed finding to tracked docs.",
			"warning",
		);
		return;
	}
	const basePath = projectRoot();
	const opened = await ensureDbOpen(basePath);
	if (!opened) {
		ctx.ui.notify("No SF database available. Run /init first.", "warning");
		return;
	}
	const profile = profileRepository(basePath);
	recordRepoProfile(profile);
	ctx.ui.notify(formatProfileSummary(profile), "info");
}
