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, name, status, payload, error}){
    if (type === 'PROMISE'){
        return {
            ...state,
            [name]:{status, payload, error}
        }
    }
    return state
}

const jwtDecode = token => {
    try {
        let [,payload] = token.split(".");
        let payloadParsed= JSON.parse(atob(payload));
        return payloadParsed;
    }
    catch(e){
        console.log(e);
    }
}

function authReducer(state, {type, token}) {
    if (state === undefined) {
        if(localStorage.authToken) {
            type = "AUTH_LOGIN";
            token = localStorage.authToken;
        } else {
            return {};
        }
    }
    if (type === 'AUTH_LOGIN') {
        let payload = jwtDecode(token);
        if (payload) {
            localStorage.authToken = token;
            return {token, payload};
        }
    }
    if (type === 'AUTH_LOGOUT') {
        localStorage.authToken="";
        return {};
    }
    return state;
}

function combineReducers(reducers) {
    return (state = {}, action) => {
        const newState = {}
        for (const [reducerName, reducer] of Object.entries(reducers)) {
            let newSubState = reducer(state[reducerName], action)

            if (newSubState !== state[reducerName]) {
                newState[reducerName] = newSubState
            }
        }

        if (Object.keys(newState).length !== 0) {
            return { ...state, ...newState }
        } else {
            return state
        }
    }
}

function cartReducer(state={}, {type, good={}, count=1}) {
//каков state
//{
//  _id1: {count:1, good: {_id1, name, price, images}}
//  _id2: {count:1, good: {_id1, name, prica, images}}
//}
//
const {_id} = good;
    if (type==='CART_ADD') {
        return {
            ...state,
            [_id]:{count: (state[_id]? (state[_id].count+=count) : count), good: good}
        }
    }
    if (type==='CART_CHANGE') {
        return {
            ...state,
            [_id]:{count: count, good: good}
        }
    }
    if (type==='CART_DELETE') {
        let newState = {... state};
        delete newState[_id];
        // const {_id, ...newState} = state; 
        return {
            ...newState, 
        }
    }
    if (type==='CART_CLEAR') {
        return {}
    }


    return state;
}

const store = createStore(combineReducers({promise: promiseReducer,
    auth: authReducer, cart: cartReducer}));


const actionCartAdd = (good, count=1)       => ({type: 'CART_ADD', good, count});
const actionCartChange = (good, count=1)    => ({type: 'CART_CHANGE', good, count});
const actionCartDelete = (good)             => ({type: 'CART_DELETE', good});
const actionCartClear = ()                 => ({type: 'CART_CLEAR'});

const cartEl = document.getElementsByClassName("cartN")[0];
let username;
const loginEl = document.getElementsByClassName("loginQ")[0];
const logoEl = document.getElementById("logo");

// const store = createStore(promiseReducer) //не забудьте combineReducers если он у вас уже есть

store.subscribe(() => console.log(store.getState()));


const actionPending             = name => ({type:'PROMISE',name, status: 'PENDING'});
const actionFulfilled = (name,payload) => ({type:'PROMISE',name, status: 'FULFILLED', payload});
const actionRejected  = (name,error)   => ({type:'PROMISE',name, status: 'REJECTED', error});
const actionAuthLogin = (token)        => ({ type: 'AUTH_LOGIN', token });
const actionAuthLogout = ()            => ({ type: 'AUTH_LOGOUT' });
const actionPromise = (name, promise) =>
    async dispatch => {
        dispatch(actionPending(name))
        try {
            let payload = await promise
            dispatch(actionFulfilled(name, payload))
            return payload
        }
        catch(error){
            dispatch(actionRejected(name, error))
        }
    }

const getGQL = url =>
    (query, variables) => fetch(url, {
        method: 'POST',
        headers: {
            "Content-Type": "application/json",
            ...(localStorage.authToken ? {"Authorization": "Bearer " + localStorage.authToken} : {})
        },
        body: JSON.stringify({query, variables})
    }).then(res => res.json())
        .then(data => {
            if (data.data){
                return Object.values(data.data)[0] 
            } 
            else throw new Error(JSON.stringify(data.errors))
        })

const backendURL = "http://shop-roles.node.ed.asmer.org.ua";

const gql = getGQL(backendURL + '/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){
          subCategories{
            name _id goods {
              _id
             name
            }
           }
            _id name goods {
                _id name price images {
                    url
                }
            }
        }
    }`, { q: JSON.stringify([{ _id }]) }));


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


const orderHistory = () =>
    actionPromise(
      'history',
      gql(` query OrderFind{
        OrderFind(query:"[{}]"){
            _id total createdAt orderGoods{
                count good{
                    _id name price images{
                      url
                    }
                }
                owner{
                    _id login 
                }
            }
        }
    }
  `));

  const actionLogin = (login, password) =>
  actionPromise(
    'login',
    gql(
      `query log($login: String, $password: String) {
        login(login: $login, password: $password)
      }`,
      { login: login, password: password }
    )
  );

const actionFullLogin = (login, password) => async (dispatch) => {
  let token = await dispatch(actionLogin(login, password));
  console.log(token);
  if (token) {
    dispatch(actionAuthLogin(token));
  }
  alerting();
};

const actionRegister = (login, password) =>
  actionPromise(
    'register',
    gql(
      `mutation register ($login:String, $password:String){
  UserUpsert(user:{login:$login, password:$password}){
	_id login
 }
}`,
      { login: login, password: password }
    )
  );

const actionOrder = () => async (dispatch, getState) => {
let { cart } = getState();
const orderGoods = Object.entries(cart).map(([_id, { count }]) => ({ good: { _id }, count, }));

let result = await dispatch(
    actionPromise(
    'order',
    gql(
        `
                    mutation newOrder($order:OrderInput){
                    OrderUpsert(order:$order)
                        { _id total 	}
                    }
            `,
        { order: { orderGoods } }
    )
    )
);
if (result?._id) {
    dispatch(actionCartClear());
    document.location.hash = "#/cart/";
    alert("Поздравляем, вы оформили заказ!");
}
};


const actionFullRegister = (login, password) => async (dispatch) => {
  let id = await dispatch(actionRegister(login, password));
//   console.log(id);
  alertingReg(id, login);
  if (id) {
    dispatch(actionFullLogin(login, password));
  }
};

store.dispatch(actionRootCats());

store.subscribe(() => {
    const {rootCats} = store.getState().promise
    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(){
            // console.log("good")
            //задиспатчить actionGoodById
            // console.log('ТОВАРОСТРАНИЦА')
            store.dispatch(actionGoodById(_id));
        },
        login(){
            main.innerHTML = `<h2>Для осуществления заказов:<br>если вы зарегистрированы,
            введите логин и пароль и нажмите Войти;<br>если не зарегистрированы - нужно зарегистрироваться</h2>`;
            const formEl = document.createElement("div");
            const p1el = document.createElement("p");
            const p2el = document.createElement("p");
            const p3el = document.createElement("p");
            const br1El = document.createElement("br");
            const br2El = document.createElement("br");
            const br3El = document.createElement("br");
            const input1El = document.createElement("input");
            const input2El = document.createElement("input");
            const buttonLogEl = document.createElement("button");
            const buttonRegEl = document.createElement("button");
            const buttonLogOutEl = document.createElement("button");
            p1el.innerText = "Введите логин:";
            p2el.innerText = "Введите пароль:";
            input1El.id = "inputLogin";
            input2El.id = "inputPassword";
            input2El.type = "password";
            buttonLogEl.innerText = "Войти";
            buttonRegEl.innerText = "Зарегистрироваться";
            buttonLogOutEl.innerText = "Выйти";
            if(username) {
                buttonLogEl.disabled = true;
                buttonRegEl.disabled = true;
            }
            buttonLogEl.onclick = () => {
                store.dispatch(actionFullLogin(input1El.value, input2El.value));
                // if (username) location.hash = "#/goods";
            }
            buttonRegEl.onclick = () => {
                store.dispatch(actionFullRegister(input1El.value, input2El.value));
            }
            buttonLogOutEl.onclick = () => {
                store.dispatch(actionAuthLogout());
                username = "";
                document.location.hash = "#/login/";
            }
            formEl.append(p1el,input1El,p2el,input2El,br1El,br2El,buttonLogEl,buttonLogOutEl,buttonRegEl);
            main.append(formEl);
            //по кнопке - store.dispatch(actionFullLogin(login, password))
        },
        register(){
            //отрисовка тут
            //по кнопке - store.dispatch(actionFullRegister(login, password))
        },
        dashboard(){
            store.dispatch(orderHistory());

        },
        cart(){
            const cart = store.getState().cart;
            if(Object.entries(cart).length>0) {
                main.innerHTML = `<h2>Содержимое вашей корзины</h2>`;
                const {cart} = store.getState();
                let totalScore = 0;
                let totalCount = 0;
                const tableCartEl = document.createElement("table");
                const trEl = document.createElement("tr");
                const td1El = document.createElement("td");
                const td2El = document.createElement("td");
                const td3El = document.createElement("td");
                const td4El = document.createElement("td");
                const td5El = document.createElement("td");
                td1El.innerText = "Удалить";
                td2El.innerText = "Наименование товара";
                td3El.innerText = "Цена";
                td4El.innerText = "Количество";
                td5El.innerText = "Изменить";
                trEl.append(td1El,td2El,td3El,td4El,td5El);
                tableCartEl.append(trEl);
    
                if (Object.entries(cart).length>0) {
                    for (let [key, value] of Object.entries(cart)) {
                        const {count, good} = value;
                        const {name, _id, price} = good;
                        const buttonDel = document.createElement("button");
                        const buttonMinus = document.createElement("button");
                        const buttonPlus = document.createElement("button");
                        buttonMinus.innerText = "-";
                        buttonPlus.innerText = "+";
                        buttonDel.innerText = "Удалить";


                        buttonMinus.onclick = () => {
                            if(count==1) {
                                store.dispatch(actionCartDelete({_id, name, price}));
                            } else {
                            store.dispatch(actionCartChange({_id, name, price}, count-1));
                            }
                            document.location.hash = `#/cart/${_id}/${count}`
                        }
                        buttonPlus.onclick = () => {
                            store.dispatch(actionCartChange({_id, name, price}, count+1));
                            document.location.hash = `#/cart/${_id}/${count}`
                        }
                        buttonDel.onclick = () => {
                            store.dispatch(actionCartDelete({_id, name, price}));
                            document.location.hash = `#/cart/${_id}`;
                        }
    
                        const trEl = document.createElement("tr");
                        const td1El = document.createElement("td");
                        const td2El = document.createElement("td");
                        const td3El = document.createElement("td");
                        const td4El = document.createElement("td");
                        const td5El = document.createElement("td");
                        td1El.append(buttonDel);
                        td2El.innerText = name;
                        td3El.innerText = price;
                        td4El.innerText = count;
                        td5El.append(buttonMinus,buttonPlus);
                        trEl.append(td1El,td2El,td3El,td4El,td5El);
                        tableCartEl.append(trEl);
    
                        totalScore+=price*count;
                        totalCount+=count;
                        
                    }
                    const trEl = document.createElement("tr");
                    const td1El = document.createElement("td");
                    const td2El = document.createElement("td");
                    const td3El = document.createElement("td");
                    const td4El = document.createElement("td");
                    const td5El = document.createElement("td");
                    td1El.innerText = "";
                    td2El.innerText = "";
                    td3El.innerText = totalScore;
                    td4El.innerText = totalCount;
                    td5El.innerText = "";
                    trEl.append(td1El,td2El,td3El,td4El,td5El);
                    tableCartEl.append(trEl);
                
                }
                
                main.append(tableCartEl);
    
                const buttonCartClear = document.createElement("button");
                const buttonSend = document.createElement("button");
                const buttonHistory = document.createElement("button");
                buttonSend.innerText = "Отправить заказ";
                buttonCartClear.innerText = "Очистить корзину";
                buttonHistory.innerText = "История заказов";
                buttonSend.onclick = () => {
                    store.dispatch(actionOrder());
                    
                }
                buttonCartClear.onclick = () => {
                    store.dispatch(actionCartClear());
                    document.location.hash = "#/cart/";
                }
                buttonHistory.onclick = () => {
                    document.location.hash = "#/dashboard";
                }

                main.append(buttonSend,buttonCartClear, buttonHistory);
            } else {
                main.innerHTML = `<h2>Ваша корзина пуста...</h2><a href="#/dashboard">история заказов</a>`;
            }

        },
    }
    if (route in routes)
        routes[route]()
}
window.onhashchange()


store.subscribe(() => {
    const {catById} = store.getState().promise;
    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 card      = document.createElement('div')
            card.innerHTML = `<a href="#/category/${_id}"><h2>${name}</h2></a>`;
            main.append(card);
        }
    }
    for (const {_id, name, price, images} of catById.payload.goods){
        const card      = document.createElement('div')
        card.innerHTML = `<h2>${name}</h2>
                            <img src="${backendURL}/${images[0].url}" /><br>
                            <strong>Цена: ${price} грн</strong><br>
                            <button onclick="store.dispatch(actionCartAdd({_id: '${_id}',
                                        name: '${name}', price: '${price}'}))">Купить</button>
                            <a href="#/good/${_id}">описание товара</a>`
        main.append(card)
    }

    }
})

store.subscribe(() => {
const {goodById} = store.getState().promise;
const [,route, _id] = location.hash.split('/');
    //ТУТ ДОЛЖНА БЫТЬ ПРОВЕРКА НА НАЛИЧИЕ goodById в редакс
    //и проверка на то, что сейчас в адресной строке адрес ВИДА #/good/АЙДИ
    //в таком случае очищаем main и рисуем информацию про товар с подробностями
    if (goodById?.payload && route === 'good'){
        const {_id, name, price, description, images} = goodById.payload; 
        main.innerHTML = `<h2>${name}</h2>`;
        const card      = document.createElement('div');
        card.innerHTML = `<img src="${backendURL}/${images[0].url}" /><br>
                              <strong>Цена: ${price} грн</strong><br>
                              <button onclick="store.dispatch(actionCartAdd({_id: '${_id}',
                                            name: '${name}', price: '${price}'}))">Купить</button>
                                <p>Описание товара:</p>
                                <p>${description}</p>
                                `
        main.append(card);
    }
})

store.subscribe(() => {
const {cart} = store.getState();
// console.log(cart);
let numInCart = 0;
if (Object.entries(cart).length>0) {
    for (let [key, value] of Object.entries(cart)) {
        const {count} = value;
        numInCart+=count;
    }
}
cartEl.innerText=numInCart;
    //достаем всю корзину
    //считаем общее количество всех позиций (3 айфона + 7 пицц = 10)
    //выводим число в кошик на странице через его HTML id
});

store.subscribe(() => {
    const {auth} = store.getState();
    // const [,route,] = location.hash.split('/');
    if(Object.keys(auth).length!==0) {
        username = auth?.payload.sub.login;
    }
    if(localStorage.authToken) {
        // console.log("login");
        loginEl.innerText = "";
        logoEl.innerHTML = `\n                <h1>${username}, добро пожаловать в интернет магазин NotРОЗЕТКА</h1>\n            `
    } else {
        // console.log("logout");
        loginEl.innerText = "?";
        logoEl.innerHTML = `\n                <h1>Добро пожаловать в интернет магазин</h1>\n            `  
    }
});

//store.dispatch(actionPromise('delay1000', delay(1000)))
//store.dispatch(actionPromise('delay2000', delay(2000)))
//store.dispatch(actionPromise('failedfetch', fetch('https://swapi.dev/api/people/1/')
  //.then(res => res.json())))

store.subscribe(() => {
    const {history} = store.getState().promise;
    const [,route] = location.hash.split('/');

    if (history?.payload && route === 'dashboard'){  

        main.innerHTML = "<p><b>История ваших заказов:</b></p>";
        const card      = document.createElement('div'); 
        
        for(let [key,value] of Object.entries(history.payload)) {
            const {_id, createdAt, total, orderGoods} = value;
            const p1El = document.createElement("p");
            const dateOfOrder = new Date(+createdAt);
            p1El.innerHTML = `${dateOfOrder.toLocaleDateString()} ${dateOfOrder.toLocaleTimeString()}
                            Заказ ID: ${_id} от , c ${orderGoods.length} товарами на сумму ${total} грн`;
            card.append(p1El); 
        }
        
        if (Object.keys(history.payload).length==0) {
            const p1El = document.createElement("p");
            p1El.innerHTML = "<p>Вы раньше еще ничего не заказывали</p>";
            card.append(p1El);
        }
        main.append(card);

    }
    
});

function alerting() {
    const {auth} = store.getState();
    if(Object.keys(auth).length!==0) {
        username = auth?.payload.sub.login;
    }
    if(username) {
        alert(`${username}, вы успешно зашли на сайт!`);
        document.location.hash = "#/category/5dc49f4d5df9d670df48cc64";
        
    } else {
        alert("Вы ввели неправильно логин или пароль!");
    }
}

function alertingReg(id, login) {
    if(id) {
        alert(`Вы успешно зарегистрировались с логином ${login}`);
    } else {
        alert(`Логин ${login} уже занят, придумайте другой!`);
    }
}