/*Store Class
Переделайте задание Store на синтаксис ES6-классов:
Добавьте нужные параметры в методы, их код, а так же геттер state, который работает аналогично getState. Проверьте на ларьке, ведь объект, созданный из этого класса будет таким же, как и объект, созданный createStore
*/
{
    class Store {
        #reducer;
        #state;
        #cbs = []

        constructor(reducer) {
            this.#reducer = reducer
            this.#state = reducer(undefined, {})
        }

        get getState() {
            return this.#state
        }

        subscribe(cb) {
            this.#cbs.push(cb)
            return () => this.#cbs = this.#cbs.filter(c => c !== cb)
        }

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




    function reducer(state, { type, productName, productQuantity, summ }) { //объект action деструктуризируется на три переменных
        if (!state) { //начальная уборка в ларьке:
            return {
                пиво: {
                    quantity: 100,
                    price: 30,
                },
                чипсы: {
                    quantity: 100,
                    price: 52,
                },
                сиги: {
                    quantity: 100,
                    price: 89,
                },
                касса: 0
            }
        }
        if (type === 'КУПИТЬ' && productQuantity <= state[productName].quantity && summ >= state[productName].price * productQuantity) { //если тип action - КУПИТЬ, то:
            return {
                ...state, //берем все что было из ассортимента
                [productName]: {
                    quantity: state[productName].quantity - productQuantity,
                    price: state[productName].price
                },
                касса: state.касса + state[productName].price * productQuantity
                //и уменьшаем то, что покупается на количество
            }
        }

        return state //если мы не поняли, что от нас просят в `action` - оставляем все как есть
    }

    function actionCreator(type, productName, productQuantity, summ) {
        return {
            type,
            productName,
            productQuantity,
            summ
        }
    }

    const store = new Store(reducer)
    myState = store.getState

    const showcase = document.createElement('section')
    document.body.append(showcase)
    showcase.classList.add('showcase')

    const orderSection = document.createElement('section')
    document.body.append(orderSection)
    orderSection.classList.add('orderSection')

    const orderSelectProd = document.createElement('select')
    orderSection.append(orderSelectProd)

    const orderInputQuantity = document.createElement('input')
    orderInputQuantity.type = 'number'
    orderInputQuantity.min = '0'
    orderSection.append(orderInputQuantity)

    const orderInputQuantityName = document.createElement('div')
    orderInputQuantity.before(orderInputQuantityName)
    orderInputQuantityName.innerText = 'количество товара:'

    const orderSendMoney = document.createElement('input')
    orderSendMoney.type = 'number'
    orderSendMoney.min = '0'
    orderSection.append(orderSendMoney)

    const orderSendMoneyName = document.createElement('div')
    orderSendMoney.before(orderSendMoneyName)
    orderSendMoneyName.innerText = 'сумма:'

    const orderBuyButton = document.createElement('input')
    orderBuyButton.type = 'button'
    orderBuyButton.value = 'КУПИТЬ'
    orderSection.append(orderBuyButton)
    orderBuyButton.onclick = () => { store.dispatch(actionCreator(orderBuyButton.value, orderSelectProd.value, +orderInputQuantity.value, +orderSendMoney.value)) }


    for (const elemProduct in store.getState) {
        if (elemProduct === 'касса') continue

        const productCard = document.createElement('div')
        const productName = document.createElement('h2')
        const productPrice = document.createElement('div')
        const productQuantity = document.createElement('div')

        productCard.append(productName)
        productCard.append(productQuantity)
        productCard.append(productPrice)
        showcase.append(productCard)

        productCard.classList.add('productCard')
        productName.classList.add('productName')
        productPrice.classList.add('productPrice')
        productQuantity.classList.add('productQuantity')

        productName.innerText = elemProduct
        productPrice.innerText = store.getState[elemProduct].price + ' грн'
        productQuantity.innerText = store.getState[elemProduct].quantity + ' шт\nв наличии'

        const selectProdOption = document.createElement('option')
        selectProdOption.value = selectProdOption.innerText = elemProduct
        orderSelectProd.append(selectProdOption)
        const productPriceUnsubscribe = store.subscribe(() => {
            productPrice.innerText = store.getState[elemProduct].price + ' грн'
        })
        const productQuantityUnsubscribe = store.subscribe(() => {
            productQuantity.innerText = store.getState[elemProduct].quantity + ' шт\nв наличии'
        })

    }
}

/*Password Class
По аналогии, переделайте код задания Password в синтаксис классов ES6. Спрячьте все что можно в #приватные свойства объектов класса. Проверьте на форме логина - ведь она использует Password*/

{
    class Password {
        #inputPass = document.createElement('input')
        #checkboxPass = document.createElement('input')
        #open = ''

        constructor(parent, openTrueOrFalse) {
            parent.append(this.#inputPass)
            this.#checkboxPass.type = 'checkbox'
            parent.append(this.#checkboxPass)
            if (openTrueOrFalse === true) {
                this.#open = true
                this.#checkboxPass.checked = true
                this.#inputPass.type = "text"
            }
            else {
                this.#open = false
                this.#checkboxPass.checked = false
                this.#inputPass.type = "password"
            }
            this.#inputPass.oninput = () => { if (typeof this.onChange === 'function') this.onChange(this.#inputPass.value) }
            this.#checkboxPass.oninput = () => this.open = this.#checkboxPass.checked
        }

        set value(value) {
            this.#inputPass.value = value
            if (typeof this.onChange === 'function') this.onChange(this.#inputPass.value) // запускается по событию oninput в поле ввода, передает текст в колбэк
        }      //задает зн

        get value() { return this.#inputPass.value }

        set open(openTrueOrFalse) {
            if (openTrueOrFalse === true && this.#open === false) {
                this.#open = true
                this.#checkboxPass.checked = true
                this.#inputPass.type = "text"
                if (typeof this.onOpenChange === 'function') this.onOpenChange(this.#open)
            }
            else if (this.#open === true) {
                this.#open = false
                this.#checkboxPass.checked = false
                this.#inputPass.type = "password"
                if (typeof this.onOpenChange === 'function') this.onOpenChange(this.#open)
            }
        }

        get open() { return this.#checkboxPass.checked }

    }

    let pass = new Password(document.body, false)

    pass.onChange = function (data) { console.log(data) }
    pass.onOpenChange = function (open) { console.log(open) }

    pass.value = 'qwerty'
    console.log(pass.value)

    pass.open = true
    console.log(pass.open)

    console.log(pass)
}

/*StoreThunk Class
Унаследуйте класс Store в новом классе StoreThunk. Новый класс должен перекрывать метод dispatch, проверять тип переданного экшона и если это функция, запускать её, передав у неё this.dispatch и this.getState. Данное условие написано тут. Учтите, что в thunk передаются функции dispatch и getState без объекта до точечки, а эти методы в классе Store являются обычными функциями, склонными к потере this. Для прибития this намертво к функции используйте метод bind. Посмотреть можно тут и тут Проверьте на модульном проекте */

{
    class Store {
        #reducer;
        #state;
        #cbs = []

        constructor(reducer) {
            this.#reducer = reducer
            this.#state = reducer(undefined, {})
        }

        getState() {
            return this.#state
        }

        get state() {
            return this.#state
        }

        subscribe(cb) {
            this.#cbs.push(cb)
            return () => this.#cbs = this.#cbs.filter(c => c !== cb)
        }

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

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

    const actionPending = promiseName => ({ promiseName, type: 'PROMISE', status: 'PENDING' })
    const actionFulfilled = (promiseName, payload) => ({ promiseName, type: 'PROMISE', status: 'FULFILLED', payload })
    const actionRejected = (promiseName, error) => ({ promiseName, type: 'PROMISE', status: 'REJECTED', error })

    const actionPromise = (promiseName, promise) =>
        async dispatch => {

            dispatch(actionPending(promiseName)) //сигнализируем redux, что промис начался
            try {
                const payload = await promise //ожидаем промиса
                dispatch(actionFulfilled(promiseName, payload)) //сигнализируем redux, что промис успешно выполнен
                return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
            }
            catch (error) {
                dispatch(actionRejected(promiseName, error)) //в случае ошибки - сигнализируем redux, что промис несложился
            }
        }

    class StoreThunk extends Store {


        constructor(...params) {
            super(...params) //вызов конструктора предка для создания нового объекта
            console.log(this)

        }

        dispatch(action) {
            if (typeof action === 'function') { //если action - не объект, а функция
                return action(super.dispatch.bind(this), super.getState.bind(this)) //запускаем эту функцию и даем ей dispatch и getState для работы
            }
            return super.dispatch(action)

        }
    }

    let store = new StoreThunk(promiseReducer)
    store.subscribe(() => console.log(store.getState))
    store.dispatch(actionPromise('tatooine', fetch("https://swapi.dev/api/planets/1").then(res => res.json())))
}

/*Напишите класс RGB, приватными свойствами которого являются три числа #r, #g, #b. Класс должен обладать следующими геттерами и сеттерами:
r. Служит для чтения/изменения #r
g. Служит для чтения/изменения #g
b. Служит для чтения/изменения #b
rgb. Служит для чтения/изменения всех трех цветовых каналов. Используется строковой CSS синтаксис типа rgb(128,255,64)
hex. Служит для чтения/изменения всех трех цветовых каналов. Используется строковой CSS синтаксис типа #RRGGBB
Для проверки строк в сеттерах rgb и hex используйте следующие регулярные выражения:
hex: /^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$/. Так же это регулярное выражение, при использовании метода match даст вам все три цветовых канала по отдельности в отдельных ячейках результирующего массива:
console.log('#FFAA08'.match(/^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$/))
rgb: возьмите отсюда
В случае, если match возвращает null, выбрасывайте исключение SyntaxError. Также, в сеттерах r, g, b, проверяйте тип и диапазон (он должен быть от 0 до 255) и выбрасывайте исключение RangeError.*/


class RGB {
    #r
    #g
    #b
    #regExpRgb = /^(rgb||rgba)?\(?([01]?\d\d?|2[0-4]\d|25[0-5])(\W+)([01]?\d\d?|2[0-4]\d|25[0-5])\W+(([01]?\d\d?|2[0-4]\d|25[0-5])\)?)$/
    #regExpHex = /^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$/

    get r() { return this.#r }
    get g() { return this.#g }
    get b() { return this.#b }
    set r(value) {
        if (typeof value === 'string' && +value <= 255) {
            this.#r = +value
        } else if (typeof value === 'number' && value <= 255) {
            this.#r = value
        } else throw RangeError("ожидается число до 255 включительно")
    }
    set g(value) {
        if (typeof value === 'string' && +value <= 255) {
            this.#g = +value
        } else if (typeof value === 'number' && value <= 255) {
            this.#g = value
        } else throw RangeError("ожидается число до 255 включительно")
    }
    set b(value) {
        if (typeof value === 'string' && +value <= 255) {
            this.#b = +value
        } else if (typeof value === 'number' && value <= 255) {
            this.#b = value
        } else throw RangeError("ожидается число до 255 включительно")
    }
    get rgb() { return `rgb(${this.#r},${this.#g},${this.#b})` }
    set rgb(rgbColor) {
        if (this.#regExpRgb.test(rgbColor)) {
            const arrRgbColor = rgbColor.match(this.#regExpRgb)
            this.#r = +arrRgbColor[2]
            this.#g = +arrRgbColor[4]
            this.#b = +arrRgbColor[6]
            return this.rgb
        } else { throw new SyntaxError }
    }
    get hex() {
        const hex = '#' +
            (this.#r > 15 ? this.#r.toString(16) : "0" + this.#r.toString(16)) +
            (this.#g > 15 ? this.#g.toString(16) : "0" + this.#g.toString(16)) +
            (this.#b > 15 ? this.#b.toString(16) : "0" + this.#b.toString(16));
        return hex
    }

    set hex(hexColor) {
        if (this.#regExpHex.test(hexColor)) {
            const arrHexColor = hexColor.match(this.#regExpHex)
            this.#r = +(parseInt(arrHexColor[1], 16).toString(10))
            this.#g = +(parseInt(arrHexColor[2], 16).toString(10))
            this.#b = +(parseInt(arrHexColor[3], 16).toString(10))
            return this.hex
        } else { throw new SyntaxError }
    }
}


const rgb = new RGB
rgb.r = 15
rgb.g = 128
rgb.b = 192
console.log(rgb.hex) //#0F80C0
console.log(rgb.rgb) //rgb(15,128,192)
rgb.hex = '#203040'
console.log(rgb.rgb) //rgb(32, 48, 64)
rgb.rgb = 'rgb(100, 90, 50)'
console.log(rgb.r, rgb.g, rgb.b) //100, 90, 50

//rgb.hex = 'дичь' //SyntaxError
//rgb.r   = 1000   //RangeError

/* RGBA Class
Создайте класс-наследник класса RGB под названием RGBA. В нем должно добавиться новое приватное поле #a, содержащее значение прозрачности в диапазоне от 0 до 1. Создайте сеттер и геттер a. Перекройте сеттер и геттер hex, что бы в классе-наследнике работал синтаксис #RRGGBBAA. Учтите, что сеттер и геттер предка могут вам помочь. Также, сеттер hex должен поддерживать синтаксис #RRGGBB без прозрачности. Добавьте сеттер и геттер rgba, которые работают с CSS-синтаксисом вида rgba(128,255,64, 0.5). Добавьте сеттер color, в который можно присваивать любой из синтаксисов CSS - #RRGGBB, #RRGGBBAA, rgb(1,2,3) и rgba(1,2,3,0.5). Сеттер a должен проверять диапазон и выбрасывать исключение в случае несоответствия диапазона.
Для приведения целочисленного значения прозрачности в шестнадцатиричной нотации к диапазону 0..1, поделите на 255.*/


class RGBA extends RGB {
    #a = 1
    #regExpHexA = /^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$/
    #regExpRGBA = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/

    set a(value) {
        if (typeof value === 'string' && 0 <= +value && +value <= 1) {
            this.a = +value
        } else if (typeof value === 'number' && 0 <= +value && +value <= 1) {
            this.#a = value
        } else throw RangeError("ожидается число от 0 до 1 включительно")
    }

    get a() { return this.#a }

    set hex(hexColorA) {
        if (this.#regExpHexA.test(hexColorA)) {
            const arrHexColorA = hexColorA.match(this.#regExpHexA)
            this.#a = +((+(parseInt(arrHexColorA[4], 16).toString(10))) / 255).toFixed(2)
            super.hex = hexColorA.slice(0, -2)
        } else super.hex = hexColorA
    }

    get hex() {
        if (this.#a) {
            const hex = super.hex + (+(this.#a * 255).toFixed()).toString(16)
            return hex
        } else return super.hex
    }

    get rgba() {
        let rgb = (super.rgb).slice(4, -1)
        return 'rgba(' + rgb + ',' + this.#a + ')'
    }

    set rgba(value) {
        if (this.#regExpRGBA.test(value)) {
            const arrRgbaColor = value.match(this.#regExpRGBA)
            if (0 <= arrRgbaColor[4] && arrRgbaColor[4] <= 1) {
                this.#a = arrRgbaColor.pop()
                const [, ...newArr] = arrRgbaColor
                super.rgb = newArr.join(',')
                return this.rgb
            }
        } else { throw new SyntaxError }
    }

    set color(value) {
        if (this.#regExpRGBA.test(value)) {
            const arrRgbaColor = value.match(this.#regExpRGBA)
            if (0 <= arrRgbaColor[4] && arrRgbaColor[4] <= 1) {
                this.#a = arrRgbaColor.pop()
                const [, ...newArr] = arrRgbaColor
                super.rgb = newArr.join(',')
                return this.rgb
            }
        }
        else if (this.#regExpHexA.test(value)) {
            const arrHexColorA = value.match(this.#regExpHexA)
            this.#a = +((+(parseInt(arrHexColorA[4], 16).toString(10))) / 255).toFixed(2)
            super.hex = value.slice(0, -2)
        } else super.hex = value
    }
}

let aaa = new RGBA

const rgba = new RGBA
rgba.hex = '#80808080'
console.log(rgba.a) //0.5
console.log(rgba.rgba) //rgba(128,128,128,0.5)
rgba.r = 192
rgba.a = 0.25
console.log(rgba.hex)  //#C0808040
rgba.color = 'rgba(1,2,3,0.70)'
rgba.b *= 10
console.log(rgba.hex)  //#01021EB3