/**
 * headless-reflect.ts — `sf headless reflect`
 *
 * Purpose: operator-driven entry point for the SF reflection layer. Assembles
 * the full corpus snapshot (open + recent-resolved self-feedback, recent
 * commits, milestone state, validation files, prior report) and outputs
 * either:
 *   - A rendered prompt (default): the markdown brief that would be sent to
 *     an LLM for meta-analysis. Lets the operator pipe it into any model
 *     they choose (gemini, codex, claude) without SF dispatching.
 *   - A JSON dump of the raw corpus (--json): structured snapshot for
 *     tooling.
 *
 * The actual LLM-driven analysis pass and the autonomous-loop dispatch
 * (reflection-pass unit type) land in follow-up commits — both depend on
 * the corpus this command produces, and on the triage-not-a-first-class-
 * unit-type fix being landed first.
 *
 * Consumer: headless.ts when command === "reflect".
 */

import { existsSync } from "node:fs";
import { join } from "node:path";
import { createJiti } from "@mariozechner/jiti";
import { resolveBundledSourceResource } from "./bundled-resource-path.js";
import { getSfEnv } from "./env.js";

const jiti = createJiti(import.meta.filename, {
	interopDefault: true,
	debug: false,
});

const agentExtensionsDir = join(getSfEnv().agentDir, "extensions", "sf");
const useAgentDir = existsSync(join(agentExtensionsDir, "state.js"));

function sfExtensionPath(moduleName: string): string {
	if (useAgentDir) return join(agentExtensionsDir, `${moduleName}.js`);
	const tsPath = resolveBundledSourceResource(
		import.meta.url,
		"extensions",
		"sf",
		`${moduleName}.ts`,
	);
	if (existsSync(tsPath)) return tsPath;
	return resolveBundledSourceResource(
		import.meta.url,
		"extensions",
		"sf",
		`${moduleName}.js`,
	);
}

export interface HandleReflectOptions {
	json?: boolean;
	run?: boolean;
	model?: string;
}

export interface HandleReflectResult {
	exitCode: number;
}

/**
 * Render the corpus to stdout. Default: markdown prompt brief that includes
 * the reflection prompt template + the corpus rendered as markdown. With
 * --json: raw corpus object as JSON.
 *
 * Never throws — best-effort, returns non-zero exit on assembly failure.
 */
export async function handleReflect(
	cwd: string,
	options: HandleReflectOptions = {},
): Promise<HandleReflectResult> {
	// Open the project DB before assembling. The one-shot bypass path doesn't
	// run the full SF agent bootstrap, so DB-open isn't done for us.
	try {
		const autoStartModule = (await jiti.import(
			sfExtensionPath("auto-start"),
			{},
		)) as { openProjectDbIfPresent?: (cwd: string) => Promise<unknown> };
		if (typeof autoStartModule.openProjectDbIfPresent === "function") {
			await autoStartModule.openProjectDbIfPresent(cwd);
		}
	} catch (err) {
		// Non-fatal — the corpus assembler will return null and we will emit
		// the friendly "DB must be open" error below.
		const msg = err instanceof Error ? err.message : String(err);
		process.stderr.write(`[reflect] DB pre-open warning: ${msg}\n`);
	}

	let mod: {
		assembleReflectionCorpus: (basePath: string) => unknown;
		renderReflectionCorpusBrief: (corpus: unknown) => string;
		renderReflectionPrompt: (
			corpus: unknown,
			options?: { workingDirectory?: string },
		) => Promise<string>;
		writeReflectionReport: (basePath: string, content: string) => string | null;
		runGeminiReflection: (
			prompt: string,
			options?: { model?: string; timeoutMs?: number },
		) => Promise<{
			ok: boolean;
			content?: string;
			error?: string;
			cleanFinish?: boolean;
			stderr?: string;
			exitCode?: number;
		}>;
	};
	try {
		mod = (await jiti.import(sfExtensionPath("reflection"))) as typeof mod;
	} catch (err) {
		const msg = err instanceof Error ? err.message : String(err);
		process.stderr.write(
			`[reflect] failed to load reflection module: ${msg}\n`,
		);
		return { exitCode: 1 };
	}

	let corpus: unknown;
	try {
		corpus = mod.assembleReflectionCorpus(cwd);
	} catch (err) {
		const msg = err instanceof Error ? err.message : String(err);
		process.stderr.write(`[reflect] corpus assembly failed: ${msg}\n`);
		return { exitCode: 1 };
	}

	if (!corpus) {
		const payload = {
			ok: false,
			error:
				"Could not assemble reflection corpus — sf.db must be open and at least one self-feedback entry must exist.",
		};
		process.stdout.write(
			options.json ? `${JSON.stringify(payload)}\n` : `${payload.error}\n`,
		);
		return { exitCode: 1 };
	}

	if (options.json) {
		process.stdout.write(`${JSON.stringify({ ok: true, corpus })}\n`);
		return { exitCode: 0 };
	}

	// renderReflectionPrompt routes through reflection.js → loadPrompt so the
	// gap-audit detector sees the call site in EXTENSION_SRC and won't flag
	// the prompt as orphan (sf-mp4warqc-y1u0b3). It also picks up fragment
	// composition + variable validation for free.
	let rendered: string;
	try {
		rendered = await mod.renderReflectionPrompt(corpus, {
			workingDirectory: cwd,
		});
	} catch (err) {
		const msg = err instanceof Error ? err.message : String(err);
		process.stderr.write(`[reflect] prompt render failed: ${msg}\n`);
		return { exitCode: 1 };
	}

	if (!options.run) {
		process.stdout.write(`${rendered}\n`);
		return { exitCode: 0 };
	}

	// --run: dispatch the rendered prompt to gemini-cli, capture the report,
	// persist to .sf/reflection/<ts>-report.md, emit the report path on stdout.
	process.stderr.write(
		"[reflect] dispatching to gemini-cli (this can take a few minutes)…\n",
	);
	const result = await mod.runGeminiReflection(rendered, {
		model: options.model,
	});
	if (!result.ok) {
		const payload = {
			ok: false,
			error: result.error ?? "unknown gemini error",
			exitCode: result.exitCode,
			stderrTail: (result.stderr ?? "").slice(-500),
		};
		process.stdout.write(
			options.json
				? `${JSON.stringify(payload)}\n`
				: `[reflect] failed: ${payload.error}\n`,
		);
		return { exitCode: 1 };
	}
	const reportPath = mod.writeReflectionReport(cwd, result.content ?? "");
	const payload = {
		ok: true,
		reportPath,
		cleanFinish: result.cleanFinish === true,
	};
	if (options.json) {
		process.stdout.write(`${JSON.stringify(payload)}\n`);
	} else {
		process.stdout.write(`Reflection report written to: ${reportPath}\n`);
		if (!result.cleanFinish) {
			process.stderr.write(
				"[reflect] WARNING: report did not include REFLECTION_COMPLETE terminator — output may be truncated\n",
			);
		}
	}
	return { exitCode: 0 };
}
