/**
 * Client-only fetch service.
 * Import as: import { clientFetch } from '@/lib/api/client'
 *
 * Use this in Client Components and service hooks — never in Server Components or Server Actions.
 *
 * Error behaviour:
 *  - 401: clears auth cookies, toasts "session expired", redirects to login (no throw)
 *  - 403: toasts "forbidden", throws ApiError
 *  - 5xx: toasts "server error", throws ApiError
 *  - Other 4xx: throws ApiError without toasting — the calling hook owns the message
 *  - Network failure: toasts "network error", throws ApiError
 */
import { API_URL } from '@/config';
import { COOKIE_NAMES } from '@/lib/cookies';
import { deleteClientCookie, getClientCookie } from '@/lib/cookies/client';
import { dispatchToast } from '@/lib/toast/dispatchToast';
import { ROUTES } from '@/constants';
import { ApiError } from './errors';
import { attemptTokenRefresh } from './refresh';

// Routes that must never trigger the silent-refresh-on-401 retry: the refresh call itself
// (a 401 there is terminal reuse/expiry) and the unauthenticated OTP endpoints.
const NO_REFRESH_PATHS = ['/auth/refresh', '/auth/request_otp', '/auth/verify_otp'];

async function parseBody(response: Response): Promise<{ message?: string; code?: string }> {
  try {
    return await response.json();
  } catch {
    return {};
  }
}

export async function clientFetch<T>(path: string, options?: RequestInit, isRetry = false): Promise<T> {
  const token = getClientCookie(COOKIE_NAMES.ACCESS_TOKEN);
  const locale = window.location.pathname.split('/')[1] || 'fa';

  const reqHeaders: Record<string, string> = {
    'Accept-Language': locale,
  };
  // Let the browser set `Content-Type` (with the multipart boundary) for FormData bodies — a manual
  // JSON content-type there breaks the upload. JSON bodies still declare it explicitly.
  if (!(options?.body instanceof FormData)) {
    reqHeaders['Content-Type'] = 'application/json';
  }
  if (token) {
    reqHeaders['Authorization'] = `Bearer ${token}`;
  }

  let response: Response;
  try {
    response = await fetch(`${API_URL}${path}`, {
      ...options,
      headers: { ...reqHeaders, ...(options?.headers as Record<string, string>) },
    });
  } catch {
    dispatchToast('Network error. Please check your connection.', 'error');
    throw new ApiError(0, 'Network error');
  }

  if (response.ok) {
    if (response.status === 204) return undefined as T;
    return response.json() as Promise<T>;
  }

  const body = await parseBody(response);
  const message = body.message ?? response.statusText;
  const { code } = body;

  if (response.status === 401) {
    // Try one silent refresh + retry before giving up — an expired access token is rotated
    // transparently so the user keeps their session. Skip for the refresh/OTP endpoints and
    // never loop (single retry).
    const refreshEligible = !isRetry && !NO_REFRESH_PATHS.some((p) => path.includes(p));
    if (refreshEligible && (await attemptTokenRefresh())) {
      return clientFetch<T>(path, options, true);
    }
    deleteClientCookie(COOKIE_NAMES.ACCESS_TOKEN);
    deleteClientCookie(COOKIE_NAMES.REFRESH_TOKEN);
    dispatchToast('Session expired. Please log in again.', 'error');
    window.location.replace(`/${locale}${ROUTES.LOGIN}`);
    return undefined as T;
  }

  if (response.status === 403) {
    dispatchToast('You do not have permission to perform this action.', 'error');
    throw new ApiError(403, message, code);
  }

  if (response.status >= 500) {
    dispatchToast('Server error. Please try again later.', 'error');
    throw new ApiError(response.status, message, code);
  }

  throw new ApiError(response.status, message, code);
}
