/**
 * Self-aligning template upgrades for SF config files.
 *
 * Goal: when sf evolves, projects' .sf/PREFERENCES.md (and friends) get
 * brought into alignment WITHOUT human intervention. The file's frontmatter
 * stamps `last_synced_with_sf: <semver>`; on every load, sf compares to its
 * own version and silently re-writes the frontmatter to match. The body
 * (Markdown after the frontmatter) is preserved verbatim — users can keep
 * notes there and sf won't clobber them.
 *
 * What this is NOT: a human-facing warning system. The end-user shouldn't
 * have to read drift advisories; sf keeps its own house in order.
 */
import { existsSync, readFileSync, writeFileSync } from "node:fs";

import { serializePreferencesToFrontmatter } from "./commands-prefs-wizard.js";
import type { SFPreferences } from "./preferences-types.js";

export interface TemplateDrift {
	/** SF version recorded in the file (or "0.0.0" if missing). */
	fromVersion: string;
	/** SF version currently running. */
	toVersion: string;
	/** True iff fromVersion ≠ toVersion AND both are known. */
	needsUpgrade: boolean;
}

export function detectTemplateDrift(prefs: SFPreferences): TemplateDrift {
	const toVersion = process.env.SF_VERSION || "0.0.0";
	const fromVersion = prefs.last_synced_with_sf || "0.0.0";

	// Skip when either side is unknown — better to no-op than re-write with
	// "0.0.0" stamp on a file that was hand-edited or generated outside the
	// normal sf load path.
	if (toVersion === "0.0.0" || fromVersion === "0.0.0") {
		return { fromVersion, toVersion, needsUpgrade: false };
	}

	return {
		fromVersion,
		toVersion,
		needsUpgrade: fromVersion !== toVersion,
	};
}

/**
 * If the file at `path` is drifted from current sf, re-render its
 * frontmatter with a fresh `last_synced_with_sf` stamp (and any other
 * fields that may have been migrated by validatePreferences). Body is
 * preserved verbatim — only the leading `--- ... ---` block is rewritten.
 *
 * Returns the (possibly stamp-updated) prefs object so callers can use the
 * canonical view without re-reading the file.
 *
 * Failure modes are non-fatal: if the file disappeared, frontmatter can't
 * be located, or write fails, the original prefs are returned unchanged.
 * sf load paths must continue working even if disk-level upgrade is blocked.
 */
export function upgradePreferencesFileIfDrifted(
	path: string,
	prefs: SFPreferences,
): SFPreferences {
	const drift = detectTemplateDrift(prefs);
	if (!drift.needsUpgrade) return prefs;

	const upgraded: SFPreferences = {
		...prefs,
		last_synced_with_sf: drift.toVersion,
	};

	if (!existsSync(path)) return upgraded;

	let content: string;
	try {
		content = readFileSync(path, "utf-8");
	} catch {
		return upgraded;
	}

	const startMarker = content.startsWith("---\r\n") ? "---\r\n" : "---\n";
	if (!content.startsWith(startMarker)) {
		// File doesn't have the canonical frontmatter shape — leave it alone.
		return upgraded;
	}
	const endIdx = content.indexOf("\n---", startMarker.length);
	if (endIdx === -1) return upgraded;

	// Body is everything after the frontmatter's closing `---` line.
	const bodyStart = content.indexOf("\n", endIdx + 1);
	const body = bodyStart === -1 ? "" : content.slice(bodyStart);

	const newFrontmatter = `---\n${serializePreferencesToFrontmatter(
		upgraded as unknown as Record<string, unknown>,
	)}---`;

	try {
		writeFileSync(path, newFrontmatter + body, "utf-8");
	} catch {
		// Read-only filesystem, permission error, etc. — not fatal.
		return upgraded;
	}

	return upgraded;
}
