/**
 * Minimal JSON transport for Hyper's non-streaming endpoints, with timeouts and
 * error details taken from the OpenAI/Anthropic error envelopes Hyper returns.
 */

const TIMEOUT_MS = 10_000;

export class HttpError extends Error {
	readonly status: number;

	constructor(status: number, url: string, detail: string) {
		super(`${url} returned HTTP ${status}${detail ? `: ${detail}` : ""}`);
		this.name = "HttpError";
		this.status = status;
	}
}

async function request(url: string, init: RequestInit, timeoutMs: number): Promise<unknown> {
	const response = await fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
	const body = await response.text();
	if (!response.ok) throw new HttpError(response.status, url, errorDetail(body));
	if (!body.trim()) throw new Error(`${url} returned an empty body`);
	return JSON.parse(body) as unknown;
}

/**
 * `{error: {message}}` (the OpenAI-shaped endpoints), `{error: {error: {message}}}`
 * (Anthropic-shaped), and a bare `{error: "invalid refresh_token"}` on `/token/exchange`
 * all appear in practice, and the last one is what tells a rejected session apart from a
 * rejected request.
 */
function errorDetail(body: string): string {
	try {
		const payload = JSON.parse(body) as { error?: unknown };
		const error = payload.error;
		if (typeof error === "string") return error;
		const fields = (error ?? {}) as { message?: unknown; error?: { message?: unknown } };
		const message = fields.message ?? fields.error?.message;
		return typeof message === "string" ? message : "";
	} catch {
		return body.slice(0, 120).trim();
	}
}

const jsonHeaders = (extra?: Record<string, string>): Record<string, string> => ({
	"Content-Type": "application/json",
	...extra,
});

export const getJson = (url: string, headers?: Record<string, string>, timeoutMs = TIMEOUT_MS): Promise<unknown> =>
	request(url, { method: "GET", headers: jsonHeaders(headers) }, timeoutMs);

export const postJson = (
	url: string,
	body: unknown,
	headers?: Record<string, string>,
	timeoutMs = TIMEOUT_MS,
): Promise<unknown> =>
	request(url, { method: "POST", headers: jsonHeaders(headers), body: JSON.stringify(body) }, timeoutMs);

export const authorization = (apiKey: string): Record<string, string> => ({ Authorization: `Bearer ${apiKey}` });
