type HttpMethod =
  | "GET"
  | "POST"
  | "PUT"
  | "DELETE"
  | "PATCH"
  | "HEAD"
  | "OPTIONS";

export function yiiAjaxHeader(): HeadersInit {
  return { "X-Requested-With": "XMLHttpRequest" };
}
/**
 * Perform a standard fetch operation with a _JSON_ object expected as a response.
 *
 * ## Example
 *
 * ```ts
 * // Set an interface for the expected response
 * interface RedirectEstate {
 *   redirect: string;
 * }
 *
 * // Handle response in 1 line
 * goFetch(url, postData, "POST")
 *     .then((data) => (window.location.href = decodeURIComponent((data as RedirectEstate).redirect)))
 *     .catch((e) => console.error(e));
 *
 * // OR Handle reponse within function body
 * goFetch(url, postData, "POST")
 *     .then((response) => {
 *         const data = response as RedirectEstate;
 *         window.location.href = decodeURIComponent(data.redirect);
 *     }
 *     .catch((e) => console.error(e));
 * ```
 */

export async function goFetch<TResponse = object>(
  url: string,
  body: object = {},
  method: HttpMethod = "GET",
  errorMessage: string | null = null,
  throwFullResponse: boolean = false,
  customHeaders: HeadersInit = {},
): Promise<TResponse> {
  const postBody = JSON.stringify(body);
  return fetch(url, {
    method: method,
    headers: {
      "Content-Type": "application/json",
      "x-from-inertia": "1",
      ...yiiAjaxHeader(),
      ...customHeaders,
    },
    body: method !== "GET" && method !== "HEAD" ? postBody : undefined,
  }).then(async (response) => {
    if (!response.ok) {
      console.warn(response);
      // console.warn(await response.text());
      console.table({
        url: response.url,
        status: response.status,
        message: response.statusText,
      });
      // if the user is unauthorised, reload the page to force a permission check
      if (response.status == 401) {
        window.location.reload();
      }
      if (errorMessage !== null) {
        throw errorMessage;
      } else {
        if (throwFullResponse) {
          // console.log("Throwing full response...");
          throw await response.json();
        }
        throw `${response.statusText} with error code ${response.status}`;
      }
    }
    return response.json() as TResponse;
  });
}

/**
 * Shortcut to send a GET fetch request.
 * @param url Url enpoint to send the HTTP request to.
 * @param customErrorMessage A custom error message to throw as desired.
 * @returns `Promise<object>` Promise of the return object.
 */
export async function goGet<TResponse = object>(
  url: string,
  customErrorMessage: string | null = null,
  customHeaders: HeadersInit = {},
): Promise<TResponse> {
  return goFetch(url, {}, "GET", customErrorMessage, false, customHeaders);
}

/**
 * Shortcut to send a POST fetch request.
 * @param url Url enpoint to send the HTTP request to.
 * @param body The JSON object to send as params to the endpoint.
 * @param customErrorMessage A custom error message to throw as desired.
 * @returns `Promise<object>` Promise of the return object.
 */
export async function goPost<TResponse = object>(
  url: string,
  body: object = {},
  customErrorMessage: string | null = null,
  throwFullResponse: boolean = false,
  customHeaders: HeadersInit = {},
): Promise<TResponse> {
  return goFetch(
    url,
    body,
    "POST",
    customErrorMessage,
    throwFullResponse,
    customHeaders,
  );
}

/**
 * Shortcut to send a PUT fetch request.
 * @param url Url enpoint to send the HTTP request to.
 * @param body The JSON object to send as params to the endpoint.
 * @param customErrorMessage A custom error message to throw as desired.
 * @returns `Promise<object>` Promise of the return object.
 */
export async function goPut<TResponse = object>(
  url: string,
  body: object = {},
  customErrorMessage: string | null = null,
  customHeaders: HeadersInit = {},
): Promise<TResponse> {
  return goFetch(url, body, "PUT", customErrorMessage, false, customHeaders);
}

/**
 * Shortcut to send a DELETE fetch request.
 * @param url Url enpoint to send the HTTP request to.
 * @param body The JSON object to send as params to the endpoint.
 * @param customErrorMessage A custom error message to throw as desired.
 * @returns `Promise<object>` Promise of the return object.
 */
export async function goDelete<TResponse = object>(
  url: string,
  body: object = {},
  customErrorMessage: string | null = null,
  customHeaders: HeadersInit = {},
): Promise<TResponse> {
  return goFetch(url, body, "DELETE", customErrorMessage, false, customHeaders);
}

/**
 * Perform a standard fetch operation with _plain text_ expected as a response.
 */

export async function goFetchText(
  url: string,
  body: object = {},
  method: HttpMethod = "GET",
  errorMessage: string | null = null,
  customHeaders: HeadersInit = {},
): Promise<string> {
  const postBody = JSON.stringify(body);
  return fetch(url, {
    method: method,
    headers: {
      "Content-Type": "application/json",
      "x-from-inertia": "1",
      "X-Requested-With": "XMLHttpRequest",
      ...yiiAjaxHeader(),
      ...customHeaders,
    },
    body: method !== "GET" && method !== "HEAD" ? postBody : undefined,
  }).then(async (response) => {
    if (!response.ok) {
      console.warn(response);
      // console.warn(await response.text());
      console.table({
        url: response.url,
        status: response.status,
        message: response.statusText,
      });
      // if the user is unauthorised, reload the page to force a permission check
      if (response.status == 401) {
        window.location.reload();
      }
      if (errorMessage !== null) {
        throw errorMessage;
      } else {
        throw `${response.statusText} with error code ${response.status}`;
      }
    }
    return response.text();
  });
}

/**
 * Perform a standard fetch operation with _binary blob_ expected as a response. An image for example.
 */

export async function goFetchBlob(
  url: string,
  body: object = {},
  method: HttpMethod = "GET",
  errorMessage: string | null = null,
  customHeaders: HeadersInit = {},
): Promise<Blob> {
  const postBody = JSON.stringify(body);
  return fetch(url, {
    method: method,
    headers: {
      "Content-Type": "application/json",
      "x-from-inertia": "1",
      ...yiiAjaxHeader(),
      ...customHeaders,
    },
    body: method !== "GET" && method !== "HEAD" ? postBody : undefined,
  }).then(async (response) => {
    if (!response.ok) {
      console.warn(response);
      // console.warn(await response.text());
      console.table({
        url: response.url,
        status: response.status,
        message: response.statusText,
      });
      // if the user is unauthorised, reload the page to force a permission check
      if (response.status == 401) {
        window.location.reload();
      }
      if (errorMessage !== null) {
        throw errorMessage;
      } else {
        throw `${response.statusText} with error code ${response.status}`;
      }
    }
    return response.blob();
  });
}

type FileInput = File | File[];

/**
 * Performs a post to upload a file or array of files to the service
 * @param url Url endpoint to send the HTTP request to.
 * @param formData The form data, other than the files to be sent to the server
 * @param files The file Objects to be sent to the server
 * @param fileProperty The property in the formData to attach the files to
 * @param customErrorMessage A custom error message to throw as desired.
 * @returns `Promise<object>` Promise of the return object.
 */
export async function goPostFile<TResponse = object>(
  url: string,
  formData: FormData,
  files: FileInput | undefined,
  fileProperty: string = "file",
  errorMessage: string | null = null,
  customHeaders: HeadersInit = {},
): Promise<TResponse> {
  // Check there are files to send
  if (files !== undefined) {
    // Normalize to array
    const fileArray: File[] = Array.isArray(files) ? files : [files];
  
    fileArray.forEach((file) => {
      formData.append(
        fileArray.length > 1 ? `${fileProperty}[]` : `${fileProperty}`,
        file,
      );
    });
  }

  return fetch(url, {
    method: "POST",
    headers: {
      // DO NOT set Content-Type here
      "x-from-inertia": "1",
      ...yiiAjaxHeader(),
      ...customHeaders,
    },
    body: formData,
  }).then(async (response) => {
    if (!response.ok) {
      console.warn(response);
      console.table({
        url: response.url,
        status: response.status,
        message: response.statusText,
      });

      if (response.status === 401) {
        window.location.reload();
      }

      if (errorMessage !== null) {
        throw errorMessage;
      } else {
        try {
          throw await response.json();
        } catch {
          throw `${response.statusText} with error code ${response.status}`;
        }
      }
    }

    return response.json() as TResponse;
  });
}
