import { AgentIdPError } from './errors.js';

type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';

interface RequestOptions {
  method: HttpMethod;
  path: string;
  token: string;
  body?: unknown;
  query?: Record<string, string | number | boolean | undefined>;
}

/**
 * Shared HTTP request helper for all AgentIdP API calls.
 * Sets Authorization header, serialises JSON body, parses response,
 * and maps API error shapes to AgentIdPError.
 */
export async function request<T>(baseUrl: string, opts: RequestOptions): Promise<T> {
  const url = new URL(opts.path, baseUrl);

  if (opts.query) {
    for (const [key, value] of Object.entries(opts.query)) {
      if (value !== undefined) {
        url.searchParams.set(key, String(value));
      }
    }
  }

  const headers: Record<string, string> = {
    Authorization: `Bearer ${opts.token}`,
    Accept: 'application/json',
  };

  let bodyPayload: string | undefined;
  if (opts.body !== undefined) {
    headers['Content-Type'] = 'application/json';
    bodyPayload = JSON.stringify(opts.body);
  }

  let response: Response;
  try {
    response = await fetch(url.toString(), {
      method: opts.method,
      headers,
      body: bodyPayload,
    });
  } catch (err) {
    throw new AgentIdPError(
      'NETWORK_ERROR',
      `Network error: ${err instanceof Error ? err.message : String(err)}`,
      0,
    );
  }

  if (response.status === 204) {
    return undefined as unknown as T;
  }

  let data: unknown;
  const contentType = response.headers.get('content-type') ?? '';
  if (contentType.includes('application/json')) {
    data = await response.json();
  } else {
    data = await response.text();
  }

  if (!response.ok) {
    throw AgentIdPError.fromApiError(data, response.status);
  }

  return data as T;
}
