/**
 * Fetch-based HTTP client for runtimes with native HTTP/2 support.
 *
 * @remarks
 * Used by Deno, Bun, React Native, and any other runtime that provides a
 * spec-compliant `fetch()`. These runtimes negotiate HTTP/2 automatically
 * via ALPN, so the {@link AptosClientRequest.http2 | http2} option is
 * ignored.
 *
 * @module index.fetch
 */
import { CookieJar } from "./cookieJar.js";
import {
  applyCookiesToHeaders,
  applyJsonContentType,
  buildUrl,
  headersToRecord,
  parseJsonSafely,
  serializeBody,
  storeResponseCookies,
} from "./shared.js";
import type { AptosClientRequest, AptosClientResponse } from "./types.js";

export type { Cookie } from "./cookieJar.js";
export { CookieJar } from "./cookieJar.js";
export type { AptosClientRequest, AptosClientResponse, CookieJarLike } from "./types.js";

const defaultCookieJar = new CookieJar();

let http2Warned = false;

/**
 * Send a JSON request to an Aptos API endpoint.
 *
 * This is the default export and the primary entry point for most callers.
 *
 * @typeParam Res - Expected shape of the JSON response body.
 * @param options - Request configuration.
 * @returns Parsed response with status, headers, and deserialized body.
 *
 * @example
 * ```ts
 * import aptosClient from "@aptos-labs/aptos-client";
 *
 * const { data } = await aptosClient<{ chain_id: number }>({
 *   url: "https://fullnode.mainnet.aptoslabs.com/v1",
 *   method: "GET",
 * });
 * ```
 */
export default async function aptosClient<Res>(options: AptosClientRequest): Promise<AptosClientResponse<Res>> {
  return jsonRequest<Res>(options);
}

/**
 * Send a request and parse the response as JSON.
 *
 * Identical to the default export; useful when a named import is preferred.
 *
 * @typeParam Res - Expected shape of the JSON response body.
 * @param options - Request configuration.
 */
export async function jsonRequest<Res>(options: AptosClientRequest): Promise<AptosClientResponse<Res>> {
  const { requestUrl, requestConfig, jar } = buildRequest(options);

  const res = await fetch(requestUrl, requestConfig);
  storeResponseCookies(new URL(requestUrl), res.headers, jar);
  const data = await parseJsonSafely(res);

  return {
    status: res.status,
    statusText: res.statusText,
    data,
    headers: headersToRecord(res.headers),
    config: requestConfig,
  };
}

/**
 * Send a request and return the response as an `ArrayBuffer`.
 *
 * Intended for BCS-encoded responses from the Aptos API.
 *
 * @experimental
 * @param options - Request configuration.
 */
export async function bcsRequest(options: AptosClientRequest): Promise<AptosClientResponse<ArrayBuffer>> {
  const { requestUrl, requestConfig, jar } = buildRequest(options);

  const res = await fetch(requestUrl, requestConfig);
  storeResponseCookies(new URL(requestUrl), res.headers, jar);
  const data = await res.arrayBuffer();

  return {
    status: res.status,
    statusText: res.statusText,
    data,
    headers: headersToRecord(res.headers),
    config: requestConfig,
  };
}

/** Build the URL and `RequestInit` from the caller's options. @internal */
function buildRequest(options: AptosClientRequest) {
  if (options.method !== "GET" && options.method !== "POST") {
    throw new Error(`Unsupported method: ${options.method}`);
  }

  if (!http2Warned && options.http2 !== undefined) {
    http2Warned = true;
    console.warn("[aptos-client] The `http2` option is only supported by the Node entry point and is ignored here.");
  }

  const jar = options.cookieJar ?? defaultCookieJar;

  const headers = new Headers();
  for (const [key, value] of Object.entries(options?.headers ?? {})) {
    if (value !== undefined) {
      headers.set(key, String(value));
    }
  }

  const requestUrl = buildUrl(options.url, options.params);

  applyCookiesToHeaders(headers, requestUrl, jar);

  const body = serializeBody(options.body);
  if (body !== undefined) {
    applyJsonContentType(options.body, headers);
  }

  const requestConfig: RequestInit = {
    method: options.method,
    headers: headersToRecord(headers) as Record<string, string>,
    body,
  };

  return { requestUrl: requestUrl.toString(), requestConfig, jar };
}
