const startTime = Date.now();

var conversation = null;

class Conversation {
    constructor(name) {
        this.messages = [];
        this.name = romanize(name);
        this.score = 1.0;
    }

    setInteractive(isInteractive) {
        const children = document.getElementById("input-panel").children;
        for (let i = 0; i < children.length; i++) {
            children[i].disabled = !isInteractive;
        }
    }

    initialize(initialMessages) {
        document.title = this.name;
        document.getElementById("header-title").innerHTML = this.name;

        this.messages = initialMessages;
    }

    addMessage(message) {
        this.messages.push(message);
        this.render();

        var elements = document.getElementById("messages").children;
        var lastElement = elements[elements.length - 1];
        lastElement.scrollIntoView();
    }

    // for the user to send their own messages
    sendUserMessage(text) {

        const message = new UserMessage(text);
        message.updateStatus("sent");

        const url = 'http://192.168.1.115:5000/chat';
        const data = text;

        fetch(url, {
            method: 'POST', // Corresponds to -X POST
            headers: {
              'Content-Type': 'text/plain' // Corresponds to -H "Content-Type: text/plain"
            },
            body: data // Corresponds to -d "..."
        })
        .then(response => {
            // Check if the request was successful (status code 2xx)
            if (!response.ok) {
              // If not successful, throw an error to be caught by .catch()
              throw new Error(`HTTP error! status: ${response.status}`);
            }
            // Get the response body as text
            return response.text();
        })
        .then(response => {
            // TODO: check JSON
            const json = JSON.parse(response);

            // Success!
            var messageText = json.message;

            console.log(json);
            var score = parseFloat(json.score);
            this.score += score;
            console.log(this.score);
            if (this.score > 2.0)
            {
                messageText = "shit they're here D:";
                this.setInteractive(false);
            }
            else if (this.score < 0.0)
            {
                messageText = "shit u won :D";
                this.setInteractive(false);
            }
            else
            {
                messageText = json.message;
            }

            this.addMessage(new AgentMessage(messageText));
            this.render();
        })
        .catch(error => {
            // Handle any errors that occurred during the fetch
            console.error('Error during fetch:', error);
            alert(`Error fetching data: ${error.message}`);
        });

        setTimeout(() => {
            message.updateStatus("delivered");
            this.render();
            //setTimeout(() => {
            //    message.updateStatus("read");
            //    this.render();
            //}, 5000);
        }, 1000);
        this.addMessage(message);
    }

    // update the current HTML based on messages
    render() {
        // clear stale HTML 
        getMessageList().innerHTML = "";

        // if there are multiple 'read' messages, we only want the last one to display
        // the status. the other ones must have been read, so clear their statuses.
        let foundReadMessage = false;
        for (let i = this.messages.length - 1; i >= 0; i--) {
            const message = this.messages[i];
            if (!message.getIsOurs())
                continue;

            // if we haven't found a read message yet, let's check to see if we have now
            if (!foundReadMessage) {
                if (message.status == "read") {
                    foundReadMessage = true;
                    continue;
                }
            } else {
                // we have found a read message, which means all messages above it should
                // have empty status
                message.updateStatus("");
            }
        }

        // render message elements
        for (let i = 0; i < this.messages.length; i++)
        {
            const messageRoot = document.getElementById("messages");
            const newMessage = this.messages[i];
            messageRoot.appendChild(newMessage.getElement());
        }
    }
}

function getMessageList() {
    return document.getElementById("messages");
}

function romanize(text) {
    text = text.replaceAll('u', 'v');
    text = text.replaceAll('U', 'V');
    return text;
}

class AgentMessage {
    constructor(text, senderName) {
        this.text = text;
        this.senderName = senderName;
    }

    getIsOurs() {
        return false;
    }

    getElement() {
        const liElem = document.createElement("li");
        liElem.className = "message";

        const contentElem = document.createElement("span");
        contentElem.className = "message-content rounded-rectangle theirs";
        liElem.appendChild(contentElem);

        if (this.senderName) {
            const nameElem = document.createElement("h3");
            nameElem.innerHTML = romanize(this.senderName);
            contentElem.appendChild(nameElem);
        }

        const textElem = document.createElement("span");
        textElem.className = "message-text";
        textElem.innerHTML = romanize(this.text);
        contentElem.appendChild(textElem);

        return liElem;
    }
}

class UserMessage {
    constructor(text) {
        this.createdTime = Date.now();
        this.text = romanize(text);
        this.status = "";
    }

    getIsOurs() {
        return true;
    }

    getElement() {
        const liElem = document.createElement("li");
        liElem.className = "message";

        const contentElem = document.createElement("span");
        contentElem.className = "message-content rounded-rectangle ours";
        liElem.appendChild(contentElem);

        const textElem = document.createElement("span");
        textElem.className = "message-text";
        textElem.innerHTML = this.text;
        contentElem.appendChild(textElem);

        const statusElem = document.createElement("p");
        statusElem.className = "message-status";
        statusElem.innerHTML = this.status;
        liElem.appendChild(statusElem);

        return liElem;
    }

    updateStatus(newStatus) {
        this.status = newStatus;
    }
}

class SystemMessage {
    constructor(text) {
        this.text = romanize(text);
    }

    getIsOurs() {
        return false;
    }

    getElement() {
        const liElem = document.createElement("li");
        liElem.className = "system-message";
        liElem.innerHTML = this.text;

        return liElem;
    }
}

function setTypingIndicator(isTyping) {
    document.getElementById("typing-indicator").innerHTML = isTyping
        ? `${conversation.contactName} is typing...`
        : "";
}

// add the message at the index to the displayed messages
function addMessage(message) {
    getMessageList().innerHTML += message.getHtml();
    
    // scroll as far as we can so that messages aren't hidden
    window.scrollTo(0, document.body.scrollHeight);
}

function updatePings() {
    const title = conversation.name;
    let newTitle = conversation.pings > 0
        ? `(${conversation.pings}) ${title}`
        : title;
    
    document.title = newTitle;
}

function clearPings() {
    conversation.pings = 0;
    updatePings();
}

// returns a decimal value between min and max
function getRandomInRange(min, max) {
    const range = max - min;
    return min + Math.random() * range;
}

function updateChat(message) {
    addMessage(message);
    const previewText = conversation.getTypedMessageText();
    document.getElementById("textbox-input").value = previewText;
    updatePings();
}

function pressSendButton() {
    const textBox = document.getElementById("textbox-input");

    // get the content of the text box
    const text = textBox.value;
    if (!text)
        return;

    if (event.type == "keydown" && event.key != "Enter")
    {
        textBox.value = romanize(text);
        return;
    }

    // we have interacted with the page so remove all pings
    clearPings();

    textBox.value = "";

    conversation.sendUserMessage(text);
    conversation.render();

    // TODO: start process of receiving next message from server (or fake it for now)
}

function onMessageReceived(message) {
    updateChat(message);
    setTypingIndicator(false);
}

function onMessageSent(message) {
    updateChat(message);
}

// probably a bit hacky! but this saves having to do like, state or something in CSS?
// which probably is possible and probably would be the better way to do it, but that
// sounds like a bunch of learning i'm not SUPER in the mood for
function setVisibleOnMobile(element, isVisible) {
    let classes = element.className.split().filter(c => c != "");
    const invisibleClass = "invisible-on-mobile";
    const visibleClass = "visible";

    if (isVisible && !classes.includes(visibleClass)) {
        const idx = classes.indexOf(invisibleClass);
        if (idx != -1) {
            classes.splice(idx, 1);
        }
        classes.push(visibleClass);
    } else if (!isVisible && !classes.includes(invisibleClass)) {
        const idx = classes.indexOf(visibleClass);
        if (idx != -1) {
            classes.splice(idx, 1);
        }
        classes.push(invisibleClass);
    }

    element.className = classes.join(" ");
}

function showSidePanel() {
    // this function can only be called on mobile. the main conversation should be
    // hidden and the side conversations panel should take up the whole screen.

    const mainPanel = document.getElementById("main-panel");
    const conversationListElem = document.getElementById("side-panel");

    setVisibleOnMobile(mainPanel, false);
    setVisibleOnMobile(conversationListElem, true);
}

function readConversationJson(path, callback) {
    fetch(path)
        .then(response => response.json())
        .then(json => callback(json));
}

function showConversation(path) {

    const mainPanel = document.getElementById("main-panel");
    const conversationListElem = document.getElementById("side-panel");

    setVisibleOnMobile(mainPanel, true);
    setVisibleOnMobile(conversationListElem, false);


    readConversationJson(path, json => {
        conversation = new Conversation(json.title);
        const jsonMessages = json.messages;
        const participants = json.characters;

        let initialMessages = [];

        for (let i = 0; i < jsonMessages.length; i++) {
            const data = jsonMessages[i];
            const text = data.text;
            if (data.character == -1) {
                const message = new SystemMessage(text);
                initialMessages.push(message);
            } else if (data.character == 0) {
                const message = new UserMessage(text);
                message.updateStatus("read");
                initialMessages.push(message);
            } else {
                const message = participants.length > 2 
                    ? new AgentMessage(text, participants[data.character])
                    : new AgentMessage(text);
                initialMessages.push(message);
            }
        }

        conversation.initialize(initialMessages);
        conversation.setInteractive(json.interactive);
        conversation.render();
    });
}

function addConversationPreview(path) {
    const listRoot = document.getElementById("side-panel");

    readConversationJson(path, json => {
        const messages = json.messages;

        const elem = document.createElement("div");
        elem.onclick = () => showConversation(path);
        elem.className = "conversation";

        const headerElem = document.createElement("h2");
        headerElem.innerHTML = romanize(json.title);
        elem.appendChild(headerElem);

        const previewElem = document.createElement("span");
        previewElem.innerHTML = messages.length > 0 ? romanize(messages[messages.length - 1].text) : "";
        elem.appendChild(previewElem);

        listRoot.appendChild(elem);
    });
}

function populateConversationList() {
    const conversationFiles = [
        "caesar.json",
        "lucius.json",
        "ides-of-march.json",
        "lepidus.json",
        "publius.json",
        "sextus.json"
    ];

    for (let i = 0; i < conversationFiles.length; i++) {
        const path = conversationFiles[i];
        addConversationPreview(path);
    }
}

setTypingIndicator(false);
populateConversationList();

showConversation("ides-of-march.json");