import {useState, useEffect} from 'react';

/**
 * Hook permettant d’obtenir des résultats asynchrones.
 *
 * @param initial Valeur initiale lorsque la promesse n’a pas répondu.
 * @param func Fonction renvoyant une promesse.
 * @param ...args Arguments à passer à `func` qui sont susceptibles de changer.
 * @return Résultats de la promesse.
 */
export const useAsync = (initial, func, ...args) =>
{
    const [results, setResults] = useState(initial);

    useEffect(() =>
    {
        const fetcher = async () =>
        {
            setResults(await func(...args));
        };

        fetcher();
    }, args);

    return results;
};

/**
 * Crée un état composé d’une liste et d’un élément ayant le focus dans cette
 * liste. À la modification de la liste ou de l’indice de l’élément ayant le
 * focus, la contrainte suivante est imposée :
 *
 * — si la liste n’est pas vide, focus ∈ [0, taille de la liste].
 * — sinon, focus = 0.
 */
export const useFocusableList = () =>
{
    const [list, setList] = useState([]);
    const [focus, setFocus] = useState(0);

    return {
        /**
         * Contenu courant de la liste.
         */
        list,

        /**
         * Indice de l’élément ayant présentement le focus.
         */
        focus,

        /**
         * Modifie la liste et s’assure que l’élément ayant le focus est
         * toujours dans sa plage de valeurs possibles.
         *
         * @param nextList Nouvelle liste.
         */
        setList(nextList)
        {
            setList(nextList);
            setFocus(
                nextList.length === 0
                    ? 0
                    : Math.min(nextList.length - 1, focus)
            );
        },


        /**
         * Modifie l’élément ayant le focus en imposant les contraintes
         * d’intégrité.
         *
         * @param nextFocus Indice du nouvel élément ayant le focus.
         */
        setFocus(nextFocus)
        {
            const itemCount = list.length;

            if (itemCount === 0)
            {
                // Sur la liste vide, on ne peut pas changer le focus
                return;
            }

            // Ramène une valeur négative dans les positifs
            while (nextFocus < 0) { nextFocus += itemCount; }

            // Ramène une valeur trop grande à l’intérieur de la liste
            nextFocus = nextFocus % itemCount;

            setFocus(nextFocus);
        },
    };
};

/**
 * Crée une liste de termes de recherche.
 */
export const useQuery = () =>
{
    const [query, setQuery] = useState([]);

    return {
        /**
         * Liste des termes de la requête.
         */
        query,

        /**
         * Ajoute un terme à la requête.
         *
         * @param term Terme à ajouter.
         */
        addTerm(term)
        {
            if (query.some(({id}) => id === term.id))
            {
                // N’ajoute pas les termes déjà sélectionnés
                return;
            }

            setQuery(query.concat([term]));
        },

        /**
         * Supprime un terme de la requête.
         *
         * @param index Indice du terme à supprimer dans la liste.
         */
        removeTerm(index)
        {
            setQuery([
                ...query.slice(0, index),
                ...query.slice(index + 1),
            ]);
        },
    };
};
