<header>reducers</header>

<body>
    <div id="testDiv">
        Test
    </div>

    <script>
        const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))  // для наглядности отладки
        function jwtDecode(token) {                         // расщифровки токена авторизации
            if (!token || typeof token != "string")
                return undefined;
            let tokenArr = token.split(".");
            if (tokenArr.length != 3)
                return undefined;
            try {
                let tokenJsonStr = atob(tokenArr[1]);
                let tokenJson = JSON.parse(tokenJsonStr);
                return tokenJson;
            }
            catch {
                return undefined;
            }
        }
        function gql(url, query, vars) {             // формирование запроса GQl
            let fetchSettings =
            {
                method: "POST",
                headers:
                {
                    "Content-Type": "application/json",
                    "Accept": "application/json"
                },
                body: JSON.stringify(
                    {
                        query: query,
                        variables: vars

                    })
            };
            return fetch(url, fetchSettings).then(res => res.json());
        }
        function signIn(login, password, url) {             // авторизация через GQl
            const loginQuery =
                `query login($login:String, $password:String){
                login(login:$login, password:$password)
            }`;
            return gql(
                url,
                loginQuery,
                { login: login, password: password });
        }



        function treatAuth(action, state) {                         // обработка успешной авторизации  и смена состояния
            let payload = action.payload;
            return setAuthState({ token: payload.data.login, payload: jwtDecode(payload.data.login) })
        }
        function setAuthState(authPayload, state) {                   //смена состояния авторизации
            let newState = { ...state };
            newState.auth = { ...state.auth };
            newState.auth.payload = authPayload;
            return newState;
        }

        function treatBuy(action, state) {                         // обработка покупки 

        }

        function promiseReducer(state = {}, action) {                   // диспетчер обработки
            if (action) {
                if (action.type === 'PROMISE') {
                    let newState = treatPayload(action, "buy", state, treatBuy);
                    newState = treatPayload(action, "auth", newState, treatAuth);
                    return newState;
                }
            }
            return state;
        }


        function treatPayload(action, name, state, onPayloadFunc) { //вызов функций обработки состояния по статусу с сбросом текущего состояния и обработки payload
            let resultState = state;
            let actionName = action.name;
            if (actionName == name) {
                resultState = changeStatus(name, state, action, resetStateFunc);
                if (state !== resultState && action.status == "FULLFILLED")
                    resultState = onPayloadFunc(action, resultState)
            }
            return resultState;
        }
        function changeStatus(name, state, action) {                            //обработка состояния по статусу
            let result = { ...state };
            result[name] = { status: status, payload: undefined, error: actionData.error };
            return result;
        }

        function createStore(reducer) {
            let state = reducer(undefined, {})              //стартовая инициализация состояния, запуск редьюсера со state === undefined
            let cbs = []                                      //массив подписчиков

            const getState = () => state                   //функция, возвращающая переменную из замыкания
            const subscribe = cb => (cbs.push(cb),            //запоминаем подписчиков в массиве
                () => cbs = cbs.filter(c => c !== cb))      //возвращаем функцию unsubscribe, которая удаляет подписчика из списка

            function dispatch(action) {
                if (typeof action === 'function') {         //если action - не объект, а функция
                    return action(dispatch, getState)       //запускаем эту функцию и даем ей dispatch и getState для работы
                }
                const newState = reducer(state, action)      //пробуем запустить редьюсер
                if (newState !== state) {                    //проверяем, смог ли редьюсер обработать action
                    state = newState                        //если смог, то обновляем state 
                    for (let cb of cbs) cb()                //и запускаем подписчиков
                }
            }

            return {
                getState,                                   //добавление функции getState в результирующий объект
                dispatch,
                subscribe                                   //добавление subscribe в объект
            }
        }

        function actionPromise({ name, promise }) {
            return async function Exec(dispatch) {
                dispatch(actionPending(name))               //сигнализируем redux, что промис начался
                try {
                    const payload = await promise           //ожидаем промиса;
                    dispatch(actionFulfilled(name, payload)); //сигнализируем redux, что промис успешно выполнен
                    return payload                              //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
                }
                catch (error) {
                    dispatch(actionRejected(name, error))           //в случае ошибки - сигнализируем redux, что промис несложился
                }
            };
        }
        const actionPending = (name) => ({ type: 'PROMISE', name: name, status: 'PENDING' });
        const actionFulfilled = (name, payload) => ({ type: 'PROMISE', name: name, payload: payload, status: 'FULFILLED' });
        const actionRejected = (name, error) => ({ type: 'PROMISE', name: name, error: error, status: 'REJECTED' });

        const store = createStore(promiseReducer)

        store.subscribe(() => {
            let state = store.getState();
            if (state.buy) {
                if (state.buy.status == "PENDING")
                    testDiv.style.color = "orange";
                else if (state.buy.status == "FULLFILLED")
                    testDiv.style.color = "green";
                else if (state.buy.status == "REJECTED") {
                    testDiv.style.color = "red";
                    testDiv.innerText = state.buy.errorMessage;
                }
            }
        });

        let execFunc = actionPromise({ name: "auth", promise: signIn("test457", "123123", "http://shop-roles.node.ed.asmer.org.ua/graphql") });
        store.dispatch(execFunc);

        /*
                const actionPending = () => ({ type: 'PROMISE', status: 'PENDING' })
                const actionFulfilled = payload => ({ type: 'PROMISE', status: 'FULFILLED', payload })
                const actionRejected = error => ({ type: 'PROMISE', status: 'REJECTED', error })
        
        
                store.subscribe(() => console.log(store.getState()))
        
                store.dispatch({ type: 'COUNTER_INC' })
                store.dispatch({ type: 'BOOLEAN_SET' })
                store.dispatch({ type: 'COUNTER_INC' })
                store.dispatch({ type: 'BOOLEAN_TOGGLE' })
                store.dispatch({ type: 'COUNTER_DEC' })
                store.dispatch({ type: 'ДИЧЬ' }) //не вызывает подписчика
        */

    </script>
</body>

function combineReducers(reducers) {
function totalReducer(totalState = {}, action) {
const newTotalState = {} //объект, который будет хранить только новые состояния дочерних редьюсеров

//цикл + квадратные скобочки позволяют написать код, который будет работать с любыми количеством дочерных редьюсеров
for (const [reducerName, childReducer] of Object.entries(reducers)) {
const newState = childReducer(totalState[reducerName], action) //запуск дочернего редьюсера
if (newState !== totalState[reducerName]) { //если он отреагировал на action
newTotalState[reducerName] = newState //добавляем его в newTotalState
}
}

//Универсальная проверка на то, что хотя бы один дочерний редьюсер создал новый стейт:
if (Object.values(newTotalState).length) {
return { ...totalState, ...newTotalState } //создаем новый общий стейт, накладывая новый стейты дочерних редьюсеров на
старые
}

return totalState //если экшен не был понят ни одним из дочерних редьюсеров, возвращаем общий стейт как был.
}

return totalReducer
}

//////


/////