// Get the link element
var md = null;
var references = {};
var tabs = [];
let saved = false;
let textDirty = false;
var to = null;
let controller;
var BOOKS = {};

$(function () {
    setHeight();
    setBooks();
    setEventListeners();
    $('#note-table').DataTable({
        paging: false,
        ajax: {
            url: '/get-notes',
            type: 'POST'
        },
        columns: [
            { data: 'link' },
            { data: 'speaker.name' },
            { data: 'passage' },
            {
                data: 'date.date',
                render: DataTable.render.date("L")
            },
        ]
    });
    $('#shareBtn').on('click', openShareNote);
    $('#modal-backdrop').on('click', closeShareNote);
});

/**
 * Fetches data from '/js/data.json', assigns it to BOOKS, and handles errors.
 *
 * @return {void}
 */
function setBooks() {
    fetch('js/data.json')
        .then((res) => {
            if (!res.ok) {
                throw new Error('HTTP Error: Status: ${res.status}');
            }
            return res.json();
        })
        .then((data) => {
            BOOKS = data;
        })
        .catch((error) => {
            console.log(error);
        })
}

/**
 * Sets event listeners for keyup events on the document and the '#notes' element.
 *
 * @return {void}
 */
function setEventListeners() {
    document.addEventListener('keyup', function (event) {
        if (event.key == "F3") {
            openRef(false);
        }
    });

    document.querySelector('#notes').addEventListener('keyup', function (event) {
        let key = event.keyCode;

        if (key >= 48 && key <= 90 || key >= 96 && key <= 111 || key >= 186 && key <= 222) {
            textDirty = true;
            document.querySelector('#note-header-left h2').classList.add('dirty');
        }
    });
}

/**
 * Sets the height of various elements on the page based on the window's inner height.
 * Also initializes a datepicker and event listener for the search input field.
 *
 * @return {void}
 */
function setHeight() {
    md = new markdownit({
        html: true,
        linkify: true,
        breaks: true
    });

    body = document.querySelector('body');
    body.style.height = window.innerHeight + 'px';

    cont = document.querySelector('#main');
    cont.style.height = (window.innerHeight) + 'px';

    tabs = document.querySelector('.ref-tab');
    tabs.style.height = (window.innerHeight - 13) + 'px';

    ref = document.querySelector('.ref');
    ref.style.height = (window.innerHeight - 60) + 'px';

    noteList = document.querySelector('#note-list');
    noteList.style.height = (window.innerHeight - 60) + 'px';

    notes = document.querySelector('.notes');
    notes.style.height = (window.innerHeight - 60) + 'px';

    notePreview = document.querySelector('#notePreview');
    notePreview.style.height = (window.innerHeight - 50) + 'px';

    if ($('#noteDate')) {
        $('#noteDate').datepicker();
    }

    if ($('#query')) {
        document.querySelector('#query').addEventListener('keyup', function (event) {
            if (event.key == "Enter") {
                search();
            }
        });
    }
    if (!to) {
        to = setTimeout(saveNote, saveInterval);
    }
}

/**
 * Searches for notes based on the query entered in the search field.
 * Sends a POST request to the '/search' endpoint with the query as a JSON payload.
 * Updates the '#old-notes' element with the search results.
 *
 * @return {Promise} A Promise that resolves with the search results.
 */
function search() {
    query = document.querySelector('#query').value;
    fetch('/search', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            'query': query
        })
    })
        .then(response => response.json())
        .then(results => {
            var oldNotes = document.querySelector('#old-notes');
            oldNotes.innerHTML = '';
            for (var n in results) {
                var link = document.createElement('a');
                link.href = '#';
                link.setAttribute('onclick', "retrieveNote('" + results[n].id + "');openNote();");
                link.innerHTML = results[n].title;

                var p = document.createElement('p');
                p.innerHTML = results[n].passage;

                var article = document.createElement('article');
                article.appendChild(link);
                article.appendChild(p);

                oldNotes.append(article);
            }
        });
}

/**
 * Resets the state of the note editor by clearing the text and form fields,
 * resetting the references, and removing any dirty classes. It also sets the
 * date to the current date, clears the speaker, series, template, passage,
 * recording, and note ID fields. Finally, it clears the reference list and
 * reference display.
 *
 * @return {void} This function does not return anything.
 */
function newNote() {
    notes = document.querySelector('#notes');
    notes.text = '';
    notes.value = '';
    references = {};
    saved = true;
    textDirty = false;
    document.querySelector('#note-header-left h2').classList.remove('dirty');

    dt = new Date();
    document.querySelector('#noteDate').value = dt.getFullYear() + '-' +
        (dt.getMonth() < 9 ? '0' + (dt.getMonth() + 1) : (dt.getMonth() + 1)) + '-' +
        (dt.getDate() < 10 ? '0' + dt.getDate() : dt.getDate());
    document.querySelector('#noteTitle').value = '';
    document.querySelector('#speaker').value = 0;
    document.querySelector('#series').value = 0;
    document.querySelector('#template').value = 0;
    document.querySelector('#passage').value = '';
    document.querySelector('#recording').value = '';
    document.querySelector('#noteId').value = '';

    document.querySelector('#ref-list').innerHTML = '';
    document.querySelector('#ref').innerHTML = '';
    document.querySelector('.toggle').click();
}

/**
 * Save a note by sending it to the server for storage.
 *
 * @param {Event} event - The event object triggering the save action.
 * @return {void} No explicit return value.
 */
function saveNote(event) {
    if (event) {
        event.preventDefault();
    }

    if (!textDirty || !validateNote()) {
        clearTimeout(to);
        to = setTimeout(saveNote, saveInterval);
        return;
    }

    let saveCheck = document.querySelector('#save-check');
    var noteText = document.querySelector('#notes').value;

    startSave();

    var note = {
        id: document.querySelector("#noteId").value,
        date: document.querySelector('#noteDate').value,
        title: document.querySelector('#noteTitle').value,
        speaker: document.querySelector('#speaker').value,
        series: document.querySelector('#series').value,
        passage: document.querySelector('#passage').value,
        note: document.querySelector('#notes').value,
        recording: document.querySelector('#recording').value,
        refs: references
    };
    $.ajax({
        url: '/save-note',
        method: 'POST',
        contentType: 'application/json',
        data: JSON.stringify(note),
        dataType: 'json',
        timeout: saveTimeout
    })
        .done(function (data) {
            if (data.msg == 'saved' && !saved) {
                saveFailureCount = SAVE_FAILURE_LIMIT;
                saveCheck.classList.remove('saving', 'error', 'fa-times-circle', 'fa-save');
                showSave();
                if (noteText == document.querySelector('#notes').value) {
                    saved = true;
                    textDirty = false;
                    document.querySelector('#note-header-left h2').classList.remove('dirty');
                }

                if (data.new) {
                    document.querySelector('#noteId').value = data.id;
                }
            }
        })
        .fail(function (xhr, status, error) {
            saveFailureCount--;
            saveCheck.classList.remove('saving', 'fa-save');
            saveCheck.classList.add('fa-times-circle', 'error');
            console.error(error);
        })
        .always(function (xhr, status) {
            if (status == 'timeout') {
                saveCheck.classList.remove('saving', 'fa-save');
                saveCheck.classList.add('error', 'fa-times-circle');
            }
            clearTimeout(to);
            if (saveFailureCount > 0) {
                to = setTimeout(saveNote, saveInterval);
            } else {
                saveFailureCount = SAVE_FAILURE_LIMIT;
            }
        });
}

/**
 * Validates a note by checking if all required fields are filled.
 *
 * @return {boolean} Returns true if all required fields are filled, false otherwise.
 */
function validateNote() {
    const note = document.querySelector('#notes');
    const date = document.querySelector('#noteDate');
    const speaker = document.querySelector('#speaker');
    const series = document.querySelector('#series');
    const title = document.querySelector('#noteTitle');
    const psg = document.querySelector('#passage');

    if (!title.value.length) { return false; }
    if (!date.value) { return false; }
    if (!parseInt(speaker.value)) { return false; }
    if (!parseInt(series.value)) { return false; }
    if (!psg.value) { return false; }
    if (!note.value.length) { return false; }

    return true;
}

/**
 * Checks if a given UUID is valid.
 *
 * @param {string} uuid - The UUID to be validated.
 * @return {boolean} Returns true if the UUID is valid, false otherwise.
 */
function isUuidValid(uuid) {
    const regex = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[8|9|a|b][a-f0-9]{3}-[a-f0-9]{12}$/i;
    return regex.test(uuid);
}

/**
 * Starts the save process by updating the save-check element's classList, removing error, fa-times-circle, and fa-save classes,
 * adding the 'saving' and 'fa-save' classes, and setting the opacity to 1.
 *
 * @return {void} This function does not return anything.
 */
function startSave() {
    document.querySelector('#save-check').classList.remove('error', 'fa-times-circle', 'fa-save');
    document.querySelector('#save-check').classList.add('saving', 'fa-save');
    document.querySelector('#save-check').style.opacity = 1;
}

/**
 * Displays a checkmark animation on the screen.
 *
 * @param {none} - This function does not take any parameters.
 * @return {none} - This function does not return any value.
 */
function showSave() {
    if (saved) { return; }

    var checkmark = document.getElementById("save-check");
    checkmark.classList.add('fa-save');

    // Schedule the animation to run every 1 second (which is equivalent to a 1-second delay between each iteration)
    var si = setInterval(function () {
        // Increment the opacity of the checkmark by 0.01 each time
        op = parseFloat(checkmark.style.opacity);
        checkmark.style.opacity = op - 0.1;

        // If the opacity is greater than or equal to 1, reset it back to 0 and stop the animation
        if (checkmark.style.opacity == 0.1) {
            checkmark.style.opacity = 0;
            clearInterval(si);
            saved = false;
        }
    }, 100);
}

/**
 * Function to discard the note by clearing all input fields and closing the menu.
 */
function deleteNote(noteId, link) {
    document.querySelector('#noteTitle').value = '';
    document.querySelector('#speaker').value = 0;
    document.querySelector('#series').value = 0;
    document.querySelector('#template').value = 0;
    document.querySelector('#passage').value = '';
    document.querySelector('#notes').value = '';
    document.querySelector('#recording').value = '';
    document.querySelector('#noteDate').value = '';
    document.querySelector('#noteId').value = '';

    var row = link.parentElement.parentElement;

    fetch('/delete-note', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            'id': noteId
        })
    })
        .then(response => response.json())
        .then(data => {
            if (data.msg != 'deleted') {
                return;
            }

            alert('Note deleted.');
            row.remove();
        });
}

/**
 * Toggles the display of the new speaker input field and hides the speaker select field.
 *
 * @return {void} This function does not return anything.
 */
function newSpeaker() {
    if (document.querySelector('#speaker').value == 'new') {
        document.querySelector('#newSpeaker').style.display = 'inline-block';
        document.querySelector('#speaker').style.display = 'none';
    }

    saved = false;
    textDirty = true;
}

/**
 * Saves a new speaker to the database and updates the UI with the new speaker option.
 *
 * @param {Event} event - The keydown event triggered by the user.
 * @return {Promise} A Promise that resolves with the results of the fetch request.
 */
function saveSpeaker(event) {
    if (event.keyCode == 13) {
        fetch('/save-speaker', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                'speakerName': document.querySelector('#newSpeaker').value
            })
        })
            .then(response => response.json())
            .then(results => {
                var newSpeaker = document.createElement('option');
                newSpeaker.text = document.querySelector('#newSpeaker').value;
                newSpeaker.value = results.id;
                document.querySelector('#speaker').add(newSpeaker);

                alert(results.msg);
                document.querySelector('#newSpeaker').style.display = 'none';
                document.querySelector('#speaker').style.display = 'inline-block';

                document.querySelector('#newSpeaker').value = '';
                document.querySelector('#speaker').value = results.id;
            });
    }
}

/**
 * A description of the entire function.
 */
function newSeries() {
    if (document.querySelector('#series').value == 'new') {
        document.querySelector('#newSeries').style.display = 'inline-block';
        document.querySelector('#series').style.display = 'none';
    }

    saved = false;
    textDirty = true;
}

/**
 * Saves a series by making a POST request to '/save-series' with the series name as the request body.
 *
 * @param {Event} event - The keydown event.
 * @return {Promise} A Promise that resolves with the response from the server.
 */
function saveSeries(event) {
    if (event.keyCode == 13) {
        fetch('/save-series', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                'seriesName': document.querySelector('#newSeries').value
            })
        })
            .then(response => response.json())
            .then(results => {
                var newSeries = document.createElement('option');
                newSeries.text = document.querySelector('#newSeries').value;
                newSeries.value = results.id;
                document.querySelector('#series').add(newSeries);

                alert(results.msg);
                document.querySelector('#newSeries').style.display = 'none';
                document.querySelector('#series').style.display = 'inline-block';

                document.querySelector('#newSeries').value = '';
                document.querySelector('#series').value = results.id;
            });
    }
}

/**
 * Opens the reference with the option to close the sidebar.
 *
 * @param {boolean} closeSidebar - Indicates whether to close the sidebar when opening the reference.
 */
function openRef(closeSidebar = true) {
    document.querySelector('#openRefBtn').classList.add('active');

    refQuery = document.querySelector('#refQuery');
    refQuery.style.display = 'block';

    ref = document.querySelector('#ref');
    refQuery.style.left = ref.offsetLeft + 'px';
    refQuery.style.top = ref.offsetTop + 'px';
    if (closeSidebar) {
        document.querySelector('.toggle').click();
    }
}

/**
 * Closes the reference query and resets the reference search form.
 *
 * @return {void} This function does not return anything.
 */
function closeRef() {
    document.querySelector('#referenceSearch').value = '';
    document.querySelector('#referenceSearch').style.display = '';
    document.querySelector('#referenceType').value = '';
    document.querySelector('#referenceBook').value = '';
    document.querySelector('#referenceBook').style.display = 'none';
    document.querySelector('#chapter-range').innerText = '';
    document.querySelector('#verse-range').innerText = '';

    document.querySelector('#refQuery').style.display = 'none';
    document.querySelector('#openRefBtn').classList.remove('active');
}

/**
 * Fetches a reference based on the provided type, book, and input.
 *
 * @param {string} type - The type of reference.
 * @param {string} book - The book of the reference.
 * @param {string} input - The input for the reference.
 * @return {void} This function does not return anything directly, but processes the fetched reference data.
 */
function queryRef(type = null, book = null, input = null) {
    if (!input) {
        var input = document.querySelector('#refQuery #referenceSearch').value;
    }
    if (!type) {
        var type = document.querySelector('#referenceType').value;
    }
    if (!book) {
        var book = document.querySelector('#referenceBook').value;
    }
    fetch('/retrieve-reference', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            'type': type,
            'book': book,
            'reference': input,
        })
    })
        .then(response => response.json())
        .then(results => {
            const list = document.querySelector('#ref-list');
            var newList = document.createElement('li');
            newList.className = 'tab';
            button = makeButton(results.title);
            newList.appendChild(button);
            list.appendChild(newList);

            const ref = document.querySelector('#ref');
            ref.innerHTML = md.render(results.text);

            references[results.title] = results.text;

            closeRef();

            saved = false;
            textDirty = true;
            saveNote();
            findRefLinks();
        });
}

/**
 * A function to create a button element with the specified title and event listeners for click and double click actions.
 *
 * @param {string} title - The title to be displayed on the button.
 * @return {Element} The created button element.
 */
function makeButton(title) {
    var btn = document.createElement('button');
    btn.innerText = title;
    btn.class = 'button';
    btn.style = 'line-height:normal;'
    btn.addEventListener('click', function () {
        removeActiveRef();
        document.querySelector('#ref').innerHTML = md.render(references[title]);
        this.classList.add('active');
        findRefLinks();
    });

    btn.addEventListener('dblclick', function () {
        document.querySelector('#ref').innerHTML = '';
        delete references[title];
        var list = this.parentElement;
        list.remove();
        saved = false;
        textDirty = true;
        saveNote();
    });

    removeActiveRef();
    btn.classList.add('active');

    return btn;
}

/**
 * Removes the 'active' class from all elements with the class 'active'.
 *
 * @return {void} This function does not return a value.
 */
function removeActiveRef() {
    tabs = document.querySelectorAll('.active');
    for (var t in tabs) {
        if (isFinite(parseInt(t))) {
            tabs[t].classList.remove('active');
        }
    }
}

/**
 * Retrieves a template from the server and sets it as the value of a specified destination element.
 *
 * @param {string} orig - The ID of the element containing the original template value.
 * @param {string} dest - The ID of the destination element where the retrieved template will be set.
 * @return {Promise} A Promise that resolves when the template is successfully retrieved and set as the value of the destination element.
 */
function retrieveTemplate(orig, dest) {
    const temp = document.querySelector('#' + orig);
    if (temp.value == '0') {
        document.querySelector('#' + dest).value = '';
        return;
    }
    fetch('/retrieve-template', {
        method: 'POST',
        headers: {
            'Content-Type': 'plain/text'
        },
        body: JSON.stringify({
            'template': temp.value
        })
    })
        .then(response => response.text())
        .then(results => {
            const div = document.querySelector('#' + dest);
            div.value = results;
        });
}

/**
 * Saves the template by sending a POST request to the server with template data.
 */
function saveTemplate() {
    fetch('/save-template', {
        method: 'POST',
        headers: {
            'Content-Type': 'plain/text'
        },
        body: JSON.stringify({
            'template_id': document.querySelector('#template_id').value,
            'template_name': document.querySelector('#template_name').value,
            'template_value': document.querySelector('#template_value').value,
        })
    })
        .then(response => response.text())
        .then(results => {
            alert(results);
        });
}

/**
 * Toggles the visibility of the fields container and updates the active state of the show/hide button.
 *
 * @return {void}
 */
function toggleFields() {
    const fieldsContainer = document.getElementById('fields-container');
    const showHideBtn = document.getElementById('show-hide-btn');

    if (fieldsContainer.classList.contains('show')) {
        fieldsContainer.classList.remove('show');
        fieldsContainer.style.display = 'none';
        showHideBtn.classList.remove('active');
    } else {
        fieldsContainer.classList.add('show');
        fieldsContainer.style.display = 'block';
        showHideBtn.classList.add('active');
    }

    setHeight();
}

/**
 * Retrieves the list of books based on the selected reference type.
 *
 * @return {void}
 */
function retrieveBooks() {
    document.querySelector('#chapter-range').innerText = '';
    document.querySelector('#verse-range').innerText = '';
    document.querySelector('#referenceSearch').value = '';
    document.querySelector('#referenceSearch').style.display = 'none';
    const selectedType = document.querySelector('#referenceType').value;
    if (!selectedType) { return; }

    var bookList = document.querySelector('#referenceBook');
    bookList.style.display = "block";
    bookList.innerHTML = '';
    if (selectedType == 'bible') {
        document.querySelector('#referenceSearch').style.display = 'block';
        var none = document.createElement("option");
        none.value = '';
        none.text = '-- Select --';
        bookList.appendChild(none);
        for (var x in BOOKS.bible) {
            var newBook = document.createElement("option");
            newBook.text = x;
            bookList.appendChild(newBook);
        }
    } else if (selectedType == 'creed') {
        var none = document.createElement('option');
        none.value = '';
        none.text = '-- Select --';
        bookList.appendChild(none);
        for (var x in BOOKS.creed) {
            var newBook = document.createElement('option');
            newBook.value = x;
            newBook.text = BOOKS.creed[x];
            bookList.appendChild(newBook);
        }
    } else if (selectedType == 'cd') {
        var none = document.createElement("option");
        none.value = '';
        none.text = '-- Select --';
        bookList.appendChild(none);
        for (var x in BOOKS.cd) {
            var newBook = document.createElement("option");
            newBook.text = BOOKS.cd[x];
            bookList.appendChild(newBook);
        }
    } else if (selectedType == 'hc') {
        var none = document.createElement("option");
        none.value = '';
        none.text = '-- Select --';
        bookList.appendChild(none);
        for (var x in BOOKS[selectedType]) {
            var newBook = document.createElement("optgroup");
            newBook.label = "Lord's Day " + (parseInt(x) + 1)
            var ld = document.createElement("option");
            ld.value = 'ld' + (parseInt(x) + 1);
            ld.text = "LD " + (parseInt(x) + 1) + " All";
            newBook.appendChild(ld);

            for (var y in BOOKS[selectedType][x]) {
                var question = document.createElement("option");
                question.value = 'hc' + BOOKS[selectedType][x][y];
                question.text = "HC" + BOOKS[selectedType][x][y];
                newBook.appendChild(question);
            }
            bookList.appendChild(newBook);
        }
    } else if (selectedType == 'note') {
        var none = document.createElement("option");
        none.value = '';
        none.text = '-- Select --';
        bookList.appendChild(none);

        fetch('/retrieve-reference', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                'type': 'note'
            })
        })
            .then(response => response.json())
            .then(results => {
                for (var x in results) {
                    var newBook = document.createElement("option");
                    newBook.value = results[x].id;
                    newBook.text = results[x].title;
                    bookList.appendChild(newBook);
                }
            });
    } else {
        var min = BOOKS[selectedType][0];
        var max = BOOKS[selectedType][1];
        var none = document.createElement("option");
        none.value = '';
        none.text = '-- Select --';
        bookList.appendChild(none);
        for (var x = min; x <= max; x++) {
            var newBook = document.createElement("option");
            newBook.value = x;
            newBook.text = x;
            bookList.appendChild(newBook);
        }
    }
}

/**
 * Filters the books based on the selected reference type and updates the chapter range.
 *
 * @return {void} This function does not return anything.
 */
function filterBooks() {
    document.querySelector('#chapter-range').innerText = '';
    document.querySelector('#verse-range').innerText = '';
    if (document.querySelector('#referenceType').value != 'bible') {
        return;
    }

    var bookList = document.querySelector('#referenceBook');
    var book = BOOKS.bible[bookList.value];
    var max = Object.keys(book).length;

    var chapterRange = document.querySelector('#chapter-range');
    chapterRange.innerText = 'Chapters: ' + max;
}

/**
 * Filters the verse based on the selected book and chapter.
 *
 * @return {void} This function does not return anything.
 */
function filterVerse() {
    if (document.querySelector('#referenceType').value != 'bible') {
        return;
    }

    var bookList = document.querySelector('#referenceBook').value;
    var search = document.querySelector('#referenceSearch').value;
    var chapter = search.split(':')[0];
    var verseRange = document.querySelector('#verse-range');

    if (!BOOKS.bible[bookList] || !BOOKS.bible[bookList][chapter]) {
        verseRange.innerText = 'Unknown Chapter';
        return;
    }
    var verse = BOOKS.bible[bookList][chapter];
    verseRange.innerText = 'Verse: ' + verse;
}

/**
 * Retrieves the reference type from the server and populates the reference series dropdown.
 *
 * @param {HTMLElement} el - The element that triggered the function.
 * @return {Promise} A promise that resolves with the response from the server.
 */
function retrieveReferenceType(el) {
    fetch('/reference/' + el.value, {
        method: 'GET',
        header: {
            'Content-Type': 'application/json'
        }
    })
        .then(response => response.json())
        .then(results => {
            document.querySelector('#referenceSeries').innerHTML = '';
            var none = document.createElement('option');
            none.value = '';
            none.text = '-- Select --';
            document.querySelector('#referenceSeries').appendChild(none);

            for (var x in results) {
                var newSeries = document.createElement('option');
                newSeries.value = results[x].id;
                newSeries.text = results[x].label;
                document.querySelector('#referenceSeries').appendChild(newSeries);
            }
        })
}

/**
 * Retrieves a reference based on the provided element value.
 *
 * @param {Element} el - The element triggering the reference retrieval
 * @return {void} No return value
 */
function retrieveReference(el) {
    if (el.value == 'new') {
        document.querySelector('#refName').style.display = 'inline-block';
        return;
    }
    fetch('/get-reference', {
        method: "POST",
        header: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            id: el.value
        })
    })
        .then(response => response.json())
        .then(results => {
            document.querySelector('#reference').value = results.text;
        });
}

/**
 * Saves a reference by sending a POST request to the server with the selected type,
 * file, and text values. Displays an alert with the response message, and clears
 * the reference and file input fields.
 *
 * @return {Promise} A Promise that resolves with the response message from the server.
 */
function saveReference() {
    let ref = document.querySelector('#referenceSeries');
    let cont = document.querySelector('#reference');
    fetch('/save-reference', {
        method: 'POST',
        header: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            refId: ref.value,
            text: cont.value
        })
    })
        .then(response => response.json())
        .then(results => {
            alert(results.msg);

            document.querySelector('#reference').value = '';
            document.querySelector('#references').value = '';
            document.querySelector('#referenceType').value = '';
            document.querySelector('#referenceSeries').value = '';
        });
}

/**
 * Previews a note by rendering the markdown content of the note in a preview section.
 * Toggles between the note text and preview sections.
 *
 * @return {void} This function does not return anything.
 */
function previewNote() {
    var noteText = document.querySelector('#notes');
    var notePreview = document.querySelector('#notePreview');
    var previewButton = document.querySelector('#previewBtn');

    const title = document.querySelector('#noteTitle');
    const speaker = document.querySelector('#speaker');
    const passage = document.querySelector('#passage');

    const markdownPreview = "# " + title.value + " - " +
        speaker.options[speaker.selectedIndex].text + " - " + passage.value + "\n\n" + noteText.value;

    notePreview.innerHTML = md.render(markdownPreview);

    if (previewButton.classList.contains('active')) {
        noteText.style.display = 'block';
        notePreview.style.display = 'none';
        previewButton.classList.remove('active');
    } else {
        noteText.style.display = 'none';
        notePreview.style.display = 'block';
        previewButton.classList.add('active');
    }

    findLinks();
}

/**
 * Finds all links in the note preview and adds event listeners to them.
 *
 * @return {void}
 */
function findLinks() {
    var links = document.querySelector('#notePreview').querySelectorAll('a');

    for (var i = 0; i < links.length; i++) {
        links[i].addEventListener('click', function (e) {
            e.preventDefault();
            if (!this.href.includes('get-passage')) {
                return;
            }
            var passage = this.href.split('/');
            passage = passage[passage.length - 1];

            fetch(this.href, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    'passage': passage
                })
            })
                .then(response => response.text())
                .then(result => {
                    passage = passage.replace(/\+/g, ' ');
                    psg = passage.split(' ');
                    if (psg.length > 2) {
                        book = psg[0] + ' ' + psg[1];
                        cv = psg[2];
                    } else {
                        book = psg[0];
                        cv = psg[1];
                    }
                    showPassage(
                        e,
                        "<button onclick='closePopup()'>Close</button>&nbsp;&nbsp;" +
                        "<button onclick=\"queryRef('bible', '" + book + "', '" + cv + "')\">Open Ref</button><br/>" +
                        result);
                });
        });
    }
}

/**
 * Function that finds reference links and fetches passage data when clicked.
 */
function findRefLinks() {
    var links = document.querySelector('#ref').querySelectorAll('a');

    for (var i = 0; i < links.length; i++) {
        links[i].addEventListener('click', function (e) {
            e.preventDefault();
            if (!this.href.includes('get-passage')) {
                return;
            }
            var passage = this.href.split('/');
            passage = passage[passage.length - 1];

            fetch(this.href, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    'passage': passage
                })
            })
                .then(response => response.text())
                .then(result => {
                    passage = passage.replace(/\+/g, ' ');
                    psg = passage.split(' ');
                    if (psg.length > 2) {
                        book = psg[0] + ' ' + psg[1];
                        cv = psg[2];
                    } else {
                        book = psg[0];
                        cv = psg[1];
                    }
                    showPassage(
                        e,
                        "<button onclick='closePopup()'>Close</button>&nbsp;&nbsp;" +
                        "<button onclick=\"queryRef('bible', '" + book + "', '" + cv + "')\">Open Ref</button><br/>" +
                        result);
                });
        });
    }
}

/**
 * Shows a passage in a popup element relative to the cursor position.
 *
 * @param {Event} event - The event that triggered the function.
 * @param {string} text - The text to be displayed in the popup.
 * @return {void} This function does not return a value.
 */
function showPassage(event, text) {
    // Create a new div element for the popup
    const popup = document.querySelector('#passage-popup');
    popup.innerHTML = md.render(text);

    // Position the popup relative to the cursor
    let x = event.clientX + window.scrollX;
    let y = event.clientY + window.scrollY;

    // Set the position of the popup element
    popup.style.top = `${y}px`;
    popup.style.left = `${x}px`;
    popup.style.display = 'block';
}

/**
 * Closes the passage popup by clearing its content and hiding it.
 *
 * @return {void} This function does not return anything.
 */
function closePopup() {
    const popup = document.querySelector('#passage-popup');
    popup.innerHTML = '';
    popup.style.display = 'none';
}

/**
 * Toggles the visibility of the note list and reference elements.
 *
 * @param {boolean} [openSidebar=true] - Whether to open the sidebar after toggling the visibility.
 * @return {void}
 */
function openNote(openSidebar = true) {
    const noteList = document.querySelector('#note-list');
    const refs = document.querySelector('#ref');

    if (noteList.style.display == 'block') {
        noteList.style.display = 'none';
        refs.style.display = 'block';
    } else {
        noteList.style.display = 'block';
        refs.style.display = 'none';
    }

    if (openSidebar) {
        document.querySelector('.toggle').click();
    }
}

/**
 * Retrieves a note from the server based on the provided ID.
 *
 * @param {string} id - The ID of the note to retrieve.
 * @param {boolean} [runOpen=true] - Whether to open the note sidebar after retrieving the note.
 * @return {Promise<void>} A promise that resolves when the note is successfully retrieved and the UI is updated.
 */
function retrieveNote(id, runOpen = true) {
    fetch('/get-note', {
        method: 'POST',
        header: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            'id': id
        })
    })
        .then(response => response.json())
        .then(result => {
            var dt = new Date(result.date.date);

            document.querySelector('#notes').value = result.text;
            document.querySelector('#passage').value = result.passage;
            document.querySelector('#series').value = result.series.id;
            document.querySelector('#speaker').value = result.speaker.id;
            document.querySelector('#noteTitle').value = result.title;
            document.querySelector('#noteDate').value = '';
            document.querySelector('#noteDate').value =
                (dt.getMonth() < 9 ? '0' + (dt.getMonth() + 1) : (dt.getMonth() + 1)) + '/' +
                (dt.getDate() < 10 ? '0' + dt.getDate() : dt.getDate()) + '/' +
                dt.getFullYear();
            document.querySelector('#noteId').value = result.id;

            if (result.refs) {
                references = result.refs;
            }

            const list = document.querySelector('#ref-list');
            list.innerHTML = '';
            var newList = null;
            for (var x in references) {
                var newList = document.createElement('li');
                newList.className = 'tab';
                var button = makeButton(x);
                newList.appendChild(button);
                list.appendChild(newList);
            }

            if (runOpen) {
                openNote(false);
            }
        });
}

/**
 * Opens the share note functionality.
 */
function openShareNote() {
    var id = document.querySelector('#noteId').value;
    if (!id) {
        alert('No Open Note Found');
        return;
    }

    bd = document.querySelector('#modal-backdrop');
    bd.style.display = 'block';
    cont = document.querySelector('#modal-container');
    cont.style.display = bd.style.display;

    emailCont = document.querySelector('#modal-container');
    emailCont.style.left = ((window.innerWidth / 2) - (emailCont.clientWidth / 2)) + 'px';
    emailCont.style.top = ((window.innerHeight / 2) - (emailCont.clientHeight / 2)) + 'px';
}

/**
 * Closes the share note modal by hiding the backdrop and container,
 * and clears the email input value.
 */
function closeShareNote() {
    var bd = document.querySelector('#modal-backdrop');
    var cont = document.querySelector('#modal-container');
    bd.style.display = 'none';
    cont.style.display = 'none';
    document.querySelector('#shareEmail').value = '';
}

/**
 * Function to share a note by sending the note ID and email to the server.
 */
function shareNote(event) {
    var id = document.querySelector('#noteId').value;
    var email = document.querySelector('#shareEmail').value;
    if (!id || !email) {
        alert('Invalid Input');
        return;
    }

    fetch('/share-note', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            'id': id,
            'email': email
        })
    })
        .then(response => response.json())
        .then(result => {
            if (result) {
                alert(result.msg);
            }
        });

    closeShareNote();
}

/**
 * Increases the font size of the element with the id 'ref' by 1 point.
 *
 * @return {void} This function does not return a value.
 */
function increaseFont() {
    var currentSize = document.querySelector('#ref').style.fontSize;
    document.querySelector('#ref').style.fontSize = (parseInt(currentSize) + 1) + 'pt';
}

/**
 * Decreases the font size of the element with the id 'ref' by 1 point.
 *
 * @return {void} This function does not return a value.
 */
function decreaseFont() {
    var currentSize = document.querySelector('#ref').style.fontSize;
    document.querySelector('#ref').style.fontSize = (parseInt(currentSize) - 1) + 'pt';
}

/**
 * Generates a random UUIDv4 string.
 *
 * @return {string} The generated UUIDv4 string.
 */
function uuidv4() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
        .replace(/[xy]/g, function (c) {
            const r = Math.random() * 16 | 0,
                v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
}