/// Gateway API client — used when running in gateway mode.
///
/// The gateway mode is detected by checking `GET /gateway/mode`. If it returns
/// `{ "mode": "gateway" }` the frontend switches to the gateway UI.

export interface JoinedAgent {
	id: string;
	label: string;
	address: string;
	registered_at: number;
	/// Unix timestamp of the last heartbeat from this agent.
	last_seen: number;
	/// Project this agent is assigned to, if any.
	assigned_project?: string;
}

export interface GatewayProject {
	name: string;
	url: string;
}

export interface GatewayInfo {
	active: string;
	projects: GatewayProject[];
}

/** Display column for a work item — derived server-side from `Stage::pipeline()` (story 1085). */
export type Pipeline =
	| "backlog"
	| "coding"
	| "qa"
	| "merge"
	| "done"
	| "closed"
	| "archived";

/** Badge/indicator for a work item — derived server-side from `Stage::status()` (story 1085). */
export type Status =
	| "active"
	| "frozen"
	| "review-hold"
	| "blocked"
	| "merge-failure"
	| "merge-failure-final"
	| "abandoned"
	| "superseded"
	| "rejected"
	| "done";

export interface PipelineItem {
	story_id: string;
	name: string;
	/** Legacy stage string (kept for back-compat); prefer `pipeline` + `status`. */
	stage: string;
	/** Display column (story 1085). Optional until all servers are upgraded. */
	pipeline?: Pipeline;
	/** Display badge (story 1085). Optional until all servers are upgraded. */
	status?: Status;
	agent?: { agent_name: string; model: string; status: string } | null;
	blocked?: boolean;
	retry_count?: number;
	merge_failure?: string;
}

export interface ProjectPipelineStatus {
	active: PipelineItem[];
	backlog: { story_id: string; name: string }[];
	backlog_count: number;
	archived?: PipelineItem[];
	error?: string;
}

export interface AllProjectsPipeline {
	active: string;
	projects: Record<string, ProjectPipelineStatus>;
}

export interface GenerateTokenResponse {
	token: string;
}

export interface ServerMode {
	mode: "gateway" | "standard";
}

/// Type guard: verify that an unknown value has the AllProjectsPipeline shape.
/// Prevents silent "no active stories" when the backend response shape drifts.
function isAllProjectsPipeline(value: unknown): value is AllProjectsPipeline {
	if (typeof value !== "object" || value === null) return false;
	const v = value as Record<string, unknown>;
	if (typeof v.active !== "string") return false;
	if (typeof v.projects !== "object" || v.projects === null) return false;
	for (const proj of Object.values(v.projects as Record<string, unknown>)) {
		if (typeof proj !== "object" || proj === null) return false;
		const p = proj as Record<string, unknown>;
		if (!Array.isArray(p.active) && typeof p.error !== "string") return false;
	}
	return true;
}

async function gatewayRequest<T>(
	path: string,
	options: RequestInit = {},
): Promise<T> {
	const res = await fetch(path, {
		headers: { "Content-Type": "application/json", ...(options.headers ?? {}) },
		...options,
	});
	if (!res.ok) {
		const text = await res.text();
		throw new Error(text || `Request failed (${res.status})`);
	}
	// DELETE /gateway/agents/:id returns 204 No Content.
	if (res.status === 204) {
		return undefined as unknown as T;
	}
	return res.json() as Promise<T>;
}

let _mcpRequestId = 1;

/// Call a gateway MCP tool via JSON-RPC and return the result.
async function gatewayMcpCall<T>(
	toolName: string,
	args: Record<string, unknown> = {},
): Promise<T> {
	const id = _mcpRequestId++;
	const body = JSON.stringify({
		jsonrpc: "2.0",
		id,
		method: "tools/call",
		params: { name: toolName, arguments: args },
	});
	const res = await fetch("/mcp", {
		method: "POST",
		headers: { "Content-Type": "application/json" },
		body,
	});
	if (!res.ok) {
		const text = await res.text();
		throw new Error(text || `MCP request failed (${res.status})`);
	}
	const json = (await res.json()) as {
		result?: Record<string, unknown>;
		error?: { message: string };
	};
	if (json.error) {
		throw new Error(json.error.message);
	}
	return json.result as T;
}

export const gatewayApi = {
	/// Returns `{ mode: "gateway" }` if this server is a gateway, otherwise rejects.
	getServerMode(): Promise<ServerMode> {
		return gatewayRequest<ServerMode>("/gateway/mode");
	},

	/// Generate a one-time join token for a new build agent.
	generateToken(): Promise<GenerateTokenResponse> {
		return gatewayRequest<GenerateTokenResponse>("/gateway/tokens", {
			method: "POST",
		});
	},

	/// List all build agents that have registered with this gateway.
	listAgents(): Promise<JoinedAgent[]> {
		return gatewayMcpCall<{ agents: JoinedAgent[] }>("agents.list").then(
			(result) => result.agents ?? [],
		);
	},

	/// Remove a registered build agent by its ID.
	removeAgent(id: string): Promise<void> {
		return gatewayRequest<void>(`/gateway/agents/${id}`, {
			method: "DELETE",
		});
	},

	/// Assign an agent to a project, or unassign it by passing null.
	assignAgent(id: string, project: string | null): Promise<JoinedAgent> {
		return gatewayRequest<JoinedAgent>(`/gateway/agents/${id}/assign`, {
			method: "POST",
			body: JSON.stringify({ project }),
		});
	},

	/// Get the list of registered projects from the gateway.
	getGatewayInfo(): Promise<GatewayInfo> {
		return gatewayRequest<GatewayInfo>("/api/gateway");
	},

	/// Send a heartbeat for an agent to update its last-seen timestamp.
	heartbeat(id: string): Promise<void> {
		return gatewayRequest<void>(`/gateway/agents/${id}/heartbeat`, {
			method: "POST",
		});
	},

	/// Fetch pipeline status from all registered projects via the pipeline.get read-RPC.
	async getAllProjectsPipeline(): Promise<AllProjectsPipeline> {
		const res = await fetch("/mcp", {
			method: "POST",
			headers: { "Content-Type": "application/json" },
			body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "pipeline.get", params: {} }),
		});
		if (!res.ok) {
			const text = await res.text();
			throw new Error(text || `Request failed (${res.status})`);
		}
		const rpc = await res.json() as { result?: unknown; error?: { message: string } };
		if (rpc.error) {
			throw new Error(rpc.error.message);
		}
		const result = rpc.result;
		if (!isAllProjectsPipeline(result)) {
			throw new Error("pipeline.get returned unexpected shape");
		}
		return result;
	},

	/// Switch the active project via the MCP switch_project tool.
	async switchProject(project: string): Promise<{ ok: boolean; error?: string }> {
		const res = await fetch("/mcp", {
			method: "POST",
			headers: { "Content-Type": "application/json" },
			body: JSON.stringify({
				jsonrpc: "2.0",
				id: 1,
				method: "tools/call",
				params: { name: "switch_project", arguments: { project } },
			}),
		});
		const data = await res.json();
		if (data.error) {
			return { ok: false, error: data.error.message ?? String(data.error) };
		}
		return { ok: true };
	},
};
