import { spawnSync } from "node:child_process";
import { randomUUID } from "node:crypto";
import { importExtensionModule } from "@singularity-forge/coding-agent";
import { consumeRouteTraceForCommand } from "../chat-command-router.js";
import { emitJournalEvent } from "../journal.js";
import { appendTraceEvent } from "../uok/trace-writer.js";
import {
	DIRECT_SF_COMMANDS,
	getSfTopLevelCommandCompletions,
	PRIMARY_COMMANDS,
	SF_COMMAND_DESCRIPTION,
} from "./catalog.js";

function listChangedFiles(cwd) {
	if (!cwd) return [];
	const result = spawnSync("git", ["status", "--porcelain=v1"], {
		cwd,
		encoding: "utf8",
		timeout: 3000,
	});
	if (result.status !== 0 || !result.stdout) return [];
	return result.stdout
		.split("\n")
		.map((line) => line.trim())
		.filter(Boolean)
		.map((line) => line.replace(/^.. /, ""))
		.sort();
}

function nextActionForCommand(command, outcome) {
	if (outcome === "fail")
		return "inspect blocker and retry or switch to /discuss";
	if (command === "autonomous") return "monitor /status or /queue";
	if (command === "next")
		return "review evidence, then run /next or /autonomous";
	if (command === "discuss") return "continue discussion until plan is ready";
	if (command === "quick") return "review result and decide whether to promote";
	if (command === "ship") return "review PR/ship output";
	return "continue workflow";
}

function emitCommandEvidence(basePath, traceId, evidence, parentTrace = null) {
	if (!basePath) return;
	emitJournalEvent(basePath, {
		ts: new Date().toISOString(),
		flowId: traceId,
		seq: 1,
		eventType: "workflow-command-complete",
		rule: "direct-sf-command",
		...(parentTrace ? { causedBy: { flowId: parentTrace, seq: 1 } } : {}),
		data: evidence,
	});
	appendTraceEvent(basePath, traceId, {
		type: "workflow_command_completion",
		...(parentTrace ? { parentTrace } : {}),
		surface: "interactive",
		runControl:
			evidence.command === "autonomous"
				? "autonomous"
				: evidence.command === "next"
					? "assisted"
					: "manual",
		permissionProfile: "normal",
		...evidence,
	});
}

async function dispatchDirectSFCommand(command, args, ctx, pi) {
	const { handleSFCommand } = await importExtensionModule(
		import.meta.url,
		"./dispatcher.js",
	);
	const { setStderrLoggingEnabled } = await importExtensionModule(
		import.meta.url,
		"../workflow-logger.js",
	);
	const previousStderrSetting = setStderrLoggingEnabled(false);
	const basePath = ctx?.cwd ?? process.cwd();
	const routedFrom = consumeRouteTraceForCommand(command);
	const traceId = `workflow-command:${command}:${randomUUID()}`;
	const parentTrace = routedFrom?.traceId ?? null;
	const changedBefore = new Set(listChangedFiles(basePath));
	const startedAt = Date.now();
	let outcome = "pass";
	let blocker = null;
	try {
		const suffix =
			typeof args === "string" && args.trim().length > 0
				? ` ${args.trim()}`
				: "";
		await handleSFCommand(`${command}${suffix}`, ctx, pi);
	} catch (err) {
		outcome = "fail";
		blocker = err instanceof Error ? err.message : String(err);
		throw err;
	} finally {
		setStderrLoggingEnabled(previousStderrSetting);
		const changedAfter = listChangedFiles(basePath);
		const newChangedFiles = changedAfter.filter(
			(file) => !changedBefore.has(file),
		);
		emitCommandEvidence(
			basePath,
			traceId,
			{
				command,
				args: typeof args === "string" ? args.trim() : "",
				outcome,
				changedFiles: changedAfter,
				newChangedFiles,
				blockers: blocker ? [blocker] : [],
				nextAction: nextActionForCommand(command, outcome),
				durationMs: Date.now() - startedAt,
				...(routedFrom
					? {
							routedFrom: {
								traceId: routedFrom.traceId,
								input: routedFrom.input,
								routedText: routedFrom.routedText,
							},
						}
					: {}),
			},
			parentTrace,
		);
	}
}

export function registerSFCommands(pi) {
	for (const command of DIRECT_SF_COMMANDS) {
		pi.registerCommand(command.cmd, {
			description: command.desc || SF_COMMAND_DESCRIPTION,
			menuTier: PRIMARY_COMMANDS.has(command.cmd) ? "primary" : "secondary",
			getArgumentCompletions: (prefix) =>
				getSfTopLevelCommandCompletions(command.cmd, prefix),
			handler: async (args, ctx) => {
				// Cache this command ctx so shortcut handlers (Ctrl+Y) can fall back
				// to a valid ExtensionCommandContext that has newSession().
				// Import lazily to avoid a circular dep at module load time.
				importExtensionModule(import.meta.url, "../auto/session.js")
					.then(({ getAutoSession }) => {
						const s = getAutoSession();
						if (typeof ctx.newSession === "function") {
							s.lastCommandCtx = ctx;
						}
					})
					.catch(() => {});
				await dispatchDirectSFCommand(command.cmd, args, ctx, pi);
			},
		});
	}
}

export function registerSFCommand(pi) {
	registerSFCommands(pi);
}
