/**
 * Headless Query — `sf headless query`
 *
 * Single read-only command that returns the full project snapshot as JSON
 * to stdout, without spawning an LLM session. Instant (~50ms).
 *
 * Output: { schemaVersion, state, next, cost }
 *   schemaVersion — output contract version
 *   state         — deriveState() output (phase, milestones, progress, blockers)
 *   next          — dry-run dispatch preview (what auto-mode would do next)
 *   cost          — aggregated parallel worker costs
 *
 * Note: Extension modules are .ts files loaded via jiti (not compiled to .js).
 * We use createJiti() here because this module is imported directly from cli.ts,
 * bypassing the extension loader's jiti setup (#1137).
 */

import { homedir } from "node:os";
import { join } from "node:path";
import { createJiti } from "@mariozechner/jiti";
import { resolveBundledSourceResource } from "./bundled-resource-path.js";
import type { SFState } from "./resources/extensions/sf/types.js";

const jiti = createJiti(import.meta.filename, {
	interopDefault: true,
	debug: false,
});
// Resolve extensions from the synced agent directory so headless-query
// loads the same extension copy as interactive/auto modes (#3471).
// Falls back to bundled source for source-tree dev workflows.
const agentExtensionsDir = join(
	process.env.SF_AGENT_DIR || join(homedir(), ".sf", "agent"),
	"extensions",
	"sf",
);
const { existsSync } = await import("node:fs");
const useAgentDir = existsSync(join(agentExtensionsDir, "state.ts"));
const sfExtensionPath = (...segments: string[]) =>
	useAgentDir
		? join(agentExtensionsDir, ...segments)
		: resolveBundledSourceResource(
				import.meta.url,
				"extensions",
				"sf",
				...segments,
			);

async function loadExtensionModules() {
	const stateModule = (await jiti.import(
		sfExtensionPath("state.ts"),
		{},
	)) as any;
	const dispatchModule = (await jiti.import(
		sfExtensionPath("auto-dispatch.ts"),
		{},
	)) as any;
	const sessionModule = (await jiti.import(
		sfExtensionPath("session-status-io.ts"),
		{},
	)) as any;
	const prefsModule = (await jiti.import(
		sfExtensionPath("preferences.ts"),
		{},
	)) as any;
	const autoStartModule = (await jiti.import(
		sfExtensionPath("auto-start.ts"),
		{},
	)) as any;
	return {
		openProjectDbIfPresent: autoStartModule.openProjectDbIfPresent as (
			basePath: string,
		) => Promise<void>,
		deriveState: stateModule.deriveState as (
			basePath: string,
		) => Promise<SFState>,
		resolveDispatch: dispatchModule.resolveDispatch as (
			opts: any,
		) => Promise<any>,
		readAllSessionStatuses: sessionModule.readAllSessionStatuses as (
			basePath: string,
		) => any[],
		loadEffectiveSFPreferences:
			prefsModule.loadEffectiveSFPreferences as () => any,
	};
}

// ─── Types ──────────────────────────────────────────────────────────────────

export interface QuerySnapshot {
	schemaVersion: 1;
	state: SFState;
	next: {
		action: "dispatch" | "stop" | "skip";
		unitType?: string;
		unitId?: string;
		reason?: string;
	};
	cost: {
		workers: Array<{
			milestoneId: string;
			pid: number;
			state: string;
			cost: number;
			lastHeartbeat: number;
		}>;
		total: number;
	};
}

export interface QueryResult {
	exitCode: number;
	data?: QuerySnapshot;
}

// ─── Implementation ─────────────────────────────────────────────────────────

export async function buildQuerySnapshot(
	basePath: string,
): Promise<QuerySnapshot> {
	const {
		openProjectDbIfPresent,
		deriveState,
		resolveDispatch,
		readAllSessionStatuses,
		loadEffectiveSFPreferences,
	} = await loadExtensionModules();
	await openProjectDbIfPresent(basePath);
	const state = await deriveState(basePath);

	// Derive next dispatch action
	let next: QuerySnapshot["next"];
	if (!state.activeMilestone?.id) {
		next = {
			action: "stop",
			reason:
				state.phase === "complete"
					? "All milestones complete."
					: state.nextAction,
		};
	} else {
		const loaded = loadEffectiveSFPreferences();
		const dispatch = await resolveDispatch({
			basePath,
			mid: state.activeMilestone.id,
			midTitle: state.activeMilestone.title,
			state,
			prefs: loaded?.preferences,
		});
		next = {
			action: dispatch.action,
			unitType: dispatch.action === "dispatch" ? dispatch.unitType : undefined,
			unitId: dispatch.action === "dispatch" ? dispatch.unitId : undefined,
			reason: dispatch.action === "stop" ? dispatch.reason : undefined,
		};
	}

	// Aggregate parallel worker costs
	const statuses = readAllSessionStatuses(basePath);
	const workers = statuses.map((s) => ({
		milestoneId: s.milestoneId,
		pid: s.pid,
		state: s.state,
		cost: s.cost,
		lastHeartbeat: s.lastHeartbeat,
	}));

	const snapshot: QuerySnapshot = {
		schemaVersion: 1,
		state,
		next,
		cost: { workers, total: workers.reduce((sum, w) => sum + w.cost, 0) },
	};

	return snapshot;
}

export async function handleQuery(basePath: string): Promise<QueryResult> {
	const snapshot = await buildQuerySnapshot(basePath);
	process.stdout.write(JSON.stringify(snapshot) + "\n");
	return { exitCode: 0, data: snapshot };
}
