/**
 * auto/phases-helpers.js — Shared helper functions for pipeline phases.
 *
 * Contains: _resolveReportBasePath, maybeFireProductAudit, generateMilestoneReport,
 * closeoutAndStop, emitCancelledUnitEnd, recordLearningOutcomeForUnit.
 */
import { importExtensionModule } from "@singularity-forge/coding-agent";
import { debugLog } from "../debug-logger.js";
import { recordLearnedOutcome } from "../learning/runtime.js";
import { handleProductAudit } from "../tools/product-audit-tool.js";
import { logWarning } from "../workflow-logger.js";
import { resolveWorktreeProjectRoot } from "../worktree-root.js";

/**
 * Resolve the base path for milestone reports.
 * Prefers originalBasePath (project root) over basePath (which may be a worktree).
 * Exported for testing as _resolveReportBasePath.
 */
export function _resolveReportBasePath(s) {
	return s.originalBasePath || s.basePath;
}

/**
 * Fire the product-audit for a milestone after successful merge.
 * Uses s.productAuditMilestoneId as a guard to ensure the audit fires exactly
 * once per milestone (mergeAndExit can be called multiple times for the same
 * milestone at different transition points).
 *
 * The audit is fired with a "no-gaps" placeholder verdict. Re-run
 * `/product-audit` manually for full LLM-powered gap analysis.
 */
export async function maybeFireProductAudit(s, ctx) {
	const mid = s.currentMilestoneId;
	if (!mid) return;
	// Guard: only fire once per milestone
	if (s.productAuditMilestoneId === mid) return;
	s.productAuditMilestoneId = mid;
	const params = {
		milestoneId: mid,
		verdict: "no-gaps",
		summary:
			"Auto-fired placeholder audit at milestone merge. Re-run `/product-audit` for full LLM-powered gap analysis.",
		gaps: [],
	};
	const result = await handleProductAudit(params, s.basePath);
	if ("error" in result) {
		logWarning("engine", "Product audit auto-fire failed", {
			milestone: mid,
			error: result.error,
		});
		ctx.ui.notify(
			`Product audit for ${mid} auto-fired but may need manual refresh: ${result.error}`,
			"warning",
		);
	} else {
		debugLog("autoLoop", {
			phase: "product-audit-fired",
			milestone: mid,
			jsonPath: result.jsonPath,
		});
	}
}

/**
 * Resolve the authoritative project base for dispatch guards.
 * Prior-milestone completion lives at the project root, even when the active
 * unit is running inside an auto worktree.
 */
export function _resolveDispatchGuardBasePath(s) {
	return resolveWorktreeProjectRoot(s.basePath, s.originalBasePath);
}
const PLANNING_FLOW_GATE_PHASES = new Set([
	"executing",
	"summarizing",
	"validating-milestone",
	"completing-milestone",
]);
export function shouldRunPlanningFlowGate(phase) {
	return PLANNING_FLOW_GATE_PHASES.has(phase);
}
export function shouldSkipArtifactVerification(unitType) {
	return unitType.startsWith("hook/") || unitType === "custom-step";
}
export function recordLearningOutcomeForUnit(
	ic,
	unitType,
	unitId,
	startedAt,
	outcome,
) {
	if (!startedAt) return;
	const unitModel = ic.s.currentUnitModel;
	const unitEntry = ic.deps.getLedger()?.units
		? [...(ic.deps.getLedger()?.units ?? [])]
				.reverse()
				.find(
					(u) =>
						u.type === unitType && u.id === unitId && u.startedAt === startedAt,
				)
		: undefined;
	const provider = unitModel?.provider ?? null;
	const modelId = unitModel?.id ?? unitEntry?.model ?? null;
	if (!provider || !modelId || !unitEntry) return;
	recordLearnedOutcome({
		modelId,
		provider,
		unitType,
		unitId,
		succeeded: outcome.succeeded,
		retries: outcome.retries ?? 0,
		escalated: outcome.escalated ?? false,
		verification_passed: outcome.verificationPassed,
		blocker_discovered: outcome.blockerDiscovered ?? false,
		duration_ms: Math.max(0, unitEntry.finishedAt - unitEntry.startedAt),
		tokens_total: unitEntry.tokens.total,
		cost_usd: unitEntry.cost,
		recorded_at: unitEntry.startedAt,
	});
}

/**
 * Generate and write an HTML milestone report snapshot.
 * Extracted from the milestone-transition block in autoLoop.
 */
export async function generateMilestoneReport(s, ctx, milestoneId) {
	const { loadVisualizerData } = await importExtensionModule(
		import.meta.url,
		"../visualizer-data.js",
	);
	const { generateHtmlReport } = await importExtensionModule(
		import.meta.url,
		"../export-html.js",
	);
	const { writeReportSnapshot } = await importExtensionModule(
		import.meta.url,
		"../reports.js",
	);
	const { basename } = await import("node:path");
	const reportBasePath = _resolveReportBasePath(s);
	const snapData = await loadVisualizerData(reportBasePath);
	const completedMs = snapData.milestones.find((m) => m.id === milestoneId);
	const msTitle = completedMs?.title ?? milestoneId;
	const sfVersion = process.env.SF_VERSION ?? "0.0.0";
	const projName = basename(reportBasePath);
	const doneSlices = snapData.milestones.reduce(
		(acc, m) => acc + m.slices.filter((sl) => sl.done).length,
		0,
	);
	const totalSlices = snapData.milestones.reduce(
		(acc, m) => acc + m.slices.length,
		0,
	);
	const outPath = writeReportSnapshot({
		basePath: reportBasePath,
		html: generateHtmlReport(snapData, {
			projectName: projName,
			projectPath: reportBasePath,
			sfVersion,
			milestoneId,
			indexRelPath: "index.html",
		}),
		milestoneId,
		milestoneTitle: msTitle,
		kind: "milestone",
		projectName: projName,
		projectPath: reportBasePath,
		sfVersion,
		totalCost: snapData.totals?.cost ?? 0,
		totalTokens: snapData.totals?.tokens.total ?? 0,
		totalDuration: snapData.totals?.duration ?? 0,
		doneSlices,
		totalSlices,
		doneMilestones: snapData.milestones.filter((m) => m.status === "complete")
			.length,
		totalMilestones: snapData.milestones.length,
		phase: snapData.phase,
	});
	ctx.ui.notify(
		`Report saved: .sf/reports/${basename(outPath)} — open index.html to browse progression.`,
		"info",
	);
}
// ─── closeoutAndStop ──────────────────────────────────────────────────────────
/**
 * If a unit is in-flight, close it out, then stop autonomous mode.
 * Extracted from ~4 identical if-closeout-then-stop sequences in autoLoop.
 */
export async function closeoutAndStop(ctx, pi, s, deps, reason) {
	if (s.currentUnit) {
		await deps.closeoutUnit(
			ctx,
			s.basePath,
			s.currentUnit.type,
			s.currentUnit.id,
			s.currentUnit.startedAt,
			deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id),
		);
		s.currentUnit = null;
	}
	await deps.stopAuto(ctx, pi, reason);
}
export async function emitCancelledUnitEnd(
	ic,
	unitType,
	unitId,
	unitStartSeq,
	errorContext,
) {
	ic.deps.emitJournalEvent({
		ts: new Date().toISOString(),
		flowId: ic.flowId,
		seq: ic.nextSeq(),
		eventType: "unit-end",
		data: {
			unitType,
			unitId,
			status: "cancelled",
			artifactVerified: false,
			...(errorContext ? { errorContext } : {}),
		},
		causedBy: { flowId: ic.flowId, seq: unitStartSeq },
	});
}
