import { useEffect, useState } from 'react'

import { SetState } from '../utils/types'
import useSend from './useSend'

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

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

type UseFetchErrored = {
    error: string,
    data: undefined
} & UseFetchShared

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

const fallbackError = <T>(res: UseFetchSucced<T> | UseFetchErrored) => (
    gotError(res) ? res.error : res.data
)

type UseFetchReturn<T> = ({
    error: null,
    data: T
} | {
    error: string,
    data: undefined
}) & {
    loading: boolean,
    setData: SetState<T | undefined>
    abort?: (() => void)
}

function useFetch<R, T>(
    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 { doSend, loading, error } = useSend(url, method, needAuth, guardResponse, processResponse, params)

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

    return {
        ...(
            error === null ? ({
                data: data!, error: null
            }) : ({ data: undefined, error })
        ),
        loading,
        setData
    }
}

export default useFetch

export { gotError, fallbackError }
