import { useState, useCallback } from 'react'

/**
 * Хук для состояния загрузки и ошибки.
 * Возвращает isFetching и switchState для переключения состояния
 * switchState слушает Promise и завершает загрузку при завершении Promise
 */
const useRequestState = () => {
  const [isFetching, setFetchingState] = useState(false)
  const [error, setError] = useState<Error | null>(null)

  const switchState = useCallback(
    (promise: Promise<unknown>) => {
      setFetchingState(true)
      setError(null)
      return promise
        .catch((err) => {
          setError(err)
          return Promise.reject(err)
        })
        .finally(() => setFetchingState(false))
    },
    [],
  )

  return {
    error,
    isFetching,
    switchState,
  }
}

/**
 * Хук для состояния загрузки.
 * Получает asyncFunction функция возвращающая Promise
 * и возвращает функцию обертку над asyncFunction
 *
 * Загрузка начинается при вызове обертки, заканчивается при завершении Promise
 *
 * ```ts
 * const [countries, setCountries] = useState([])
 *
 * const { request: fetchCountries, isFetching } = useRequest(getCountries)
 *
 * useEffect(() => {
 *  fetchCountries().then(setCountries)
 * }, [])
 *
 * return (
 *   <div>
 *      {isFetching && 'Loading...'}
 *      ...
 *   </div>
 * )
 * ```
 */
export const useRequest = <
  F extends (...args: any) => Promise<unknown>,
>(asyncFunction: F) => {
  const {
    error,
    isFetching,
    switchState,
  } = useRequestState()

  const request = useCallback(
    (...args: Parameters<F>) => (
      switchState(asyncFunction(...args)) as ReturnType<F>
    ),
    [switchState, asyncFunction],
  )

  return {
    error,
    isFetching,
    request,
  }
}
