/**
 * UOK Task State Machine
 *
 * Purpose: map ORCH-style task lifecycle states onto UOK gate outcomes so
 * the /tasks surface can query and display background work without inventing
 * a parallel state system. Task state is a VIEW over gate runs and unit
 * runtime records, not a separate authority.
 *
 * Consumer: /tasks command, execution-graph scheduler, and UOK kernel
 * background-work tracking.
 */

import { isTerminalUnitRuntimeStatus } from "./unit-runtime.js";

export const TASK_STATES = [
	"todo",
	"running",
	"verifying",
	"reviewing",
	"done",
	"blocked",
	"paused",
	"retrying",
	"failed",
	"cancelled",
];

export const TASK_TERMINAL_STATES = new Set(["done", "failed", "cancelled"]);

export const TASK_STATE_TRANSITIONS = {
	todo: ["running", "cancelled"],
	running: [
		"verifying",
		"reviewing",
		"done",
		"blocked",
		"paused",
		"retrying",
		"failed",
		"cancelled",
	],
	verifying: [
		"reviewing",
		"done",
		"blocked",
		"paused",
		"retrying",
		"failed",
		"cancelled",
	],
	reviewing: [
		"running",
		"verifying",
		"done",
		"blocked",
		"paused",
		"failed",
		"cancelled",
	],
	done: [],
	blocked: ["todo", "running", "retrying", "cancelled"],
	paused: ["running", "retrying", "cancelled"],
	retrying: ["running", "failed", "cancelled"],
	failed: ["retrying", "cancelled"],
	cancelled: [],
};

export const TASK_STATE_SOURCE_LABELS = {
	claimed: "todo",
	completed: "done",
	complete: "done",
	in_progress: "running",
	"manual-attention": "reviewing",
	manual_attention: "reviewing",
	pending: "todo",
	progress: "running",
	queued: "todo",
	review: "reviewing",
	validating: "verifying",
};

/**
 * Normalize runtime and scheduler source labels into task lifecycle labels.
 *
 * Purpose: store one canonical task.status vocabulary even when inputs arrive
 * from unit runtime, scheduler, graph, or gate outcome sources.
 *
 * Consumer: task-state derivation, SQLite graph persistence, and /tasks filters.
 */
export function normalizeTaskState(value) {
	if (typeof value !== "string" || value.trim() === "") return "todo";
	const normalized = value.trim().toLowerCase();
	if (TASK_STATES.includes(normalized)) return normalized;
	return TASK_STATE_SOURCE_LABELS[normalized] ?? "running";
}

/**
 * Derive task state from a collection of gate results.
 *
 * Purpose: reconcile multiple gate outcomes (security, policy, verification,
 * etc.) into a single task-level state for /tasks display.
 *
 * Consumer: ExecutionGraphScheduler after a node completes all its gates.
 */
export function gateOutcomesToTaskState(gateResults) {
	if (!gateResults || gateResults.length === 0) return "todo";

	const outcomes = gateResults.map((r) => r.outcome);

	if (outcomes.some((o) => o === "manual-attention")) return "reviewing";
	if (outcomes.some((o) => o === "blocked")) return "blocked";
	if (outcomes.some((o) => o === "paused")) return "paused";
	if (outcomes.some((o) => o === "retry")) return "retrying";
	if (outcomes.every((o) => o === "pass")) return "done";
	if (outcomes.some((o) => o === "fail")) return "failed";

	return "running";
}

/**
 * Derive task state from a unit runtime record.
 *
 * Purpose: bridge the unit-runtime projection to the ORCH-style task state
 * vocabulary so /tasks shows one unified lifecycle view.
 *
 * Consumer: /tasks query when no active gate run exists for a unit.
 */
export function unitRuntimeToTaskState(record) {
	if (!record) return "todo";

	const status = record.status ?? record.phase ?? "queued";

	switch (status) {
		case "queued":
		case "claimed":
			return "todo";
		case "running":
		case "progress":
			return "running";
		case "verifying":
		case "validating":
			return "verifying";
		case "review":
		case "reviewing":
		case "manual-attention":
			return "reviewing";
		case "completed":
			return "done";
		case "failed":
			return "failed";
		case "blocked":
			return "blocked";
		case "paused":
			return "paused";
		case "cancelled":
			return "cancelled";
		case "stale":
		case "runaway-recovered":
			return "retrying";
		case "notified":
			return isTerminalUnitRuntimeStatus(status) ? "done" : "running";
		default:
			return normalizeTaskState(status);
	}
}

/**
 * Check if a task state transition is valid.
 *
 * Purpose: prevent illegal state jumps in /tasks UI and background scheduler.
 */
export function canTransitionTaskState(from, to) {
	const allowed = TASK_STATE_TRANSITIONS[normalizeTaskState(from)];
	if (!allowed) return false;
	return allowed.includes(normalizeTaskState(to));
}

/**
 * Compute aggregate task state for a milestone or slice.
 *
 * Purpose: give /tasks a single-line summary: "3 done, 1 in_progress, 2 todo".
 *
 * Consumer: /tasks header line and footer summary chips.
 */
export function aggregateTaskStates(taskStates) {
	const counts = Object.fromEntries(TASK_STATES.map((s) => [s, 0]));
	for (const s of taskStates) {
		const state = normalizeTaskState(s);
		if (state in counts) counts[state]++;
	}
	const total = taskStates.length;
	const terminal = TASK_STATES.filter((s) =>
		TASK_TERMINAL_STATES.has(s),
	).reduce((sum, s) => sum + counts[s], 0);
	return {
		counts,
		total,
		terminal,
		progress: total > 0 ? Math.round((terminal / total) * 100) : 0,
		isComplete: terminal === total && total > 0,
	};
}

/**
 * Build a Task record from execution graph node + gate results.
 *
 * Purpose: standardize what /tasks stores and queries so the surface does
 * not need to understand UOK internals.
 *
 * Consumer: ExecutionGraphScheduler after node completion, /tasks command.
 */
export function buildTaskRecord({
	nodeId,
	unitType,
	unitId,
	milestoneId,
	sliceId,
	taskId,
	title,
	gateResults = [],
	runtimeRecord = null,
	startedAt = null,
	endedAt = null,
	durationMs = null,
	costUsd = null,
	modelId = null,
	workerId = null,
}) {
	const state =
		gateResults.length > 0
			? gateOutcomesToTaskState(gateResults)
			: unitRuntimeToTaskState(runtimeRecord);

	return {
		id: nodeId,
		unitType,
		unitId,
		milestoneId,
		sliceId,
		taskId,
		title: title ?? unitId,
		state,
		gateResults: gateResults.map((r) => ({
			gateId: r.gateId,
			outcome: r.outcome,
			failureClass: r.failureClass,
			attempt: r.attempt,
			durationMs: r.durationMs,
		})),
		startedAt,
		endedAt,
		durationMs,
		costUsd,
		modelId,
		workerId,
		updatedAt: new Date().toISOString(),
	};
}
