user/notifications.js

/**
 * @module UserNotifications
 */
import { GET, PUT, DELETE } from '../../infrastructure/http/HttpClient.ts'
import  eventsAPI from '../eventsAPI.js'
import './types.js'
import {globalConfig} from "../config";
import {fetchLiveEvent} from "../sanity";

const baseUrl = `/api/notifications`

const NotificationChannels = {
  EMAIL: 'email',
  PUSH: 'push',
  BELL: 'bell',
};

/**
 * Fetches notifications for a given brand with optional filters for unread status and limit.
 *
 * @param {Object} [options={}] - Options for fetching notifications.
 * @param {number} [options.limit=10] - The maximum number of notifications to fetch.
 * @param {number} [options.page=1] - The page number for pagination.
 * @param {boolean} [options.onlyUnread=false] - Whether to fetch only unread notifications. If true, adds `unread=1` to the query.
 *
 * @returns {Promise<Array<Object>>} - A promise that resolves to an array of notifications.
 *
 * @throws {Error} - Throws an error if the brand is not provided.
 *
 * @example
 * fetchNotifications({ limit: 5, onlyUnread: true,  page: 2  })
 *   .then(notifications => console.log(notifications))
 *   .catch(error => console.error(error));
 */
export async function fetchNotifications({ limit = 10, onlyUnread = false, page = 1 } = {}) {
  const unreadParam = onlyUnread ? '&unread=1' : ''
  const url = `${baseUrl}/v1?limit=${limit}&page=${page}${unreadParam}`
  return await GET(url)
}

/**
 * Marks a specific notification as read.
 *
 * @param {number} notificationId - The ID of the notification to mark as read.
 *
 * @returns {Promise<any>} - A promise that resolves when the notification is successfully marked as read.
 *
 * @throws {Error} - Throws an error if notificationId is not provided.
 *
 * @example
 * markNotificationAsRead(123)
 *   .then(response => console.log(response))
 *   .catch(error => console.error(error));
 */
export async function markNotificationAsRead(notificationId) {
  if (!notificationId) {
    throw new Error('notificationId is required')
  }

  const url = `${baseUrl}/v1/read?id=${notificationId}`
  return await PUT(url, null)
}

/**
 * Marks all notifications as read for the current user.
 *
 * This also pauses live event polling if there is an active event, to prevent immediate re-polling.
 *
 * @param {string} [brand='drumeo'] - The brand context for live event handling before marking notifications.
 * @returns {Promise<Object>} - A promise resolving to the API response from the notifications read endpoint.
 */
export async function markAllNotificationsAsRead() {
  await eventsAPI.pauseLiveEventCheck()
  const url = `${baseUrl}/v1/read`
  return await PUT(url, null)
}

/**
 * Marks a specific notification as unread.
 *
 * @param {number} notificationId - The ID of the notification to mark as unread.
 *
 * @returns {Promise<any>} - A promise that resolves when the notification is successfully marked as unread.
 *
 * @throws {Error} - Throws an error if notificationId is not provided.
 *
 * @example
 * markNotificationAsUnread(123)
 *   .then(response => console.log(response))
 *   .catch(error => console.error(error));
 */
export async function markNotificationAsUnread(notificationId) {
  if (!notificationId) {
    throw new Error('notificationId is required')
  }

  const url = `${baseUrl}/v1/unread?id=${notificationId}`
  return await PUT(url, null)
}

/**
 * Deletes a specific notification.
 *
 * @param {number} notificationId - The ID of the notification to delete.
 *
 * @returns {Promise<any>} - A promise that resolves when the notification is successfully deleted.
 *
 * @throws {Error} - Throws an error if notificationId is not provided.
 *
 * @example
 * deleteNotification(123)
 *   .then(response => console.log(response))
 *   .catch(error => console.error(error));
 */
export async function deleteNotification(notificationId) {
  if (!notificationId) {
    throw new Error('notificationId is required')
  }
  const url = `${baseUrl}/v1/${notificationId}`
  return await DELETE(url)
}

/**
 * Restores a specific notification.
 *
 * @param {number} notificationId - The ID of the notification to restore.
 *
 * @returns {Promise<any>} - A promise that resolves when the notification is successfully restored.
 *
 * @throws {Error} - Throws an error if notificationId is not provided.
 *
 * @example
 * restoreNotification(123)
 *   .then(response => console.log(response))
 *   .catch(error => console.error(error));
 */
export async function restoreNotification(notificationId) {
  if (!notificationId) {
    throw new Error('notificationId is required')
  }

  const url = `${baseUrl}/v1/${notificationId}`
  return await PUT(url, null)
}

/**
 * Fetches the count of unread notifications for the current user in a given brand context.
 *
 * This function first checks for standard unread notifications. If none are found,
 * it checks if live event polling is active. If so, it will query for any ongoing live events.
 * If a live event is active, it counts as an unread item.
 *
 * @param {Object} [options={}] - Options for fetching unread count.
 * @param {string} options.brand - The brand to filter unread notifications by. Defaults to 'drumeo'.
 * @returns {Promise<Object>} - A promise that resolves to an object with a `data` property indicating the unread count (0 or 1).
 *
 * @throws {Error} If the brand is not provided or if network requests fail.
 *
 * @example
 * fetchUnreadCount({ brand: 'drumeo' })
 *   .then(data => console.log(data.data)) // 0 or 1
 *   .catch(error => console.error(error));
 */
export async function fetchUnreadCount({ brand = 'drumeo'} = {}) {
  const url = `${baseUrl}/v1/unread-count`
  const notifUnread =  await GET(url)
  if (notifUnread && notifUnread.data > 0) {
    return notifUnread
  }
  const liveEventPollingState = await fetchLiveEventPollingState()
  if(liveEventPollingState.data?.read_state === true){
    const liveEvent = await fetchLiveEvent(brand)
    return { data: liveEvent ? 1 : 0, liveEvent}
  }
  return { data: 0}
}

/**
 * Fetches the notification settings for the current user grouped by brand.
 *
 * @returns {Promise<Object>} A promise that resolves to an object where keys are brands and values are arrays of settings objects.
 *
 * @example
 * fetchNotificationSetting()
 *   .then(settings => {
 *     console.log(settings);
 *   })
 *   .catch(error => {
 *     console.error(error);
 *   });
 */
export async function fetchNotificationSettings() {
  const url = `/api/notifications/v1/settings`;
  const settings = await GET(url);

  if (!settings || typeof settings !== 'object') return {};

  const result = {};

  for (const [brand, brandSettings] of Object.entries(settings)) {
    result[brand] = Object.entries(brandSettings).map(([name, value]) => ({
      name,
      ...value,
    }));
  }

  return result;
}

/**
 * Updates notification settings for specified channels within a given brand.
 *
 * @param {Object} options - Options to update notification settings.
 * @param {string} options.brand - The brand context for the notification settings.
 * @param {string} options.settingName - The name of the notification setting to update (required).
 * @param {boolean} [options.email] - Whether email notifications are enabled or disabled.
 * @param {boolean} [options.push] - Whether push notifications are enabled or disabled.
 * @param {boolean} [options.bell] - Whether bell notifications are enabled or disabled.
 * @returns {Promise<Object>} - A promise that resolves to the server response after updating settings.
 *
 * @throws {Error} Throws an error if `settingName` is not provided or if no channels are specified.
 *
 * @example
 * updateNotificationSetting({
 *   brand: 'drumeo',
 *   settingName: 'new_lesson',
 *   email: true,
 *   push: false,
 *   bell: true
 * })
 * .then(response => console.log(response))
 * .catch(error => console.error(error));
 */
export async function updateNotificationSetting({ brand, settingName, email, push, bell } = {}) {
  if (!settingName) {
    throw new Error('The "settingName" parameter is required.');
  }

  const channelValues = {
    [NotificationChannels.EMAIL]: email,
    [NotificationChannels.PUSH]: push,
    [NotificationChannels.BELL]: bell,
  };

  const settings = Object.entries(channelValues)
    .filter(([, value]) => value !== undefined)
    .map(([channel, value]) => ({
      name: settingName,
      channel,
      value,
      brand,
    }));

  if (settings.length === 0) {
    throw new Error('At least one channel (email, push, or bell) must be provided.');
  }

  const payload = { settings };
  const url = '/api/notifications/v1/settings';

  return await PUT(url, payload);
}

/**
 * Pauses live event polling for the current user based on the live event end time.
 *
 * If a live event is active, polling will be paused until its end time. If no live event is found,
 * polling is not paused.
 *
 * @param {string} [brand='drumeo'] - The brand context to fetch live event data for.
 * @returns {Promise<Object>} - A promise resolving to the API response from the pause polling endpoint.
 */
export async function pauseLiveEventPolling(brand = 'drumeo') {
  const liveEvent = await fetchLiveEvent(brand)
  const until = liveEvent?.live_event_end_time || null
  const url = `/api/user-management-system/v1/users/pause-polling${until ? `?until=${until}` : ''}`
  return await PUT(url, null)
}

/**
 * Start live event polling.
 * @returns {Promise<Object>} - Promise resolving to the API response
 */
export async function startLiveEventPolling(brand = 'drumeo') {
  const url = `/api/user-management-system/v1/users/start-polling`
  return await PUT(url, null)
}

/**
 * Fetches the current live event polling state.
 + @returns {Promise<Object>} - Promise resolving to the polling state
 */

export async function fetchLiveEventPollingState() {
    const url = `/api/user-management-system/v1/users/polling`
    return await GET(url)
}

export async function deleteAllNotifications() {
  await eventsAPI.pauseLiveEventCheck()
  const url = `${baseUrl}/v1/all`
  return await DELETE(url, null)
}