Source: scripts/helper/subtask.helper.js

/**
 * Updates the visibility of the add subtask icon and the subtask actions
 * depending on whether the input field is empty or not.
 */
function updateSubtaskIcons() {
  const inputField = document.getElementById("subtasks");
  const addIconElement = document.getElementById("add-icon");
  const subtaskActionsElement = document.getElementById("subtask-actions");
  const isEmpty = inputField.value.trim() === "";

  toggleElementVisibility(addIconElement, isEmpty);
  toggleElementVisibility(subtaskActionsElement, !isEmpty);
}

/**
 * Toggles the visibility of the given element based on the boolean value of
 * `isVisible`. If `isVisible` is true, sets the element's display style to
 * "flex". Otherwise, sets it to "none". Does nothing if the element is null or
 * undefined.
 *
 * @param {HTMLElement} element - The element to update
 * @param {boolean} isVisible - Whether the element should be visible or not
 */
function toggleElementVisibility(element, isVisible) {
  if (!element) return;

  element.style.display = isVisible ? "flex" : "none";
}

/**
 * Adds a new subtask to the subtask list if the input field contains text.
 * Generates a unique subtask ID, creates a new subtask list item, appends it
 * to the subtask list, and updates the global subTasks object with the new subtask.
 * Clears the input field and checks the need for scrollbar adjustments.
 */
function addSubtask() {
  const inputElement = document.getElementById("subtasks");
  const subtaskText = inputElement.value.trim();

  if (subtaskText) {
    const subtaskId = `SUBTODO_${Date.now()}`;
    const subtaskItem = createSubtaskListItem(subtaskText, subtaskId);
    const subtaskList = document.getElementById("subtask-list");
    subtaskList.appendChild(subtaskItem);
    subTasks[subtaskId] = { state: false, text: subtaskText };
    clearSubtaskInput();
    checkScrollbar();
  }
}

/**
 * Sets focus on the subtask input field, allowing the user to immediately start typing.
 * This function assumes that an element with the ID "subtasks" exists in the DOM.
 */
function focusSubtaskInput() {
  const subtaskInput = document.getElementById("subtasks");
  subtaskInput.focus();
}

/**
 * Loads the subtasks for a given todo item from the currentTodo object.
 * If the todo item does not have any subtasks, the function does nothing.
 * @param {Object} currentTodo - The todo item to load subtasks for.
 * @returns {void}
 */
function loadSubtasks(currentTodo) {
  if (!currentTodo.hasOwnProperty("subTasks")) return;

  const subtaskList = document.getElementById("subtask-list");
  if (!subtaskList) return;

  Object.keys(currentTodo.subTasks).forEach((key) => {
    const subtaskId = key;
    const subtaskText = currentTodo.subTasks[key].text;
    subTasks[subtaskId] = { state: currentTodo.subTasks[key].state, text: subtaskText };

    const subtaskItem = createSubtaskListItem(subtaskText, subtaskId);
    subtaskList.appendChild(subtaskItem);
  });
}

/**
 * Creates a new subtask list item with the given subtask text and ID.
 * The list item is given a double click event listener that calls the editSubtask function.
 * @param {string} subtaskText - The text of the subtask.
 * @param {string} subtaskId - The ID of the subtask.
 * @returns {HTMLLIElement} - The created subtask list item.
 */
function createSubtaskListItem(subtaskText, subtaskId) {
  const listItem = document.createElement("li");
  listItem.innerHTML = getSubtaskListItemTemplate(subtaskText);
  listItem.dataset.id = subtaskId;
  listItem.addEventListener("dblclick", () => editSubtask(listItem));

  return listItem;
}

/**
 * Starts an edit session on a subtask list item. The function is called when
 * the user double clicks on the text of a subtask list item. The function
 * creates a new text input element, sets its value to the current subtask text
 * and replaces the current subtask text element with the new input element.
 * The function also updates the icon container of the subtask list item to
 * include "accept" and "delete" icons.
 *
 * @param {HTMLElement} iconElement - The element that was double clicked to
 *   start the edit session. This element should be the "edit" icon of a subtask
 *   list item.
 * @returns {void}
 */
function editSubtask(iconElement) {
  const listItem = iconElement.closest("li");
  const subtaskTextElement = listItem.querySelector(".subtask-text");
  const currentText = subtaskTextElement.textContent;
  const inputField = document.createElement("input");
  inputField.type = "text";
  inputField.value = currentText;
  inputField.setAttribute("data-id", listItem.dataset.id);
  listItem.replaceChild(inputField, subtaskTextElement);
  const iconContainer = listItem.querySelector(".list-item-actions");
  iconContainer.innerHTML = getAcceptAndDeleteIconsTemplate();
}

/**
 * Saves the current edit session of a subtask list item. The function is called
 * when the user clicks on the "accept" icon of a subtask list item. The function
 * checks if the current subtask text is empty and if so, removes the subtask
 * entirely. If the current subtask text is not empty, it updates the
 * corresponding subtask in the subTasks object and replaces the current input
 * element with a span element containing the updated text. The function also
 * updates the icon container of the subtask list item to contain the "edit" and
 * "delete" icons again.
 *
 * @param {HTMLElement} iconElement - The element that was clicked to save the
 *   edit session. This element should be the "accept" icon of a subtask list
 *   item.
 * @returns {void}
 */
function saveEdit(iconElement) {
  const listItem = iconElement.closest("li");
  const inputElement = listItem.querySelector("input");
  const updatedText = inputElement.value.trim();
  const subtaskId = inputElement.getAttribute("data-id");

  if (!updatedText) {
    removeSubtask(iconElement);
    return;
  }

  if (subTasks[subtaskId]) subTasks[subtaskId].text = updatedText;

  const subtaskTextSpan = document.createElement("span");
  subtaskTextSpan.className = "subtask-text";
  subtaskTextSpan.textContent = updatedText;
  listItem.replaceChild(subtaskTextSpan, inputElement);
  const iconsContainer = listItem.querySelector(".list-item-actions");
  iconsContainer.innerHTML = getEditAndDeleteIconsTemplate();
}

/**
 * Removes the subtask list item that the given icon element belongs to, and
 * also removes the corresponding entry from the subTasks object. The function
 * is called when the user clicks the "delete" icon of a subtask list item.
 * @param {HTMLElement} iconElement - The element that was clicked to remove
 *   the subtask. This element should be the "delete" icon of a subtask list
 *   item.
 * @returns {void}
 */
function removeSubtask(iconElement) {
  const subtaskListItem = iconElement.closest("li");
  const subtaskId = subtaskListItem.dataset.id;

  if (subTasks[subtaskId]) delete subTasks[subtaskId];

  subtaskListItem.remove();
  checkScrollbar();
}

/**
 * Clears the input field of the subtask list and triggers an input event on
 * it, so that the UI is updated correctly.
 *
 * This function is called when the user adds a subtask, to clear the input
 * field and be ready for the next subtask.
 *
 * @returns {void}
 */
function clearSubtaskInput() {
  const subtaskInput = document.getElementById("subtasks");
  if (subtaskInput) {
    subtaskInput.value = "";
    subtaskInput.dispatchEvent(new Event("input"));
  }
}

/**
 * Toggles the visibility of the subtask modal wrapper, based on whether the
 * subtasks container has any children or not.
 *
 * @returns {void}
 */
function toggleSubtaskModalWrapperVisibility() {
  const subtaskModalWrapper = document.querySelector(".subtask-modal-wrapper");
  const subtasksContainer = document.querySelector(".bigCard-subtasks-container");

  subtaskModalWrapper.style.display = subtasksContainer.children.length > 0 ? "block" : "none";
}

/**
 * Toggles the state of a subtask in the given todo.
 *
 * @param {number} index - The index of the todo in the globalTodos array.
 * @param {string} subtaskKey - The key of the subtask to be toggled.
 *
 * @returns {Promise<void>} - Resolves when the subtask state has been toggled.
 */
async function toggleSubtask(index, subTaskKey) {
  const currentTodo = globalTodos[index];
  const { subTasks = {} } = currentTodo;
  const subtask = subTasks[subTaskKey];

  subTasks[subTaskKey] = { ...subtask, state: !subtask?.state };
  const response = await updateTodosInFirebase("guest", arrayToObject(globalTodos));
  if (!response.ok) showToastMessage("error", response);
  globalTodos[index] = { ...currentTodo, subTasks };

  updateSubTasksDisplay(index, subTaskKey);
  triggerRender();
}

/**
 * Updates the display of the big task card modal with the todo item at the given index.
 * This is called when a subtask is checked or unchecked, and the big card modal needs
 * to be updated to reflect the change.
 *
 * @param {number} index - The index of the todo item in the globalTodos array
 * @returns {void}
 */
function updateSubTasksDisplay(index, subTaskKey) {
  const todoItem = globalTodos[index];
  const subTask = todoItem.subTasks[subTaskKey];
  const imgElement = document.getElementById(`subTaskImageChecked${subTaskKey}`);
  const isChecked = subTask.state === true ? "subtask-checked.svg" : "subtask-non-checked.svg";
  imgElement.src = `./assets/svg/${isChecked}`;
}