Source: public/js/settings.js

/**
 * An array of available themes from the global window.THEMES.
 * @type {string[]}
 */
const themes = window.THEMES;

/**
 * An array of available layouts from the global window.LAYOUTS.
 * @type {string[]}
 */
const layouts = window.LAYOUTS;

/**
 * An array of available fonts from the global window.FONTS.
 * @type {string[]}
 */
const fonts = window.FONTS;

/**
 * Aggregated list of quotes from selected quote packs.
 * @type {string[]}
 */
let quotes = [];

/**
 * List of identifiers for the quote packs that are currently checked/selected.
 * @type {string[]}
 */
let checkedQuotes = [];

preload();

/**
 * Event listener that initializes quote handling after the window fully loads.
 */
window.addEventListener("load", () => {
    quotesLoad();
    updateQuotes();
    cycleQuotes();
  });

/**
 * Preloads user settings from localStorage and dynamically generates the UI elements
 * for selecting layouts, themes, and fonts. This includes:
 * - Applying the saved theme and font to the document.
 * - Populating layout menu with clickable layout options.
 * - Populating theme menu and fetching sample color values from each theme.
 * - Populating font menu with clickable font options.
 */
  function preload() {
    // Apply saved theme
    document.body.classList.add(localStorage.getItem("theme"));
    // Apply saved font
    document.body.style.fontFamily = localStorage.getItem("font");


    // Generate divs/buttons for settings selection
    // Layouts
    const layoutMenu = document.getElementById("layouts");
    for (let layout of layouts) {
      const layoutSelection = document.createElement("div");
      layoutSelection.classList.add("layout-selection");
      const layoutBody = document.createElement("div");
      layoutBody.classList.add("layout-body");
      layoutSelection.appendChild(layoutBody);
      layoutBody.id = layout;

          // Create dummy elements to simulate a calendar view.
      const dummyCalendar = document.createElement("div");
      dummyCalendar.id = "calendar";
      dummyCalendar.textContent = "calendar";
      layoutBody.appendChild(dummyCalendar);
      const dummyToday = document.createElement("div");
      dummyToday.id = "today";
      dummyToday.textContent = "tdy";
      layoutBody.appendChild(dummyToday);
      const dummyWeek = document.createElement("div");
      dummyWeek.id = "week";
      dummyWeek.textContent = "week";
      layoutBody.appendChild(dummyWeek);
      const dummyMonth = document.createElement("div");
      dummyMonth.id = "month";
      dummyMonth.textContent = "mnth";
      layoutBody.appendChild(dummyMonth);
      const dummyHabits = document.createElement("div");
      dummyHabits.id = "habits";
      dummyHabits.textContent = "hbts";
      layoutBody.appendChild(dummyHabits);

      if (layout === localStorage.getItem("layout")) {
        layoutSelection.id = "selected-layout";
      }

      layoutSelection.addEventListener("click", () => {
        setLayout(layout);
        document.getElementById("selected-layout").removeAttribute("id");
        layoutSelection.id = "selected-layout";
      });

      layoutMenu.appendChild(layoutSelection);
    }

    // Themes
    const themeMenu = document.getElementById("themes");
    const colorFetcher = document.createElement("div"); // will inherit the colors of the themes without updating the screen
    colorFetcher.style.display = "none";
    document.head.appendChild(colorFetcher); // add to head so it is not visible
    for (let theme of themes) {
      const themeSelection = document.createElement("div");
      themeSelection.classList.add("theme-selection");
      
      // retrieve 4 significant colors from the theme
      // will fetch background, text, primary, and secondary colors
      colorFetcher.className = theme;
      const backgroundColor = getComputedStyle(colorFetcher).getPropertyValue('--bg-color');
      const textColor = getComputedStyle(colorFetcher).getPropertyValue('--text-color');
      const primaryColor = getComputedStyle(colorFetcher).getPropertyValue('--primary-color');
      const secondaryColor = getComputedStyle(colorFetcher).getPropertyValue('--secondary-color');
      const buttonColor = getComputedStyle(colorFetcher).getPropertyValue('--button-bg-color');
      
      const backgroundDiv = document.createElement('div');
      backgroundDiv.style.backgroundColor = backgroundColor;
      const textColorDiv = document.createElement('div');
      textColorDiv.style.backgroundColor = textColor;
      const primaryDiv = document.createElement('div');
      primaryDiv.style.backgroundColor = primaryColor;
      const secondaryDiv = document.createElement('div');
      secondaryDiv.style.backgroundColor = secondaryColor;

      //themeSelection.textContent = "text";

      if (theme === localStorage.getItem("theme")) {
        themeSelection.id = "selected-theme";
      }
      themeSelection.addEventListener("click", () => {
        setTheme(theme);
        document.getElementById("selected-theme").removeAttribute("id");
        themeSelection.id = "selected-theme";
      });

      themeMenu.appendChild(themeSelection);
      themeSelection.appendChild(secondaryDiv);
      themeSelection.appendChild(primaryDiv);
      themeSelection.appendChild(textColorDiv);
      themeSelection.appendChild(backgroundDiv);
    }

    // Fonts
    const fontsMenu = document.getElementById("fonts");
    for (let font of fonts) {
      const fontSelection = document.createElement("div");
      fontSelection.classList.add("font-selection");
      fontSelection.textContent = "Tasty Pomodoro!";
      fontSelection.style.fontFamily = font;

      if (font === localStorage.getItem("font")) {
        fontSelection.id = "selected-font";
      }

      fontSelection.addEventListener("click", () => {
        setFont(font);
        document.getElementById("selected-font").removeAttribute("id");
        fontSelection.id = "selected-font";
      });

      fontsMenu.appendChild(fontSelection);
    }
  }

  /**
 * Sets the layout for the application by saving the selected layout to localStorage.
 *
 * @param {string} layout - The identifier of the layout to set.
 */
  function setLayout(layout) {
    localStorage.setItem("layout", layout);
  }

/**
 * Sets the theme for the application.
 * Removes any previously applied theme classes from the document body and applies the new theme.
 *
 * @param {string} theme - The identifier of the theme to set.
 */
  function setTheme(theme) {
    const body = document.body;
    localStorage.setItem("theme", theme);
    // remove the current theme
    for (let t of themes) {
      body.classList.remove(t);
    }
    body.classList.add(theme);
  }

  /**
 * Sets the font for the application by updating the body's font family style and saving the selection to localStorage.
 *
 * @param {string} font - The font-family name to set.
 */
  function setFont(font) {
    localStorage.setItem("font", font);
    document.body.style.fontFamily = font;
  }

/**
 * Loads saved quote pack selections from localStorage and initializes change event listeners
 * for each quote pack option in the quotes menu.
 *
 * Retrieves the saved "checkedQuotes" and applies the checked status to the corresponding DOM elements.
 */
function quotesLoad() {
  checkedQuotes = localStorage.getItem('checkedQuotes');
  console.log("checked quotes: " + checkedQuotes);
  const quoteMenu = document.getElementById("quotes");
  const quoteOptions = quoteMenu.children;
  for (let option of quoteOptions) {
    option = option.children[0];
    if (checkedQuotes != null && checkedQuotes.includes(option.value)) {
      option.checked = true;
    }
    option.addEventListener("change", () => {
      updateQuotes();
    });
  }
}

/**
 * Updates the quotes list based on the currently selected quote packs.
 * This function:
 * - Resets the quotes and checkedQuotes arrays.
 * - Iterates over the quote pack options in the DOM.
 * - Appends quotes from selected quote packs to the global quotes array.
 * - Shuffles the quotes if there are any.
 * - Saves the updated list of checked quotes to localStorage.
 */
function updateQuotes() {
  checkedQuotes = [];
  quotes = [];
  const quoteMenu = document.getElementById("quotes");
  const quoteOptions = quoteMenu.children;
  for (let option of quoteOptions) {
    option = option.children[0];
    if (option.checked) {
      checkedQuotes.push(option.value);
      quotes = quotes.concat(window[option.value]);
    }
  }
  if (quotes.length <= 0) {
    quotes.push('Select a quote pack.');
  } else {
    shuffleArray(quotes);
  }
  localStorage.setItem("checkedQuotes", checkedQuotes);
}

// https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
/**
 * Randomly shuffles the elements of the provided array in place using the Fisher–Yates algorithm.
 *
 * @param {Array} array - The array to shuffle.
 */
function shuffleArray(array) {
  for (let i = array.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [array[i], array[j]] = [array[j], array[i]];
  }
}

/**
 * Cycles through the quotes in the global quotes array, updating the quote display with a fade-out/in effect.
 * Displays each quote in sequence within the HTML element with the id "motivational-quote", changing quotes every 5 seconds.
 */
  function cycleQuotes() {
  const quoteElement = document.getElementById("motivational-quote");
    let quoteIndex = 0;
  
    // Function to display the next quote
      /**
   * Displays the next quote in the array. It first fades out the current quote,
   * updates the text, and then fades in the new quote. The fade effect is achieved
   * by toggling the "visible" class and using a timeout to match the CSS transition duration.
   */
    const showNextQuote = () => {
      // Remove 'visible' class to start fade-out
      quoteElement.classList.remove("visible");
  
      // After the fade-out transition ends, update the text and fade in
      setTimeout(() => {
        quoteElement.textContent = quotes[quoteIndex];
        quoteElement.classList.add("visible");
  
        // Update index to the next quote, looping back if necessary
        quoteIndex = (quoteIndex + 1) % quotes.length;
      }, 1000); // 1000ms matches the CSS transition duration
    };
  
    // Initially show the first quote
    showNextQuote();
  
    // Set interval to change quotes every 5 seconds
    setInterval(showNextQuote, 5000);
  }