/**
 * Thin preferences serializer — no intra-repo imports.
 *
 * Extracted from commands-prefs-wizard.js so that preferences-template-upgrade.js
 * can serialize preferences without importing the full wizard module (which
 * transitively imports preferences.js, creating a cycle through
 * preferences.js → preferences-template-upgrade.js → commands-prefs-wizard.js → preferences.js).
 *
 * commands-prefs-wizard.js re-exports both symbols for back-compat.
 *
 * Purpose: convert a raw SFPreferences object to YAML frontmatter text
 * in a deterministic key order, with YAML-safe quoting for string values.
 * Consumer: preferences-template-upgrade.js (upgradePreferencesFileIfDrifted),
 *           commands-prefs-wizard.js (wizard configure* functions).
 */

/**
 * Quote a scalar value for safe inclusion in YAML frontmatter.
 * Non-string scalars are coerced to string; strings that contain YAML
 * metacharacters or leading/trailing whitespace are double-quoted.
 *
 * @param {unknown} val
 * @returns {string}
 */
export function yamlSafeString(val) {
	if (typeof val !== "string") return String(val);
	if (/[:#{[\]'"`,|>&*!?@%]/.test(val) || val.trim() !== val || val === "") {
		return `"${val.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
	}
	return val;
}

/**
 * Serialize an SFPreferences object to YAML frontmatter text (without the
 * surrounding `---` delimiters).  Keys are emitted in a canonical order for
 * stable diffs; unknown keys are appended after the ordered set.
 *
 * Purpose: produce the YAML block written between `---` markers in
 * preferences.yaml (or legacy PREFERENCES.md).  Relies only on yamlSafeString — no filesystem or
 * preferences-loading imports needed.
 *
 * Consumer: preferences-template-upgrade.js, commands-prefs-wizard.js.
 *
 * @param {Record<string, unknown>} prefs
 * @returns {string}
 */
export function serializePreferencesToFrontmatter(prefs) {
	const lines = [];
	function serializeValue(key, value, indent) {
		const prefix = "  ".repeat(indent);
		if (value === null || value === undefined) return;
		if (Array.isArray(value)) {
			if (value.length === 0) {
				return; // Omit empty arrays — avoids parse/serialize cycle bug with "[]" strings
			}
			lines.push(`${prefix}${key}:`);
			for (const item of value) {
				if (typeof item === "object" && item !== null) {
					const entries = Object.entries(item);
					if (entries.length > 0) {
						const [firstKey, firstVal] = entries[0];
						lines.push(`${prefix}  - ${firstKey}: ${yamlSafeString(firstVal)}`);
						for (let i = 1; i < entries.length; i++) {
							const [k, v] = entries[i];
							if (Array.isArray(v)) {
								lines.push(`${prefix}    ${k}:`);
								for (const arrItem of v) {
									lines.push(`${prefix}      - ${yamlSafeString(arrItem)}`);
								}
							} else {
								lines.push(`${prefix}    ${k}: ${yamlSafeString(v)}`);
							}
						}
					}
				} else {
					lines.push(`${prefix}  - ${yamlSafeString(item)}`);
				}
			}
			return;
		}
		if (typeof value === "object") {
			const entries = Object.entries(value);
			if (entries.length === 0) {
				return; // Omit empty objects — avoids parse/serialize cycle bug with "{}" strings
			}
			lines.push(`${prefix}${key}:`);
			for (const [k, v] of entries) {
				serializeValue(k, v, indent + 1);
			}
			return;
		}
		lines.push(`${prefix}${key}: ${yamlSafeString(value)}`);
	}
	// Ordered keys for consistent output
	const orderedKeys = [
		"version",
		"mode",
		"always_use_skills",
		"prefer_skills",
		"avoid_skills",
		"skill_rules",
		"custom_instructions",
		"models",
		"skill_discovery",
		"skill_staleness_days",
		"auto_supervisor",
		"uat_dispatch",
		"unique_milestone_ids",
		"budget_ceiling",
		"budget_enforcement",
		"context_pause_threshold",
		"notifications",
		"cmux",
		"remote_questions",
		"git",
		"post_unit_hooks",
		"pre_dispatch_hooks",
		"dynamic_routing",
		"uok",
		"token_profile",
		"phases",
		"parallel",
		"auto_visualize",
		"auto_report",
		"verification_commands",
		"verification_auto_fix",
		"verification_max_retries",
		"verification_auto_defer_threshold",
		"search_provider",
		"context_selection",
	];
	const seen = new Set();
	for (const key of orderedKeys) {
		if (key in prefs) {
			serializeValue(key, prefs[key], 0);
			seen.add(key);
		}
	}
	// Any remaining keys not in the ordered list
	for (const [key, value] of Object.entries(prefs)) {
		if (!seen.has(key)) {
			serializeValue(key, value, 0);
		}
	}
	return lines.join("\n") + "\n";
}
