/**
 * Prompt Ordering Optimizer — reorders assembled prompt sections
 * to maximize cache prefix stability.
 *
 * Identifies sections by markdown heading patterns and rearranges
 * them so stable content appears first. Anthropic caches the last
 * user message by prefix match, so placing static/semi-static
 * content before dynamic content improves cache hit rates.
 */
/**
 * Known heading to role mappings for SF prompts.
 * Static: templates, executor constraints, system instructions
 * Semi-static: slice plan, decisions, requirements, prior summaries, overrides
 * Dynamic: task plan, resume state, carry-forward, verification
 */
const HEADING_ROLES = {
	// Static — never changes per task
	"Output Template": "static",
	"Executor Context Constraints": "static",
	"Working Directory": "static",
	"Backing Source Artifacts": "static",
	// Semi-static — changes per slice but not per task
	"Slice Plan Excerpt": "semi-static",
	Decisions: "semi-static",
	Requirements: "semi-static",
	"Prior Task Summaries": "semi-static",
	Overrides: "semi-static",
	"Project Knowledge": "semi-static",
	"Dependency Summaries": "semi-static",
	// Dynamic — changes per task
	"Inlined Task Plan": "dynamic",
	"Resume State": "dynamic",
	"Carry-Forward Context": "dynamic",
	Verification: "dynamic",
	"Verification Evidence": "dynamic",
};
/**
 * Extract the heading text from a line like "## Some Heading" or "## UNIT: Execute Task ...".
 * Returns the full text after "## " for role lookup.
 */
function extractHeadingText(line) {
	return line.replace(/^##\s+/, "").trim();
}
/**
 * Classify a heading by matching against known roles.
 * Uses exact matching so inlined artifact headings such as
 * `## Requirements Advanced` stay inside their original prompt position instead
 * of being mistaken for the top-level `## Requirements` context block.
 */
function classifyHeading(heading) {
	for (const [key, role] of Object.entries(HEADING_ROLES)) {
		if (heading === key) {
			return role;
		}
	}
	return "dynamic";
}
/**
 * Split a prompt into sections at ## heading boundaries.
 * Sub-headings (### and deeper) stay with their parent ## section.
 * Returns a preamble (content before first ##) and an array of sections.
 */
function splitSections(prompt) {
	const lines = prompt.split("\n");
	let preamble = "";
	const sections = [];
	let currentHeading = "";
	let currentContent = [];
	for (const line of lines) {
		// Match ## headings but NOT ### or deeper
		if (/^## (?!#)/.test(line)) {
			// Flush previous section
			if (currentHeading) {
				sections.push({
					heading: currentHeading,
					content: currentContent.join("\n"),
					role: classifyHeading(currentHeading),
				});
			} else if (currentContent.length > 0) {
				preamble = currentContent.join("\n");
			}
			currentHeading = extractHeadingText(line);
			currentContent = [line];
		} else {
			currentContent.push(line);
		}
	}
	// Flush last section
	if (currentHeading) {
		sections.push({
			heading: currentHeading,
			content: currentContent.join("\n"),
			role: classifyHeading(currentHeading),
		});
	} else if (currentContent.length > 0) {
		preamble = currentContent.join("\n");
	}
	return { preamble, sections };
}
const ROLE_ORDER = {
	static: 0,
	"semi-static": 1,
	dynamic: 2,
};
/**
 * Reorder a prompt's sections for cache efficiency.
 * Extracts sections by ## heading markers, classifies them,
 * and reorders: static -> semi-static -> dynamic.
 *
 * Content before the first ## heading is treated as a preamble
 * and always placed first (it's usually static instructions).
 *
 * @param prompt The assembled prompt string
 * @returns Reordered prompt string
 */
export function reorderForCaching(prompt) {
	const { preamble, sections } = splitSections(prompt);
	// Nothing to reorder
	if (sections.length <= 1) {
		return prompt;
	}
	// Stable sort: sections with the same role keep their original relative order
	const sorted = [...sections].sort((a, b) => {
		return ROLE_ORDER[a.role] - ROLE_ORDER[b.role];
	});
	const parts = [];
	if (preamble) {
		parts.push(preamble);
	}
	for (const section of sorted) {
		parts.push(section.content);
	}
	return parts.join("\n");
}
/**
 * Reorder a prompt and split it at the semi-static → dynamic boundary so the
 * static+semi-static prefix can be marked with cache_control: ephemeral on
 * Anthropic-compatible providers.
 *
 * Purpose: keep SF autonomous prompt prefixes byte-stable across adjacent task
 * dispatches so provider prompt caches can reuse expensive context.
 *
 * Consumer: auto/phases-unit.js before runUnit dispatches an autonomous unit.
 *
 * Returns `{before: string, after: string}` where:
 * - `before` = preamble + all static + all semi-static sections (cache this)
 * - `after`  = all dynamic sections (do not cache)
 *
 * Returns `null` if the prompt has no dynamic sections (nothing to split on).
 *
 * @param prompt The assembled prompt string
 * @returns Split prompt or null if no dynamic content exists
 */
export function reorderAndSplitForCaching(prompt) {
	const { preamble, sections } = splitSections(prompt);
	if (sections.length === 0) return null;

	const hasDynamic = sections.some((s) => s.role === "dynamic");
	if (!hasDynamic) return null;

	const sorted = [...sections].sort((a, b) => {
		return ROLE_ORDER[a.role] - ROLE_ORDER[b.role];
	});

	const beforeParts = preamble ? [preamble] : [];
	const afterParts = [];
	for (const section of sorted) {
		if (section.role === "dynamic") {
			afterParts.push(section.content);
		} else {
			beforeParts.push(section.content);
		}
	}

	// If nothing ended up in before (no static/semi-static sections), return null —
	// there is no stable prefix to cache.
	if (beforeParts.length === 0) return null;

	return {
		before: beforeParts.join("\n"),
		after: afterParts.join("\n"),
	};
}

/**
 * Analyze a prompt's cache efficiency without reordering.
 * Returns stats about how much of the prompt is cacheable.
 */
export function analyzeCacheEfficiency(prompt) {
	const { preamble, sections } = splitSections(prompt);
	let staticChars = preamble.length;
	let semiStaticChars = 0;
	let dynamicChars = 0;
	for (const section of sections) {
		switch (section.role) {
			case "static":
				staticChars += section.content.length;
				break;
			case "semi-static":
				semiStaticChars += section.content.length;
				break;
			case "dynamic":
				dynamicChars += section.content.length;
				break;
		}
	}
	const totalChars = staticChars + semiStaticChars + dynamicChars;
	const cacheEfficiency =
		totalChars > 0 ? (staticChars + semiStaticChars) / totalChars : 0;
	return {
		totalChars,
		staticChars,
		semiStaticChars,
		dynamicChars,
		cacheEfficiency,
	};
}
