import { useCallback, useEffect, useState } from 'react'

import useSend from './useSend'

type UseFetchShared = {
    abort?: () => void,
    refetch: () => void,
}

type UseFetchSucced<T> = {
    data: T,
    loading: false,
    error: null,
} & UseFetchShared

type UseFetchLoading = {
    data: undefined,
    loading: true,
    error: null,
} & UseFetchShared

type UseFetchErrored = {
    data: null,
    loading: false,
    error: string,
} & UseFetchShared

type UseFetchReturn<T> = UseFetchSucced<T> | UseFetchLoading | UseFetchErrored

const gotError = <T>(res: UseFetchReturn<T>): res is UseFetchErrored => (
    typeof res.error === 'string'
)

function fallbackError<T>(res: UseFetchSucced<T> | UseFetchErrored): T | string
function fallbackError<T>(res: UseFetchReturn<T>): T | string | null | undefined {
    return (
        gotError(res) ? res.error : res.data
    )
}

const gotResponse = <T>(res: UseFetchReturn<T>): res is UseFetchSucced<T> | UseFetchErrored => (
    !res.loading
)

function useFetch<R, T extends NonNullable<unknown>>(
    url: string,
    method: RequestInit['method'],
    needAuth: boolean,
    guardResponse: (data: unknown) => data is R,
    processResponse: (data: R) => T,
    initialData?: T,
    params?: Omit<RequestInit, 'method'>,
): UseFetchReturn<T> {
    const [data, setData] = useState(initialData)

    const [fetchLoading, setFetchLoading] = useState(true)

    const { doSend, error } = useSend(
        url,
        method,
        needAuth,
        guardResponse,
        processResponse,
        params,
    )

    const refetch = useCallback(() => {
        setFetchLoading(true)
        doSend().then(
            data => {
                if (data !== undefined && data !== null) {
                    setData(data)
                }

                if (data !== undefined) {
                    setFetchLoading(false)
                }
            }
        ).catch( // must never occur
            err => import.meta.env.DEV && console.error('Failed to do fetch request', err)
        )
    }, [doSend])

    useEffect(refetch, [refetch])

    if (fetchLoading === true) {
        return {
            data: undefined,
            loading: fetchLoading,
            error: null,
            refetch,
        }
    }

    if (error !== null) {
        return {
            data: null,
            loading: fetchLoading,
            error,
            refetch,
        }
    }

    return {
        data: data!,
        loading: fetchLoading,
        error,
        refetch,
    }
}

export type { UseFetchReturn }

export default useFetch

export { gotError, gotResponse, fallbackError }
