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

import { handleHTTPErrors, isAborted } from '../../utils'
import { getToken } from '../../utils/auth'
import { useNavigate } from 'react-router-dom'
import { SetState } from '../../utils/types'

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)
}

const useFetch = <R, T>(
    url: string,
    method: RequestInit['method'],
    needAuth: boolean,
    guardResponse: (data: unknown) => data is R,
    processData: (data: R) => T,
    initialData?: T,
    params?: RequestInit
): UseFetchReturn<T> => {
    const [data, setData] = useState(initialData)
    const [loading, setLoading] = useState(true)
    const [error, setError] = useState<string | null>(null)

    const navigate = useNavigate()

    const abortControllerRef = useRef<AbortController>()

    useEffect(() => {
        if (abortControllerRef.current) {
            abortControllerRef.current.abort()
        }

        const abortController = new AbortController()
        abortControllerRef.current = abortController

        const headers = new Headers({
            ...params?.headers
        })

        if (needAuth) {
            const token = getToken()

            if (token === null) {
                return navigate('/login')
            }

            headers.append('Auth', `Bearer ${token}`)
        }

        fetch(url, {
            method,
            ...params,
            headers,
            signal: abortControllerRef.current.signal,
        })
            .then(res => {
                handleHTTPErrors(res)

                return res.json()
            })
            .then(data => {
                if (!guardResponse(data)) {
                    throw new Error('Malformed server response')
                }

                setData(processData(data))
                setLoading(false)
            })
            .catch(err => {
                if (err instanceof Error && !isAborted(err)) {
                    setError('Ошибка сети')

                    if (import.meta.env.DEV) {
                        console.log(url, params, err)
                    }
                }

                setLoading(false)
            })

        return () => abortControllerRef.current?.abort()
    }, [url, method, needAuth, params, guardResponse, processData, navigate])

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

export default useFetch

export { gotError, fallbackError }
