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, которая удаляет подписчика из списка

    const 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 promiseReducer(state={}, {type, status, payload, error, name}){
    if (type === 'PROMISE'){
        return {
            ...state,
            [name]:{status, payload, error}
        }
    }
    return state;
}
const actionPending  = name => ({type: 'PROMISE', status: 'PENDING', name})
const actionResolved = (name, payload) => ({type: 'PROMISE', status: 'RESOLVED', name, payload})
const actionRejected = (name, error) => ({type: 'PROMISE', status: 'REJECTED', name,  error})
const actionPromise = (name, promise) =>
    async dispatch => {
        dispatch(actionPending(name))
        try {
            let data = await promise
            dispatch(actionResolved(name, data))
            return data
        }
        catch(error){
            dispatch(actionRejected(name, error))
        }
    }

const store = createStore(promiseReducer)
store.subscribe(() => console.log(store.getState()))

const getGQL = url =>
    async (query, variables={}) => {
        let obj = await fetch(url, {
            method: 'POST',
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify({ query, variables })
        })
        let a = await obj.json()
        if (!a.data && a.errors)
            throw new Error(JSON.stringify(a.errors))
        return a.data[Object.keys(a.data)[0]]
    }

const backURL = 'http://shop-roles.asmer.fs.a-level.com.ua'
const gql = getGQL(backURL + '/graphql');

const actionRootCats = () =>
    actionPromise('rootCats', gql(`query {
        CategoryFind(query: "[{\\"parent\\":null}]"){
            _id name
        }
    }`))

const actionCatById = (_id) =>  //добавить подкатегории
    actionPromise('catById', gql(`query catById($q: String){
        CategoryFindOne(query: $q){
            _id name,
            goods{
              _id name price images {
                 url
              }
            },
            subCategories{
              _id name, subCategories{
                name
              }
            }
          }
    }`, {q: JSON.stringify([{_id}])}))

const actionGoodById = (_id) =>
    actionPromise('goodById', gql(`query goodById($q: String){
    GoodFindOne(query: $q){
            _id name description price images{
              url
            }
        }
    }`, {q: JSON.stringify([{_id}])}))


store.dispatch(actionRootCats())
store.subscribe(() => {
    const {rootCats} = store.getState()
    if (rootCats?.payload){
        aside.innerHTML = ''
        for (const {_id, name} of rootCats?.payload){
            const link      = document.createElement('a')
            link.href       = `#/category/${_id}`
            link.innerText  = name
            aside.append(link)
        }
    }
})

window.onhashchange = () => {
    const [,route, _id] = location.hash.split('/')
    const routes = {
        category(){
            store.dispatch(actionCatById(_id))
        },
        good(){
            store.dispatch(actionGoodById(_id))
        },
    }
    if (route in routes)
        routes[route]()
}
window.onhashchange()

store.subscribe(() => {
    const {catById} = store.getState()
    const [,route, _id] = location.hash.split('/')
    if (catById?.payload && route === 'category'){
        const {name} = catById.payload
        main.innerHTML = `<h1>${name}</h1>`
        if(catById.payload?.subCategories){
            for (const {_id , name} of catById.payload?.subCategories) {
                const link      = document.createElement('a')
                link.href       = `#/category/${_id}`
                link.textContent = name
                main.append(link)
            }
        }
        for (const {_id, name, price, images} of catById.payload.goods){
            const card      = document.createElement('div')
            card.className = 'item--main'
            card.innerHTML = `<h2>${name}</h2>
                              <img src="${backURL}/${images[0].url}" />
                              <strong>Цена: ${price} USD</strong><br>
                              <a href="#/good/${_id}">Ссылка на страницу товара: #/good/${_id}</a>`
            main.append(card)
        }
    }
})
store.subscribe(() => {
    const {goodById} = store.getState()
    const [,route, _id] = location.hash.split('/')
    if (goodById?.payload && route === 'good' && window.location.href.includes(`#/good/${_id}`)){
        const {_id ,name, images, price, description } = goodById.payload
        main.innerHTML = ''
        let div = document.createElement('div')
        div.className = 'item--main'
        div.innerHTML = `<h1>${name}</h1>
                         <img src="${backURL}/${images[0].url}" />
                         <p><strong>Описание:</strong> <br> ${description}</p>
                         <p><strong>ID:</strong> ${_id}</p>
                         <strong>Цена: ${price} USD</strong>`
        main.append(div);
    }
})