{% include '_header.jinja' %}

/**
 * HTTP Client Adapter Pattern
 *
 * Allows switching between fetch/axios/httpx without changing generated code.
 * Provides unified interface for making HTTP requests.
 */

export interface HttpRequest {
  method: string;
  url: string;
  headers?: Record<string, string>;
  body?: any;
  params?: Record<string, any>;
  /** FormData for file uploads (multipart/form-data) */
  formData?: FormData;
  /** Binary data for octet-stream uploads */
  binaryBody?: Blob | ArrayBuffer;
}

export interface HttpResponse<T = any> {
  data: T;
  status: number;
  statusText: string;
  headers: Record<string, string>;
}

/**
 * HTTP Client Adapter Interface.
 * Implement this to use custom HTTP clients (axios, httpx, etc.)
 */
export interface HttpClientAdapter {
  request<T = any>(request: HttpRequest): Promise<HttpResponse<T>>;
}

/**
 * Default Fetch API adapter.
 * Uses native browser fetch() with proper error handling.
 */
export class FetchAdapter implements HttpClientAdapter {
  async request<T = any>(request: HttpRequest): Promise<HttpResponse<T>> {
    const { method, url, headers, body, params, formData, binaryBody } = request;

    // Build URL with query params
    let finalUrl = url;
    if (params) {
      const searchParams = new URLSearchParams();
      Object.entries(params).forEach(([key, value]) => {
        if (value !== null && value !== undefined) {
          searchParams.append(key, String(value));
        }
      });
      const queryString = searchParams.toString();
      if (queryString) {
        finalUrl = url.includes('?') ? `${url}&${queryString}` : `${url}?${queryString}`;
      }
    }

    // Build headers
    const finalHeaders: Record<string, string> = { ...headers };

    // Determine body and content-type
    let requestBody: string | FormData | Blob | ArrayBuffer | undefined;

    if (formData) {
      // For multipart/form-data, let browser set Content-Type with boundary
      requestBody = formData;
      // Don't set Content-Type - browser will set it with boundary
    } else if (binaryBody) {
      // Binary upload (application/octet-stream)
      finalHeaders['Content-Type'] = 'application/octet-stream';
      requestBody = binaryBody;
    } else if (body) {
      // JSON request
      finalHeaders['Content-Type'] = 'application/json';
      requestBody = JSON.stringify(body);
    }

    // Make request
    const response = await fetch(finalUrl, {
      method,
      headers: finalHeaders,
      body: requestBody,
      credentials: 'include',  // Include Django session cookies
    });

    // Parse response
    let data: any = null;
    const contentType = response.headers.get('content-type');

    if (response.status !== 204 && contentType?.includes('application/json')) {
      data = await response.json();
    } else if (response.status !== 204) {
      data = await response.text();
    }

    // Convert Headers to plain object
    const responseHeaders: Record<string, string> = {};
    response.headers.forEach((value, key) => {
      responseHeaders[key] = value;
    });

    return {
      data,
      status: response.status,
      statusText: response.statusText,
      headers: responseHeaders,
    };
  }
}

/**
 * FetchAdapter with keepalive:true.
 *
 * Use this adapter when you need requests to survive page unload
 * (visibilitychange / beforeunload) — the browser will complete the request
 * even after the page is navigated away. Typical use: monitor / analytics flush.
 *
 * @example
 * ```typescript
 * import { APIClient, KeepAliveFetchAdapter } from './client';
 * const client = new APIClient(baseUrl, { httpClient: new KeepAliveFetchAdapter() });
 * ```
 */
export class KeepAliveFetchAdapter extends FetchAdapter {
  async request<T = any>(request: HttpRequest): Promise<HttpResponse<T>> {
    const origFetch = globalThis.fetch;
    globalThis.fetch = (input: RequestInfo | URL, init?: RequestInit) =>
      origFetch(input, { ...init, keepalive: true });
    try {
      return await super.request<T>(request);
    } finally {
      globalThis.fetch = origFetch;
    }
  }
}
