(async () => {
    // check notification permission
    // This is useful to alert people that they should do something
    await Notification.requestPermission()
})()

const MAX_MESSAGES = 50

let channels = {}
let messages = {}
let selected_channel_id = null

/**
 * Display a new notification with the given title and the given body.
 * @param title The title of the notification
 * @param body The body of the notification
 * @param timeout The time (in milliseconds) after that the notification automatically closes. 0 to make indefinite. Default to 5000 ms.
 * @return Notification
 */
function showNotification(title, body, timeout = 5000) {
    Notification.requestPermission().then((status) => {
        if (status === 'granted')
            new Notification(title, {'body': body, 'icon': "/static/tfjm-192.png"})
    })
}

function selectChannel(channel_id) {
    let channel = channels[channel_id]
    if (!channel) {
        console.error('Channel not found:', channel_id)
        return
    }

    selected_channel_id = channel_id

    window.history.replaceState({}, null, `#channel-${channel['id']}`)

    let channelTitle = document.getElementById('channel-title')
    channelTitle.innerText = channel['name']

    let messageInput = document.getElementById('input-message')
    messageInput.disabled = !channel['write_access']

    redrawMessages()
}

function sendMessage() {
    let messageInput = document.getElementById('input-message')
    let message = messageInput.value
    messageInput.value = ''

    if (!message) {
        return
    }

    socket.send(JSON.stringify({
        'type': 'send_message',
        'channel_id': selected_channel_id,
        'content': message,
    }))
}

function setChannels(new_channels) {
    channels = {}
    let navTab = document.getElementById('nav-channels-tab')
    navTab.innerHTML = ''

    for (let channel of new_channels) {
        channels[channel['id']] = channel
        if (!messages[channel['id']])
            messages[channel['id']] = new Map()

        let navItem = document.createElement('li')
        navItem.classList.add('list-group-item')
        navItem.id = `tab-channel-${channel['id']}`
        navItem.setAttribute('data-bs-dismiss', 'offcanvas')
        navItem.onclick = () => selectChannel(channel['id'])
        navTab.appendChild(navItem)

        let channelButton = document.createElement('button')
        channelButton.classList.add('nav-link')
        channelButton.type = 'button'
        channelButton.innerText = channel['name']
        navItem.appendChild(channelButton)

        fetchMessages(channel['id'])
    }

    if (new_channels && (!selected_channel_id || !channels[selected_channel_id])) {
        if (window.location.hash)
            selectChannel(window.location.hash.substring(9))
        else
            selectChannel(Object.keys(channels)[0])
    }
}

function receiveMessage(message) {
    let scrollableContent = document.getElementById('chat-messages')
    let isScrolledToBottom = scrollableContent.scrollHeight - scrollableContent.clientHeight <= scrollableContent.scrollTop + 1

    messages[message['channel_id']].set(message['id'], message)
    redrawMessages()

    // Scroll to bottom if the user was already at the bottom
    if (isScrolledToBottom)
        scrollableContent.scrollTop = scrollableContent.scrollHeight - scrollableContent.clientHeight

    if (message['content'].includes("@everyone"))
        showNotification(channels[message['channel_id']]['name'], `${message['author']} : ${message['content']}`)
}

function fetchMessages(channel_id, offset = 0, limit = MAX_MESSAGES) {
    socket.send(JSON.stringify({
        'type': 'fetch_messages',
        'channel_id': channel_id,
        'offset': offset,
        'limit': limit,
    }))
}

function fetchPreviousMessages() {
    let channel_id = selected_channel_id
    let offset = messages[channel_id].size
    fetchMessages(channel_id, offset, MAX_MESSAGES)
}

function receiveFetchedMessages(data) {
    let channel_id = data['channel_id']
    let new_messages = data['messages']

    if (!messages[channel_id])
        messages[channel_id] = new Map()

    for (let message of new_messages)
        messages[channel_id].set(message['id'], message)

    // Sort messages by timestamp
    messages[channel_id] = new Map([...messages[channel_id].values()]
      .sort((a, b) => new Date(a['timestamp']) - new Date(b['timestamp']))
      .map(message => [message['id'], message]))

    redrawMessages()
}

function redrawMessages() {
    let messageList = document.getElementById('message-list')
    messageList.innerHTML = ''

    let lastMessage = null
    let lastContentDiv = null

    for (let message of messages[selected_channel_id].values()) {
        if (lastMessage && lastMessage['author'] === message['author']) {
            let lastTimestamp = new Date(lastMessage['timestamp'])
            let newTimestamp = new Date(message['timestamp'])
            if ((newTimestamp - lastTimestamp) / 1000 < 60 * 10) {
                let messageContentDiv = document.createElement('div')
                messageContentDiv.innerText = message['content']
                lastContentDiv.appendChild(messageContentDiv)
                continue
            }
        }

        let messageElement = document.createElement('li')
        messageElement.classList.add('list-group-item')
        messageList.appendChild(messageElement)

        let authorDiv = document.createElement('div')
        messageElement.appendChild(authorDiv)

        let authorSpan = document.createElement('span')
        authorSpan.classList.add('text-muted', 'fw-bold')
        authorSpan.innerText = message['author']
        authorDiv.appendChild(authorSpan)

        let dateSpan = document.createElement('span')
        dateSpan.classList.add('text-muted', 'float-end')
        dateSpan.innerText = new Date(message['timestamp']).toLocaleString()
        authorDiv.appendChild(dateSpan)

        let contentDiv = document.createElement('div')
        messageElement.appendChild(contentDiv)

        let messageContentDiv = document.createElement('div')
        messageContentDiv.innerText = message['content']
        contentDiv.appendChild(messageContentDiv)

        lastMessage = message
        lastContentDiv = contentDiv
    }

    let fetchMoreButton = document.getElementById('fetch-previous-messages')
    if (!messages[selected_channel_id].size || messages[selected_channel_id].size % MAX_MESSAGES !== 0)
        fetchMoreButton.classList.add('d-none')
    else
        fetchMoreButton.classList.remove('d-none')
}

function toggleFullscreen() {
    let chatContainer = document.getElementById('chat-container')
    if (!chatContainer.getAttribute('data-fullscreen')) {
        chatContainer.setAttribute('data-fullscreen', 'true')
        chatContainer.classList.add('position-absolute', 'top-0', 'start-0', 'vh-100', 'z-3')
        window.history.replaceState({}, null, `?fullscreen=1#channel-${selected_channel_id}`)
    }
    else {
        chatContainer.removeAttribute('data-fullscreen')
        chatContainer.classList.remove('position-absolute', 'top-0', 'start-0', 'vh-100', 'z-3')
        window.history.replaceState({}, null, `?fullscreen=0#channel-${selected_channel_id}`)
    }
}

document.addEventListener('DOMContentLoaded', () => {
    /**
     * Process the received data from the server.
     * @param data The received message
     */
    function processMessage(data) {
        switch (data['type']) {
            case 'fetch_channels':
                setChannels(data['channels'])
                break
            case 'send_message':
                receiveMessage(data)
                break
            case 'fetch_messages':
                receiveFetchedMessages(data)
                break
            default:
                console.log(data)
                console.error('Unknown message type:', data['type'])
                break
        }
    }

    function setupSocket(nextDelay = 1000) {
        // Open a global websocket
        socket = new WebSocket(
            (document.location.protocol === 'https:' ? 'wss' : 'ws') + '://' + window.location.host + '/ws/chat/'
        )

        // Listen on websockets and process messages from the server
        socket.addEventListener('message', e => {
            // Parse received data as JSON
            const data = JSON.parse(e.data)

            processMessage(data)
        })

        // Manage errors
        socket.addEventListener('close', e => {
            console.error('Chat socket closed unexpectedly, restarting…')
            setTimeout(() => setupSocket(2 * nextDelay), nextDelay)
        })

        socket.addEventListener('open', e => {
            socket.send(JSON.stringify({
                'type': 'fetch_channels',
            }))
        })
    }

    function setupSwipeOffscreen() {
        const offcanvas = new bootstrap.Offcanvas(document.getElementById('channelSelector'))

        let lastX = null
        document.addEventListener('touchstart', (event) => {
            if (event.touches.length === 1)
                lastX = event.touches[0].clientX
        })
        document.addEventListener('touchmove', (event) => {
            if (event.touches.length === 1 && lastX !== null) {
                const diff = event.touches[0].clientX - lastX
                if (diff > window.innerWidth / 10 && lastX < window.innerWidth / 4) {
                    offcanvas.show()
                    lastX = null
                }
                else if (diff < -window.innerWidth / 10) {
                    offcanvas.hide()
                    lastX = null
                }
            }
        })
        document.addEventListener('touchend', () => {
            lastX = null
        })
    }

    function setupPWAPrompt() {
        let deferredPrompt = null

        window.addEventListener("beforeinstallprompt", (e) => {
            e.preventDefault()
            deferredPrompt = e
            let btn = document.getElementById('install-app-home-screen')
            let alert = document.getElementById('alert-download-chat-app')
            btn.classList.remove('d-none')
            alert.classList.remove('d-none')
            btn.onclick = function () {
                deferredPrompt.prompt()
                deferredPrompt.userChoice.then((choiceResult) => {
                    if (choiceResult.outcome === 'accepted') {
                        deferredPrompt = null
                        btn.classList.add('d-none')
                        alert.classList.add('d-none')
                    }
                })
            }
        })
    }

    setupSocket()
    setupSwipeOffscreen()
    setupPWAPrompt()
})
