/**
 * requirements-sift-projection.js — emit a fresh sift-friendly markdown of all R-entries.
 *
 * Purpose: write .sf/runtime/sift/requirements-projected.md from the DB so
 * sift's index sees the live state of the requirements corpus, not the
 * (possibly stale) checked-in .sf/REQUIREMENTS.md.
 *
 * Consumer: sift warmup pipeline (bootstrap/register-hooks.js, auto/loop.js).
 *
 * Failure boundary: read-only on DB; write-only on .sf/runtime/sift/.
 */
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { _getAdapter } from "./sf-db-core.js";

/** Canonical output path for the requirements corpus projection. */
export function getRequirementsProjectionPath(basePath) {
	return join(basePath, ".sf", "runtime", "sift", "requirements-projected.md");
}

/**
 * Escape a text value for inline markdown use.
 * Collapses interior newlines into spaces so each field renders on one line
 * when used in bullet form. Backtick sequences are escaped to avoid
 * unintended code-span boundaries.
 */
function mdInline(value) {
	if (value == null) return "";
	return String(value)
		.replace(/`/g, "\\`")
		.replace(/\r?\n|\r/g, " ")
		.trim();
}

/**
 * Render one R-entry as a markdown section for sift indexing.
 *
 * Uses a flat-heading + bullet-list structure so every field is
 * retrievable by keyword AND the section heading is the canonical ID.
 */
function renderRow(row) {
	const lines = [];
	const id = String(row["id"] ?? "");
	const descFirst =
		String(row["description"] ?? "")
			.split(/\r?\n/)
			.find((l) => l.trim().length > 0) ?? "Untitled";
	lines.push(`## ${id} — ${mdInline(descFirst)}`);
	lines.push(
		`Status: ${mdInline(row["status"])}  Class: ${mdInline(row["class"])}  Owner: ${mdInline(row["primary_owner"])}`,
	);
	lines.push("");
	if (row["description"]) {
		lines.push(mdInline(row["description"]));
		lines.push("");
	}
	if (row["why"]) {
		lines.push(`**Why:** ${mdInline(row["why"])}`);
		lines.push("");
	}
	const notes = String(row["notes"] ?? "").trim();
	if (notes.length > 0) {
		lines.push(`**Notes:** ${mdInline(notes)}`);
		lines.push("");
	}
	return lines.join("\n");
}

/**
 * Status ordering for grouped output — matches the canonical STATUS_SECTION_MAP
 * used by db-writer.js so sections appear in a consistent order.
 */
const STATUS_ORDER = [
	"active",
	"resolved",
	"validated",
	"deferred",
	"superseded",
	"cancelled",
	"out-of-scope",
];

function statusRank(status) {
	const idx = STATUS_ORDER.indexOf(String(status ?? "").toLowerCase());
	return idx === -1 ? STATUS_ORDER.length : idx;
}

function requirementNumber(id) {
	const match = String(id ?? "").match(/^R(\d+)$/);
	return match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
}

/**
 * Build a fresh markdown projection from the `requirements` DB table and
 * write it to `.sf/runtime/sift/requirements-projected.md`.
 *
 * Returns `{written: true, rows: N, bytes: M, projectionPath}` on success.
 * Returns `{written: false, reason, rows: 0}` when the DB adapter is unavailable.
 * Never throws — failure is non-fatal to the warmup pipeline.
 */
export async function buildRequirementsProjection(basePath) {
	const projectionPath = getRequirementsProjectionPath(basePath);
	try {
		const db = _getAdapter();
		if (!db) {
			return { written: false, reason: "sf-db adapter unavailable", rows: 0 };
		}
		// Read all non-superseded requirements ordered by status then R-number.
		const rows = db
			.prepare(
				"SELECT id, class, status, description, why, notes, primary_owner FROM requirements WHERE superseded_by IS NULL",
			)
			.all();

		// Sort: status group first, then numeric R-id within group.
		rows.sort((a, b) => {
			const sr = statusRank(a["status"]) - statusRank(b["status"]);
			if (sr !== 0) return sr;
			return requirementNumber(a["id"]) - requirementNumber(b["id"]);
		});

		const headerLines = [
			"# SF Requirements Corpus — sift projection",
			"",
			"This file is generated from the `requirements` DB table at warmup time.",
			"Do not edit manually — it is overwritten on each SF session start.",
			"",
		];

		const sections = rows.map((row) => renderRow(row));
		const content = [...headerLines, ...sections].join("\n") + "\n";

		mkdirSync(join(basePath, ".sf", "runtime", "sift"), { recursive: true });
		writeFileSync(projectionPath, content, "utf-8");

		return {
			written: true,
			rows: rows.length,
			bytes: Buffer.byteLength(content, "utf-8"),
			projectionPath,
		};
	} catch (err) {
		return {
			written: false,
			reason: String(err?.message ?? err),
			rows: 0,
			projectionPath,
		};
	}
}
