Source: calendar/calendar.js

const DISPLAY_TASK_COUNT = 2;
const RATING_FILES_NAMES = ["1angry.png", "2upset.png", "3neutral.png", "4happy.png", "5overjoyed.png"];
const PRODUCTIVITY_FILES_NAMES = ["1-icon.svg", "2-icon.svg", "3-icon.svg", "4-icon.svg", "5-icon.svg"];

// Wait for window to load
window.addEventListener('DOMContentLoaded', init);

// Get current date globals
var currDate = new Date();
var month = currDate.getMonth();
var year = currDate.getFullYear();

// Defines confetti
const confetti = window.confetti;

/**
 * Updates the global date variables to the current date.
 */
function updateDateGlobals() {
    month = currDate.getMonth();
    year = currDate.getFullYear();
}

/**
 * Initializes the page when the DOM content is fully loaded.
 */
function init() {
    // Initiaze the jump buttons
    displayJump(year - 6, year + 5);
    taskListViewHandler();

    // Initially display the calendar, calendar header, and task colors
    calendarHeader();
    displayCalendar();

    // Initialize the buttons 
    initButtons();

    loadTasks();
}

/**
 * Initializes the buttons for adding tasks, navigating months, and other functionalities.
 */
function initButtons() {

    const addTaskBtn = document.querySelector(".add-task-btn");
    addTaskBtn.addEventListener("click", () => {
        addTask();
    });

    // Previous month button
    let prevBtn = document.querySelector(".prev-date-btn");
    prevBtn.addEventListener('click', prev);

    // Next month button
    let nextBtn = document.querySelector(".next-date-btn");
    nextBtn.addEventListener('click', next);

    // Jump header buttons
    // List of months
    let monthJumpBtn = document.querySelectorAll(".month-btn");
    monthJumpBtn.forEach(btn => {
        btn.addEventListener("click", () => {
            let monthValue = btn.getAttribute("value");
            jump(monthValue, year);
        });
    });
    // List of years
    let yearJumpBtn = document.querySelectorAll(".year-btn");
    yearJumpBtn.forEach(btn => {
        btn.addEventListener("click", () => {
            let yearValue = btn.getAttribute("value");
            jump(month, yearValue);
        });
    });

    // Resize window for responsiveness
    window.addEventListener('resize', windowWidth);

    // Add left/right arrows to goto prev/next months
    window.addEventListener('keydown', function(event) {
        if (event.key === "ArrowLeft") {
            prev();
        } else if (event.key === "ArrowRight") {
            next();
        }
    });
}

/**
 * Updates the global currDate to the next date and displays the next month.
 */
function next(){
    // Increment the month
    currDate.setMonth(currDate.getMonth() + 1);
    updateDateGlobals();
    displayCalendar();
}

/**
 * Updates the global currDate to the previous month and displays the previous month's calendar.
 */
function prev() {
    // Decrement the month
    currDate.setMonth(currDate.getMonth() - 1);
    updateDateGlobals();
    displayCalendar();
}

/**
 * Adds a task to the task list upon "Add Task" button click.
 * 
 * @param {boolean} [loadTask=false] - Indicates whether the task is being loaded from storage.
 * @returns {HTMLElement} - The newly created task element.
 */
function addTask(loadTask = false) {
    // Add a task to an element of task container
    const taskList = document.querySelector(".task-container");
    const task = document.createElement("li");
    task.className = "task";
    
    // Add it at the first row
    task.insertAdjacentHTML("beforeend", `
        <div class="check-input-wrap">
            <button id="task1" class="task-checkbox" aria-label="Task Checkbox"></button>
            <div contenteditable="true" class="task-input" placeholder="Add a task..." onkeypress="return this.innerText.length <= 180;"></div>
        </div>
        <div class="color-buttons">
            <button id="purple" class="color-button" aria-label="Purle"></button>
            <button id="green" class="color-button" aria-label="Green"></button>
            <button id="blue" class="color-button" aria-label="Blue"></button>
            <button id="pink" class="color-button" aria-label="Pink"></button>
            <button id="grey" class="color-button" aria-label="Grey"></button>
        </div>
        <img class="trash-icon" src="../icons/trash-icon.svg" alt="Remove">
    `);
    task.querySelector(".task-input").addEventListener("input", saveTasks);


    taskList.append(task);

    // Listener to stop editing when user presses enter
    const task_name = task.querySelector(".task-input");
    task_name.addEventListener('keydown', function (event) {
        // Shift + Enter pressed, insert a line break
        if (event.key == 'Enter') {
            // Enter pressed, end editing
            if (!event.shiftKey) {
                // Prevent default behavior of Enter key
                event.preventDefault(); 

                // Remove focus from the element
                task_name.blur(); 
            }
        }
    });

    // Auto click into the task name text box
    if (loadTask == false){
        setTimeout(() => {
            task_name.focus();
            const selection = document.getSelection();
            if (selection.rangeCount > 0) {
                selection.collapseToEnd();
            }
        }, 0);
    }  

    // Add functionality to task buttons
    taskButtonsFunctionality(task);

    return task;
}

/**
 * Adds button functionality to a task upon creation.
 * 
 * @param {HTMLElement} task - The task element to add functionality to.
 */
function taskButtonsFunctionality(task) {
    // Implement color changing functionality 
    const colorBtns = task.querySelectorAll(".color-button");
    colorBtns.forEach(btn => {
        btn.addEventListener('click', function () {
            let color;
            switch (btn.id) {
                case "purple":
                    color = "#C380CC";
                    break;
                case "green":
                    color = "#91DC79";
                    break;
                case "blue":
                    color = "#6BB1D9";
                    break;
                case "pink":
                    color = "#EEBAE9";
                    break;
                default:
                    color = "var(--main-color)";
            }
            task.style['background-color'] = color;
            saveTasks();
        });
    });

    // Trash icon delete functionality
    const deleteIcon = task.querySelector(".trash-icon");
    deleteIcon.addEventListener("click", () => {
        task.remove();
        saveTasks();
    });

    // Checkbox move to completed tasks functionality
    const checkbox = task.querySelector(".task-checkbox");
    checkbox.addEventListener('click', function () {

        // Add or remove completed from class name
        if (task.className.includes('complete')) {
            task.classList.remove('complete');
            const taskContainer = document.querySelector('.task-container');
            taskContainer.appendChild(task);
            task.addEventListener("blur", saveTasks);
            saveTasks();
        }
        else {
            task.classList.add('complete');
            saveCompleted(task);
            task.remove()
            saveTasks();

            confetti({
                particleCount: 100,
                spread: 70,
                origin: { y: 0.6 }
            });
        }
    });
}

/**
 * Expands task list from collapsed state.
 */
function taskListViewHandler() {
    const taskList = document.querySelector('.task-list');
    const taskWrap = document.querySelector('.task-wrapper');
    const outSide = document.querySelector('.full-calendar');
    taskList.addEventListener('click', function(event) {
        if (event.target === taskList) {
            if (window.innerWidth <= 800) { 
                taskList.classList.toggle('active');
                taskWrap.classList.toggle('active');
            }
        }
    });
    outSide.addEventListener('click', function(){
        if (window.innerWidth <= 800) { 
            taskList.classList.remove('active');
            taskWrap.classList.remove('active');
        }
    });
}

/**
 * Saves the completed tasks for a specific day.
 * 
 * @param {HTMLElement} completedTaskElement - The task element that was completed.
 */
function saveCompleted(completedTaskElement) {
    let data = getJournal();
    let dateText = new Date().toLocaleDateString();
    let completedTask = loadFromStorage(data, dateText, "completedTasks") || [];
    let taskName = completedTaskElement.querySelector('.task-input').textContent;
    let taskColor = completedTaskElement.style['background-color']
    completedTask.push({
        text: taskName,
        color: taskColor,
    });
    saveToStorage(data, dateText, "completedTasks", completedTask);
    localStorage.setItem("journals", JSON.stringify(data));
    displayCalendar();
}

/**
 * Displays the calendar for the current month.
 */
function displayCalendar() {
    // Get body and clear current calendar
    let tbody = document.getElementById("tbody-calendar");
    tbody.innerHTML = "";

    // Initialize list of months
    let allMonths = [
        "January", "February", "March", "April", "May", "June",
        "July", "August", "September", "October", "November", "December"
    ];

    // Get day of the week of first day in given month
    let currCalendarMonth = new Date(year, month, 1);
    let today = new Date();
    let dayOffset = -(currCalendarMonth.getDay());

    // Get month and year header
    let monthHeader = document.getElementById("month");
    let yearHeader = document.getElementById("year");
    monthHeader.textContent = allMonths[parseInt(month, 10)];
    yearHeader.textContent = year;

    // Build Calendar
    // Loop through number of rows
    let currDay;
    for (let i = 0; i < 6; i++) {
        // Create rows
        let row = document.createElement("tr");

        // Loop through number of columns
        for (let j = 0; j < 7; j++) {
            // Create data for each table cell in the row
            let cellData = document.createElement("td");

            // Create span for cell number
            let cellNum = document.createElement('span');

            // Add offset to first date in calendar
            if (i === 0 && j === 0) {
                currCalendarMonth.setDate(currCalendarMonth.getDate() + dayOffset);
                currDay = currCalendarMonth.getDate();
            }
            // Increment date by one
            else {
                currCalendarMonth.setDate(currCalendarMonth.getDate() + 1);
                currDay = currCalendarMonth.getDate();
            }
            // Add number and class to cellNum
            cellNum.textContent = currDay;
            cellNum.className = "cell-date";
            let cellDate = new Date(currCalendarMonth);

            // If current month
            if (currCalendarMonth.getMonth() === currDate.getMonth()) {
                cellData.classList.add("curr-month-date-num");
            }
            // If other month
            else {
                cellData.classList.add("other-month-date-num");
            }

            // If cell is today
            if (currCalendarMonth.toDateString() === today.toDateString()) {
                cellData.classList.add("current-date");
            }
            // If cell is in the past
            else if (currCalendarMonth < today) {
                cellData.classList.add("past-date");
            }
            // If cell is in the future
            else {
                cellData.classList.add("future-date");
            }

            // Add cell number to calendar cell
            cellData.appendChild(cellNum);

            if (cellDate <= today) {
                loadCellDataTest(cellData, currCalendarMonth);
            }
            // Append new cell to row
            row.appendChild(cellData);
        }
        // Append row to table
        tbody.appendChild(row);
    }
    // Change the header if the window size is too small
    windowWidth();

    // Get the width of month and align the year 
    let monthWidth = document.getElementById('month-dropdown').offsetWidth;
    document.getElementById('year-dropdown').style.left = monthWidth + 5 + 'px';
}

/**
 * Loads cell data such as rating, productivity, and tasks for a specific date in the calendar.
 * 
 * @param {HTMLElement} cellData - The table cell element to populate with data.
 * @param {Date} currCalendarMonth - The current month being displayed in the calendar.
 */
function loadCellDataTest(cellData, currCalendarMonth) {
    let journals = getJournal();
    let dateText = currCalendarMonth.toLocaleDateString();

    let rating = loadFromStorage(journals, dateText, "rating");
    let productivity = loadFromStorage(journals, dateText, "productivity");
    let tasks = loadFromStorage(journals, dateText, "completedTasks");

    if (rating != null) {
        // Add sentiment icon
        let sentimentIcon = document.createElement("img");
        sentimentIcon.src = `../icons/${RATING_FILES_NAMES[rating - 1]}`;
        sentimentIcon.alt = "sentiment icon";
        sentimentIcon.className = "sentiment-icon";

        // Append sentiment icon to new cell
        cellData.appendChild(sentimentIcon);
    }

    if (productivity != null) {
        // Add productivity icon
        let productivityIcon = document.createElement("img");
        productivityIcon.src = `../icons/${PRODUCTIVITY_FILES_NAMES[productivity - 1 - 5]}`;
        productivityIcon.alt = "productivity icon";
        productivityIcon.className = "productivity-icon";

        // Append sentiment icon to new cell
        cellData.appendChild(productivityIcon);
    }

    // Add tasklist in calendar cell
    // Create tasklist div
    let taskDiv = document.createElement("div");
    taskDiv.className = "task-div";
    
    // Create unordered list
    let taskList = document.createElement("ul");
    taskList.className = "task-ul";

    if (tasks != null) {
        for (let i = 0; i < tasks.length && i < DISPLAY_TASK_COUNT; i++) {
            let taskItem = document.createElement("li");
            taskItem.textContent = tasks[i]["text"];
            taskItem.className = "task-item";
            taskItem.style.setProperty('--task-color', tasks[i]["color"]);
            taskList.appendChild(taskItem);
        }

        if (tasks.length > DISPLAY_TASK_COUNT) {
            // Handle extra tasks in calendar view
            let taskExtra = document.createElement("li");
            taskExtra.textContent = `${tasks.length - DISPLAY_TASK_COUNT} more tasks`;
            taskExtra.className = "task-indicator";
            taskList.appendChild(taskExtra);
        }
    }

    // Append taskList to task div;
    taskDiv.appendChild(taskList);

    // Append tasklist div to new cell
    cellData.appendChild(taskDiv);

    // Create buttons that link to speciic homepage and extract selected date
    let aLink = document.createElement("a");
    let dayLink = currCalendarMonth.getDate();
    let monthLink = currCalendarMonth.getMonth();
    let yearLink = currCalendarMonth.getFullYear()

    // Query is in format ?date=month-day-year
    aLink.href = `../homepage/homepage.html?date=${monthLink}-${dayLink}-${yearLink}`;
    aLink.className = "a-link";
    aLink.setAttribute("aria-label", `Link to details for ${monthLink + 1}/${dayLink}/${yearLink}`);
    cellData.appendChild(aLink);
}

/**
 * Generates a dropdown for year and month selection.
 * 
 * @param {number} startYear - The start year for the dropdown range.
 * @param {number} endYear - The end year for the dropdown range.
 */
function displayJump(startYear, endYear) {
    // Years
    let yearDropdown = document.getElementById("year-dropdown")

    // Loop through year range and append to list
    for (let yr = startYear; yr < endYear + 1; yr++) {
        let yearJump = document.createElement("button");
        yearJump.value = yr;
        yearJump.textContent = yr;
        yearJump.className = "year-btn";
        yearDropdown.appendChild(yearJump);

    }

    // Months
    let allMonths = [
        "January", "February", "March", "April", "May", "June",
        "July", "August", "September", "October", "November", "December"
    ];
    
    // Loop through months and append to list
    let monthDropdown = document.getElementById("month-dropdown")
    for (let mnth = 0; mnth < 12; mnth++) {
        let monthJump = document.createElement("button");
        monthJump.value = mnth;
        monthJump.textContent = allMonths[parseInt(mnth, 10)];
        monthJump.className = "month-btn";
        monthDropdown.appendChild(monthJump);
    }
}

/**
 * Function to jump to a specific month and year.
 * 
 * @param {number} mnth - month to jump to
 * @param {number} yr - year to jump to
 */
function jump(mnth, yr) {
    currDate = new Date(yr, mnth)
    updateDateGlobals();
    displayCalendar();
}

/**
 * Creates header of the calendar.
 */
function calendarHeader(){
    // Initialize list of days of the week
    let allDays = ["Sun", "Mon", "Tue", "Wed","Thu", "Fri", "Sat"];

    // Header of Days of the week
    let thead = document.getElementById("thead-weekheadings");
    let headerRow = document.createElement("tr");

    // Loop through allDays list and append day of week to row
    for (let dow of allDays) {
        let headerData = document.createElement("th");
        headerData.textContent = dow;
        headerRow.appendChild(headerData);
    }
    thead.appendChild(headerRow);
}

/**
 * Adjusts the month header text based on the window width.
 */
function windowWidth() {
    if (window.innerWidth < 920) {
        // Initialize list of abbreviated months
        let allMonths = [
            "Jan", "Feb", "Mar", "Apr", "May", "Jun",
            "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"
        ];

        let monthHeader = document.getElementById("month");
        monthHeader.textContent = allMonths[parseInt(month, 10)];
    }
    else {
        // Initialize list of months
        let allMonths = [
            "January", "February", "March", "April", "May", "June",
            "July", "August", "September", "October", "November", "December"
        ];
        let monthHeader = document.getElementById("month");
        monthHeader.textContent = allMonths[parseInt(month, 10)];
    }

    // Get the width of month and align the year 
    let monthWidth = document.getElementById('month-dropdown').offsetWidth;
    document.getElementById('year-dropdown').style.left = monthWidth + 5 + 'px';
}

/* ********** Storage and Population ********** */

// Get the all relevent elements from page
const tasks = document.querySelector(".task-container");

// Save journal entry and tasks to local storage on page unload
window.onbeforeunload = function () {
    saveTasks()
}

/**
 * Save journal entry to local storage.
 * 
 * @param {string} data - Journal entry text in parsed json format.
 * @param {string} dateText - Date of the journal entry in locale date string format.
 * @param {string} key - Key to store the value under.
 * @param {string} value - Value to store.
 */
function saveToStorage(data, dateText, key, value) {
    if (!(dateText in data)) {
        data[dateText] = {}
    }
    data[dateText][key] = value;
}

/**
 * Load journal entry from local storage.
 * 
 * @param {Object} data - Journal entry text in parsed JSON format.
 * @param {string} dateText - Date of the journal entry in locale date string format.
 * @param {string} key - Key to get the value from.
 * @returns {string|null} - Value of the key in the data or null if not found.
 */
function loadFromStorage(data, dateText, key) {
    if (!(dateText in data)) {
        return null;
    }
    return data[dateText][key];
}

/**
 * Get journal entry from local storage.
 * 
 * @returns {Object} - Journal entry text in parsed json format.
 */
function getJournal() {
    let data = JSON.parse(localStorage.getItem("journals"))
    if (data == null) {
        data = {}
    }
    return data;
}

/**
 * Save tasks to local storage.
 */
function saveTasks() {
    let tasks = [];
    document.querySelectorAll('.task-container li').forEach(task => {
        let taskName = task.querySelector('.task-input').textContent;
        let taskColor = task.style['background-color']
        tasks.push({
            text: taskName,
            color: taskColor,
        });
    });
    localStorage.setItem("tasks", JSON.stringify(tasks));
}

/**
 * Get tasks from local storage.
 *
 * @returns {string} - Tasks in parsed json format or empty array if no tasks.
 */
function getTasks() {
    let storedTasks = localStorage.getItem("tasks");
    return storedTasks ? JSON.parse(storedTasks) : [];
}

/**
 * Load tasks from local storage.
 */
function loadTasks() {
    let tasks = getTasks();
    if (tasks.length > 0) {
        tasks.forEach(task => {
            let curLi = addTask(true);
            curLi.querySelector(".task-input").textContent = task['text']
            curLi.style['background-color'] = task['color']
        });
    }

}

// Save journal entry and tasks to local storage on events.
tasks.addEventListener("blur", saveTasks)
tasks.addEventListener("change", saveTasks)