Source: repositories/UserRepository.js

import { setDoc, doc, runTransaction } from "firebase/firestore";
import {
  removeItemFromArrayField,
  updateArrayDocumentFields,
  addItemToArrayField,
  getArrayFieldFromCollection,
  getAllItems,
  getItemById,
  removeDocumentFromCollection,
  updateNonArrayDocumentFields,
} from "../utils/sharedRepositoryFunctions";
import { userConverter } from "../converters/userConverter";
import User from "../models/user";
import { Notification } from "../models/notification";
import { EVENT_TYPE, UpcomingEvent } from "../models/event";

/**
 * Utility class to talk to FireStore User Collection [IN PROGRESS]
 */
/**
 * @class UserRepository
 * @classdesc UserRepository - Manages user-related data interactions with the database.
 * 
 * @param {Object} database - The database connection used for accessing user data.
 * @param {QuizRepository} quizRepository - Repository for handling quiz data.
 * @param {NotificationRepository} notificationRepository - Repository for handling notification data.
 * @param {FlashcardRepository} flashcardRepository - Repository for handling flashcard data.
 * @param {EventRepository} eventRepository - Repository for handling event data.
 */
export class UserRepository {
  constructor(
    database,
    quizRepository,
    notificationRepository,
    flashcardRepository,
    eventRepository
  ) {
    this.database = database;
    this.quizRepository = quizRepository;
    this.notificationRepository = notificationRepository;
    this.flashcardRepository = flashcardRepository;
    this.eventRepository = eventRepository;
  }

  /**Add a user to the database for the first time with id: uuid*/
  /**
  * @memberof UserRepository
  * @function addUser
  * @description Adds a new user to the database.
  * @param {string} email - User's email.
  * @param {string} username - User's username.
  * @param {string} firstName - User's first name.
  * @param {string} lastName - User's last name.
  * @param {string} uuid - User's unique identifier.
  */
  async addUser(email, username, firstName, lastName, uuid) {
    try {
      const userRef = doc(this.database, "users", uuid);
      await setDoc(
        userRef,
        new User(email, username, firstName, lastName, uuid).toJSON()
      );
      console.log("Successfully added user to database", username);
    } catch (error) {
      console.log("error adding user", error);
    }
  }

  /**Given a unique uuid, return the user associated with that uuid */
  /**
   * @memberof UserRepository
   * @function getUserById
   * @description Retrieves a user by their ID.
   * @param {string} id - The ID of the user to retrieve.
   * @returns {Promise<Object>} A promise that resolves to the user object.
   */
  async getUserById(id) {
    return await getItemById(this.database, id, "users", "user");
  }
  /**
     * @memberof UserRepository
     * @function deleteUser
     * @description Deletes a user by their ID.
     * @param {string} id - The ID of the user to delete.
     * @returns {Promise<void>}
     */
  async deleteUser(id) {
    return await removeDocumentFromCollection(
      this.database,
      id,
      "users",
      "user"
    );
  }
  /**
     * @memberof UserRepository
     * @function updateNonArrayUserFields
     * @description Updates non-array fields of a user document.
     * @param {string} id - The ID of the user to update.
     * @param {Object} toUpdate - Object containing the fields to update.
     */
  async updateNonArrayUserFields(id, toUpdate) {
    await updateNonArrayDocumentFields(this.database, id, "users", toUpdate);
  }
  /**
     * @memberof UserRepository
     * @function updateArrayUserFields
     * @description Updates array fields of a user document.
     * @param {string} id - The ID of the user to update.
     * @param {Object} toUpdate - Object containing the array fields to update.
     */
  async updateArrayUserFields(id, toUpdate) {
    await updateArrayDocumentFields(this.database, id, "users", toUpdate);
  }

  /**Return all users in the user collection */
  /**
   * @memberof UserRepository
   * @function getAllUsers
   * @description Retrieves all users from the database.
   * @returns {Promise<Array>} A promise that resolves to an array of user objects.
   */
  async getAllUsers() {
    return await getAllItems(this.database, "users", userConverter);
  }

  /**Get all owned quizzes owned by user with id: {id} */
  /**
   * @memberof UserRepository
   * @function getOwnedQuizzesIds
   * @description Retrieves IDs of all quizzes owned by a specific user.
   * @param {string} id - The ID of the user.
   * @returns {Promise<Array>} A promise that resolves to an array of quiz IDs.
   */
  async getOwnedQuizzesIds(id) {
    const ownedQuizzes = await getArrayFieldFromCollection(
      this.database,
      "users",
      id,
      "ownedQuizzes"
    );
    return ownedQuizzes;
  }
  /**
     * @memberof UserRepository
     * @function getOwnedQuizzes
     * @description Retrieves all quizzes owned by a specific user.
     * @param {string} userId - The ID of the user.
     * @returns {Promise<Array>} A promise that resolves to an array of quiz objects.
     */
  async getOwnedQuizzes(userId) {
    const quizIds = await this.getOwnedQuizzesIds(userId);
    const result = [];
    for (const id of quizIds) {
      const quiz = await this.quizRepository.get_QuizById(id);
      const quizAuthor = await this.getUserById(quiz.authorId);

      quiz.author = {
        name: quizAuthor.name,
        imageURL: quizAuthor.imageURL,
        firstName: quizAuthor.firstName,
        lastName: quizAuthor.lastName,
      };
      result.push(quiz);
    }
    return result;
  }

  /**Get all quizzes shared by user with id: {id} */
  /**
  * @memberof UserRepository
  * @function getSharedQuizzesIds
  * @description Retrieves IDs of all quizzes shared by a specific user.
  * @param {string} id - The ID of the user.
  * @returns {Promise<Array>} A promise that resolves to an array of shared quiz IDs.
  */
  async getSharedQuizzesIds(id) {
    const sharedQuizzes = await getArrayFieldFromCollection(
      this.database,
      "users",
      id,
      "sharedQuizzes"
    );
    return sharedQuizzes;
  }
  /**
     * @memberof UserRepository
     * @function getSharedQuizzes
     * @description Retrieves all quizzes shared by a specific user.
     * @param {string} userId - The ID of the user.
     * @returns {Promise<Array>} A promise that resolves to an array of shared quiz objects.
     */
  async getSharedQuizzes(userId) {
    const quizIds = await this.getSharedQuizzesIds(userId);
    const result = [];
    for (const id of quizIds) {
      const quiz = await this.quizRepository.get_QuizById(id);
      const quizAuthor = await this.getUserById(quiz.authorId);

      quiz.author = {
        name: quizAuthor.name,
        imageURL: quizAuthor.imageURL,
        firstName: quizAuthor.firstName,
        lastName: quizAuthor.lastName,
      };
      result.push(quiz);
    }
    return result;
  }

  /**Get all owned flashcards owned by user with id: {id} */
  /**
   * @memberof UserRepository
   * @function getOwnedFlashcardsIds
   * @description Retrieves IDs of all flashcards owned by a specific user.
   * @param {string} id - The ID of the user.
   * @returns {Promise<Array>} A promise that resolves to an array of flashcard IDs.
   */
  async getOwnedFlashcardsIds(id) {
    const ownedFlashcardsIds = await getArrayFieldFromCollection(
      this.database,
      "users",
      id,
      "ownedFlashcards"
    );
    return ownedFlashcardsIds;
  }
  /**
     * @memberof UserRepository
     * @function getOwnedFlashcards
     * @description Retrieves all flashcards owned by a specific user.
     * @param {string} userId - The ID of the user.
     * @returns {Promise<Array>} A promise that resolves to an array of flashcard objects.
     */
  async getOwnedFlashcards(userId) {
    const quizIds = await this.getOwnedFlashcardsIds(userId);
    const result = [];
    for (const id of quizIds) {
      const flashcard = await this.flashcardRepository.getFlashcardSetBy_Id(id);
      const flashcardAuthor = await this.getUserById(flashcard.authorId);
      flashcard.author = {
        name: flashcardAuthor.name,
        imageURL: flashcardAuthor.imageURL,
        firstName: flashcardAuthor.firstName,
        lastName: flashcardAuthor.lastName,
      };
      result.push(flashcard);
    }
    return result;
  }

  /**Get all flashcards shared by user with id: {id} */
  /**
   * @memberof UserRepository
   * @function getSharedFlashcardIds
   * @description Retrieves the IDs of all flashcards shared by a specific user.
   * @param {string} id - The ID of the user.
   * @returns {Promise<Array>} A promise that resolves to an array of shared flashcard IDs.
   */
  async getSharedFlashcardIds(id) {
    const sharedFlashcardsIds = await getArrayFieldFromCollection(
      this.database,
      "users",
      id,
      "sharedFlashcards"
    );
    return sharedFlashcardsIds;
  }
  /**
     * @memberof UserRepository
     * @function getSharedFlashcards
     * @description Retrieves all flashcards shared by a specific user.
     * @param {string} userId - The ID of the user.
     * @returns {Promise<Array>} A promise that resolves to an array of shared flashcard objects.
     */
  async getSharedFlashcards(userId) {
    const quizIds = await this.getSharedFlashcardIds(userId);
    const result = [];
    for (const id of quizIds) {
      const flashcard = await this.flashcardRepository.getFlashcardSetBy_Id(id);

      const flashcardAuthor = await this.getUserById(flashcard.authorId);

      flashcard.author = {
        name: flashcardAuthor.name,
        imageURL: flashcardAuthor.imageURL,
        firstName: flashcardAuthor.firstName,
        lastName: flashcardAuthor.lastName,
      };
      result.push(flashcard);
    }
    return result;
  }

  /**Get all followers of user with id: {id} */
  /**
   * @memberof UserRepository
   * @function getFollowers
   * @description Retrieves all followers of a specific user.
   * @param {string} id - The ID of the user.
   * @returns {Promise<Array>} A promise that resolves to an array of follower user objects.
   */
  async getFollowers(id) {
    const followers = await getArrayFieldFromCollection(
      this.database,
      "users",
      id,
      "followers"
    );
    return this.getUsers(followers);
  }

  /**Get all following of user with id: {id} */
  /**
   * @memberof UserRepository
   * @function getFollowing
   * @description Retrieves all users that a specific user is following.
   * @param {string} id - The ID of the user.
   * @returns {Promise<Array>} A promise that resolves to an array of following user objects.
   */
  async getFollowing(id) {
    const following = await getArrayFieldFromCollection(
      this.database,
      "users",
      id,
      "following"
    );
    return this.getUsers(following);
  }

  /** async get user friends: friends are people who are followers and are also in users following list*/
  /**
   * @memberof UserRepository
   * @function getFriends
   * @description Retrieves all friends (mutual followers) of a specific user.
   * @param {string} id - The ID of the user.
   * @returns {Promise<Array>} A promise that resolves to an array of friend user objects.
   */
  async getFriends(id) {
    const followers = await this.getFollowersIds(id);
    const following = await this.getFollowingIds(id);

    const friendIds = [];
    for (const item of followers) {
      if (following.includes(item)) {
        friendIds.push(item);
      }
    }
    return this.getUsers(friendIds);
  }
  /**
     * @memberof UserRepository
     * @function getFollowingIds
     * @description Retrieves the IDs of all users that a specific user is following.
     * @param {string} id - The ID of the user.
     * @returns {Promise<Array>} A promise that resolves to an array of following user IDs.
     */
  async getFollowingIds(id) {
    return await getArrayFieldFromCollection(
      this.database,
      "users",
      id,
      "following"
    );
  }
  /**
     * @memberof UserRepository
     * @function getFollowersIds
     * @description Retrieves the IDs of all followers of a specific user.
     * @param {string} id - The ID of the user.
     * @returns {Promise<Array>} A promise that resolves to an array of follower user IDs.
     */
  async getFollowersIds(id) {
    return await getArrayFieldFromCollection(
      this.database,
      "users",
      id,
      "followers"
    );
  }

  /**
   *
   * Get user profile. User profile is made up of
   * User name, Bio, email, flashcards, friends, quizzes, profession, etc
   */
  /**
  * @memberof UserRepository
  * @function getProfile
  * @description Retrieves the profile of a specific user, including personal details and owned/shared content.
  * @param {string} userId - The ID of the user.
  * @returns {Promise<Object>} A promise that resolves to an object containing the user's profile information.
  */
  async getProfile(userId) {
    const user = await this.getUserById(userId);
    const {
      id,
      bio,
      email,
      imageURL,
      username,
      name,
      profession,
      phone,
      firstName,
      lastName,
    } = user;

    const flashcards = await this.getOwnedFlashcards(id);
    const sharedFlashcards = await this.getSharedFlashcards(id);
    const friends = await this.getFriends(id);
    const followers = await this.getFollowers(id);
    const following = await this.getFollowing(id);

    return {
      id: id,
      bio: bio,
      email: email,
      imageURL: imageURL,
      username: username,
      friends: friends,
      followers: followers,
      following: following,
      name: firstName + " " + lastName,
      flashcards: flashcards,
      sharedFlashcards: sharedFlashcards,
      profession: profession,
      phone: phone,
      name: name,
      firstName: firstName,
      lastName: lastName,
    };
  }

  /**Get all notifications of user with id: {id}, returns the ids */
  /**
   * @memberof UserRepository
   * @function getNotificationIds
   * @description Retrieves the IDs of all notifications for a specific user.
   * @param {string} id - The ID of the user.
   * @returns {Promise<Array>} A promise that resolves to an array of notification IDs.
   */
  async getNotificationIds(id) {
    const notificationIds = await getArrayFieldFromCollection(
      this.database,
      "users",
      id,
      "notifications"
    );
    return notificationIds;
  }

  /**Get raw notifications and not just the ids */
  /**
   * @memberof UserRepository
   * @function getNotifications
   * @description Retrieves all notifications for a specific user.
   * @param {string} id - The ID of the user.
   * @returns {Promise<Array>} A promise that resolves to an array of notification objects.
   */
  async getNotifications(id) {
    const notificationIds = await this.getNotificationIds(id);

    const listOfNotifications =
      await this.notificationRepository.getListOfNotifications(notificationIds);
    listOfNotifications.reverse();
    return listOfNotifications;
  }

  /**get all user events */
  /**
   * @memberof UserRepository
   * @function getEvents
   * @description Retrieves all events associated with a specific user.
   * @param {string} id - The ID of the user.
   * @returns {Promise<Array>} A promise that resolves to an array of event objects.
   */
  async getEvents(id) {
    const events = await getArrayFieldFromCollection(
      this.database,
      "users",
      id,
      "events"
    );
    return events;
  }

  /**add quiz with quizId to list of quizzes for user with userId */
  /**
   * @memberof UserRepository
   * @function addSharedQuiz
   * @description Adds a quiz to the shared quizzes list of a user.
   * @param {string} userId - The ID of the user.
   * @param {string} quizId - The ID of the quiz to share.
   * @returns {Promise<void>} A promise that resolves when the operation is complete.
   */
  async addSharedQuiz(userId, quizId) {
    await addItemToArrayField(
      this.database,
      userId,
      quizId,
      "users",
      "sharedQuizzes",
      "quiz"
    );
  }

  /**user with userId shares quiz with user with id sharedWithId */
  /**
   * @memberof UserRepository
   * @function shareQuiz
   * @description Shares a quiz with another user and generates the associated notifications and events.
   * @param {string} userId - The ID of the user sharing the quiz.
   * @param {string} sharedWithId - The ID of the user with whom the quiz is shared.
   * @param {string} quizId - The ID of the quiz being shared.
   * @returns {Promise<boolean>} A promise that resolves to true if the sharing is successful.
   */
  async shareQuiz(userId, sharedWithId, quizId) {
    await this.addSharedQuiz(sharedWithId, quizId);
    const eventId = await this.eventRepository.createShareQuizEvent(
      userId,
      sharedWithId,
      quizId
    );
    const notificationId = await this.notificationRepository.addNotification(
      new Notification(eventId)
    );
    this.addEvent(sharedWithId, eventId);
    this.addNotification(sharedWithId, notificationId);
    this.incrementNewNotifications(sharedWithId);
    await this.notificationRepository.update(notificationId);
    return true;
  }

  /**add flashcard with flashcardId to list of flashcards for user with userId */
  /**
   * @memberof UserRepository
   * @function addSharedFlashcard
   * @description Adds a flashcard to the shared flashcards list of a user.
   * @param {string} userId - The ID of the user.
   * @param {string} flashcardId - The ID of the flashcard to share.
   * @returns {Promise<void>} A promise that resolves when the operation is complete.
   */
  async addSharedFlashcard(userId, flashcardId) {
    await addItemToArrayField(
      this.database,
      userId,
      flashcardId,
      "users",
      "sharedFlashcards",
      "flashcard"
    );
  }

  /**user with userId shares flashcard with user with id sharedWithId */
  /**
   * @memberof UserRepository
   * @function shareFlashcard
   * @description Shares a flashcard with another user and creates the necessary notifications and events.
   * @param {string} userId - The ID of the user sharing the flashcard.
   * @param {string} sharedWithId - The ID of the user with whom the flashcard is shared.
   * @param {string} flashcardId - The ID of the flashcard being shared.
   * @returns {Promise<boolean>} A promise that resolves to true if the sharing is successful.
   */
  async shareFlashcard(userId, sharedWithId, flashcardId) {
    await this.addSharedFlashcard(sharedWithId, flashcardId);
    const eventId = await this.eventRepository.createShareFlashcardEvent(
      userId,
      sharedWithId,
      flashcardId
    );
    const notificationId = await this.notificationRepository.addNotification(
      new Notification(eventId)
    );
    this.addEvent(sharedWithId, eventId);
    this.addNotification(sharedWithId, notificationId);
    this.incrementNewNotifications(sharedWithId);
    await this.notificationRepository.update(notificationId);
    return true;
  }
  /**
     * @memberof UserRepository
     * @function addOwnedQuiz
     * @description Adds a quiz to the owned quizzes list of a user.
     * @param {string} userId - The ID of the user.
     * @param {string} quizId - The ID of the quiz to add.
     * @returns {Promise<void>} A promise that resolves when the operation is complete.
     */
  async addOwnedQuiz(userId, quizId) {
    await addItemToArrayField(
      this.database,
      userId,
      quizId,
      "users",
      "ownedQuizzes",
      "quiz"
    );
  }
  /**
     * @memberof UserRepository
     * @function addOwnedFlashcard
     * @description Adds a flashcard to the owned flashcards list of a user.
     * @param {string} userId - The ID of the user.
     * @param {string} flashcardId - The ID of the flashcard to add.
     * @returns {Promise<void>} A promise that resolves when the operation is complete.
     */
  async addOwnedFlashcard(userId, flashcardId) {
    await addItemToArrayField(
      this.database,
      userId,
      flashcardId,
      "users",
      "ownedFlashcards",
      "flashcard"
    );
  }
  /**
     * @memberof UserRepository
     * @function addEvent
     * @description Adds an event to the events list of a user.
     * @param {string} userId - The ID of the user.
     * @param {string} eventId - The ID of the event to add.
     * @returns {Promise<void>} A promise that resolves when the operation is complete.
     */
  async addEvent(userId, eventId) {
    await addItemToArrayField(
      this.database,
      userId,
      eventId,
      "users",
      "events",
      "event"
    );
  }
  /**
     * @memberof UserRepository
     * @function addNotification
     * @description Adds a notification to the notifications list of a user.
     * @param {string} userId - The ID of the user.
     * @param {string} notificationId - The ID of the notification to add.
     * @returns {Promise<void>} A promise that resolves when the operation is complete.
     */
  async addNotification(userId, notificationId) {
    await addItemToArrayField(
      this.database,
      userId,
      notificationId,
      "users",
      "notifications",
      "notification"
    );
  }
  /**
     * @memberof UserRepository
     * @function addSubject
     * @description Adds a subject to the subjects list of a user.
     * @param {string} userId - The ID of the user.
     * @param {string} subjectId - The ID of the subject to add.
     * @returns {Promise<void>} A promise that resolves when the operation is complete.
     */
  async addSubject(userId, subjectId) {
    await addItemToArrayField(
      this.database,
      userId,
      subjectId,
      "users",
      "subjects",
      "subject"
    );
  }
  /**
    * @memberof UserRepository
    * @function addFollower
    * @description Adds a follower to the followers list of a user.
    * @param {string} userId - The ID of the user.
    * @param {string} followerId - The ID of the follower to add.
    * @returns {Promise<void>} A promise that resolves when the operation is complete.
    */
  async addFollower(userId, followerId) {
    await addItemToArrayField(
      this.database,
      userId,
      followerId,
      "users",
      "followers",
      "follower"
    );
  }

  /**Whenever userId follows followingId, followingId gains a new follower which is userId */
  /**
   * @memberof UserRepository
   * @function addFollowing
   * @description Adds a user to the following list of another user.
   * @param {string} userId - The ID of the user who is following.
   * @param {string} followingId - The ID of the user being followed.
   * @returns {Promise<boolean>} A promise that resolves to true if the operation is successful.
   */
  async addFollowing(userId, followingId) {
    try {
      await addItemToArrayField(
        this.database,
        userId,
        followingId,
        "users",
        "following",
        "following"
      );
      await this.addFollower(followingId, userId);
    } catch (error) {
      console.error(`error while adding following is: ${error}`);
      return false;
    }

    return true;
  }
  /**
     * @memberof UserRepository
     * @function removeFollower
     * @description Removes a follower from the followers list of a user.
     * @param {string} userId - The ID of the user whose follower is being removed.
     * @param {string} followerId - The ID of the follower to remove.
     * @returns {Promise<void>} A promise that resolves when the operation is complete.
     */
  async removeFollower(userId, followerId) {
    await removeItemFromArrayField(
      this.database,
      userId,
      followerId,
      "users",
      "followers",
      "follower"
    );
    console.log(
      `successfully removed ${followerId} from the list of followers of ${userId}`
    );
  }
  /**
     * @memberof UserRepository
     * @function removeFollowing
     * @description Removes a user from the following list of another user.
     * @param {string} userId - The ID of the user who is unfollowing.
     * @param {string} followingId - The ID of the user being unfollowed.
     * @returns {Promise<void>} A promise that resolves when the operation is complete.
     */
  async removeFollowing(userId, followingId) {
    await removeItemFromArrayField(
      this.database,
      userId,
      followingId,
      "users",
      "following",
      "following"
    );
  }
  /**
     * @memberof UserRepository
     * @function removeNotificationById
     * @description Removes a notification from a user's notification list.
     * @param {string} userId - The ID of the user.
     * @param {string} notificationId - The ID of the notification to remove.
     * @returns {Promise<void>} A promise that resolves when the operation is complete.
     */
  async removeNotificationById(userId, notificationId) {
    await removeItemFromArrayField(
      this.database,
      userId,
      notificationId,
      "users",
      "notifications",
      "notification"
    );
  }

  /**
   * userId starts folloing followingId
   * followingId gains userId as a follower
   */
  /**
   * @memberof UserRepository
   * @function startFollowing
   * @description Initiates the process of one user starting to follow another user.
   * @param {string} userId - The ID of the user who starts following.
   * @param {string} followingId - The ID of the user being followed.
   * @returns {Promise<void>} A promise that resolves when the operation is complete.
   */
  async startFollowing(userId, followingId) {
    await this.addFollowing(userId, followingId);
    await this.addFollower(followingId, userId);
    const eventId = await this.eventRepository.createNewFollowerEvent(
      userId,
      followingId
    );
    const notificationId = await this.notificationRepository.addNotification(
      new Notification(eventId)
    );
    this.addNotification(followingId, notificationId);
    this.incrementNewNotifications(followingId);
    await this.notificationRepository.update(notificationId);
  }

  /**
   * userId stops folloing followingId
   * followingId losses userId as a follower
   */
  /**
   * @memberof UserRepository
   * @function stopFollowing
   * @description Stops one user from following another and updates the database accordingly.
   * @param {string} userId - The ID of the user who stops following.
   * @param {string} followingId - The ID of the user being unfollowed.
   * @returns {Promise<void>} A promise that resolves when the operation is complete.
   */
  async stopFollowing(userId, followingId) {
    await this.removeFollowing(userId, followingId);
    await this.removeFollower(followingId, userId);

    //Delete follow notification if a user unfollows another user
    const notifications = await this.getNotifications(followingId);
    for (const notificartion of notifications) {
      if (!notificartion.event) continue;
      if (
        notificartion.event.eventType === EVENT_TYPE.NEW_FOLLOWER &&
        notificartion.userFrom.id === userId
      ) {
        await this.removeNotificationById(followingId, notificartion.id);
      }
    }
  }

  //Given a list of user ids, get the actual user representation objects
  /**
   * @memberof UserRepository
   * @function getUsers
   * @description Retrieves user objects based on a list of user IDs.
   * @param {string[]} userIds - An array of user IDs.
   * @returns {Promise<User[]>} A promise that resolves to an array of User objects.
   */
  async getUsers(userIds) {
    const users = [];
    for (const userId of userIds) {
      users.push(await this.getUserById(userId));
    }
    return users;
  }

  //Given a user id, pass in an object to save profile
  /**
   *
   * Eg saveProfile(123, {friends:['mike', 'john'], firstName:"Joe", bio: "SWE at Amaxzon"})
   * In this example, we add mike and john to user(123) list of firends and edit their bio and firstName
   * Awlays make sure the keys you are passing into the updatedProfile match the keys in the database
   */
  /**
   * @memberof UserRepository
   * @function saveUserProfile
   * @description Saves updated profile information for a user.
   * @param {string} userId - The ID of the user.
   * @param {Object} updatedProfile - The object containing the updated profile fields.
   * @returns {Promise<void>} A promise that resolves when the profile update is complete.
   */
  async saveUserProfile(userId, updatedProfile) {
    let arrayFields = {};
    let nonArrayFields = {};

    for (const key in updatedProfile) {
      if (Array.isArray(updatedProfile[key])) {
        arrayFields[key] = updatedProfile[key];
      } else {
        nonArrayFields[key] = updatedProfile[key];
      }
    }

    await this.updateArrayUserFields(userId, arrayFields);
    await this.updateNonArrayUserFields(userId, nonArrayFields);
  }
  /**
     * @memberof UserRepository
     * @function incrementNewNotifications
     * @description Increments the count of new notifications for a user.
     * @param {string} userId - The ID of the user whose notification count is to be incremented.
     * @returns {Promise<void>} A promise that resolves when the operation is complete.
     */
  async incrementNewNotifications(userId) {
    const userRef = doc(this.database, "users", userId);
    try {
      await runTransaction(this.database, async (transaction) => {
        const userDoc = await transaction.get(userRef);
        if (!userDoc.exists()) {
          throw new Error("User document not found.");
        }
        const currentNotifications = userDoc.data().newNotifications || 0;
        const newNotifications = currentNotifications + 1;
        transaction.update(userRef, { newNotifications });

        return newNotifications;
      });
    } catch (error) {
      console.error("Error:", error);
    }
  }
  /**
     * @memberof UserRepository
     * @function getNotificationCount
     * @description Retrieves the current count of new notifications for a user.
     * @param {string} userId - The ID of the user.
     * @returns {Promise<number>} A promise that resolves to the number of new notifications.
     */
  async getNotificationCount(userId) {
    const user = await this.getUserById(userId);
    return user.newNotifications;
  }
  /**
     * @memberof UserRepository
     * @function setNotificationCountToZero
     * @description Resets the new notifications count to zero for a user.
     * @param {string} userId - The ID of the user.
     * @returns {Promise<number>} A promise that resolves to zero, indicating no new notifications.
     */
  async setNotificationCountoZero(userId) {
    await this.updateNonArrayUserFields(userId, {
      newNotifications: 0,
    });
    return 0;
  }

  /**Upcoming events */
  /**
   * @memberof UserRepository
   * @function addUpcomingEvent
   * @description Adds an upcoming event to a user's event list.
   * @param {string} userId - The ID of the user.
   * @param {Object} eventDetails - The details of the upcoming event.
   * @returns {Promise<boolean>} A promise that resolves to true if the operation is successful.
   */
  async addUpcomingEvent(userId, name, date, time, type, itemId, itemName) {
    const [year, month, day] = date.split("-");

    const y = parseInt(year, 10);
    const m = parseInt(month, 10) - 1;
    const d = parseInt(day, 10);
    const splitTime = time.split(":");
    const scheduledDate = new Date(
      y,
      m,
      d,
      parseInt(splitTime[0]),
      parseInt(splitTime[1]),
      0
    );

    const utcStamp = scheduledDate.getTime();
    const dd = new Date(date);

    const dateToStore =
      this.getDayOfWeekShort(dd) +
      " " +
      this.getMonthAbbreviation(dd) +
      " " +
      day +
      " " +
      year;

    const upcomingEventId = await this.eventRepository.createUpcomingEvent(
      new UpcomingEvent(
        name,
        dateToStore,
        this.convertTo12HourFormat(time),
        type,
        itemId,
        utcStamp,
        userId,
        itemName
      )
    );
    await addItemToArrayField(
      this.database,
      userId,
      upcomingEventId,
      "users",
      "upcomingEvents",
      "upcoming event"
    );
    return true;
  }
  /**
     * @memberof UserRepository
     * @function addUpcomingEventNotification
     * @description Adds a notification for an upcoming event to a user's notification list.
     * @param {string} userId - The ID of the user.
     * @param {UpcomingEvent} upcomingEvent - The upcoming event for which the notification is created.
     * @returns {Promise<boolean>} A promise that resolves to true if the operation is successful.
     */
  async addUpcomingEventNotification(userId, upcomingEvent) {
    const notification = new Notification(null, true, upcomingEvent);
    const notificationId = await this.notificationRepository.addNotification(
      notification
    );
    await this.addNotification(userId, notificationId);
    this.incrementNewNotifications(userId);
    return true;
  }
  /**
     * @memberof UserRepository
     * @function removeUpcomingEvent
     * @description Removes an upcoming event from a user's event list.
     * @param {string} userId - The ID of the user.
     * @param {string} eventId - The ID of the event to be removed.
     * @returns {Promise<boolean>} A promise that resolves to true if the operation is successful.
     */
  async removeUpcomingEvent(userId, eventId) {
    await this.eventRepository.deleteUpcomingEvent(eventId);
    await removeItemFromArrayField(
      this.database,
      userId,
      eventId,
      "users",
      "upcomingEvents",
      "upcoming event"
    );
    return true;
  }
  /**
     * @memberof UserRepository
     * @function getUpcomingEventIds
     * @description Retrieves IDs of all upcoming events for a user.
     * @param {string} userId - The ID of the user.
     * @returns {Promise<string[]>} A promise that resolves to an array of upcoming event IDs.
     */
  async getUpcomingEventIds(userId) {
    const upcomingEventIds = await getArrayFieldFromCollection(
      this.database,
      "users",
      userId,
      "upcomingEvents"
    );
    return upcomingEventIds;
  }
  /**
     * @memberof UserRepository
     * @function getAllUpcomingEvents
     * @description Retrieves all upcoming events for a user.
     * @param {string} userId - The ID of the user.
     * @returns {Promise<UpcomingEvent[]>} A promise that resolves to an array of UpcomingEvent objects.
     */
  async getAllUpcomingEvents(userId) {
    const upcomingEventIds = await this.getUpcomingEventIds(userId);
    const result = [];
    for (const id of upcomingEventIds) {
      result.push({
        ...(await this.eventRepository.getUpcomingEventById(id)),
        id: id,
      });
    }
    return result;
  }
  /**
     * @memberof UserRepository
     * @function getUpcomingEvents
     * @description Retrieves upcoming events for a user that are scheduled for the future.
     * @param {string} userId - The ID of the user.
     * @returns {Promise<UpcomingEvent[]>} A promise that resolves to an array of future UpcomingEvent objects.
     */
  async getUpcomingEvents(userId) {
    const upcomingEvents = await this.getAllUpcomingEvents(userId);

    return upcomingEvents.filter((item) => {
      return this.isTimestampInFuture(item.timestamp);
    });
  }
  /**
     * @memberof UserRepository
     * @function getPastEvents
     * @description Retrieves past events for a user.
     * @param {string} userId - The ID of the user.
     * @returns {Promise<UpcomingEvent[]>} A promise that resolves to an array of past UpcomingEvent objects.
     */
  async getPastEvents(userId) {
    const upcomingEvents = await this.getAllUpcomingEvents(userId);

    return upcomingEvents.filter((item) => {
      return !this.isTimestampInFuture(item.timestamp);
    });
  }
  /**
     * @memberof UserRepository
     * @function isTimestampInFuture
     * @description Checks whether a given timestamp is in the future.
     * @param {number} timestamp - The timestamp to be checked.
     * @returns {boolean} Returns true if the timestamp is in the future.
     */
  isTimestampInFuture(timestamp) {
    const currentTime = Date.now();
    return timestamp > currentTime;
  }
  /**
     * @memberof UserRepository
     * @function getMonthIndex
     * @description Retrieves the index of a month given its name.
     * @param {string} monthName - The name of the month.
     * @returns {number} The index of the month.
     */
  getMonthIndex(monthName) {
    const months = {
      Jan: 0,
      Feb: 1,
      Mar: 2,
      Apr: 3,
      May: 4,
      Jun: 5,
      Jul: 6,
      Aug: 7,
      Sep: 8,
      Oct: 9,
      Nov: 10,
      Dec: 11,
    };

    return months[monthName];
  }
  /**
     * @memberof UserRepository
     * @function convertTo12HourFormat
     * @description Converts a time string from 24-hour format to 12-hour format.
     * @param {string} timeString - The time string in 24-hour format.
     * @returns {string} The time string in 12-hour format.
     */
  convertTo12HourFormat(timeString) {
    let [hours, minutes] = timeString.split(":").map(Number);

    // Determine AM or PM suffix
    const ampm = hours >= 12 ? "PM" : "AM";

    // Convert hour from 24-hour to 12-hour format
    hours = hours % 12 || 12; // Converts '0' to '12'

    // Format the hour and minutes to ensure they always have two digits
    const formattedHour = hours.toString().padStart(2, "0");
    const formattedMinutes = minutes.toString().padStart(2, "0");

    // Return the formatted time string
    return `${formattedHour}:${formattedMinutes} ${ampm}`;
  }
  /**
     * @memberof UserRepository
     * @function getDayOfWeekShort
     * @description Returns a short form of the day of the week for a given date.
     * @param {string} dateString - The date string.
     * @returns {string} A short form of the day of the week.
     */
  getDayOfWeekShort(dateString) {
    const daysShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
    const date = new Date(dateString);
    return daysShort[date.getDay() + 1];
  }
  /**
   * @memberof UserRepository
   * @function getMonthAbbreviation
   * @description Returns the abbreviated form of the month for a given date.
   * @param {string} dateString - The date string.
   * @returns {string} The abbreviated month name.
   */
  getMonthAbbreviation(dateString) {
    const months = [
      "Jan",
      "Feb",
      "Mar",
      "Apr",
      "May",
      "Jun",
      "Jul",
      "Aug",
      "Sep",
      "Oct",
      "Nov",
      "Dec",
    ];
    const date = new Date(dateString);
    return months[date.getMonth()];
  }
}