/**
 * parity-diff-capture.ts — Per-turn plane diff capture for legacy vs UOK parity.
 *
 * Each call site captures BOTH the legacy decision and the UOK decision for one
 * of the 5 plane outputs, compares them with shallowEqualDecisions, and emits a
 * ParityDiffEvent to the parity log. Divergences in critical planes (gitops,
 * model-policy) set a session-scoped block flag that gates commits for the rest
 * of the session.
 *
 * Design constraints:
 *  - Never throws. All diff emission is best-effort.
 *  - Never calls legacy functions with side effects. Shadow-pure only.
 *  - Does not restructure kernel enter/exit/finally flow.
 *  - Wired in by ADDING call sites at plane decision points only.
 */
import { buildAuditEnvelope, emitUokAuditEvent } from "./audit.js";
import { captureParityDiff } from "./parity-report.js";

// ── Session-scoped commit-block flag ────────────────────────────────────────
// Set to true when a critical plane divergence is detected. Persists for the
// lifetime of the session; the next session_start resets it. Stored in module
// scope (one flag per Node.js process / auto session).
let _commitBlockedDueToParityDivergence = false;
let _missingExitSignal = false;
let _blockingTurnId;
let _blockingPlane;
export function isParityCommitBlocked() {
	return _commitBlockedDueToParityDivergence || _missingExitSignal;
}
export function getParityCommitBlockReason() {
	if (_missingExitSignal) return "uok-kernel-enter-without-exit";
	if (_blockingTurnId && _blockingPlane) {
		return `parity-divergence:${_blockingPlane} (turn=${_blockingTurnId})`;
	}
	return "parity-divergence";
}
/** Reset for test isolation / new session. */
export function resetParityCommitBlock() {
	_commitBlockedDueToParityDivergence = false;
	_missingExitSignal = false;
	_blockingTurnId = undefined;
	_blockingPlane = undefined;
}
/**
 * Called at kernel enter. Records that a session is in-flight so that a
 * missing-exit drain can detect orphaned enters.
 */
export function signalKernelEnter() {
	// No-op for flag management here; the kernel.ts already writes enter/exit events.
	// The missing-exit detection is handled by checkAndDrainMissingExit().
}
/**
 * On next session_start, call this to check whether the previous session's
 * parity log has an unmatched kernel enter without a corresponding exit.
 * If so, set the commit-block flag.
 *
 * @param enterCount Number of enter events seen in the parity log
 * @param exitCount Number of exit events seen in the parity log
 */
export function checkAndDrainMissingExit(enterCount, exitCount) {
	if (enterCount > exitCount) {
		_missingExitSignal = true;
	}
}
// ── Critical planes that gate commits ────────────────────────────────────────
const COMMIT_GATING_PLANES = new Set(["gitops", "model-policy"]);
function maybeTriggerCommitBlock(basePath, sessionId, event) {
	if (event.match) return;
	if (!COMMIT_GATING_PLANES.has(event.plane)) return;
	_commitBlockedDueToParityDivergence = true;
	_blockingTurnId = event.turnId;
	_blockingPlane = event.plane;
	// Emit an audit event so the divergence is permanently recorded.
	try {
		emitUokAuditEvent(
			basePath,
			buildAuditEnvelope({
				traceId: `session:${sessionId}`,
				turnId: event.turnId,
				category: "orchestration",
				type: "uok-commit-blocked",
				payload: {
					plane: event.plane,
					divergence: event.divergence,
					legacy: event.legacy,
					uok: event.uok,
				},
			}),
		);
	} catch {
		// Best-effort.
	}
	process.stderr.write(
		`[sf:uok:commit-blocked] parity divergence on plane=${event.plane} turn=${event.turnId}: ${event.divergence ?? "no divergence detail"}\n`,
	);
}
/**
 * Capture a diff for the PLAN plane.
 * Call after UOK plan compilation produces its result. Pass the legacy
 * shadow-pure equivalent for comparison.
 */
export function capturePlanDiff(args) {
	const event = captureParityDiff({
		basePath: args.basePath,
		sessionId: args.sessionId,
		turnId: args.turnId,
		plane: "plan",
		legacy: args.legacy,
		uok: args.uok,
	});
	// Plan divergence is informational — does not gate commits by itself.
	return event;
}
/**
 * Capture a diff for the GRAPH plane.
 * Call after UOK execution graph is resolved and the topological order is known.
 */
export function captureGraphDiff(args) {
	const event = captureParityDiff({
		basePath: args.basePath,
		sessionId: args.sessionId,
		turnId: args.turnId,
		plane: "graph",
		legacy: args.legacy,
		uok: args.uok,
	});
	// Graph divergence is informational.
	return event;
}
/**
 * Capture a diff for the MODEL-POLICY plane.
 * Call after UOK model policy filter produces its eligible set. If the legacy
 * path would have selected a different model, divergence is recorded and the
 * commit-block flag is set.
 */
export function captureModelPolicyDiff(args) {
	const event = captureParityDiff({
		basePath: args.basePath,
		sessionId: args.sessionId,
		turnId: args.turnId,
		plane: "model-policy",
		legacy: args.legacy,
		uok: args.uok,
	});
	maybeTriggerCommitBlock(args.basePath, args.sessionId, event);
	return event;
}
/**
 * Capture a diff for the AUDIT-ENVELOPE plane.
 * Call after UOK audit envelope emission. Divergence is informational.
 */
export function captureAuditEnvelopeDiff(args) {
	const event = captureParityDiff({
		basePath: args.basePath,
		sessionId: args.sessionId,
		turnId: args.turnId,
		plane: "audit-envelope",
		legacy: args.legacy,
		uok: args.uok,
	});
	return event;
}
/**
 * Capture a diff for the GITOPS plane.
 * Call after the gitops turn_action is resolved. Divergence sets the commit-block
 * flag and forces the UOK gitops action to status-only for safety.
 *
 * Returns { event, effectiveAction } where effectiveAction is "status-only" if
 * commit-block fired, otherwise the uok action as provided.
 */
export function captureGitopsDiff(args) {
	const event = captureParityDiff({
		basePath: args.basePath,
		sessionId: args.sessionId,
		turnId: args.turnId,
		plane: "gitops",
		legacy: args.legacy,
		uok: args.uok,
	});
	maybeTriggerCommitBlock(args.basePath, args.sessionId, event);
	// If commit block is now active, downgrade gitops action to status-only.
	const effectiveAction = isParityCommitBlocked()
		? "status-only"
		: (args.uok?.action ?? "status-only");
	return { event, effectiveAction };
}
/**
 * Resolve the legacy gitops decision from the flags config.
 * This is the shadow-pure equivalent: reads the gitops flag without any side effects.
 * Legacy behavior: gitopsTurnAction from UokFlags (which was the direct config path
 * before UOK gitops plane was introduced).
 */
export function legacyGitopsDecision(gitopsTurnAction, gitopsTurnPush) {
	return { action: gitopsTurnAction, push: gitopsTurnPush };
}
/**
 * Resolve the legacy model policy decision.
 * Shadow-pure: derives what the legacy path would have selected from the candidates list
 * without running the actual policy filter (which would emit audit events as a side effect).
 * Legacy = first available model (no policy filtering applied).
 */
export function legacyModelPolicyDecision(candidates) {
	const first = candidates[0];
	if (!first) return null;
	return {
		modelId: first.id,
		provider: first.provider,
		allowed: true,
		reason: "legacy-no-policy-filter",
	};
}
