function createStore(reducer){
    let state = reducer(undefined, {})
    let cbs   = []
    function dispatch(action){
        if (typeof action === 'function'){
            return action(dispatch)
        }
        const newState = reducer(state, action)
        if (state !== newState){
            state = newState
            cbs.forEach(cb => cb())
        }
    }
    return {
        dispatch,
        subscribe(cb){
            cbs.push(cb)
            return () =>  cbs = cbs.filter(c => c !== cb)
        },
        getState(){
            return state
        }
    }
}

function combineReducers(reducers={cart: cartReducer, promise: promiseReducer, auth: authReducer}){
    return (state={}, action) => {
        let newState = {}
        for (let key in reducers) {
            let newSubState = reducers[key](state[key], action)
            if(newSubState !== state[key]) {
                newState[key] = newSubState
            }
        }
        if (Object.keys(newState).length) {
            return {...state, ...newState}
        } else {
            return state
        }
    }
}

//promise 
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 delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))

const actionPromise = (name, promise) => 
    async dispatch => {
        dispatch(actionPending(name))
        try{
            let payload = await promise
            dispatch(actionResolved(name, payload)) 
            return payload
        }
        catch(error){
             dispatch(actionRejected(name, error))
        }
    }

const getGQL = url => {
    return function(query, variables={}) {
        return fetch(url, 
        {
            method: "POST",
            headers: 
            {"Content-Type": "application/json",
            ...(localStorage.authToken ? {Authorization: 'Bearer ' + localStorage.authToken} : {})
            },
            body: JSON.stringify({query, variables})
        }).then(resp => resp.json())
        .then(data => {
            if ("errors" in data) {
                let error = new Error('ашипка, угадывай што не так')
                throw error
            }
            else {
                return data.data[Object.keys(variables)[0]]
            }
        })
    }
}

let shopGQL = getGQL('http://shop-roles.asmer.fs.a-level.com.ua/graphql')

const goodById = goodId => {
    let id = `[{"_id":"${goodId}"}]`
    return shopGQL(`
    query good($id:String){
        GoodFindOne(query: $id) {
          _id name description price images {
            _id text url
          }
          categories {
            _id name
          }
        }
    }`, {GoodFindOne: '', id })
}

const actionGoodById = id => 
    actionPromise('goodById', goodById(id))

const actionRootCategories = () =>
    actionPromise('rootCategories', shopGQL(`
            query cats($query:String){
              CategoryFind(query:$query){
                _id name 
              }
            }
        `, {CategoryFind:'', query: JSON.stringify([{parent:null}])}))

const actionCategoryById = (_id) =>
    actionPromise('catById', shopGQL(`
    query catById($query:String){
        CategoryFindOne(query:$query){
        _id name goods{
            _id name price description images{
            url
            }
        }
        }
    }`, { CategoryFindOne: '', query: JSON.stringify([{ _id }]) }))

const actionAddOrder = (cart) => {
    let string = '['
    for (let goodId in cart) {
        string += `{good: {_id: "${goodId}"}, count: ${cart[goodId].count}},`

    }
    string = string.slice(0, string.length - 1)
    string += ']'
    store.dispatch(actionPromise('orderAdd', shopGQL(`
    mutation {
        OrderUpsert(order: {
          orderGoods: ${string}
        }) {
          _id total
        }
      }`, {OrderUpsert: ''})))
}

//cart
function cartReducer(state={}, {type, good, price, count=1}) {
    if (Object.keys(state).length === 0 && localStorage.cart?.length > 10) {
        let newState = JSON.parse(localStorage.cart)
        return newState
    }
    const types = {
        CART_ADD() {
            let newState = {
             ...state,
             [good._id]: {count: (state[good._id]?.count || 0) + count, good: {id: good._id, name: good.name}, price}
            }
            localStorage.cart = JSON.stringify(newState)
            return newState
        },
        CART_REMOVE() {
            let {[good._id]:poh, ...newState} = state
            localStorage.cart = JSON.stringify(newState)
            return newState
            // let newState = {...state}
            // delete newState[good._id]
            // return newState
        },
        CART_CLEAR() {
            let newState = {}
            localStorage.cart = JSON.stringify(newState)
            return newState
        },
        CART_SET() {
            let newState = {
                ...state,
                [good._id]: {count, good: {id: good._id, name: good.name}, price}
            }
            localStorage.cart = JSON.stringify(newState)
            return newState
        }
    }
    if (type in types) {
        return types[type]()
    }
    return state
}

const actionCartAdd = (_id, name, price, count) => ({type: 'CART_ADD', good: {_id, name}, price, count})
const actionCartRemove = (_id, name) => ({type: 'CART_REMOVE', good: {_id, name}})
const actionCartSet = (_id, name, price, count) => ({type: 'CART_SET', good: {_id, name}, price, count})
const actionCartClear = () => ({type: 'CART_CLEAR'})

//auth
const jwt_decode = (jwt) => {
    let payload = jwt.split('.')
    return JSON.parse(atob(payload[1]))
}

function authReducer(state, action={}){ //....
    if (state === undefined){
        //добавить в action token из localStorage, и проимитировать LOGIN (action.type = 'LOGIN')
        if (localStorage.authToken) {
            action.jwt = localStorage.authToken
            return {token: action.jwt, payload: jwt_decode(action.jwt)}
        }
    }
    if (action.type === 'LOGIN'){
        console.log('ЛОГИН')
        //+localStorage
        //jwt_decode
        return {token: action.jwt, payload: jwt_decode(action.jwt)}
    }
    if (action.type === 'LOGOUT'){
        console.log('ЛОГАУТ')
        //-localStorage
        //вернуть пустой объект
        return {}
    }
    if (action.type === 'LOGGING_IN'){
        return {loginPageHello: true}
    }
    return state
}

const actionLogin = (jwt) => ({type: 'LOGIN', jwt})
const thunkLogin = (login, password) => {
    return (dispatch) => {
        shopGQL(`query login($login:String, $password: String) {login(login:$login, password:$password)}`, { login, password })
        .then(jwt => {
            if (jwt) {
                localStorage.authToken = jwt
                dispatch(actionLogin(jwt))
            } else {
                throw new Error('wrong')
            }
        })
    }
}

const actionLogout = () => ({type: 'LOGOUT'})
const thunkLogout = () => {
    return (dispatch) => {
            localStorage.authToken = ''
            localStorage.cart = ''
            store.dispatch(actionCartClear())
            dispatch(actionLogout())
    }
}

const actionLoggingIn = () => ({type: 'LOGGING_IN'})

//store
const store = createStore(combineReducers({cart: cartReducer, promise: promiseReducer, auth: authReducer}))
const unsubscribe1 = store.subscribe(() => console.log(store.getState()))
store.dispatch(actionRootCategories())

window.onhashchange = () => {
    let {1: route, 2:id} = location.hash.split('/')
    if (route === 'categories'){
        store.dispatch(actionCategoryById(id))
    }
    if (route === 'good'){
        store.dispatch(actionGoodById(id))
    }
    if (route === 'login'){
        store.dispatch(actionLoggingIn())
    }
    if (route === 'cart'){
        drawCart()
    }
}
window.onhashchange()

function drawMainMenu(){
    let cats = store.getState().promise.rootCategories.payload
    if (cats){ //каждый раз дорисовываются в body
        aside.innerText = ''
        for (let {_id, name} of cats){
            let catA = document.createElement('a')
            catA.href = `#/categories/${_id}`
            catA.innerText = name
            aside.append(catA)
        }
    }
}

function drawHeader() {
    login.innerHTML = (store.getState().auth?.payload ? `${store.getState().auth.payload.sub.login} | <a id='logout' href="#/login">Log out</a>` : '<a href="#/login">Log in</a>')
    if (document.querySelector('#logout')) {
        logout.onclick = () => {
            store.dispatch(thunkLogout())
        }
    }
}

function drawCart() {
    let cart = store.getState().cart
        if (!localStorage.authToken) {
            main.innerText = 'Залогинтесь плез'
        } else if (!Object.keys(cart).length) {
            main.innerText = 'Корзина пуста'
        } else {
            main.innerText = 'Ваша корзина: '
            for (let goodId in cart) {
                let {good: {id, name}, price, count} = cart[goodId]
                let goodContainer = document.createElement('div')
                goodContainer.classList.add('good-container')
                let goodName = document.createElement('div')
                goodName.innerText = name
                let goodPrice = document.createElement('div')
                goodPrice.innerText = 'Стоимость: ' + price
                let goodCount = document.createElement('input')
                goodCount.type = 'number'
                goodCount.value = count
                goodCount.onchange = () => {
                    store.dispatch(actionCartSet(id, name, price, (goodCount.value > 0 ? +goodCount.value : 1)))
                }

                let removeBtn = document.createElement('button')
                removeBtn.innerText = 'Удалить товар'
                removeBtn.onclick = () => {
                    store.dispatch(actionCartRemove (id, name))
                }

                goodContainer.append(goodName, goodPrice, goodCount, removeBtn)
                main.append(goodContainer)
            }

            let price = 0
            for (let goodId in cart) {
                price += cart[goodId].price * cart[goodId].count
            }
            let totalPriceContainer = document.createElement('div')
            totalPriceContainer.innerText = 'Общая стоимость: ' + price
            main.append(totalPriceContainer)

            let setOrderBtn = document.createElement('button')
            setOrderBtn.innerText = 'Оформить заказ'
            setOrderBtn.onclick = () => {
                actionAddOrder(store.getState().cart)
            }
            main.append(setOrderBtn)

            let clearBtn = document.createElement('button')
            clearBtn.innerText = 'Очистить корзину'
            clearBtn.onclick = () => {
                store.dispatch(actionCartClear())
            }
            main.append(clearBtn)
        }
}

function drawOrderSuccessful() {
    if (store.getState().promise.orderAdd?.status === 'RESOLVED') {
        let order = store.getState().promise.orderAdd.payload
        main.innerText = 'Заказ оформился, всё круто'

        let orderInfo = document.createElement('div')
        orderInfo.innerText = `Номер заказа: ${order._id}. Стоимость: ${order.total}`
        main.append(orderInfo)
    }
}

store.subscribe(drawMainMenu)
store.subscribe(drawHeader)
store.subscribe(() => {
    const {1: route, 2:id} = location.hash.split('/')
    if (route === 'categories'){
        const catById = store.getState().promise.catById?.payload
        if (catById){
            main.innerText = ''
            let categoryName = document.createElement('div')
            categoryName.innerText = catById.name
            categoryName.style.fontSize = '25px'
            categoryName.style.fontWeight = 'bold'
            main.append(categoryName)
            for (let {_id, name, price} of catById.goods){
                let good = document.createElement('a')
                    good.href = `#/good/${_id}`
                    good.innerText = name

                let btn = document.createElement('button')
                    btn.onclick = () => {
                        if (!localStorage.authToken) {
                            main.innerText = 'Залогинтесь плез'
                        } else {
                            store.dispatch(actionCartAdd(_id, name, price))
                        }
                    }
                    btn.style.cursor = 'pointer'
                    btn.innerText = 'купыть'
                main.append(good, btn)
            }
        }
    }
    if (route === 'good'){
        const goodById = store.getState().promise.goodById?.payload
        if (goodById){
            main.innerText = ''
            let {name, description, price, _id} = goodById
            let goodName = document.createElement('div')
                goodName.innerText = name
                goodName.style.fontSize = '35px'
                goodName.style.fontWeight = 'bold'
                goodName.style.marginBottom = '25px'

            let goodDescription = document.createElement('div')
                goodDescription.innerText = description
                goodDescription.style.marginBottom = '25px'

            let goodPrice = document.createElement('div')
                goodPrice.innerText = 'Цена: ' + price
                goodPrice.style.marginBottom = '5px'

            let btn = document.createElement('button')
                btn.onclick = () => {
                    if (!localStorage.authToken) {
                        main.innerText = 'Залогинтесь плез'
                    }  else {
                        store.dispatch(actionCartAdd(_id, name, price))
                    }
                }
                btn.style.cursor = 'pointer'
                btn.innerText = 'купыть'

            main.append(goodName, goodDescription, goodPrice, btn)
        }
    }
    if (route === 'login') {
        main.innerText = ''

        let inputsContainer = document.createElement('div')
        inputsContainer.id = 'inputs'
        let loginInput = document.createElement('input')
        loginInput.type = 'text'
        let loginLabel = document.createElement('span')
        loginLabel.innerText = 'Login:'
        let passwordInput = document.createElement('input')
        passwordInput.type = 'password'
        let passwordLabel = document.createElement('span')
        passwordLabel.innerText = 'Password:'
        let button = document.createElement('button')
        button.innerText = 'log in cyka'
        button.onclick = () => {
            if (loginInput.value && passwordInput.value){
                store.dispatch(thunkLogin(loginInput.value, passwordInput.value))
            }
        }

        inputsContainer.append(loginLabel, loginInput, passwordLabel, passwordInput, button)
        main.append(inputsContainer)

        if (store.getState().auth?.payload) {
            button.disabled = true
        }
    }
    if (route === 'cart') {
        drawCart()
        drawOrderSuccessful()
    }
})