Source: scripts/helper/utility.helper.js

/**
 * Listens for a click event on the document and checks if the target of the event
 * is not the dropdown, its toggler, or the search input. If the conditions are
 * met, the dropdown is hidden.
 *
 * @param {{ target: HTMLElement }} event - The event object from the click event.
 * @param {string} dropdownId - The id of the dropdown to be hidden.
 * @param {string} iconId - The id of the icon to be rotated.
 * @returns {void}
 */
function outsideClickListener(event, dropdownId, iconId) {
  var dropdown = document.getElementById(dropdownId);
  var icon = document.getElementById(iconId);
  var input = document.getElementById("search");

  if (dropdown && icon) {
    if (
      !dropdown.contains(event.target) &&
      !icon.contains(event.target) &&
      !input.contains(event.target) &&
      input.value.trim() === ""
    ) {
      dropdown.classList.remove("show");
      icon.classList.remove("rotated");
    }
  }
}

/**
 * Calls outsideClickListener with the contact dropdown options and icon.
 *
 * @param {{ target: HTMLElement }} event - The event object from the click event.
 * @returns {void}
 */
function outsideClickListenerWrapper(event) {
  outsideClickListener(event, "contact-dropdown-options", "dropdown-icon");
}

/**
 * Calls outsideClickListener with the category dropdown options and icon.
 *
 * @param {{ target: HTMLElement }} event - The event object from the click event.
 * @returns {void}
 */
function outsideClickListenerWrapperCategory(event) {
  outsideClickListener(event, "category-dropdown-options", "category-dropdown-icon");
}

/**
 * Listens for a click event on the document and calls
 * outsideClickListenerWrapper and outsideClickListenerWrapperCategory
 * to hide the contact dropdown and category dropdown if the
 * target element is not one of their children.
 *
 * @listens document#click
 * @param {{ target: HTMLElement }} event - The event object from the click event
 * @returns {void}
 */
document.addEventListener("click", function (event) {
  outsideClickListenerWrapper(event);
  outsideClickListenerWrapperCategory(event);
});

/**
 * Validates the add task form fields. If the title or due date are empty,
 * or if the category is not selected, it shows a warning and returns false.
 * Otherwise, it clears any warnings and returns true.
 *
 * @returns {boolean} If the form is valid or not.
 */
function validateTodoForm() {
  const titleField = document.getElementById("title") || document.getElementById("bc-todo-titel");
  const dueDateField = document.getElementById("due-date");
  const categoryField = document.getElementById("select-category");
  let isValid = true;
  clearWarnings();

  if (!titleField || !titleField.value.trim()) {
    isValid = false;
    showWarning(titleField, "Title is required.");
  }

  if (!dueDateField || !dueDateField.value) {
    isValid = false;
    showWarning(dueDateField, "Due date is required.");
  }

  if (categoryField && categoryField.textContent.trim() === "Select task category") {
    isValid = false;
    showWarning(categoryField, "Category is required.");
  }

  return isValid;
}

/**
 * Shows a warning message for a given input field. If the input field is an input element, the message is inserted after the element.
 * If the input field is a div element, the message is inserted after the parent element of the div.
 * The warning message is visible for 3 seconds and then removed.
 * @param {HTMLElement} inputField - The input field element to display the warning message for.
 * @param {string} message - The warning message to display.
 */
function showWarning(inputField, message) {
  if (inputField.tagName === "INPUT") {
    inputField.style.borderColor = "red";
    inputField.insertAdjacentHTML("afterend", `<p class="warning-text">${message}</p>`);
  } else if (inputField.tagName === "DIV") {
    inputField.style.borderColor = "red";
    const parentElement = inputField.parentNode;
    parentElement.insertAdjacentHTML("afterend", `<p class="warning-text">${message}</p>`);
  }

  setTimeout(() => {
    inputField.style.borderColor = "";
    clearWarnings();
  }, 3000);
}

/**
 * Clears all warning messages displayed in the form by selecting elements
 * with the class "warning-text" and removing them from the DOM.
 *
 * @returns {void}
 */
function clearWarnings() {
  const warnings = document.querySelectorAll(".warning-text");
  warnings.forEach((warning) => warning.remove());
}

/**
 * Checks for overflow on specific elements and adjusts padding and margin to account for scrollbars.
 * Adds padding to the subtask list if it has a vertical scrollbar and adjusts the padding and margin
 * of specified form containers.
 *
 * @returns {void}
 */
function checkScrollbar() {
  const subtaskList = document.getElementById("subtask-list");
  if (subtaskList) {
    subtaskList.style.paddingRight = subtaskList.scrollHeight > subtaskList.clientHeight ? "5px" : "0";
  }
  const elements = [
    document.getElementById("edit-card-form-container"),
    document.getElementById("big-card-form-container"),
  ];
  elements.forEach((el) => {
    if (el) {
      const padding = el.scrollHeight > el.clientHeight ? "15px" : "0";
      const margin = el.scrollHeight > el.clientHeight ? "-25px" : "0";
      el.style.paddingRight = padding;
      el.style.marginRight = margin;
    }
  });
}

/**
 * Retrieves the column elements from the DOM by their IDs and returns them as an object.
 *
 * @returns {Object} An object containing references to the todo, progress, feedback, and done column elements.
 * @property {HTMLElement} todoColumn - The todo column element.
 * @property {HTMLElement} progressColumn - The progress column element.
 * @property {HTMLElement} feedbackColumn - The feedback column element.
 * @property {HTMLElement} doneColumn - The done column element.
 */
function getBoardColumns() {
  const todoColumn = document.getElementById("board-todo");
  const progressColumn = document.getElementById("board-progress");
  const feedbackColumn = document.getElementById("board-feedback");
  const doneColumn = document.getElementById("board-done");
  return { todoColumn, progressColumn, feedbackColumn, doneColumn };
}

/**
 * Renders the assigned members for a specific todo item by creating and appending
 * HTML elements representing each assigned member to the DOM element corresponding
 * to the todo's assigned members section.
 *
 * If the assigned members element does not exist, the function returns immediately.
 *
 * @param {number} todoIndex - The index of the todo item.
 * @param {Object} todo - The todo object containing the assigned members information.
 * @returns {void}
 */

function renderAssignedMembersForTodo(todoIndex, todo) {
  const assignedMembersElement = document.getElementById(`assigned-members-${todoIndex}`);
  if (!assignedMembersElement) return;

  const assignedMembers = objectToArray(todo.assignedMembers);
  assignedMembersElement.append(
    ...assignedMembers.map((assignedMember, memberIndex) => {
      const contact = globalContacts.find((contact) => contact.email === assignedMember.email);
      return createAssignedMemberElement(contact, memberIndex, assignedMembers.length);
    })
  );
}

/**
 * Creates an HTML element representing an assigned member badge.
 *
 * @param {Object} contact - The contact object containing information about the assigned member.
 * @param {number} index - The index of the member in the assigned members list.
 * @param {number} totalMembers - The total number of assigned members.
 * @returns {HTMLElement|string} The HTML element representing the member badge or an empty string if the index is greater than 3.
 */
function createAssignedMemberElement(contact, index, totalMembers) {
  const memberElement = document.createElement("div");
  memberElement.classList.add("card-mall-assigend-member-badge");

  if (index < 3 && contact) {
    memberElement.textContent = getInitialsFromContact(contact);
    memberElement.style.backgroundColor = contact.color;
  } else if (index === 3) {
    memberElement.textContent = `+${totalMembers - 3}`;
    memberElement.style.backgroundColor = "#c7c7c7";
  } else if (!contact) {
    memberElement.textContent = "N/A";
    memberElement.style.backgroundColor = "#c7c7c7";
  } else return "";

  return memberElement;
}

/**
 * Listens for the window resize event and calls the checkScrollbar function
 * when fired.
 *
 * This is necessary because the subtask list is rendered with a masonry
 * layout, which needs to be re-laid out when the window is resized.
 *
 * @listens window#resize
 * @returns {void}
 */
window.addEventListener("resize", checkScrollbar);