import _ from "lodash";
import twuiSerializeQuery from "../serialize-query";

export const FetchAPIMethods = [
    "POST",
    "GET",
    "DELETE",
    "PUT",
    "PATCH",
    "post",
    "get",
    "delete",
    "put",
    "patch",
] as const;

type FetchApiOptions<T extends { [k: string]: any } = { [k: string]: any }> = {
    method: (typeof FetchAPIMethods)[number];
    body?: T | string;
    headers?: FetchHeader;
    query?: T;
    csrfValue?: string;
    csrfKey?: string;
    fetchOptions?: RequestInit;
};

type FetchHeader = HeadersInit & {
    [key: string]: string | null;
};

export type FetchApiReturn = {
    success: boolean;
    payload: any;
    msg?: string;
    [key: string]: any;
};

/**
 * # Fetch API
 */
export default async function fetchApi<
    T extends { [k: string]: any } = { [k: string]: any },
    R extends any = any,
>(url: string, options?: FetchApiOptions<T>): Promise<R> {
    let data;

    let finalHeaders = {
        "Content-Type": "application/json",
    } as FetchHeader;

    if (options?.csrfKey && options.csrfValue) {
        finalHeaders[options.csrfKey] = options.csrfValue;
    }

    let finalURL = url;

    if (options?.query) {
        finalURL += twuiSerializeQuery(options.query);
    }

    if (typeof options === "string") {
        try {
            let fetchData;

            switch (options) {
                case "post":
                    fetchData = await fetch(finalURL, {
                        method: options,
                        headers: finalHeaders,
                    } as RequestInit);
                    data = await fetchData.json();
                    break;

                default:
                    fetchData = await fetch(finalURL);
                    data = await fetchData.json();
                    break;
            }
        } catch (error: any) {
            console.log("FetchAPI error #1:", error.message);
            data = { success: false, msg: error.message };
        }
    } else if (typeof options === "object") {
        try {
            let fetchData;

            if (options.body && typeof options.body === "object") {
                let oldOptionsBody = _.cloneDeep(options.body);
                options.body = JSON.stringify(oldOptionsBody);
            }

            if (options.headers) {
                options.headers = _.merge(options.headers, finalHeaders);

                const finalOptions: any = { ...options };

                fetchData = await fetch(finalURL, finalOptions);
            } else {
                const finalOptions = {
                    ...options,
                    headers: finalHeaders,
                } as RequestInit;

                fetchData = await fetch(finalURL, finalOptions);
            }

            data = await fetchData.json();
        } catch (error: any) {
            console.log("FetchAPI error #2:", error.message);
            data = { success: false, msg: error.message };
        }
    } else {
        try {
            let fetchData = await fetch(finalURL);
            data = await fetchData.json();
        } catch (error: any) {
            console.log("FetchAPI error #3:", error.message);
            data = { success: false, msg: error.message };
        }
    }

    return data;
}
