/**
 * status-projection.ts — read-model contract for supervised SF repos.
 *
 * Purpose: publish a small atomically-written status file that web and other
 * observers can read without opening a repo's live SQLite database or poking
 * at transient runtime internals.
 *
 * Consumer: `sf headless query` writes `.sf/status.projection.json`; the web
 * `/api/swarms` route reads it for multi-repo dashboard aggregation.
 */
import {
	closeSync,
	existsSync,
	fsyncSync,
	mkdirSync,
	openSync,
	renameSync,
	writeSync,
} from "node:fs";
import { basename, join, resolve } from "node:path";
import type { QuerySnapshot } from "./headless-query.js";

export const STATUS_PROJECTION_VERSION = 1;
export const STATUS_PROJECTION_FILE = "status.projection.json";

export interface SfStatusProjection {
	projectionVersion: 1;
	swarmId: string;
	repoPath: string;
	repoName: string;
	writer: {
		surface: "headless";
		pid: number;
		writtenAt: string;
	};
	supervisor: {
		kind: "systemd" | "launchd" | "daemon" | "headless" | "unknown";
		health: "running" | "idle" | "blocked" | "degraded" | "unknown";
		currentPid: number | null;
	};
	state: {
		activeMilestoneId: string | null;
		activeMilestoneTitle: string | null;
		activeSliceId: string | null;
		activeSliceTitle: string | null;
		phase: string;
		nextAction: string;
		nextUnitType: string | null;
		nextUnitId: string | null;
		queueDepth: number;
		lastCycleOutcome: string | null;
		currentUnit: string | null;
	};
	health: {
		verdict: string | null;
		classification: string | null;
		issues: number;
	};
}

function projectionPath(basePath: string): string {
	return join(basePath, ".sf", STATUS_PROJECTION_FILE);
}

function deriveSupervisor(
	snapshot: QuerySnapshot,
): SfStatusProjection["supervisor"] {
	const diagnostics = snapshot.uokDiagnostics ?? {};
	const currentUnit = diagnostics.currentUnit ?? null;
	const classification = String(diagnostics.classification ?? "unknown");
	const health =
		classification === "running"
			? "running"
			: classification === "blocked"
				? "blocked"
				: classification === "clear"
					? "idle"
					: classification === "degraded"
						? "degraded"
						: diagnostics.verdict === "clear"
							? "running"
							: "unknown";
	return {
		kind: "headless",
		health,
		currentPid:
			typeof currentUnit?.pid === "number" && Number.isFinite(currentUnit.pid)
				? currentUnit.pid
				: null,
	};
}

function deriveQueueDepth(snapshot: QuerySnapshot): number {
	const state = snapshot.state as { registry?: Array<{ status?: string }> };
	const registry = Array.isArray(state.registry) ? state.registry : [];
	return registry.filter((item) => item.status === "pending").length;
}

export function buildStatusProjection(
	basePath: string,
	snapshot: QuerySnapshot,
): SfStatusProjection {
	const repoPath = resolve(basePath);
	const diagnostics = snapshot.uokDiagnostics ?? {};
	const currentUnit = diagnostics.currentUnit;
	const currentUnitLabel =
		currentUnit?.unitType && currentUnit?.unitId
			? `${currentUnit.unitType}:${currentUnit.unitId}`
			: null;
	return {
		projectionVersion: STATUS_PROJECTION_VERSION,
		swarmId: repoPath,
		repoPath,
		repoName: basename(repoPath),
		writer: {
			surface: "headless",
			pid: process.pid,
			writtenAt: new Date().toISOString(),
		},
		supervisor: deriveSupervisor(snapshot),
		state: {
			activeMilestoneId: snapshot.state.activeMilestone?.id ?? null,
			activeMilestoneTitle: snapshot.state.activeMilestone?.title ?? null,
			activeSliceId: snapshot.state.activeSlice?.id ?? null,
			activeSliceTitle: snapshot.state.activeSlice?.title ?? null,
			phase: String(snapshot.state.phase ?? "unknown"),
			nextAction: String(snapshot.state.nextAction ?? ""),
			nextUnitType: snapshot.next.unitType ?? null,
			nextUnitId: snapshot.next.unitId ?? null,
			queueDepth: deriveQueueDepth(snapshot),
			lastCycleOutcome: diagnostics.latestRun?.status
				? String(diagnostics.latestRun.status)
				: null,
			currentUnit: currentUnitLabel,
		},
		health: {
			verdict: diagnostics.verdict ? String(diagnostics.verdict) : null,
			classification: diagnostics.classification
				? String(diagnostics.classification)
				: null,
			issues: Array.isArray(diagnostics.issues) ? diagnostics.issues.length : 0,
		},
	};
}

export function writeStatusProjection(
	basePath: string,
	snapshot: QuerySnapshot,
): SfStatusProjection {
	const projection = buildStatusProjection(basePath, snapshot);
	const target = projectionPath(basePath);
	mkdirSync(join(basePath, ".sf"), { recursive: true });
	const tmp = `${target}.tmp-${process.pid}-${Date.now()}`;
	const fd = openSync(tmp, "w", 0o644);
	try {
		writeSync(fd, JSON.stringify(projection, null, 2) + "\n");
		fsyncSync(fd);
	} finally {
		closeSync(fd);
	}
	renameSync(tmp, target);
	return projection;
}

export function hasStatusProjection(basePath: string): boolean {
	return existsSync(projectionPath(basePath));
}
