import { useState, useEffect, useCallback, useRef, useMemo } from 'react';

export type QueryStatus = 'idle' | 'error' | 'success' | 'loading';

export interface useRequestOptions<T, Data> {
    notifyErrorEnabled?: boolean;
    enabled?: boolean;
    dependencies?: any[];
    onSuccess?: (data: T) => void;
    selectData?: (data: T) => Data;
    placeholderData?: Data;
}

/**
 * This hook allows you to work with requests efficiently
 * @param {promise} queryFn - initial value for useState
 */
export default function useRequest<T, Data = T>(queryFn: () => Promise<T>, _options?: useRequestOptions<T, Data>) {
    const [status, setStatus] = useState<QueryStatus>('idle');
    const [data, setData] = useState<Data | null>(_options?.placeholderData ?? null);
    const [error, setError] = useState<unknown>();
    const [loading, setLoading] = useState(_options?.enabled ?? true);

    const notifyError = (err: unknown) => {};

    const options: Required<useRequestOptions<T, Data>> = useMemo(() => {
        return {
            enabled: _options?.enabled ?? true,
            notifyErrorEnabled: _options?.notifyErrorEnabled ?? true,
            dependencies: _options?.dependencies || [],
            onSuccess: _options?.onSuccess ?? (() => {}),
            // @ts-expect-error
            selectData: _options?.selectData ?? (data => data as Data),
            placeholderData: data!,
        };
    }, [_options]);

    const queryFnRef = useRef({
        query: queryFn,
        onSuccess: options.onSuccess,
        selectData: options.selectData,
    });

    queryFnRef.current.query = queryFn;
    queryFnRef.current.onSuccess = options.onSuccess;
    queryFnRef.current.selectData = options.selectData;

    const handleError = (error: unknown) => {
        setError(error);
        setStatus('error');
        setLoading(false);
        if (options.notifyErrorEnabled) {
            notifyError(error);
        }
    };

    const runQuery = useCallback(() => {
        const handleSuccess = (res: T) => {
            queryFnRef.current.onSuccess(res);
            setData(queryFnRef.current.selectData(res));
            setStatus('success');
            setLoading(false);
        };

        setStatus('loading');
        setLoading(true);
        queryFnRef.current.query().then(handleSuccess).catch(handleError);
    }, []);

    useEffect(() => {
        if (options.enabled) {
            runQuery();
        }
    }, [...options.dependencies]);

    return { data, loading, error, status, refetch: runQuery };
}
