import {
	existsSync,
	mkdirSync,
	readdirSync,
	readFileSync,
	rmdirSync,
	rmSync,
	writeFileSync,
} from "node:fs";
import { dirname, join } from "node:path";
import { SCAFFOLD_FILES } from "./scaffold-constants.js";
import { migrateLegacyScaffold } from "./scaffold-drift.js";
import {
	detectRepoProfile,
	readPreferencesProfile,
	resolveActiveProfileSet,
} from "./scaffold-profiles.js";
import {
	bodyHash,
	extractMarker,
	readScaffoldManifest,
	recordScaffoldApply,
	stampScaffoldFile,
	writeScaffoldManifest,
} from "./scaffold-versioning.js";
import { logWarning } from "./workflow-logger.js";

export { SCAFFOLD_FILES };

/**
 * Files in SCAFFOLD_FILES that intentionally do not carry an inline
 * version marker (per ADR-021 §2). The manifest still records that SF
 * wrote them, so legacy-hash migration in Phase C can identify them.
 */
const NO_MARKER_PATHS = new Set([".siftignore"]);

export { detectRepoProfile };

const LEGACY_ROOT_HARNESS_PATHS = [
	"harness/AGENTS.md",
	"harness/specs/AGENTS.md",
	"harness/specs/bootstrap.md",
	"harness/evals/AGENTS.md",
	"harness/graders/AGENTS.md",
];
const LEGACY_ROOT_HARNESS_TARGETS = {
	"harness/AGENTS.md": ".sf/harness/AGENTS.md",
	"harness/specs/AGENTS.md": ".sf/harness/specs/AGENTS.md",
	"harness/specs/bootstrap.md": ".sf/harness/specs/bootstrap.md",
	"harness/evals/AGENTS.md": ".sf/harness/evals/AGENTS.md",
	"harness/graders/AGENTS.md": ".sf/harness/graders/AGENTS.md",
};

function pruneEmptyDir(path) {
	try {
		if (existsSync(path) && readdirSync(path).length === 0) {
			rmdirSync(path);
		}
	} catch (err) {
		logWarning("scaffold", "failed to prune empty legacy harness dir", {
			dir: path,
			error: err.message,
		});
	}
}

function scaffoldContentForPath(relPath) {
	return SCAFFOLD_FILES.find((file) => file.path === relPath)?.content ?? null;
}

function isUnmarkedGeneratedRootHarness(relPath, body) {
	const currentRelPath = LEGACY_ROOT_HARNESS_TARGETS[relPath];
	const currentContent = currentRelPath
		? scaffoldContentForPath(currentRelPath)
		: null;
	if (!currentContent) return false;
	return body.trim() === currentContent.trim();
}

function removeLegacyRootHarnessScaffold(basePath) {
	for (const relPath of LEGACY_ROOT_HARNESS_PATHS) {
		const target = join(basePath, relPath);
		try {
			if (!existsSync(target)) continue;
			const { marker, body } = extractMarker(target);
			if (marker) {
				if (marker.template !== relPath) continue;
				if (marker.state !== "pending") continue;
				if (bodyHash(body) !== marker.hash) continue;
			} else if (
				!isUnmarkedGeneratedRootHarness(relPath, readFileSync(target, "utf-8"))
			) {
				continue;
			}
			rmSync(target);
		} catch (err) {
			logWarning("scaffold", "failed to remove legacy root harness file", {
				file: relPath,
				error: err.message,
			});
		}
	}
	pruneEmptyDir(join(basePath, "harness/specs"));
	pruneEmptyDir(join(basePath, "harness/evals"));
	pruneEmptyDir(join(basePath, "harness/graders"));
	pruneEmptyDir(join(basePath, "harness"));
}

/**
 * Drift-aware scaffold sync (ADR-021 Phase C, extended by ADR-022).
 *
 * Behavior:
 *   1. Read the active profile from the manifest. On first run, auto-detect
 *      the profile and write it to the manifest so future runs are stable.
 *   2. Run legacy migration first — unmarked files whose body hash matches a
 *      known prior version in SCAFFOLD_VERSION_ARCHIVE get promoted to pending
 *      and stamped. Handles projects that pre-date the marker system.
 *   3. For each scaffold template IN the active profile:
 *      - Missing on disk → write template, stamp marker, record manifest entry.
 *      - Present, marker, state=pending, version drifted, hash matches stamp →
 *        silent re-render with current template, restamp.
 *      - Present, marker says editing, completed, or disabled → leave alone.
 *      - Present without marker after migration → user-customised, leave alone.
 *   Files not in the active profile are skipped entirely.
 *
 * Silent contract: no stdout/stderr in normal paths. Only logWarning("scaffold")
 * for unexpected I/O failures. Failure modes are non-fatal.
 */
export function ensureAgenticDocsScaffold(basePath) {
	const sfVersion = process.env.SF_VERSION || "0.0.0";
	const appliedAt = new Date().toISOString();
	const manifest = readScaffoldManifest(basePath);
	// PREFERENCES.md frontmatter takes highest precedence (ADR-022 §6).
	// If no profile is set anywhere, auto-detect and persist to manifest.
	const {
		profileName: activeProfile,
		profileSet,
		warning,
	} = resolveActiveProfileSet(basePath, manifest, null);
	if (warning) {
		logWarning("scaffold", warning, {});
	}
	if (!manifest.profile && !readPreferencesProfile(basePath)) {
		// First run — persist auto-detected profile to manifest.
		try {
			writeScaffoldManifest(basePath, { ...manifest, profile: activeProfile });
		} catch (err) {
			logWarning("scaffold", "failed to write profile to manifest", {
				error: err.message,
			});
		}
	}
	// Step 1: legacy migration — promote unmarked-but-recognised files.
	try {
		migrateLegacyScaffold(basePath, activeProfile);
	} catch (err) {
		logWarning("scaffold", "legacy migration failed", {
			error: err.message,
		});
	}
	removeLegacyRootHarnessScaffold(basePath);
	// Step 2: missing-file creation + pending-state silent upgrade.
	for (const file of SCAFFOLD_FILES) {
		// ADR-022: skip files that are not in the active profile.
		if (!profileSet.has(file.path)) continue;
		const target = join(basePath, file.path);
		const skipMarker = NO_MARKER_PATHS.has(file.path);
		if (!existsSync(target)) {
			try {
				mkdirSync(dirname(target), { recursive: true });
				writeFileSync(target, file.content, "utf-8");
				if (!skipMarker) {
					stampScaffoldFile(target, file.path, sfVersion, "pending");
				}
				const entry = {
					path: file.path,
					template: file.path,
					version: sfVersion,
					appliedAt,
					stateAtApply: "pending",
					contentHash: bodyHash(file.content),
				};
				recordScaffoldApply(basePath, entry);
			} catch (err) {
				logWarning("scaffold", "failed to write missing scaffold file", {
					file: file.path,
					error: err.message,
				});
			}
			continue;
		}
		// Present — only refresh when state=pending AND drifted from current ship.
		// .siftignore (NO_MARKER_PATHS) skips silent refresh; the manifest version
		// alone isn't enough signal to safely overwrite a dotfile config.
		if (skipMarker) continue;
		try {
			const { marker, body } = extractMarker(target);
			if (!marker) continue; // untracked / customised after migration — leave alone
			if (marker.state !== "pending") continue; // editing, completed, or disabled — leave alone
			if (marker.version === sfVersion) continue; // already current
			// Confirm on-disk hash matches the stamped hash. If diverged, the
			// file was edited without removing the marker — treat as editing-drift
			// and leave alone.
			if (bodyHash(body) !== marker.hash) continue;
			// Silent re-render with current template + restamp.
			writeFileSync(target, file.content, "utf-8");
			stampScaffoldFile(target, file.path, sfVersion, "pending");
			const entry = {
				path: file.path,
				template: file.path,
				version: sfVersion,
				appliedAt,
				stateAtApply: "pending",
				contentHash: bodyHash(file.content),
			};
			recordScaffoldApply(basePath, entry);
		} catch (err) {
			logWarning("scaffold", "failed to refresh pending scaffold file", {
				file: file.path,
				error: err.message,
			});
		}
	}
}
