/**
 * uok/run-context.js — UOK schema-v2 run-context adapter.
 *
 * Purpose: normalize existing runtime/headless run context into the shape
 * the UOK control plane expects (schema-v2 fields surface, runControl,
 * permissionProfile, traceId, parentTrace, plus the already-existing
 * unitType/unitId/milestoneId/sliceId). The adapter is intentionally
 * thin — it does NOT define a new parallel run model, it just collects
 * fields the caller already has and emits one canonical shape.
 *
 * Slice 2 of "Make UOK the SF Control Plane". The classifier in
 * headless-uok-status.ts treats `surface` as the schema-v2 indicator;
 * rows with surface != null get classified as ok/stale/incomplete
 * based on the other fields, rows with surface == null stay legacy.
 *
 * Consumer: uok/gate-runner.js (gate-run writes), sf-db-gates.js
 * (quality_gates writes), future autonomous/headless adapters.
 */

/**
 * Required keys for a schema-v2 UOK run-context. The classifier flags
 * a row "incomplete" when any of these is missing on an otherwise
 * schema-v2 row.
 */
export const REQUIRED_UOK_RUN_CONTEXT_KEYS = Object.freeze([
	"surface",
	"runControl",
	"permissionProfile",
	"traceId",
]);

/**
 * Optional keys — present on schema-v2 rows when the caller can
 * supply them, but their absence does not flip the classification.
 */
export const OPTIONAL_UOK_RUN_CONTEXT_KEYS = Object.freeze([
	"parentTrace",
	"unitType",
	"unitId",
	"milestoneId",
	"sliceId",
	"taskId",
]);

/**
 * Allowed `surface` values. Reject anything else at build time so we
 * don't end up with `surface = "headless-something"` typos that the
 * classifier silently treats as schema-v2.
 */
export const VALID_SURFACES = Object.freeze([
	"headless",
	"autonomous",
	"interactive",
	"hook",
]);

/**
 * Allowed `runControl` values.
 *
 *   - bare           One-shot, no orchestrator gates
 *   - supervised     Orchestrator-supervised dispatch with operator gates
 *   - autonomous     Full autonomous loop
 *   - interactive    Operator-driven session
 */
export const VALID_RUN_CONTROLS = Object.freeze([
	"bare",
	"supervised",
	"autonomous",
	"interactive",
]);

/**
 * Allowed `permissionProfile` values.
 *
 *   - high   Production / write-allowed
 *   - medium Dev / write-with-confirmation
 *   - low    Read-only / sandboxed
 */
export const VALID_PERMISSION_PROFILES = Object.freeze([
	"high",
	"medium",
	"low",
]);

/**
 * Build a schema-v2 UOK run-context from the operator-supplied inputs.
 *
 * Validates enum fields, normalizes empty strings to undefined, and
 * returns a frozen plain object with only the keys the classifier
 * understands. Returns null when required fields are missing — callers
 * that build a partial ctx should NOT pass that ctx to a gate write,
 * otherwise the row lands as `incomplete` in the next status uok read.
 *
 * Consumer: headless commands, autonomous-loop phases, anywhere a UOK
 * gate is about to be written.
 *
 * @param {object} opts
 * @param {string} opts.surface            One of VALID_SURFACES.
 * @param {string} opts.runControl         One of VALID_RUN_CONTROLS.
 * @param {string} opts.permissionProfile  One of VALID_PERMISSION_PROFILES.
 * @param {string} opts.traceId            Flow id from the journal.
 * @param {string} [opts.parentTrace]      Parent flow id (nested).
 * @param {string} [opts.unitType]         Unit type if dispatching one.
 * @param {string} [opts.unitId]           Unit id.
 * @param {string} [opts.milestoneId]      Milestone id.
 * @param {string} [opts.sliceId]          Slice id.
 * @param {string} [opts.taskId]           Task id.
 *
 * @returns {object | null} The normalized context, or null when a
 *   required field is missing or an enum is invalid.
 */
export function buildUokRunContext(opts) {
	if (!opts || typeof opts !== "object") return null;
	const required = {
		surface: normalizeString(opts.surface),
		runControl: normalizeString(opts.runControl),
		permissionProfile: normalizeString(opts.permissionProfile),
		traceId: normalizeString(opts.traceId),
	};
	for (const key of REQUIRED_UOK_RUN_CONTEXT_KEYS) {
		if (!required[key]) return null;
	}
	if (!VALID_SURFACES.includes(required.surface)) return null;
	if (!VALID_RUN_CONTROLS.includes(required.runControl)) return null;
	if (!VALID_PERMISSION_PROFILES.includes(required.permissionProfile)) {
		return null;
	}
	const ctx = { ...required };
	for (const key of OPTIONAL_UOK_RUN_CONTEXT_KEYS) {
		const value = normalizeString(opts[key]);
		if (value) ctx[key] = value;
	}
	return Object.freeze(ctx);
}

function normalizeString(value) {
	if (typeof value !== "string") return undefined;
	const trimmed = value.trim();
	return trimmed.length === 0 ? undefined : trimmed;
}

/**
 * Translate a schema-v2 UOK run-context into the column-name shape the
 * SQLite writer expects. Snake_case keys, only the columns that exist
 * on the quality_gates table (after v66 migration).
 *
 * Returns null when the context isn't a valid schema-v2 ctx (caller
 * should fall through to legacy write, leaving columns NULL).
 *
 * Consumer: sf-db-gates.js insertGateRow / saveGateResult.
 */
export function uokRunContextToGateColumns(ctx) {
	if (!ctx || typeof ctx !== "object") return null;
	for (const key of REQUIRED_UOK_RUN_CONTEXT_KEYS) {
		if (typeof ctx[key] !== "string" || ctx[key].length === 0) return null;
	}
	return {
		surface: ctx.surface,
		run_control: ctx.runControl,
		permission_profile: ctx.permissionProfile,
		trace_id: ctx.traceId,
		parent_trace: ctx.parentTrace ?? null,
	};
}
