import type { FetchError, FetchOptions } from 'ofetch';
import type { ApiError } from '~/types/api/error';
import type { HttpMethod } from '~/types/http-method';
import { QueryResult } from '~/types/query-result';

export type UseApiResponse<T, B = unknown> = QueryResult<T, B>;

export interface UseApi {
  get: <T>(path: string, opts?: FetchOptions, immediate?: boolean) => UseApiResponse<T>;
  del: <T>(path: string, opts?: FetchOptions, immediate?: boolean) => UseApiResponse<T>;
  post: <T, B = unknown>(path: string, opts?: FetchOptions, immediate?: boolean, body?: B) => UseApiResponse<T, B>;
  put: <T, B = unknown>(path: string, opts?: FetchOptions, immediate?: boolean, body?: B) => UseApiResponse<T, B>;
  patch: <T, B = unknown>(path: string, opts?: FetchOptions, immediate?: boolean, body?: B) => UseApiResponse<T, B>;
}

const createRequest = <ResponseT = unknown, PayloadT = unknown>(
  method: HttpMethod,
  url: string,
  opts?: FetchOptions,
  immediate: boolean = true,
  body?: PayloadT,
): QueryResult<ResponseT, PayloadT> => {
  const response = new QueryResult<ResponseT, PayloadT>();
  const { apiBase } = useRuntimeConfig().public;

  const run = async (requestBody?: PayloadT): Promise<void> => {
    response.loading.value = true;
    response.error.value = null;

    try {
      const res = await $fetch<ResponseT>(url, {
        baseURL: apiBase,
        ...opts,
        method,
        body: requestBody ?? undefined,
      });
      response.data.value = res;
    } catch (e) {
      const fetchError = e as FetchError;
      const errBody = fetchError?.response?._data as ApiError | undefined;
      response.error.value = errBody ?? {
        message: fetchError.message || 'backend.errors.unknown',
        success: false,
      };
    } finally {
      response.loading.value = false;
    }
  };
  response.run = run;

  if (immediate) run(body);

  return response;
};

export const useApi = (): UseApi => {
  const get = <T>(path: string, opts?: FetchOptions, immediate: boolean = true) =>
    createRequest<T>('GET', path, opts, immediate);
  const del = <T>(path: string, opts?: FetchOptions, immediate: boolean = true) =>
    createRequest<T>('DELETE', path, opts, immediate);
  const post = <T, B = unknown>(path: string, opts?: FetchOptions, immediate: boolean = true, body?: B) =>
    createRequest<T, B>('POST', path, opts, immediate, body);
  const put = <T, B = unknown>(path: string, opts?: FetchOptions, immediate: boolean = true, body?: B) =>
    createRequest<T, B>('PUT', path, opts, immediate, body);
  const patch = <T, B = unknown>(path: string, opts?: FetchOptions, immediate: boolean = true, body?: B) =>
    createRequest<T, B>('PATCH', path, opts, immediate, body);

  return { get, post, put, patch, del };
};
