/**
 * Browser/React Native-safe typed fetch wrapper for platform-service.
 *
 * Client-side counterpart to @bytelyst/api-client (which is server-side).
 * Uses bearer tokens from storage (not httpOnly cookies).
 * Includes auto-retry on 401 via token refresh.
 *
 * @example
 * ```ts
 * import { createPlatformClient } from '@bytelyst/platform-client';
 *
 * const api = createPlatformClient({
 *   baseUrl: 'http://localhost:4003/api',
 *   productId: 'nomgap',
 *   getAccessToken: () => authClient.getAccessToken(),
 *   refreshAccessToken: () => authClient.refreshAccessToken(),
 * });
 *
 * const sessions = await api.get<Session[]>('/fasting-sessions');
 * await api.post('/fasting-sessions', { protocol: '16:8' });
 * ```
 */

// ── Types ────────────────────────────────────────────────────

export interface PlatformClientConfig {
  /** Platform-service base URL (e.g. "http://localhost:4003/api"). */
  baseUrl: string;

  /** Product identifier sent as x-product-id header. */
  productId: string;

  /** Function that returns the current access token, or null. */
  getAccessToken: () => string | null;

  /** Optional function to refresh the access token. Returns true on success. */
  refreshAccessToken?: () => Promise<boolean>;

  /** Request timeout in milliseconds. Default: 15000. */
  timeoutMs?: number;
}

export class ApiError extends Error {
  constructor(
    public readonly status: number,
    public readonly body: unknown,
    message?: string
  ) {
    super(message ?? `API error ${status}`);
    this.name = 'ApiError';
  }
}

export interface PlatformClient {
  get<T = unknown>(path: string, headers?: Record<string, string>): Promise<T>;
  post<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
  put<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
  del<T = unknown>(path: string, headers?: Record<string, string>): Promise<T>;
  request<T = unknown>(
    method: string,
    path: string,
    body?: unknown,
    headers?: Record<string, string>
  ): Promise<T>;
}

// ── UUID helper ──────────────────────────────────────────────

function uuid(): string {
  if (typeof globalThis.crypto?.randomUUID === 'function') {
    return globalThis.crypto.randomUUID();
  }
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
    const r = (Math.random() * 16) | 0;
    return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
  });
}

// ── Factory ──────────────────────────────────────────────────

export function createPlatformClient(config: PlatformClientConfig): PlatformClient {
  const { baseUrl, productId, getAccessToken, refreshAccessToken, timeoutMs = 15_000 } = config;

  async function doRequest<T>(
    method: string,
    path: string,
    body?: unknown,
    extraHeaders?: Record<string, string>,
    isRetry = false
  ): Promise<T> {
    const url = `${baseUrl}${path}`;
    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
      'x-product-id': productId,
      'x-request-id': uuid(),
      ...extraHeaders,
    };

    const token = getAccessToken();
    if (token) headers['Authorization'] = `Bearer ${token}`;

    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), timeoutMs);

    try {
      const res = await globalThis.fetch(url, {
        method,
        headers,
        body: body != null ? JSON.stringify(body) : undefined,
        signal: controller.signal,
      });

      if (res.status === 204) return undefined as T;

      const json = await res.json().catch(() => ({}));

      // Auto-refresh on 401 (once)
      if (res.status === 401 && !isRetry && !path.startsWith('/auth/') && refreshAccessToken) {
        clearTimeout(timer);
        const refreshed = await refreshAccessToken();
        if (refreshed) {
          return doRequest<T>(method, path, body, extraHeaders, true);
        }
      }

      if (!res.ok) {
        throw new ApiError(
          res.status,
          json,
          (json as Record<string, string>).message ?? `HTTP ${res.status}`
        );
      }

      return json as T;
    } finally {
      clearTimeout(timer);
    }
  }

  return {
    get: <T = unknown>(path: string, headers?: Record<string, string>) =>
      doRequest<T>('GET', path, undefined, headers),
    post: <T = unknown>(path: string, body?: unknown, headers?: Record<string, string>) =>
      doRequest<T>('POST', path, body, headers),
    put: <T = unknown>(path: string, body?: unknown, headers?: Record<string, string>) =>
      doRequest<T>('PUT', path, body, headers),
    del: <T = unknown>(path: string, headers?: Record<string, string>) =>
      doRequest<T>('DELETE', path, undefined, headers),
    request: <T = unknown>(
      method: string,
      path: string,
      body?: unknown,
      headers?: Record<string, string>
    ) => doRequest<T>(method, path, body, headers),
  };
}
