// Chat
// Используя функцию jsonPost на адрес http://students.a-level.com.ua:10012 напишите чат-клиент, который:
{// jsonPost function
    function jsonPost(url, data) {
        return new Promise((resolve, reject) => {
            var x = new XMLHttpRequest()
            x.onerror = () => reject(new Error('jsonPost failed'))
            x.open('POST', url, true)
            x.send(JSON.stringify(data))

            x.onreadystatechange = () => {
                if (x.readyState == XMLHttpRequest.DONE && x.status == 200) {
                    resolve(JSON.parse(x.responseText))
                }
                else if (x.status != 200) {
                    reject(new Error('status is not 200'))
                }
            }
        })
    }

    // общий div
    const allChat = document.createElement('div')
    allChat.style.cssText = `
        margin: 0 auto;
        width: min-content;
        padding: 10px;
        border: 1px solid #e5e5e5;
        border-radius: 10px;
        box-shadow: 5px 5px 10px 0px rgb(173 173 173);
        `
    document.body.append(allChat)



    // Stage 1 - работает нормаль, отслеживаем nextMessageId и записываем его в getMessageId
    const chatDiv = document.createElement('div')
    chatDiv.style.cssText = `
        border: 1px solid #e5e5e5;
        width: 550px;
        padding: 10px;
        margin-bottom: 20px;
        `
    allChat.append(chatDiv)

    const nickName = document.createElement('input')
    nickName.type = 'text'
    nickName.placeholder = 'Введите свой никнейм'
    nickName.value = 'Anon'
    chatDiv.append(nickName)

    let lineBreak = document.createElement('br')
    chatDiv.append(lineBreak)

    const inputText = document.createElement('textarea')
    inputText.maxlength = 500
    inputText.required
    inputText.style.cssText = `
        resize: none;
        margin: 20px 0px;
        height: 90px;
        width: 90%;
        `
    inputText.placeholder = 'Введите текст сообщения'
    inputText.value = 'Hi everyone!'
    chatDiv.append(inputText)

    lineBreak = document.createElement('br')
    chatDiv.append(lineBreak)

    const sendBtn = document.createElement('button')
    sendBtn.innerText = 'Send'
    sendBtn.style.padding = '3px 40px'
    chatDiv.append(sendBtn)

    // отправка сообщения по нажатию кнопки
    let getMessageId = 0

    sendBtn.onclick = () => {
        async function sendMessage() {
            const result = jsonPost('http://students.a-level.com.ua:10012', { func: 'addMessage', nick: `${nickName.value}`, message: inputText.value })
            await result.then(res => getMessageId = res.nextMessageId)
            console.log(getMessageId)
            return result.data
        }
        sendMessage()
    }



    // stage2
    {
        const chat = document.createElement('div')
        chat.style.cssText = `
        min-height: 500px;
        padding: 10px;
        height: 60%;
        width: 550px;
        overflow-y: scroll;
        overflow-x: hidden;
        `
        allChat.append(chat)

        async function getAllMessages() {
            const result = await jsonPost('http://students.a-level.com.ua:10012', { func: "getMessages", messageId: 0 })

            const chat = document.createElement('div')
            chat.style.cssText = `
        min-height: 500px;
        padding: 10px;
        height: 60%;
        width: 550px;
        overflow-y: scroll;
        overflow-x: hidden;
        `
            allChat.append(chat)

            for (const message of ((result.data).sort((a, b) => b.timestamp - a.timestamp))) {

                const chatBlock = document.createElement('div')
                chatBlock.style.cssText = `
            border: 1px solid #e5e5e5;
            border-radius: 10px;
            width: 500px;
            padding: 10px;
            margin-bottom: 20px;
            box-shadow: 5px 5px 10px 0px rgb(173 173 173);
            `
                chat.append(chatBlock)

                for (const [key, value] of Object.entries(message)) {
                    const messageBlock = document.createElement('p')
                    messageBlock.style.margin = '5px 0'
                    messageBlock.innerHTML = `<span style = 'font-weight: 800;'>${key}</span>: ${typeof value === 'string' ? value : new Date(value)}`
                    chatBlock.append(messageBlock)
                }
            }
            return result.data
        }
        getAllMessages()
    }



    // stage3
    let getNewMessageId = 0

    const chat = document.createElement('div')
    chat.style.cssText = `
        min-height: 500px;
        padding: 10px;
        height: 60%;
        width: 550px;
        overflow-y: scroll;
        overflow-x: hidden;
        `
    allChat.append(chat)

    async function getMessages(param) {
        const result = await jsonPost('http://students.a-level.com.ua:10012', { func: "getMessages", messageId: param })

        for (const message of result.data) {

            const chatBlock = document.createElement('div')
            chatBlock.style.cssText = `
            border: 1px solid #e5e5e5;
            border-radius: 10px;
            width: 500px;
            padding: 10px;
            margin-bottom: 20px;
            box-shadow: 5px 5px 10px 0px rgb(173 173 173);
            `
            chat.prepend(chatBlock)

            for (const [key, value] of Object.entries(message)) {
                const messageBlock = document.createElement('p')
                messageBlock.style.margin = '5px 0'
                messageBlock.innerHTML = `<span style = 'font-weight: 800;'>${key}</span>: ${typeof value === 'string' ? value : new Date(value)}`
                chatBlock.append(messageBlock)
            }
        }
        getNewMessageId = result.nextMessageId
        return result.data
    }
    getMessages(getNewMessageId)



    // stage 4 
    const intervalId = setInterval(() => getMessages(getNewMessageId), 4000)


    // stage 5
    {
        function jsonPost(url, data) {
            return new Promise((resolve, reject) => {
                var x = new XMLHttpRequest()
                x.onerror = () => reject(new Error('jsonPost failed'))
                x.open('POST', url, true)
                x.send(JSON.stringify(data))

                x.onreadystatechange = () => {
                    if (x.readyState == XMLHttpRequest.DONE && x.status == 200) {
                        resolve(JSON.parse(x.responseText))
                    }
                    else if (x.status != 200) {
                        reject(new Error('status is not 200'))
                    }
                }
            })
        }

        // общий div
        const allChat = document.createElement('div')
        allChat.style.cssText = `
        margin: 0 auto;
        width: min-content;
        padding: 10px;
        border: 1px solid #e5e5e5;
        border-radius: 10px;
        box-shadow: 5px 5px 10px 0px rgb(173 173 173);
        `
        document.body.append(allChat)

        const chatDiv = document.createElement('div')
        chatDiv.style.cssText = `
        border: 1px solid #e5e5e5;
        width: 550px;
        padding: 10px;
        margin-bottom: 20px;
        `
        allChat.append(chatDiv)

        const nickName = document.createElement('input')
        nickName.type = 'text'
        nickName.placeholder = 'Введите свой никнейм'
        nickName.value = 'Anon'
        chatDiv.append(nickName)

        let lineBreak = document.createElement('br')
        chatDiv.append(lineBreak)

        const inputText = document.createElement('textarea')
        inputText.maxlength = 500
        inputText.required
        inputText.style.cssText = `
        resize: none;
        margin: 20px 0px;
        height: 90px;
        width: 90%;
        `
        inputText.placeholder = 'Введите текст сообщения'
        inputText.value = 'Hi everyone!'
        chatDiv.append(inputText)

        lineBreak = document.createElement('br')
        chatDiv.append(lineBreak)

        const sendBtn = document.createElement('button')
        sendBtn.innerText = 'Send'
        sendBtn.style.padding = '3px 40px'
        chatDiv.append(sendBtn)

        // отправка сообщения по нажатию кнопки
        let getMessageId = 0

        // async function sendMessage(nick, message) отсылает сообщение.
        async function sendMessage() {
            const result = jsonPost('http://students.a-level.com.ua:10012', { func: 'addMessage', nick: `${nickName.value}`, message: inputText.value })
            await result.then(res => getMessageId = res.nextMessageId)
            console.log(getMessageId)
            return result.data
        }

        // async function getMessages() получает сообщения и отрисовывает их в DOM

        const chat = document.createElement('div')
        chat.style.cssText = `
        min-height: 500px;
        padding: 10px;
        height: 60%;
        width: 550px;
        overflow-y: scroll;
        overflow-x: hidden;
        `
        allChat.append(chat)

        let getNewMessageId = 0
        async function getMessages(param) {
            const result = await jsonPost('http://students.a-level.com.ua:10012', { func: "getMessages", messageId: param })

            for (const message of result.data) {

                const chatBlock = document.createElement('div')
                chatBlock.style.cssText = `
            border: 1px solid #e5e5e5;
            border-radius: 10px;
            width: 500px;
            padding: 10px;
            margin-bottom: 20px;
            box-shadow: 5px 5px 10px 0px rgb(173 173 173);
            `
                chat.prepend(chatBlock)

                for (const [key, value] of Object.entries(message)) {
                    const messageBlock = document.createElement('p')
                    messageBlock.style.margin = '5px 0'
                    messageBlock.innerHTML = `<span style = 'font-weight: 800;'>${key}</span>: ${typeof value === 'string' ? value : new Date(value)}`
                    chatBlock.append(messageBlock)
                }
            }
            getNewMessageId = result.nextMessageId
            return result.data
        }
        getMessages(getNewMessageId)

        // async function sendAndCheck() использует две предыдущие для минимизации задержки между отправкой сообщения и приходом их.Именно эта функция должна запускаться по кнопке.
        async function sendAndCheck() {
            await Promise.all([sendMessage(), getMessages(getNewMessageId)])
        }

        sendBtn.onclick = () => sendAndCheck()

        // async function checkLoop() использует delay и бесконечный цикл для периодического запуска getMessages().
        async function checkLoop(param) {
            const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))

            while (true) {
                await delay(param).then(() => getMessages(getNewMessageId))
            }
        }
        checkLoop(5000)
    }



    // stage 6
    // Заменить внутренности jsonPost на код, использующий fetch вместо XMLHttpRequest.

    {
        function jsonPost(url, data) {
            return new Promise((resolve, reject) => {
                fetch(url, { method: 'POST', body: JSON.stringify(data) })
                    .then(res => res.json())
                    .then(data => resolve(data), () => reject(new Error('status is not 200')))

            })
        }

        // общий div
        const allChat = document.createElement('div')
        allChat.style.cssText = `
        margin: 0 auto;
        width: min-content;
        padding: 10px;
        border: 1px solid #e5e5e5;
        border-radius: 10px;
        box-shadow: 5px 5px 10px 0px rgb(173 173 173);
        `
        document.body.append(allChat)

        const chatDiv = document.createElement('div')
        chatDiv.style.cssText = `
        border: 1px solid #e5e5e5;
        width: 550px;
        padding: 10px;
        margin-bottom: 20px;
        `
        allChat.append(chatDiv)

        const nickName = document.createElement('input')
        nickName.type = 'text'
        nickName.placeholder = 'Введите свой никнейм'
        nickName.value = 'Anon'
        chatDiv.append(nickName)

        let lineBreak = document.createElement('br')
        chatDiv.append(lineBreak)

        const inputText = document.createElement('textarea')
        inputText.maxlength = 500
        inputText.required
        inputText.style.cssText = `
        resize: none;
        margin: 20px 0px;
        height: 90px;
        width: 90%;
        `
        inputText.placeholder = 'Введите текст сообщения'
        inputText.value = 'Hi everyone!'
        chatDiv.append(inputText)

        lineBreak = document.createElement('br')
        chatDiv.append(lineBreak)

        const sendBtn = document.createElement('button')
        sendBtn.innerText = 'Send'
        sendBtn.style.padding = '3px 40px'
        chatDiv.append(sendBtn)

        // отправка сообщения по нажатию кнопки
        let getMessageId = 0

        // async function sendMessage(nick, message) отсылает сообщение.
        async function sendMessage() {
            const result = jsonPost('http://students.a-level.com.ua:10012', { func: 'addMessage', nick: `${nickName.value}`, message: inputText.value })
            await result.then(res => getMessageId = res.nextMessageId)
            console.log(getMessageId)
            return result.data
        }

        // async function getMessages() получает сообщения и отрисовывает их в DOM

        const chat = document.createElement('div')
        chat.style.cssText = `
        min-height: 500px;
        padding: 10px;
        height: 60%;
        width: 550px;
        overflow-y: scroll;
        overflow-x: hidden;
        `
        allChat.append(chat)

        let getNewMessageId = 0
        async function getMessages(param) {
            const result = await jsonPost('http://students.a-level.com.ua:10012', { func: "getMessages", messageId: param })

            for (const message of result.data) {

                const chatBlock = document.createElement('div')
                chatBlock.style.cssText = `
            border: 1px solid #e5e5e5;
            border-radius: 10px;
            width: 500px;
            padding: 10px;
            margin-bottom: 20px;
            box-shadow: 5px 5px 10px 0px rgb(173 173 173);
            `
                chat.prepend(chatBlock)

                for (const [key, value] of Object.entries(message)) {
                    const messageBlock = document.createElement('p')
                    messageBlock.style.margin = '5px 0'
                    messageBlock.innerHTML = `<span style = 'font-weight: 800;'>${key}</span>: ${typeof value === 'string' ? value : new Date(value)}`
                    chatBlock.append(messageBlock)
                }
            }
            getNewMessageId = result.nextMessageId
            return result.data
        }
        getMessages(getNewMessageId)

        // async function sendAndCheck() использует две предыдущие для минимизации задержки между отправкой сообщения и приходом их.Именно эта функция должна запускаться по кнопке.
        async function sendAndCheck() {
            await Promise.all([sendMessage(), getMessages(getNewMessageId)])
        }

        sendBtn.onclick = () => sendAndCheck()

        // async function checkLoop() использует delay и бесконечный цикл для периодического запуска getMessages().
        async function checkLoop(param) {
            const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))

            while (true) {
                await delay(param).then(() => getMessages(getNewMessageId))
            }
        }
        checkLoop(5000)
    }
}


// SWAPI Links
// Напишите промисифицированную функцию, которая будет принимать параметром ссылку на swapi.dev(например, https://swapi.dev/api/people/20) и скачивать всю информацию по ссылке, включая вложенные ссылки. Все ссылки внутри скачанного объекта должны заменяться на скачанные объекты. Полученный общий объект должен стать результатом промиса, который возвращает функция.
{
    const swapiLinks = async (url) => {

        let result = {}
        let arrWithLinks = []
        let arrWithPromises = []

        const hero = await fetch(url)
        const heroData = await hero.json()

        for (const [key, values] of Object.entries(heroData)) {

            // сортируем массивы/ссылки/строки из fetch и складываем ссылки (обычные и из массивов) в новый массив
            if (Array.isArray(values)) {
                if (values.length > 0) {
                    for (const value of values) {
                        arrWithLinks.push({
                            [key]: value,
                        })
                    }
                } else {
                    result[key] = values
                }
            } else if (values.startsWith('https://swapi.dev/api/')) {
                arrWithLinks.push({
                    [key]: values,
                })
            } else {
                result[key] = values
            }
        }

        // итерируем новый массив со ссылками, запускаем параллельный fetch для каждой ссылки
        for (const item of arrWithLinks) {
            for (const [keyToParse, urlToParse] of Object.entries(item)) {
                arrWithPromises.push(fetch(urlToParse)
                    .then(res => res.json())
                    .then(res => {
                        let itemOfPromise = {
                            [keyToParse]: res
                        }
                        return itemOfPromise
                    }))
            }
        }

        // парсим все ссылки и возвращаем все в объект
        const resultsAll = Promise.all(arrWithPromises)
            .then(res => {
                // собираем все обратно в лоб
                let arrKeys = []
                let resultObj = {}

                // удаляем дубли повторяющихся ключей и пушим оригинальные в массив
                for (const item of res) {
                    for (const key in item) {
                        if (!(arrKeys.includes(key))) {
                            arrKeys.push(key)
                        }
                    }
                }

                // собираем все в один объект с массивами в ключах
                for (const item of arrKeys) {
                    resultObj[item] = []
                    for (const itemObject of res) {
                        for (const [key, value] of Object.entries(itemObject)) {
                            if (key === item) {
                                resultObj[item].push(value)
                            }
                        }
                    }
                }

                Object.assign(result, resultObj)

                return result  // расскомментировать, когда будет все готово. этот result отвечает за вывод всего в консоль
            })

        return resultsAll
    }

    swapiLinks("https://swapi.dev/api/people/20/")
        .then(res => console.log('FULL RESULT: ', JSON.stringify(res, null, 4)))
}



// domEventPromise
// Реализуйте промисифицированную функцию, которая резолвит промис по событию в DOM:
// Функция должна:
//      используя addEventListener повесить свой обработчик события на DOM element событие eventName
//      по событию зарезолвить промис отдав в качестве результата объект события
//      убрать обработчик с DOM - элемента, используя removeEventListener.
{
    function domEventPromise(element, eventName) {
        function executor(resolve) {
            function event(param) {
                resolve(param)
                element.removeEventListener(eventName, event)
            }
            element.addEventListener(eventName, event)
        }
        return new Promise(executor)
    }

    const btn = document.createElement('button')
    btn.innerHTML = `click`
    document.body.append(btn)

    domEventPromise(btn, 'click').then(e => console.log('event click happens', e))
}