/**
 * progress-event.js - typed autonomous progress event helpers.
 *
 * Purpose: keep Droid's useful machine-readable progress vocabulary while
 * preserving SF's existing journal/audit systems as the transport.
 *
 * Consumer: autonomous loop projections, dashboards, and future machine
 * surfaces that need stable progress event names instead of parsing prose.
 */

export const UOK_PROGRESS_EVENT_TYPES = Object.freeze([
	"unit_selected",
	"model_auto_resolved",
	"worker_started",
	"validation_started",
	"unit_handoff_recorded",
	"unit_blocked",
	"unit_completed",
]);

const EVENT_TYPES = new Set(UOK_PROGRESS_EVENT_TYPES);

function stringOrNull(value) {
	return typeof value === "string" && value.trim().length > 0
		? value.trim()
		: null;
}

function objectOrEmpty(value) {
	return value && typeof value === "object" && !Array.isArray(value)
		? value
		: {};
}

/**
 * Validate a typed UOK progress event.
 *
 * Purpose: reject unstable ad hoc event names before they become dashboard or
 * machine-surface contracts.
 *
 * Consumer: buildUokProgressEvent and tests that need non-throwing diagnostics.
 */
export function validateUokProgressEvent(event) {
	const issues = [];
	if (!event || typeof event !== "object" || Array.isArray(event)) {
		return { ok: false, issues: ["event must be an object"] };
	}
	if (!EVENT_TYPES.has(event.eventType)) {
		issues.push(
			`eventType must be one of ${UOK_PROGRESS_EVENT_TYPES.join(", ")}`,
		);
	}
	if (
		"unitId" in event &&
		event.unitId !== null &&
		typeof event.unitId !== "string"
	) {
		issues.push("unitId must be a string or null");
	}
	if (
		"unitType" in event &&
		event.unitType !== null &&
		typeof event.unitType !== "string"
	) {
		issues.push("unitType must be a string or null");
	}
	if (
		"data" in event &&
		(typeof event.data !== "object" ||
			event.data === null ||
			Array.isArray(event.data))
	) {
		issues.push("data must be an object");
	}
	return { ok: issues.length === 0, issues };
}

/**
 * Build a typed UOK progress event with canonical timestamp and payload shape.
 *
 * Purpose: give SF the useful Droid-style progress stream shape without
 * coupling event creation to a specific file, DB, or transport.
 *
 * Consumer: future autonomous journal/audit adapters and focused tests.
 */
export function buildUokProgressEvent(args = {}) {
	const event = {
		schemaVersion: 1,
		ts: args.ts ?? new Date().toISOString(),
		eventType: args.eventType,
		unitType: stringOrNull(args.unitType),
		unitId: stringOrNull(args.unitId),
		role: stringOrNull(args.role),
		sessionId: stringOrNull(args.sessionId),
		workerSessionId: stringOrNull(args.workerSessionId),
		traceId: stringOrNull(args.traceId),
		data: args.data === undefined ? {} : args.data,
	};
	const validation = validateUokProgressEvent(event);
	if (!validation.ok) {
		throw new TypeError(
			`Invalid UOK progress event: ${validation.issues.join("; ")}`,
		);
	}
	return {
		...event,
		data: objectOrEmpty(event.data),
	};
}
