import CommonError from '@/components/errorBoundary/CommonError';

// fetch의 RequestInit을 확장하여 params 옵션 추가
interface FetchOptions extends RequestInit {
  params?: Record<string, string>;
}

// http 메서드에서 사용할 옵션 타입
type HttpOptions = {
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
  body?: BodyInit | null;
  headers?: HeadersInit;
};

/**
 * 기본 요청 핸들러
 * - baseURL 자동 적용
 * - params 쿼리스트링 변환
 * - 'Content-Type': 'application/json' 기본 적용
 * - API 응답 에러(!response.ok)를 CommonError로 throw
 * - 네트워크 에러 등 fetch 실패 시 CommonError(500)로 throw
 */
async function request<T>(url: string, options?: FetchOptions): Promise<T> {
  const { params, ...fetchOptions } = options || {};

  // params가 있으면 쿼리스트링으로 변환
  const urlWithParams = params ? `${url}?${new URLSearchParams(params)}` : url;

  try {
    const response = await fetch(`${import.meta.env.VITE_API_URL}${urlWithParams}`, {
      ...fetchOptions,
      headers: {
        'Content-Type': 'application/json',
        ...fetchOptions.headers
      }
    });

    // API 레벨 에러 처리 (4xx, 5xx 등)
    if (!response.ok) {
      // 에러 응답 본문을 파싱하되, 파싱 실패(예: 500 에러) 시 빈 객체로 대체
      const errorData = await response.json().catch(() => ({}));
      throw new CommonError(response.status, errorData.message || `HTTP ${response.status} 에러`);
    }

    // 성공 시 JSON 파싱하여 반환
    return await response.json();

  } catch (error) {
    // 이미 CommonError인 경우 그대로 throw (위의 !response.ok)
    if (error instanceof CommonError) throw error;

    // 네트워크 에러 등 fetch 자체의 실패
    throw new CommonError(500, error instanceof Error ? error.message : '알 수 없는 네트워크 에러');
  }
}

/**
 * http 클라이언트 객체
 * - get, post, put, delete 메서드 제공
 * - post, put은 data 객체를 자동으로 JSON.stringify
 */
export const http = {
  get: <T>(path: string, options?: Omit<HttpOptions, 'method' | 'body'>) =>
    request<T>(path, { ...options, method: 'GET' }),

  post: <K, T>(path: string, data: K, options?: Omit<HttpOptions, 'method' | 'body'>) =>
    request<T>(path, {
      ...options,
      method: 'POST',
      body: JSON.stringify(data)
    }),

  put: <K, T>(path: string, data: K, options?: Omit<HttpOptions, 'method' | 'body'>) =>
    request<T>(path, {
      ...options,
      method: 'PUT',
      body: JSON.stringify(data)
    }),

  delete: <T>(path: string, options?: Omit<HttpOptions, 'method' | 'body'>) =>
    request<T>(path, { ...options, method: 'DELETE' })
};