/**
 * HTTP transport layer for the Huskies API client.
 * Provides the `callMcpTool` function for MCP JSON-RPC calls, the
 * `resolveWsHost` utility, and the `api` object exposing all endpoints.
 */

import { rpcCall } from "../rpc";
import type {
	OkResult,
	OpenProjectResult,
	SetAnthropicApiKeyParams,
	SetModelPreferenceParams,
} from "../rpcContract";
import type {
	AllTokenUsageResponse,
	AnthropicModelInfo,
	FileEntry,
	OAuthStatus,
	TestResultsResponse,
	TokenCostResponse,
	WorkItemContent,
} from "./types";

/**
 * Resolve the WebSocket host to connect to.
 * In development, uses the injected port (or 3001); in production, uses the
 * current page's host so the socket connects to the same origin.
 */
export function resolveWsHost(
	isDev: boolean,
	envPort: string | undefined,
	locationHost: string,
): string {
	return isDev ? `127.0.0.1:${envPort || "3001"}` : locationHost;
}

/**
 * Invoke an MCP tool via the server's JSON-RPC `/mcp` endpoint.
 * Returns the first text content block from the tool result, or an empty
 * string if the result has no content.
 */
export async function callMcpTool(
	toolName: string,
	args: Record<string, unknown>,
): Promise<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: toolName, arguments: args },
		}),
	});
	const json = await res.json();
	if (json.error) {
		throw new Error(json.error.message);
	}
	const text = json.result?.content?.[0]?.text ?? "";
	return text;
}

/** Typed wrappers for all Huskies server endpoints. */
export const api = {
	getCurrentProject(_baseUrl?: string) {
		return rpcCall<string | null>("project.current");
	},
	getKnownProjects(_baseUrl?: string) {
		return rpcCall<string[]>("project.known");
	},
	async forgetKnownProject(path: string, _baseUrl?: string) {
		const r = await rpcCall<OkResult>("project.forget", { path });
		return r.ok;
	},
	async openProject(path: string, _baseUrl?: string) {
		const r = await rpcCall<OpenProjectResult>("project.open", { path });
		return r.path;
	},
	async closeProject(_baseUrl?: string) {
		const r = await rpcCall<OkResult>("project.close");
		return r.ok;
	},
	getModelPreference(_baseUrl?: string) {
		return rpcCall<string | null>("model.get_preference");
	},
	async setModelPreference(model: string, _baseUrl?: string) {
		const params: SetModelPreferenceParams = { model };
		const r = await rpcCall<OkResult>("model.set_preference", params);
		return r.ok;
	},
	getOllamaModels(baseUrlParam?: string, _baseUrl?: string) {
		return rpcCall<string[]>(
			"ollama.list_models",
			baseUrlParam ? { base_url: baseUrlParam } : {},
		);
	},
	getAnthropicApiKeyExists(_baseUrl?: string) {
		return rpcCall<boolean>("anthropic.key_exists");
	},
	getAnthropicModels(_baseUrl?: string) {
		return rpcCall<AnthropicModelInfo[]>("anthropic.list_models");
	},
	async setAnthropicApiKey(api_key: string, _baseUrl?: string) {
		const params: SetAnthropicApiKeyParams = { api_key };
		const r = await rpcCall<OkResult>("anthropic.set_api_key", params);
		return r.ok;
	},
	readFile(path: string) {
		return rpcCall<string>("io.read_file", { path });
	},
	listDirectoryAbsolute(path: string) {
		return rpcCall<FileEntry[]>("io.list_directory_absolute", { path });
	},
	getHomeDirectory(_baseUrl?: string) {
		return rpcCall<string>("io.home_directory");
	},
	listProjectFiles(_baseUrl?: string) {
		return rpcCall<string[]>("io.list_project_files");
	},
	async cancelChat(_baseUrl?: string) {
		const r = await rpcCall<OkResult>("chat.cancel");
		return r.ok;
	},
	getWorkItemContent(storyId: string, _baseUrl?: string) {
		return rpcCall<WorkItemContent>("work_items.get", { story_id: storyId });
	},
	getTestResults(storyId: string, _baseUrl?: string) {
		return rpcCall<TestResultsResponse | null>("work_items.test_results", {
			story_id: storyId,
		});
	},
	getTokenCost(storyId: string, _baseUrl?: string) {
		return rpcCall<TokenCostResponse>("work_items.token_cost", {
			story_id: storyId,
		});
	},
	getAllTokenUsage(_baseUrl?: string) {
		return rpcCall<AllTokenUsageResponse>("token_usage.all");
	},
	/** Trigger a server rebuild and restart. */
	rebuildAndRestart() {
		return callMcpTool("rebuild_and_restart", {});
	},
	/** Approve a story in QA, moving it to merge. */
	approveQa(storyId: string) {
		return callMcpTool("approve_qa", { story_id: storyId });
	},
	/** Reject a story in QA, moving it back to current with notes. */
	rejectQa(storyId: string, notes: string) {
		return callMcpTool("reject_qa", { story_id: storyId, notes });
	},
	/** Launch the QA app for a story's worktree. */
	launchQaApp(storyId: string) {
		return callMcpTool("launch_qa_app", { story_id: storyId });
	},
	/** Delete a story from the pipeline, stopping any running agent and removing the worktree. */
	deleteStory(storyId: string) {
		return callMcpTool("delete_story", { story_id: storyId });
	},
	/** Fetch OAuth status from the server. */
	getOAuthStatus() {
		return rpcCall<OAuthStatus>("oauth.status");
	},
	/** Execute a bot slash command without LLM invocation. Returns markdown response text. */
	botCommand(command: string, args: string) {
		return rpcCall<{ response: string }>("bot.command", { command, args });
	},
};
