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
        }
    }
}

//reducers
function promiseReducer(state = {}, { type, status, payload, error, name }) {
    if (type === 'PROMISE') {
        return {
            ...state,
            [name]: { status, payload, error }
        }
    }
    return state
}

function cartReducer(state = {}, { type, count = 1, _id, name }) {
    if (type === "CART_ADD") {
        return {
            ...state,
            [_id]: {
                name: name,
                count: state[_id] ? state[_id].count + count : count
            }
        }
    }
    if (type === "CART_CHANGE") {
        return {
            ...state,
            [_id]: {
                name: name,
                count: count
            }
        }
    }
    if (type === "CART_REMOVE") {
        let { [_id]: a, ...rest } = state
        return rest
    }
    if (type === "CART_CLEAR") {
        return {}
    }
    return state
}

function authReducer(state, { type, token }) {

    if (state === undefined) {
        if (localStorage.authToken) {
            type = "LOGIN"
            token = localStorage.authToken
        } else {
            return {}
        }
    }

    if (type === "LOGIN") {
        localStorage.authToken = token
        return { token, payload: jwt_decode(token) }
    }
    if (type === "LOGOUT") {
        localStorage.removeItem("authToken")
        return {}
    }
    return state
}

let reducers = {
    promise: promiseReducer,
    cart: cartReducer,
    auth: authReducer
}

let jwt_decode = (token) => {
    let result = JSON.parse(atob(token.split(".")[1]))
    return result
}

function combineReducers(reducers) {
    function commonReducer(state = {}, action) {
        let newState = {}

        for (let key in reducers) {
            let innerState = reducers[key](state[key], action)

            innerState === state[key] ? newState[key] = state[key] : newState[key] = innerState
        }

        return newState
    }

    return commonReducer
}

const store = createStore(combineReducers(reducers))

//запросики
const getGQL = url => {
    return (query, variables) => {
        return fetch(url, {
            method: 'POST',
            headers: {
                "content-type": "application/json",
                ...(localStorage.authToken ? { Authorization: "Bearer " + localStorage.authToken } : {})
            },
            body: JSON.stringify({ query, variables }),
        }).then(res => res.json())
    }
}

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

let categoryById = async (id) => {
    let query = `query fndcategory($id: String) {
        CategoryFind(query: $id){
          name goods{
              _id name price images {
                  url
              }
          }
        }
      }`

    let qVariables = {
        "id": JSON.stringify([{ "_id": id }])
    }

    let res = await shopGQL(query, qVariables)
    console.log(res)
    return res
}

let goodById = async (id) => {
    let query = `query fndgood($id: String) {
        GoodFind(query: $id){
          name description price images {
              url
          }
        }
      }`

    let qVariables = {
        "id": JSON.stringify([{ "_id": id }])
    }

    let res = await shopGQL(query, qVariables)
    return res
}

let newOrder = async (obj) => {
    let option = Object.entries(obj);
    let orderGoods = [];

    for (let key of option) {
        let i = {
            "count": key[1],
            "good": { "_id": key[0] }
        }
        orderGoods.push(i);
    }

    let query = `mutation sndOrder($order: OrderInput) {
       OrderUpsert(order: $order) {
         _id createdAt total
       }
     }`

    let qVariables = {
        "order": {
            "orderGoods": orderGoods
        }
    }

    let res = await shopGQL(query, qVariables)
    console.log(res)
    return res
}

let log = async (login, password) => {
    let query = `query log($l: String, $p: String) {
          login(login: $l, password: $p)
        }`

    let qVariables = {
        "l": login,
        "p": password
    }

    let token = await shopGQL(query, qVariables)
    console.log(token)
    return token.data.login
}

let reg = async (login, password) => {
    let query = `mutation reg($l: String, $p: String) {
          UserUpsert(user:  {
              login: $l,
              password: $p
            } ) {
              _id
            }
        }`

    let qVariables = {
        "l": login,
        "p": password
    }

    let res = await shopGQL(query, qVariables)
    return res
}

//actions
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 payload = await promise
            dispatch(actionResolved(name, payload))
            return payload
        }
        catch (error) {
            dispatch(actionRejected(name, error))
        }
    }

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

const actionCategoryById = id => actionPromise('catById', categoryById(id))

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

const actionBuyGood = (obj) => actionPromise('newOrder', newOrder(obj))

const actionCartAdd = (n, id, name) => ({ type: "CART_ADD", count: n, _id: id, name })

const actionCartChange = (n, id, name) => ({ type: "CART_CHANGE", count: n, _id: id, name })

const actionCartRemove = id => ({ type: "CART_REMOVE", _id: id })

const actionCartClear = () => ({ type: "CART_CLEAR" })

const actionAuthLogin = token => ({ type: "LOGIN", token })

const actionAuthLogout = () => ({ type: "LOGOUT" })

const actionLogin = (login, password) => actionPromise("login", log(login, password))

const actionFullLogin = (login, password) => {
    return async (dispatch) => {
        let result = await dispatch(actionLogin(login, password))

        dispatch(actionAuthLogin(result))
    }
}

const actionRegister = (login, password) => actionPromise("register", reg(login, password))

const actionFullRegister = (login, password) => {
    return async (dispatch) => {
        let result = await dispatch(actionRegister(login, password))

        if (result.data.UserUpsert !== null) {
            dispatch(actionFullLogin(login, password))
        }
    }
}

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 === 'cart') {
        cartDraw(store.getState().cart, main)
    }

    if (route === "login") {
        loginDraw(main)
    }

    if (route === "registration") {
        registrationDraw(main)
    }
}

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

let goodDraw = (obj, parent, _id) => {
    let box = document.createElement("div")
    let goodName = document.createElement("h2")
    let goodIMG = document.createElement("img")
    let description = document.createElement("p")
    let price = document.createElement("span")
    let count = document.createElement("input")
    let goodBtnBox = document.createElement("div")
    let btn1 = document.createElement("button")
    let btn2 = document.createElement("button")
    price.textContent = "Цена: " + obj.price + "грн"
    goodIMG.src = "http://shop-roles.asmer.fs.a-level.com.ua/" + obj.images[0].url
    box.style.width = "40%"
    box.style.margin = "20px"
    box.style.display = "flex"
    box.style.flexDirection = "column"
    goodName.textContent = obj.name
    description.textContent = obj.description
    count.type = "number"
    count.min = 1
    count.value = 1
    goodBtnBox.style.display = "flex"
    goodBtnBox.style.alignItems = "center"
    btn1.textContent = "Купить"
    btn2.textContent = "В корзину"
    goodBtnBox.append(count, btn2, btn1)

    let order = {
        [_id]: +count.value
    }

    count.oninput = () => order[_id] = +count.value

    btn1.onclick = () => {
        store.dispatch(actionBuyGood(order))
    }
    btn2.onclick = () => {
        store.dispatch(actionCartAdd(+count.value, _id, obj.name))
    }

    box.append(goodIMG, goodName, description, price, goodBtnBox)
    parent.append(box)
}

let cardDraw = (obj, parent) => {
    let box = document.createElement("div");
    let img = document.createElement("img");
    let price = document.createElement("span");
    let productName = document.createElement("h5");
    img.src = "http://shop-roles.asmer.fs.a-level.com.ua/" + obj.images[0].url;
    productName.textContent = obj.name
    price.textContent = "Цена: " + obj.price + " грн"
    let productBody = document.createElement("div")
    box.className = "card"
    productBody.append(productName, price)
    box.append(img, productBody)
    let a = document.createElement("a")
    a.href = "#/good/" + obj._id
    a.append(box)
    parent.append(a)
}

let cartDraw = (obj, parent) => {
    while (parent.lastChild) {
        parent.lastChild.remove()
    }
    let cartName = document.createElement("h2")
    cartName.textContent = "Ваш заказ"
    parent.append(cartName)

    let order = {}

    for (let key in obj) {
        order[key] = obj[key].count
        let cartItem = document.createElement("div")
        let name = document.createElement("span")
        let count = document.createElement("input")
        let btnDelItem = document.createElement("button")
        let btnPlus = document.createElement("button")
        let btnMinus = document.createElement("button")
        count.min = 1
        count.value = obj[key].count
        count.className = "cart-item-count"
        btnPlus.className = "btn-plus"
        btnMinus.className = "btn-minus"
        btnDelItem.className = "btn-del"
        cartItem.className = "cart-item"
        name.textContent = obj[key].name
        cartItem.append(btnMinus, count, btnPlus, name, btnDelItem)
        parent.append(cartItem)

        if (+count.value === 1) {
            btnMinus.disabled = "true"
        }

        count.oninput = () => {
            if (+count.value >= 1) {
                store.dispatch(actionCartChange(+count.value, key, obj[key].name))
            }
        }

        btnPlus.onclick = () => {
            store.dispatch(actionCartChange(+count.value + 1, key, obj[key].name))
        }

        btnMinus.onclick = () => {
            store.dispatch(actionCartChange(+count.value - 1, key, obj[key].name))
        }

        btnDelItem.onclick = () => {
            store.dispatch(actionCartRemove(key))
        }
    }

    let btnBox = document.createElement("div")
    let btnOrder = document.createElement("button")
    let btnClear = document.createElement("button")
    btnClear.textContent = "ОЧИСТИТЬ КОРЗИНУ"
    btnClear.className = "btn-clear"
    btnOrder.textContent = "КУПИТЬ"

    btnOrder.onclick = () => {
        store.dispatch(actionBuyGood(order))
        store.dispatch(actionCartClear())
    }

    btnClear.onclick = () => {
        store.dispatch(actionCartClear())
    }

    btnBox.append(btnOrder, btnClear)
    parent.append(btnBox)
}

let cartValueDraw = () => {
    if (cart.children.length === 1) {
        let span = document.createElement("span")
        cart.append(span)
    }

    let sum = 0;
    let cartState = store.getState().cart
    for (let key in cartState) {
        sum += cartState[key].count
    }

    cart.lastChild.textContent = sum
}

let userStatus = (parent = userpanel) => {
    if (parent.children.length > 1) {
        parent.lastChild.remove()
    }

    let status = store.getState().auth
    let box = document.createElement("div")
    box.style.display = "flex"
    box.style.alignItems = "center"
    let userName = document.createElement("span")

    box.append(userName)
    parent.append(box)

    if (status.payload) {
        let btn = document.createElement("button")
        userName.textContent = status.payload.sub.login
        btn.textContent = "Выход"
        btn.onclick = () => {
            store.dispatch(actionAuthLogout())
        }
        box.append(btn)
    } else {
        let btn = document.createElement("a")
        let btnREG = document.createElement("a")
        userName.textContent = "Гость"
        btn.textContent = "Вход"
        btnREG.textContent = "Регистрация"
        btn.href = "#/login"
        btnREG.href = "#/registration"

        box.append(btn, btnREG)
    }
}

function Form(parent, type, open) {
    let h3 = document.createElement("h3");
    let loginInput = document.createElement("input");
    let passwordInput = document.createElement("input");
    let checkbox = document.createElement("input");
    let btn = document.createElement("button");
    let form = document.createElement("div");
    let box = document.createElement("div")
    box.style.display = "flex"
    box.style.alignItems = "center"
    box.append(passwordInput, checkbox)
    loginInput.id = "login"
    loginInput.placeholder = "логин"
    passwordInput.id = "password"
    passwordInput.placeholder = "пароль"
    btn.id = "btn"
    checkbox.type = "checkbox";
    checkbox.style.marginLeft = "5px"
    btn.disabled = true;

    if (type === "reg") {
        h3.textContent = "РЕГИСТРАЦИЯ"
        btn.textContent = "ЗАРЕГИСТРИРОВАТЬСЯ";
    } else {
        h3.textContent = "ВХОД"
        btn.textContent = "Войти";
    }

    form.append(h3, loginInput, box, btn)
    form.className = "form"
    parent.append(form)

    let btnOpen = () => {
        if (type === "reg" && checkbox.checked === false) {
            passwordInput.value !== "" && password.value === passwordVerify.value ? btn.disabled = false : btn.disabled = true;
        } else {
            (loginInput.value != "" && passwordInput.value != "") ? btn.disabled = false : btn.disabled = true;
        }
    }

    let checker = (check) => {
        if (check) {
            passwordInput.type = "text";
            checkbox.checked = true;

            if (type === "reg" && passwordVerify) {
                passwordVerify.remove()
            }
        } else {
            passwordInput.type = "password";
            checkbox.checked = false;

            if (type === "reg") {
                let passwordInput2 = document.createElement("input");
                passwordInput2.placeholder = "повторите пароль"
                passwordInput2.id = "passwordVerify"
                passwordInput2.type = "password"

                passwordInput2.oninput = () => {
                    btnOpen();
                }
                form.append(passwordInput2)
            }
        }
    }
    checker(open);

    loginInput.oninput = () => {
        btnOpen();

        if (typeof this.onChange === "function") {
            this.onChange([loginInput.value, passwordInput.value]);
        }
    }

    passwordInput.oninput = () => {
        btnOpen();

        if (typeof this.onChange === "function") {
            this.onChange([loginInput.value, passwordInput.value]);
        }
    }

    checkbox.onchange = () => {
        checker(checkbox.checked)
        btnOpen()

        if (typeof this.onOpenChange === "function") {
            this.onOpenChange(checkbox.checked);
        }
    }

    this.getValue = () => [loginInput.value, passwordInput.value];

    this.setValue = (valueLogin, valuePassword) => {
        loginInput.value = valueLogin;
        passwordInput.value = valuePassword;
        btnOpen();
    }

    this.getOpen = () => checkbox.checked;

    this.setOpen = (open) => {
        checker(open);
    }
}

let loginDraw = (parent) => {
    while (parent.lastChild) {
        parent.lastChild.remove()
    }

    if (store.getState().auth.payload) {
        let h2 = document.createElement("h2")
        h2.textContent = "ВЫ ВОШЛИ"
        h2.style.color = "grey"
        parent.append(h2)
    } else {
        let form = new Form(parent)
        btn.onclick = () => {
            store.dispatch(actionFullLogin(login.value, password.value))
        }
    }
}

let registrationDraw = (parent) => {
    while (parent.lastChild) {
        parent.lastChild.remove()
    }

    if (store.getState().auth.payload) {
        let h2 = document.createElement("h2")
        h2.textContent = "ВЫ УСПЕШНО ЗАРЕГИСТРИРОВАЛИСЬ"
        h2.style.color = "grey"
        parent.append(h2)
    } else if (store.getState().promise.register && store.getState().promise.register.status === "RESOLVED") {
        let h3 = document.createElement("h3")
        h3.textContent = "Error: " + store.getState().promise.register.payload.errors[0].message
        h3.style.color = "red"
        parent.append(h3)
    } else {
        let form = new Form(parent, "reg")

        btn.onclick = () => {
            store.dispatch(actionFullRegister(login.value, password.value))
        }
    }
}

//subscribers
const unsubscribe1 = store.subscribe(() => console.log(store.getState()))
store.subscribe(drawMainMenu)
store.subscribe(cartValueDraw)
store.subscribe(userStatus)

store.subscribe(() => {
    const { 1: route, 2: id } = location.hash.split('/')
    if (route === 'categories') {
        const catById = store.getState().promise.catById?.payload
        if (catById) {
            while (main.lastChild) {
                main.lastChild.remove()
            }

            let categoryName = document.createElement("h2")
            let cards = document.createElement("div")
            cards.className = "cards"
            categoryName.textContent = catById.data.CategoryFind[0].name

            for (let key of catById.data.CategoryFind[0].goods) {
                cardDraw(key, cards)
            }
            main.append(categoryName, cards)
        }
    }
    if (route === 'good') {
        const goodById = store.getState().promise.goodById?.payload
        if (goodById) { //вывести в main страницу товара
            while (main.lastChild) {
                main.lastChild.remove()
            }
            goodDraw(goodById.data.GoodFind[0], main, id)
        }
    }
    if (route === 'cart') {
        cartDraw(store.getState().cart, main)
    }
    if (route === 'login') {
        loginDraw(main)
    }
    if (route === "registration") {
        registrationDraw(main)
    }
})