{% extends "base.html" %}

{% block title %}Chatroom{% endblock %}

{% block content %}
    <div id="chat-container">
        <!-- Chat area where messages will be displayed -->
        <div id="chat"></div>
        <!-- Form for sending messages -->
        <form id="message-form">
            <textarea id="message" rows="4" placeholder="Type your message..."></textarea>
        </form>
    </div>

<script>
// Retrieve username and room name from the URL parameters
const urlParams = new URLSearchParams(window.location.search);
const username = urlParams.get("username");
const room_name = "{{ room_name }}";

// Configuration for DOMPurify to specify which tags and attributes are allowed
const dompurify_config = {
  ADD_TAGS: ["iframe", "img"],
  FORBID_TAGS: ["form"], 
  ALLOWED_ATTR: [
    "src", "width", "height", "frameborder", "allowfullscreen", 
    "alt", "class", "title", "style"
  ]
};

// keeping track of scrolling to prevent autoscrolling.
let userHasScrolledUp = false;

document.addEventListener('DOMContentLoaded', (event) => {
    const chatContainer = document.getElementById("chat");

    chatContainer.addEventListener('scroll', () => {
        const distanceFromBottom = chatContainer.scrollHeight - chatContainer.scrollTop - chatContainer.clientHeight;
        userHasScrolledUp = distanceFromBottom > 5;
    });
});

// Function to handle sending the message
function sendMessage() {
    const message = document.getElementById("message").value;
    if (message.trim() !== "") {  // Ensure we're not sending empty messages
        socket.emit("message", {"username": username, "message": message, "room_name": room_name});
        document.getElementById("message").value = "";
    }
}

// Function to handle deleting a message
function deleteMessage(messageId, room_name) {
    socket.emit("delete_message", {"message_id": messageId, "room_name": room_name});
}

// Event listener for form submission to send a message
document.getElementById("message-form").addEventListener("submit", (e) => {
    e.preventDefault();
    sendMessage();
});

// Event listener for the Enter key press in the textarea to send a message
document.getElementById("message").addEventListener("keydown", function(e) {
    if (e.key === "Enter" && !e.shiftKey) {
        e.preventDefault();
        sendMessage();
    }
});

// Socket event for updating the room title
socket.on("update_room_title", (data) => {
    document.title = data.title; // Update the window's title
});

// Socket event to update the room title in the sidebar.
socket.on('update_room_list', function(updatedRoom) {
    // Find the room list item by its data-room-id attribute
    const roomListItem = document.querySelector(`#rooms-list li[data-room-id="${updatedRoom.id}"]`);

    if (roomListItem) {
        // Update the room list item's content with the new title
        roomListItem.innerHTML = `<b>${updatedRoom.name}</b> ${updatedRoom.title ? '<br />' + updatedRoom.title : ''}`;
    }
});

// Socket event when the user connects
socket.on("connect", () => {
    socket.emit("join", {"username": username, "room_name": room_name});
});

// Socket event for receiving a new message
socket.on("message", (data) => {
    const messageWrapper = document.createElement("div");
    messageWrapper.className = "message-wrapper";
    messageWrapper.id = "message-" + data.id;

    const newMessage = document.createElement("div");
    newMessage.className = "message-content";

    // Check if the username is present and prepend it to the message content
    const messageContent = data.username ? `**${data.username}:**\n\n${data.content}` : data.content;

    // Check if the message starts with a base64 image tag
    if (data.content.startsWith('<img src="data:image/jpeg;base64')) {
        // Directly assign the message as innerHTML if it starts with a base64 image
        newMessage.innerHTML = data.content;
    } else {
        // Otherwise, sanitize and process the message with marked
        newMessage.innerHTML = DOMPurify.sanitize(marked.marked(messageContent), dompurify_config);
    }

    // Check if the message has an id which means we can delete it.
    if (data.id) {
        newMessage.dataset.rawMarkdown = data.content;

        // Create the delete button
        const deleteButton = document.createElement("button");
        deleteButton.innerHTML = "x";
        deleteButton.onclick = () => deleteMessage(data.id, room_name);
        messageWrapper.appendChild(deleteButton);

        // Create the edit button
        const editButton = document.createElement("button");
        editButton.textContent = "Edit";
        editButton.className = "edit-button";
        editButton.onclick = () => editMessage(data.id, newMessage, data.content);
        messageWrapper.appendChild(editButton);
    }

    messageWrapper.appendChild(newMessage);

    document.getElementById("chat").appendChild(messageWrapper);

    // Apply syntax highlighting to code blocks within the message
    newMessage.querySelectorAll("pre code").forEach((block) => {
        addCopyButtonToCodeBlock(block);
        truncateCodeBlock(block);
        hljs.highlightElement(block);
        addLineNumbers(block);
    });

    // Scroll to the bottom of the chat container to show the new message.
    if (data.id) {
        document.getElementById("chat").scrollTop = document.getElementById("chat").scrollHeight;
    }
});

// Socket event for receiving previous messages
socket.on("previous_messages", (data) => {

    if (document.getElementById("message-" + data.id)) {
        // If it exists, skip appending it
        return;
    }

    const messageWrapper = document.createElement("div");
    messageWrapper.className = "message-wrapper";
    messageWrapper.id = "message-" + data.id;

    const newMessage = document.createElement("div");
    newMessage.className = "message-content";

    // Check if the message contains a base64 image
    if (data.content.startsWith('<img src="data:image/jpeg;base64')) {
        // Directly assign the message as innerHTML if it's a base64 image
        newMessage.innerHTML = data.content;
    } else {
        // Otherwise, sanitize and process the message with marked
        newMessage.innerHTML = DOMPurify.sanitize(marked.marked(`**${data.username}:**\n\n${data.content}`), dompurify_config);
    }

    newMessage.dataset.rawMarkdown = data.content;

    // Create the delete button
    const deleteButton = document.createElement("button");
    deleteButton.innerHTML = "x";
    deleteButton.onclick = () => deleteMessage(data.id, room_name);
    messageWrapper.appendChild(deleteButton);

    // Create the edit button
    const editButton = document.createElement("button");
    editButton.textContent = "Edit";
    editButton.className = "edit-button";
    editButton.onclick = () => editMessage(data.id, newMessage);
    messageWrapper.appendChild(editButton);

    messageWrapper.appendChild(newMessage);

    document.getElementById("chat").appendChild(messageWrapper);

    // Apply syntax highlighting to code blocks within the message
    newMessage.querySelectorAll("pre code").forEach((block) => {
        addCopyButtonToCodeBlock(block);
        truncateCodeBlock(block);
        hljs.highlightElement(block);
        addLineNumbers(block);
    });

    // Scroll to the bottom of the chat container
    document.getElementById("chat").scrollTop = document.getElementById("chat").scrollHeight;

});

// Socket event for deleting a processing message
socket.on("delete_processing_message", (msg_id) => {
    const tempMessages = document.querySelectorAll("#message-null");
    tempMessages.forEach((tempMessage) => {
        tempMessage.remove();
    });
    // Clear the message buffer for the corresponding message ID
    delete messageBuffers[msg_id];
});

// A dictionary to hold buffers for each message ID
const messageBuffers = {};

// Socket event for receiving chunks of a message
socket.on("message_chunk", (data) => {
    const wrapperId = "message-" + data.id;
    let messageWrapper = document.getElementById(wrapperId);
    let targetMessageElement;

    // If the message wrapper doesn't exist, it's an initial chunk
    if (!messageWrapper) {
        messageWrapper = document.createElement("div");
        messageWrapper.className = "message-wrapper";
        messageWrapper.id = wrapperId;
        document.getElementById("chat").appendChild(messageWrapper);

        // Create the div element to hold the message content
        targetMessageElement = document.createElement("div");
        targetMessageElement.className = "message-content";

        // Create the "x" button for deletion
        const deleteButton = document.createElement("button");
        deleteButton.innerHTML = "x";
        deleteButton.onclick = () => deleteMessage(data.id, room_name);
        messageWrapper.appendChild(deleteButton);

        // Create the edit button
        const editButton = document.createElement("button");
        editButton.textContent = "Edit";
        editButton.className = "edit-button";
        editButton.onclick = () => editMessage(data.id, targetMessageElement);
        messageWrapper.appendChild(editButton);

        messageWrapper.appendChild(targetMessageElement);
    } else {
        // If the wrapper already exists, get the message-content div inside it
        targetMessageElement = messageWrapper.querySelector(".message-content");
    }

    // If the message buffer for this ID doesn't exist, create it
    if (!messageBuffers[data.id]) {
        messageBuffers[data.id] = "";
    }

    // Append the chunk to the buffer
    messageBuffers[data.id] += data.content;

    // Process the entire buffer with marked and set it as the content of the target element
    const sanitizedContent = DOMPurify.sanitize(marked.marked(messageBuffers[data.id]), dompurify_config);
    targetMessageElement.innerHTML = sanitizedContent;

    // Store the raw markdown in a data attribute for later use in editing
    targetMessageElement.dataset.rawMarkdown = messageBuffers[data.id];

    // Apply syntax highlighting to code blocks within the content
    targetMessageElement.querySelectorAll("pre code").forEach((block) => {
        addCopyButtonToCodeBlock(block);
        hljs.highlightElement(block);
        addLineNumbers(block);
    });

    // Scroll to the bottom of the chat container, but skip it if the user has scrolled up.
    if (!userHasScrolledUp) {
        document.getElementById("chat").scrollTop = document.getElementById("chat").scrollHeight;
    }
});


// Socket event for when a message is deleted
socket.on("message_deleted", (data) => {
    const messageElement = document.getElementById("message-" + data.message_id);
    if (messageElement) {
        messageElement.remove();
    }
});


// Socket event for when a message is updated
socket.on("message_updated", (data) => {
    // Find the existing message wrapper by ID
    const messageWrapper = document.getElementById("message-" + data.message_id);

    if (messageWrapper) {
        // Find the specific element that contains the message content
        const messageContentContainer = messageWrapper.querySelector(".message-content");

        // Update the message content
        if (data.content.startsWith('<img src="data:image/jpeg;base64')) {
            // If it's a base64 image, set it directly
            messageContentContainer.innerHTML = data.content;
        } else {
            // If it's not an image, sanitize and process the message with marked
            messageContentContainer.innerHTML = DOMPurify.sanitize(marked.marked(data.content), dompurify_config);
        }

        // Update the raw markdown stored in the data attribute
        messageContentContainer.dataset.rawMarkdown = data.content;

        // Apply syntax highlighting and other functionalities to code blocks within the message
        messageContentContainer.querySelectorAll("pre code").forEach((block) => {
            addCopyButtonToCodeBlock(block);
            truncateCodeBlock(block);
            hljs.highlightElement(block);
            addLineNumbers(block);
        });
    }
});


// Function to enter edit mode
function editMessage(messageId, messageContentContainer) {
    // Store the current HTML in a data attribute
    messageContentContainer.dataset.originalHtml = messageContentContainer.innerHTML;
    const rawMarkdown = messageContentContainer.dataset.rawMarkdown;

    // Create a textarea for editing
    const textarea = document.createElement("textarea");
    textarea.value = rawMarkdown;
    textarea.rows = 16;
    textarea.className = "message-edit";

    // Replace the message content with the textarea
    messageContentContainer.innerHTML = '';
    messageContentContainer.appendChild(textarea);

    // Find the message wrapper to access the edit and save buttons
    const messageWrapper = messageContentContainer.closest('.message-wrapper');

    // Create a save button with the 'save-button' class
    const saveButton = document.createElement("button");
    saveButton.textContent = "Save";
    saveButton.className = "save-button"; // Add the class here
    saveButton.onclick = () => saveEditedMessage(messageId, textarea, messageContentContainer);

    // Change the edit button to a cancel button
    const editButton = messageWrapper.querySelector(".edit-button");
    editButton.textContent = "Cancel";
    editButton.onclick = () => cancelEdit(messageId, messageContentContainer);

    // Append the save button next to the cancel button
    editButton.after(saveButton);
}

// Function to save the edited message
function saveEditedMessage(messageId, textarea, messageContentContainer) {
    // Get the updated markdown from the textarea
    const updatedMarkdown = textarea.value;

    // Emit the update_message event to the server
    socket.emit("update_message", {
        "message_id": messageId,
        "content": updatedMarkdown,
        "room_name": room_name
    });

    // Reset the edit button to its original state
    const messageWrapper = messageContentContainer.closest('.message-wrapper');
    const editButton = messageWrapper.querySelector(".edit-button");
    editButton.textContent = 'Edit';
    editButton.onclick = () => editMessage(messageId, messageContentContainer, updatedMarkdown);

    // Remove the save button using the 'save-button' class
    const saveButton = messageWrapper.querySelector(".save-button");
    if (saveButton) {
        saveButton.remove();
    }
}

// Function to cancel the edit and revert changes
function cancelEdit(messageId, messageContentContainer) {
    // Restore the original HTML of the message content from the data attribute
    messageContentContainer.innerHTML = messageContentContainer.dataset.originalHtml;

    // Reset the edit button to its original state
    const messageWrapper = messageContentContainer.closest('.message-wrapper');
    const editButton = messageWrapper.querySelector(".edit-button");
    editButton.textContent = 'Edit';
    editButton.onclick = () => editMessage(messageId, messageContentContainer, messageContentContainer.dataset.rawMarkdown);

    // Remove the save button using the 'save-button' class
    const saveButton = messageWrapper.querySelector(".save-button");
    if (saveButton) {
        saveButton.remove();
    }
}


function truncateCodeBlock(block, maxLines = 100) {
    // Split the content by new lines and check if it exceeds the maxLines
    const lines = block.textContent.split('\n');
    if (lines.length > maxLines) {
        // Store the full content in a data attribute
        block.dataset.fullContent = block.textContent;

        // Truncate the displayed content
        const truncatedText = lines.slice(0, maxLines).join('\n') + '\n...';
        block.textContent = truncatedText;

        // Create a button to expand the code block
        const expandButton = document.createElement('button');
        expandButton.textContent = 'Show More';
        expandButton.onclick = function() {
            // Restore the full content from the data attribute
            block.textContent = block.dataset.fullContent;
            // Reapply syntax highlighting
            hljs.highlightElement(block);
            addLineNumbers(block);
            // Change the button text to "Show Less"
            expandButton.textContent = 'Show Less';
            // Change the onclick function to truncate the block again
            expandButton.onclick = function() {
                block.textContent = truncatedText;
                // Reapply syntax highlighting
                hljs.highlightElement(block);
                addLineNumbers(block);
                // Change the button text back to "Show More"
                expandButton.textContent = 'Show More';
                // Set the onclick function back to the original expand function
                expandButton.onclick = originalExpandFunction;
            };
        };

        // Keep a reference to the original expand function
        const originalExpandFunction = expandButton.onclick;

        // Insert the expand button after the code block
        block.parentNode.insertBefore(expandButton, block.nextSibling);
    }
}


// Modify the addCopyButtonToCodeBlock function to use the full content
function addCopyButtonToCodeBlock(block) {
    // Check if the full content is stored in a data attribute, otherwise use textContent
    const contentToCopy = block.dataset.fullContent || block.textContent;

    // Create a button to copy the code block's content
    const copyButton = document.createElement('button');
    copyButton.textContent = 'Copy';
    copyButton.classList.add('copy-button'); // Add a class for styling if needed
    copyButton.onclick = function() {
        // Copy the content to the clipboard
        navigator.clipboard.writeText(contentToCopy).then(() => {
            // Optionally, indicate that the text was copied
            copyButton.textContent = 'Copied!';
            setTimeout(() => {
                copyButton.textContent = 'Copy';
            }, 2000); // Reset button text after 2 seconds
        }).catch(err => {
            console.error('Error copying text: ', err);
        });
    };

    // Insert the button before the code block
    block.parentNode.insertBefore(copyButton, block);
}

function addLineNumbers(block) {
    const lines = block.textContent.split('\n').length - 1;
    const lineNumbersWrapper = document.createElement('div');
    lineNumbersWrapper.className = 'line-numbers-rows';
    for (let i = 0; i < lines; i++) {
        lineNumbersWrapper.appendChild(document.createElement('span'));
    }
    block.appendChild(lineNumbersWrapper);
}
</script>
{% endblock %}
