const DEFAULT_TIMEOUT = 10000;

function toQueryString(params: Record<string, any> = {}): string {
  const searchParams = new URLSearchParams();

  Object.entries(params).forEach(([key, value]) => {
    if (value === undefined || value === null) return;
    if (Array.isArray(value)) {
      value.forEach((item) => searchParams.append(key, String(item)));
      return;
    }
    searchParams.append(key, String(value));
  });

  const query = searchParams.toString();
  return query ? `?${query}` : "";
}

function buildUrl(url: string, params?: Record<string, any>): string {
  if (!params || Object.keys(params).length === 0) return url;
  const query = toQueryString(params);
  return `${url}${url.includes("?") ? `&${query.slice(1)}` : query}`;
}

async function parseResponse(response: Response): Promise<any> {
  const contentType = response.headers.get("content-type") || "";

  if (contentType.includes("application/json")) {
    return response.json();
  }

  return response.text();
}

function normalizeError(status: number, message?: string, payload?: any): Error {
  const error = new Error(message || "Request failed");
  (error as any).status = status;
  (error as any).payload = payload;
  return error;
}

export async function request(
  url: string,
  options: {
    method?: string;
    params?: Record<string, any>;
    data?: any;
    headers?: Record<string, string>;
    timeout?: number;
    baseURL?: string;
    cache?: RequestCache;
  } = {},
): Promise<any> {
  const {
    method = "GET",
    params,
    data,
    headers = {},
    timeout = DEFAULT_TIMEOUT,
    baseURL = import.meta.env.VITE_API_BASE_URL || "",
    cache,
    ...restOptions
  } = options;

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

  const fullUrl = buildUrl(`${baseURL}${url}`, params);
  const finalHeaders = { ...headers };
  const fetchOptions: RequestInit = {
    method,
    headers: finalHeaders,
    signal: controller.signal,
    cache: cache ?? (method.toUpperCase() === "GET" ? "no-store" : undefined),
    ...restOptions,
  };

  if (data !== undefined) {
    if (data instanceof FormData) {
      fetchOptions.body = data;
    } else {
      finalHeaders["Content-Type"] = finalHeaders["Content-Type"] || "application/json";
      fetchOptions.body = JSON.stringify(data);
    }
  }

  try {
    const response = await fetch(fullUrl, fetchOptions);
    const payload = await parseResponse(response);

    if (!response.ok) {
      throw normalizeError(response.status, payload?.message || response.statusText, payload);
    }

    return payload;
  } catch (error) {
    if ((error as Error).name === "AbortError") {
      throw normalizeError(408, "Request timeout");
    }
    throw error;
  } finally {
    clearTimeout(timer);
  }
}

export const get = (
  url: string,
  params?: Record<string, any>,
  options: Record<string, any> = {},
): Promise<any> => request(url, { ...options, method: "GET", params });

export const post = (url: string, data: any, options: Record<string, any> = {}): Promise<any> =>
  request(url, { ...options, method: "POST", data });
export const put = (url: string, data: any, options: Record<string, any> = {}): Promise<any> =>
  request(url, { ...options, method: "PUT", data });
export const patch = (url: string, data: any, options: Record<string, any> = {}): Promise<any> =>
  request(url, { ...options, method: "PATCH", data });
export const del = (url: string,params?: Record<string, any>,options: Record<string, any> = {},): Promise<any> => 
  request(url, { ...options, method: "DELETE", params });
