import { useCallback, useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'

import { getToken } from '../utils/auth'
import { AbortError, handleHTTPErrors, isAborted } from '../utils'

function useSend<R, T extends NonNullable<unknown>>(
    url: string,
    method: RequestInit['method'],
    needAuth: boolean,
    guardResponse: (data: unknown) => data is R,
    processResponse: (data: R) => T,
    defaultParams?: Omit<RequestInit, 'method'>,
) {
    const [loading, setLoading] = useState(false)
    const [error, setError] = useState<string | null>(null)

    const navigate = useNavigate()

    const abortControllerRef = useRef<AbortController>()

    useEffect(() => () => {
        const reason = new AbortError('unmount')
        abortControllerRef.current?.abort(reason)
    }, [])

    /** Don't use in useEffect. If you need request result, go with useFetch instead */
    const doSend = useCallback(async (urlProps?: Record<string, string>, params?: Omit<RequestInit, 'method'>) => {
        setLoading(true)
        setError(null)

        if (abortControllerRef.current) {
            const reason = new AbortError('resent')
            abortControllerRef.current.abort(reason)
        }

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

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

        if (needAuth) {
            const token = getToken()

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

                return null
            }

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

        try {
            const res = await fetch(url + new URLSearchParams(urlProps).toString(), {
                method,
                ...defaultParams,
                ...params,
                headers,
                signal: abortControllerRef.current.signal,
            })

            handleHTTPErrors(res)

            const data: unknown = await res.json()

            if (!guardResponse(data)) {
                throw new Error('Malformed server response')
            }

            setLoading(false)

            return processResponse(data)

        } catch (err) {
            if (err instanceof Error) {
                if (isAborted<T>(err)) {
                    if (err.message !== 'resent') {
                        setLoading(false)
                    }

                    if (err.fallback !== undefined) {
                        return err.fallback
                    }

                    return undefined
                } else {
                    if (err instanceof TypeError) {
                        setError('Ошибка сети')
                    } else {
                        setError(err.message)
                    }

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

                    setLoading(false)
                }
            }

            return null
        }
    }, [defaultParams, needAuth, navigate, url, method, guardResponse, processResponse])

    return {
        doSend, loading, error,
        abort: abortControllerRef.current?.abort.bind(abortControllerRef.current),
    }
}

export default useSend
