railcontent.js

/**
 * @module Railcontent-Services
 */
import { contentStatusCompleted } from './contentProgress.js'

import { globalConfig } from './config.js'

/**
 * Exported functions that are excluded from index generation.
 *
 * @type {string[]}
 */
const excludeFromGeneratedIndex = [
  'fetchUserLikes',
  'postContentLiked',
  'postContentUnliked',
  'postRecordWatchSession',
  'postContentStarted',
  'postContentCompleted',
  'postContentReset',
  'fetchUserPermissionsData',
]

let challengeIndexMetaDataPromise = null

/**
 * Fetches the completion status of a specific lesson for the current user.
 *
 * @param {string} content_id - The ID of the lesson content to check.
 * @returns {Promise<Object|null>} - Returns the completion status object if found, otherwise null.
 * @example
 * fetchCurrentSongComplete('user123', 'lesson456', 'csrf-token')
 *   .then(status => console.log(status))
 *   .catch(error => console.error(error));
 */
export async function fetchCompletedState(content_id) {
  const url = `/content/user_progress/${globalConfig.railcontentConfig.userId}?content_ids[]=${content_id}`

  const headers = {
    'Content-Type': 'application/json',
    Accept: 'application/json',
    'X-CSRF-TOKEN': globalConfig.railcontentConfig.token,
  }

  try {
    const response = await fetchAbsolute(url, { headers })
    const result = await response.json()

    if (result && result[content_id]) {
      return result[content_id] // Return the correct object
    } else {
      return null // Handle unexpected structure
    }
  } catch (error) {
    console.error('Fetch error:', error)
    return null
  }
}

/**
 * Fetches the completion status for multiple songs for the current user.
 *
 * @param {Array<string>} contentIds - An array of content IDs to check.
 * @returns {Promise<Object|null>} - Returns an object containing completion statuses keyed by content ID, or null if an error occurs.
 * @example
 * fetchAllCompletedStates('user123', ['song456', 'song789'], 'csrf-token')
 *   .then(statuses => console.log(statuses))
 *   .catch(error => console.error(error));
 */
export async function fetchAllCompletedStates(contentIds) {
  const url = `/content/user_progress/${globalConfig.railcontentConfig.userId}?${contentIds.map((id) => `content_ids[]=${id}`).join('&')}`

  const headers = {
    'Content-Type': 'application/json',
    Accept: 'application/json',
    'X-CSRF-TOKEN': globalConfig.railcontentConfig.token,
  }

  try {
    const response = await fetchAbsolute(url, { headers })
    const result = await response.json()
    if (result) {
      return result
    } else {
      console.log('result not json')
    }
  } catch (error) {
    console.error('Fetch error:', error)
    return null
  }
}

/**
 * Fetches a list of songs that are currently in progress for the current user.
 *
 * @param {string} brand - The brand associated with the songs.
 * @returns {Promise<Object|null>} - Returns an object containing in-progress songs if found, otherwise null.
 * @example
 * fetchSongsInProgress('drumeo')
 *   .then(songs => console.log(songs))
 *   .catch(error => console.error(error));
 */
export async function fetchSongsInProgress(brand) {
  const url = `/content/in_progress/${globalConfig.railcontentConfig.userId}?content_type=song&brand=${brand}`

  const headers = {
    'Content-Type': 'application/json',
    Accept: 'application/json',
    'X-CSRF-TOKEN': globalConfig.railcontentConfig.token,
  }

  try {
    const response = await fetchAbsolute(url, { headers })
    const result = await response.json()
    if (result) {
      //console.log('fetchSongsInProgress', result);
      return result
    } else {
      console.log('result not json')
    }
  } catch (error) {
    console.error('Fetch error:', error)
    return null
  }
}

/**
 * Fetches a list of content that is currently in progress for the current user.
 *
 * @param {string} type - The content type associated with the content.
 * @param {string} brand - The brand associated with the content.
 * @param {number} [params.limit=20] - The limit of results per page.
 * @param {number} [params.page=1] - The page number for pagination.
 * @returns {Promise<Object|null>} - Returns an object containing in-progress content if found, otherwise null.
 * @example
 * fetchContentInProgress('song', 'drumeo')
 *   .then(songs => console.log(songs))
 *   .catch(error => console.error(error));
 */
export async function fetchContentInProgress(type = 'all', brand, { page, limit } = {}) {
  let url
  const limitString = limit ? `&limit=${limit}` : ''
  const pageString = page ? `&page=${page}` : ''

  if (type === 'all') {
    url = `/content/in_progress/${globalConfig.railcontentConfig.userId}?brand=${brand}${limitString}${pageString}`
  } else {
    url = `/content/in_progress/${globalConfig.railcontentConfig.userId}?content_type=${type}&brand=${brand}${limitString}${pageString}`
  }
  const headers = {
    'Content-Type': 'application/json',
    Accept: 'application/json',
    'X-CSRF-TOKEN': globalConfig.railcontentConfig.token,
  }
  try {
    const response = await fetchAbsolute(url, { headers })
    const result = await response.json()
    if (result) {
      //console.log('contentInProgress', result);
      return result
    } else {
      console.log('result not json')
    }
  } catch (error) {
    console.error('Fetch error:', error)
    return null
  }
}

/**
 * Fetches a list of content that has been completed for the current user.
 *
 * @param {string} type - The content type associated with the content.
 * @param {string} brand - The brand associated with the content.
 * @param {number} [params.limit=20] - The limit of results per page.
 * @param {number} [params.page=1] - The page number for pagination.
 * @returns {Promise<Object|null>} - Returns an object containing in-progress content if found, otherwise null.
 * @example
 * fetchCompletedContent('song', 'drumeo')
 *   .then(songs => console.log(songs))
 *   .catch(error => console.error(error));
 */
export async function fetchCompletedContent(type = 'all', brand, { page, limit } = {}) {
  let url
  const limitString = limit ? `&limit=${limit}` : ''
  const pageString = page ? `&page=${page}` : ''

  if (type === 'all') {
    url = `/content/completed/${globalConfig.railcontentConfig.userId}?brand=${brand}${limitString}${pageString}`
  } else {
    url = `/content/completed/${globalConfig.railcontentConfig.userId}?content_type=${type}&brand=${brand}${limitString}${pageString}`
  }
  const headers = {
    'Content-Type': 'application/json',
    Accept: 'application/json',
    'X-CSRF-TOKEN': globalConfig.railcontentConfig.token,
  }
  try {
    const response = await fetchAbsolute(url, { headers })
    const result = await response.json()
    if (result) {
      //console.log('completed content', result);
      return result
    } else {
      console.log('result not json')
    }
  } catch (error) {
    console.error('Fetch error:', error)
    return null
  }
}

/**
 * Fetches user context data for a specific piece of content.
 *
 * @param {int} contentId - The content id.
 * @returns {Promise<Object|null>} - Returns an object containing user context data if found, otherwise null.
 * @example
 * fetchContentPageUserData(406548)
 *   .then(data => console.log(data))
 *   .catch(error => console.error(error));
 */
export async function fetchContentPageUserData(contentId) {
  let url = `/content/${contentId}/user_data/${globalConfig.railcontentConfig.userId}`
  const headers = {
    'Content-Type': 'application/json',
    Accept: 'application/json',
    'X-CSRF-TOKEN': globalConfig.railcontentConfig.token,
  }

  try {
    const response = await fetchAbsolute(url, { headers })
    const result = await response.json()
    if (result) {
      console.log('fetchContentPageUserData', result)
      return result
    } else {
      console.log('result not json')
    }
  } catch (error) {
    console.error('Fetch error:', error)
    return null
  }
}

/**
 * Fetches the ID and Type of the piece of content that would be the next one for the user
 *
 * @param {int} contentId - The id of the parent (method, level, or course) piece of content.
 * @returns {Promise<Object|null>} - Returns and Object with the id and type of the next piece of content if found, otherwise null.
 */
export async function fetchNextContentDataForParent(contentId) {
  let url = `/content/${contentId}/next/${globalConfig.railcontentConfig.userId}`
  const headers = {
    'Content-Type': 'application/json',
    'X-CSRF-TOKEN': globalConfig.railcontentConfig.token,
  }

  try {
    const response = await fetchAbsolute(url, { headers })
    const result = await response.json()
    if (result) {
      // console.log('fetchNextContentDataForParent', result);
      return result.next
    } else {
      console.log('fetchNextContentDataForParent result not json')
      return null
    }
  } catch (error) {
    console.error('Fetch error:', error)
    return null
  }
}

export async function fetchUserPermissionsData() {
  let url = `/content/user/permissions`
  // in the case of an unauthorized user, we return empty permissions
  return (await fetchHandler(url, 'get')) ?? []
}

async function fetchDataHandler(url, dataVersion, method = 'get') {
  return fetchHandler(url, method, dataVersion)
}

async function postDataHandler(url, data) {
  return fetchHandler(url, 'post', null, data)
}

async function patchDataHandler(url, data) {
  return fetchHandler(url, 'patch', null, data)
}

export async function fetchHandler(url, method = 'get', dataVersion = null, body = null) {
  let headers = {
    'Content-Type': 'application/json',
    Accept: 'application/json',
    'X-CSRF-TOKEN': globalConfig.railcontentConfig.token,
  }

  if (!globalConfig.isMA) {
    const params = new URLSearchParams(window.location.search)
    if (params.get('testNow')) {
      headers['testNow'] = params.get('testNow')
    }
    if (params.get('timezone')) {
      headers['M-Client-Timezone'] = params.get('timezone')
    }
  }

  if (globalConfig.localTimezoneString) {
    headers['M-Client-Timezone'] = globalConfig.localTimezoneString
  }

  if (globalConfig.railcontentConfig.authToken) {
    headers['Authorization'] = `Bearer ${globalConfig.railcontentConfig.authToken}`
  }

  if (dataVersion) headers['Data-Version'] = dataVersion
  const options = {
    method,
    headers,
  }
  if (body) {
    options.body = JSON.stringify(body)
  }
  try {
    const response = await fetchAbsolute(url, options)
    if (response.ok) {
      return await response.json()
    } else {
      console.error(`Fetch error: ${method} ${url} ${response.status} ${response.statusText}`)
      console.log(response)
    }
  } catch (error) {
    console.error('Fetch error:', error)
  }
  return null
}

export async function fetchUserLikes(currentVersion) {
  let url = `/content/user/likes/all`
  return fetchDataHandler(url, currentVersion)
}

export async function postContentLiked(contentId) {
  let url = `/content/user/likes/like/${contentId}`
  return await postDataHandler(url)
}

export async function postContentUnliked(contentId) {
  let url = `/content/user/likes/unlike/${contentId}`
  return await postDataHandler(url)
}

export async function fetchContentProgress(currentVersion) {
  let url = `/content/user/progress/all`
  return fetchDataHandler(url, currentVersion)
}

export async function postRecordWatchSession(
  contentId,
  mediaTypeId,
  mediaLengthSeconds,
  currentSeconds,
  secondsPlayed,
  sessionId
) {
  let url = `/railtracker/v2/media-playback-session`
  return postDataHandler(url, {
    content_id: contentId,
    media_type_id: mediaTypeId,
    media_length_seconds: mediaLengthSeconds,
    current_second: currentSeconds,
    seconds_played: secondsPlayed,
    session_id: sessionId,
  })
}

/**
 * Fetch enrolled user data for a given challenge. Intended to be used in the enrolled modal
 *
 * @param contentId - railcontent id of the challenge
 * @returns {Promise<any|null>}
 */
export async function fetchChallengeMetadata(contentId) {
  let url = `/challenges/${contentId}`
  return await fetchHandler(url, 'get')
}

/**
 * Fetch lesson, user, and challenge data for a given lesson
 *
 * @param contentId - railcontent id of the lesson
 * @returns {Promise<any|null>}
 */
export async function fetchChallengeLessonData(contentId) {
  let url = `/challenges/lessons/${contentId}`
  return await fetchHandler(url, 'get')
}

/**
 * Fetch all owned brand challenges for user
 * @param {string|null} brand - brand
 * @param {int} page - page of data to pull
 * @param {int} limit - number of elements to pull
 * @returns {Promise<any|null>}
 */
export async function fetchOwnedChallenges(brand = null, page, limit) {
  let brandParam = brand ? `&brand=${brand}` : ''
  let pageAndLimit = `?page=${page}&limit=${limit}`
  let url = `/challenges/tab_owned/get${pageAndLimit}${brandParam}`
  return await fetchHandler(url, 'get')
}

/**
 * Fetch all completed brand challenges for user
 * @param {string|null} brand - brand
 * @param {int} page - page of data to pull
 * @param {int} limit - number of elements to pull
 * @returns {Promise<any|null>}
 */
export async function fetchCompletedChallenges(brand = null, page, limit) {
  let brandParam = brand ? `&brand=${brand}` : ''
  let pageAndLimit = `?page=${page}&limit=${limit}`
  let url = `/challenges/tab_completed/get${pageAndLimit}${brandParam}`
  return await fetchHandler(url, 'get')
}

/**
 * Fetch challenge, lesson, and user metadata for a given challenge
 *
 * @param contentId - railcontent id of the challenge
 * @returns {Promise<any|null>}
 */
export async function fetchUserChallengeProgress(contentId) {
  let url = `/challenges/user_data/${contentId}`
  return await fetchHandler(url, 'get')
}

/**
 * Fetch the user's best award for this challenge
 *
 * @param contentId - railcontent id of the challenge
 * @returns {Promise<any|null>} - streamed PDF
 */
export async function fetchUserAward(contentId) {
  let url = `/challenges/download_award/${contentId}`
  return await fetchHandler(url, 'get')
}

/**
 * Get challenge duration, user progress, and status for the list of challenges
 * Intended to be used on the index page for challenges
 *
 * @param {array} contentIds - arary of railcontent ids of the challenges
 * @returns {Promise<any|null>}
 */
export async function fetchChallengeIndexMetadata(contentIds) {
  if (!challengeIndexMetaDataPromise) {
    challengeIndexMetaDataPromise = getChallengeIndexMetadataPromise()
  }
  let results = await challengeIndexMetaDataPromise
  if (Array.isArray(contentIds)) {
    results = results.filter(function (challenge) {
      return contentIds.includes(challenge.content_id)
    })
  }
  return results
}

async function getChallengeIndexMetadataPromise() {
  let url = `/challenges/user_progress_for_index_page/get`
  const result = await fetchHandler(url, 'get')
  challengeIndexMetaDataPromise = null
  return result
}

/**
 * Get active brand challenges for the authorized user
 *
 * @returns {Promise<any|null>}
 */
export async function fetchChallengeUserActiveChallenges(brand = null) {
  let brandParam = brand ? `?brand=${brand}` : ''
  let url = `/challenges/user_active_challenges/get${brandParam}`
  return await fetchHandler(url, 'get')
}

/**
 * Fetch All Carousel Card Data
 *
 * @returns {Promise<any|null>}
 */
export async function fetchCarouselCardData(brand = null) {
  const urlParams = []
  if (brand) urlParams.push(`brand=${brand}`)
  if (globalConfig.sanityConfig.perspective === 'previewDrafts') urlParams.push(`sanityPreview`)
  const url = `/api/v2/content/carousel${urlParams.length ? `?${urlParams.join('&')}` : ''}`
  return await fetchHandler(url, 'get')
}

/**
 * Fetch all completed badges for the user ordered by completion date descending
 *
 * @param {string|null} brand -
 * @returns {Promise<any|null>}
 */
export async function fetchUserBadges(brand = null) {
  let brandParam = brand ? `?brand=${brand}` : ''
  let url = `/challenges/user_badges/get${brandParam}`
  return await fetchHandler(url, 'get')
}

/**
 * Enroll a user in a challenge and set the start date of the challenge to the provided day.
 * Clears any existing progress data for this challenge
 *
 * @param {int|string} contentId - railcontent id of the challenge
 * @param {string} startDate - prefered format YYYYMMDD, but any Carbon parsable string will do.
 * @returns {Promise<any|null>}
 */
export async function postChallengesSetStartDate(contentId, startDate) {
  let url = `/challenges/set_start_date/${contentId}?start_date=${startDate}`
  return await fetchHandler(url, 'post')
}

/**
 * Enroll the user in the provided challenge and set to unlocked
 * Clears any current progress data for this challenge
 *
 * @param {int|string} contentId - railcontent id of the challenge
 * @returns {Promise<any|null>}
 */
export async function postChallengesUnlock(contentId) {
  let url = `/challenges/unlock/${contentId}`
  return await fetchHandler(url, 'post')
}

/**
 * Enroll the user in the given challenge on the challenge published_on date
 *  Clears any current progress data for this challenge
 *
 * @param {int|string} contentId - railcontent id of the challenge
 * @returns {Promise<any|null>}
 */
export async function postChallengesEnroll(contentId) {
  let url = `/challenges/enroll/${contentId}`
  return await fetchHandler(url, 'post')
}

/**
 * Remove the user from the provided challenge
 * Clears any current progress data for this challenge
 *
 * @param {int|string} contentId - railcontent id of the challenge
 * @returns {Promise<any|null>}
 */
export async function postChallengesLeave(contentId) {
  let url = `/challenges/leave/${contentId}`
  return await fetchHandler(url, 'post')
}

/**
 * Enable enrollment notifications for the provided challenge
 *
 * @param {int|string} contentId - railcontent id of the challenge
 * @returns {Promise<any|null>}
 */
export async function postChallengesEnrollmentNotification(contentId) {
  let url = `/challenges/notifications/enrollment_open/${contentId}`
  return await fetchHandler(url, 'post')
}

/**
 * Enable community notifications for the provided challenge
 *
 * @param {int|string} contentId - railcontent id of the challenge
 * @returns {Promise<any|null>}
 */
export async function postChallengesCommunityNotification(contentId) {
  let url = `/challenges/notifications/community_reminders/${contentId}`
  return await fetchHandler(url, 'post')
}

/**
 * Enable solo notifications for the provided challenge
 *
 * @param {int|string} contentId - railcontent id of the challenge
 * @returns {Promise<any|null>}
 */
export async function postChallengesSoloNotification(contentId) {
  let url = `/challenges/notifications/solo_reminders/${contentId}`
  return await fetchHandler(url, 'post')
}

/**
 * Complete the challenge lesson and update challenge progress
 *
 * @param {int|string} contentId - railcontent id of the challenge
 * @returns {Promise<any|null>} - Modal data to display
 */
export async function postChallengesCompleteLesson(contentId) {
  let url = `/challenges/complete_lesson/${contentId}`
  await contentStatusCompleted(contentId)
  return await fetchHandler(url, 'post')
}

/**
 * Hide challenge completed award bannare
 *
 * @param {int|string} contentId - railcontent id of the challenge
 * @returns {Promise<any|null>}
 */
export async function postChallengesHideCompletedBanner(contentId) {
  let url = `/challenges/hide_completed_banner/${contentId}`
  return await fetchHandler(url, 'post')
}

/**
 * Fetches user playlists for a specific brand.
 *
 * Allows optional pagination, sorting, and search parameters to control the result set.
 *
 * @param {string} brand - The brand identifier for which playlists are being fetched.
 * @param {number} [params.limit=10] - The maximum number of playlists to return per page (default is 10).
 * @param {number} [params.page=1] - The page number for pagination.
 * @param {string} [params.sort='-created_at'] - The sorting order for the playlists (default is by created_at in descending order).
 * @param {string} [params.searchTerm] - A search term to filter playlists by name.
 * @param {int|string} [params.content_id] - If content_id exists, the endpoint checks in each playlist if we have the content in the items.
 *
 * @returns {Promise<Object|null>} - A promise that resolves to the response from the API, containing the user playlists data.
 *
 * @example
 * fetchUserPlaylists('drumeo', { page: 1, sort: 'name', searchTerm: 'rock' })
 *   .then(playlists => console.log(playlists))
 *   .catch(error => console.error(error));
 */
export async function fetchUserPlaylists(
  brand,
  { page, limit, sort, searchTerm, content_id, categories } = {}
) {
  let url
  const limitString = limit ? `&limit=${limit}` : ''
  const pageString = page ? `&page=${page}` : ''
  const sortString = sort ? `&sort=${sort}` : ''
  const searchFilter = searchTerm ? `&term=${searchTerm}` : ''
  const content = content_id ? `&content_id=${content_id}` : ''
  const categoryString =
    categories && categories.length ? categories.map((cat) => `categories[]=${cat}`).join('&') : ''
  url = `/playlists/all?brand=${brand}${limitString}${pageString}${sortString}${searchFilter}${content}${categoryString ? `&${categoryString}` : ''}`
  return await fetchHandler(url)
}

/**
 * Duplicates an existing playlist by sending a POST request to the API.
 *
 * This function calls the `/playlists/duplicate` endpoint, where the server replicates the specified playlist,
 * including its items. Optionally, new `name`, `description`, `category`, or `thumbnail_url` parameters can be provided
 * to customize the duplicated playlist. If a new name is not provided, the server appends " (Duplicate)" to the original name.
 *
 * @param {string|number} playlistId - The unique identifier of the playlist to be duplicated.
 * @param {Object} [playlistData] - Optional data to customize the duplicated playlist.
 * @param {string} [playlistData.name] - New name for the duplicated playlist (default is original name + " (Duplicate)").
 * @param {string} [playlistData.description] - New description for the duplicated playlist (defaults to original description).
 * @param {string} [playlistData.category] - New category for the duplicated playlist (defaults to original category).
 * @param {string} [playlistData.thumbnail_url] - New URL for the duplicated playlist thumbnail (defaults to original thumbnail).
 *
 * @returns {Promise<Object>} - A promise that resolves to the duplicated playlist data, or rejects with an error if the duplication fails.
 *
 * The duplicated playlist contains:
 *  - `name` (string): Name of the new playlist.
 *  - `description` (string|null): Description of the duplicated playlist.
 *  - `category` (string|null): Category of the duplicated playlist.
 *  - `thumbnail_url` (string|null): URL of the playlist thumbnail.
 *  - `items` (Array): A list of items (e.g., songs, tracks) copied from the original playlist.
 *
 * @example
 * duplicatePlaylist(12345, { name: "My New Playlist" })
 *   .then(duplicatedPlaylist => console.log(duplicatedPlaylist))
 *   .catch(error => console.error('Error duplicating playlist:', error));
 */
export async function duplicatePlaylist(playlistId, playlistData) {
  let url = `/playlists/duplicate/${playlistId}`
  return await fetchHandler(url, 'post', null, playlistData)
}

/**
 * Deletes a user’s playlist along with all associated items by sending a DELETE request to the API.
 *
 * This function calls the `/playlists/playlist` endpoint, where the server verifies the user’s ownership of the specified playlist.
 * If the user is authorized, it deletes both the playlist and its associated items.
 *
 * @param {string|number} playlistId - The unique identifier of the playlist to be deleted.
 *
 * @returns {Promise<Object>} - A promise that resolves to an object containing:
 *  - `success` (boolean): Indicates if the deletion was successful (`true` for success).
 *  - `message` (string): Success confirmation message (e.g., "Playlist and associated items deleted successfully").
 *
 * If the user is unauthorized or the playlist does not exist, the promise rejects with an error.
 *
 * @example
 * deletePlaylist(12345)
 *   .then(response => {
 *       if (response.success) {
 *           console.log(response.message);
 *       }
 *   })
 *   .catch(error => console.error('Error deleting playlist:', error));
 */
export async function deletePlaylist(playlistId) {
  let url = `/playlists/playlist/${playlistId}`
  return await fetchHandler(url, 'delete')
}

/**
 * Updates a user’s playlist by sending a PUT request with updated data to the API.
 *
 * This function calls the `/playlists/playlist/{playlistId}` endpoint, where the server validates the incoming data
 * and verifies that the authenticated user is the playlist owner. If authorized, it updates the playlist details with the provided data.
 *
 * @param {string|number} playlistId - The unique identifier of the playlist to be updated.
 * @param {Object} updatedData - An object containing the playlist data to update. The possible fields include:
 *  - `name` (string): The new name of the playlist (max 255 characters).
 *  - `description` (string): A new description for the playlist (max 1000 characters).
 *  - `category` (string): The updated category of the playlist (max 255 characters).
 *  - `private` (boolean): Whether the playlist is private.
 *  - `thumbnail_url` (string): The URL of the playlist thumbnail.
 *
 * @returns {Promise<Object>} - A promise that resolves to an object containing:
 *  - `success` (boolean): Indicates if the update was successful (`true` for success).
 *  - `message` (string): Success confirmation message if the update is successful.
 *  - Other fields containing the updated playlist data.
 *
 * If the user is unauthorized or the data validation fails, the promise rejects with an error.
 *
 * @example
 * updatePlaylist(12345, { name: "My New Playlist Name", description: "Updated description" })
 *   .then(response => {
 *       if (response.success) {
 *           console.log(response.message);
 *       }
 *   })
 *   .catch(error => console.error('Error updating playlist:', error));
 */
export async function updatePlaylist(playlistId, updatedData) {
  const url = `/playlists/playlist/${playlistId}`
  return await fetchHandler(url, 'PUT', null, updatedData)
}

/**
 * Creates a new user playlist by sending a POST request with playlist data to the API.
 *
 * This function calls the `/playlists/playlist` endpoint, where the server validates the incoming data and associates
 * the new playlist with the authenticated user. The `name` field is required, while other fields are optional.
 *
 * @param {Object} playlistData - An object containing data to create the playlist. The fields include:
 *  - `name` (string): The name of the new playlist (required, max 255 characters).
 *  - `description` (string): A description of the playlist (optional, max 1000 characters).
 *  - `category` (string): The category of the playlist.
 *  - `thumbnail_url` (string): The URL of the playlist thumbnail (optional, must be a valid URL).
 *  - `private` (boolean): Whether the playlist is private (optional, defaults to true).
 *  - `brand` (string): Brand identifier for the playlist.
 *
 * @returns {Promise<Object>} - A promise that resolves to the created playlist data if successful, or an error response if validation fails.
 *
 * The server response includes:
 *  - `message`: Success message indicating playlist creation (e.g., "Playlist created successfully").
 *  - `playlist`: The data for the created playlist, including the `user_id` of the authenticated user.
 *
 * @example
 * createPlaylist({ name: "My Playlist", description: "A cool playlist", private: true })
 *   .then(response => console.log(response.message))
 *   .catch(error => console.error('Error creating playlist:', error));
 */
export async function createPlaylist(playlistData) {
  const url = `/playlists/playlist`
  return await fetchHandler(url, 'POST', null, playlistData)
}

/**
 * Sends a request to "like" a playlist on behalf of the authenticated user.
 *
 * This function calls the `/playlists/playlist/like` endpoint, where the server validates the `playlist_id` and checks
 * if the authenticated user has already liked the playlist. If not, it creates a new "like" entry associated with the playlist.
 *
 * @param {string|number} playlistId - The unique identifier of the playlist to be liked.
 *
 * @returns {Promise<Object>} - A promise that resolves with the response from the API. The response contains:
 *  - `success` (boolean): Indicates if the like action was successful (`true` for success).
 *  - `message` (string): A success message if the playlist is liked successfully, or a notification if it was already liked.
 *  - `like` (Object|null): Details of the created "like" entry if the playlist is newly liked, or null if it was already liked.
 *
 * The `like` object in the response includes:
 *  - `playlist_id`: The ID of the liked playlist.
 *  - `user_id`: The ID of the user who liked the playlist.
 *  - `brand`: The brand associated with the like.
 *  - `created_at`: Timestamp of when the like was created.
 *
 * @example
 * likePlaylist(12345)
 *   .then(response => {
 *       if (response.success) {
 *           console.log(response.message);
 *       }
 *   })
 *   .catch(error => console.error('Error liking playlist:', error));
 */
export async function likePlaylist(playlistId) {
  const url = `/playlists/like`
  const payload = { playlist_id: playlistId }
  return await fetchHandler(url, 'PUT', null, payload)
}

/**
 * Removes a "like" from a playlist for the authenticated user.
 *
 * This function sends a DELETE request to the `/playlists/like` endpoint, where the server validates the `playlist_id`
 * and checks if a like by the authenticated user already exists for the specified playlist. If so, it deletes the like.
 *
 * @param {string|number} playlistId - The unique identifier of the playlist whose like is to be removed.
 *
 * @returns {Promise<Object>} - A promise that resolves with the response from the API. The response contains:
 *  - `success` (boolean): Indicates if the removal was successful (`true` for success).
 *  - `message` (string): A success message if the playlist like is removed successfully or a notification if the playlist was not previously liked.
 *
 * @example
 * deletePlaylistLike(12345)
 *   .then(response => {
 *       if (response.success) {
 *           console.log(response.message);
 *       }
 *   })
 *   .catch(error => console.error('Error removing playlist like:', error));
 */
export async function deletePlaylistLike(playlistId) {
  const url = `/playlists/like`
  const payload = { playlist_id: playlistId }
  return await fetchHandler(url, 'DELETE', null, payload)
}

/**
 * Retrieves details of a specific playlist by its ID.
 *
 * This function sends a GET request to the `/playlists/playlist` endpoint with a specified playlist ID.
 * The server validates the user's access to the playlist and returns playlist details if the user is authorized.
 *
 * @param {string|number} playlistId - The unique identifier of the playlist to retrieve.
 *
 * @returns {Promise<Object>} - A promise that resolves to the response from the API, containing:
 *  - `data` (Object): The playlist details, or an error message if access is denied or the playlist is not found.
 *
 * @example
 * fetchPlaylist(12345)
 *   .then(response => console.log(response.data))
 *   .catch(error => console.error('Error fetching playlist:', error));
 */
export async function fetchPlaylist(playlistId) {
  const url = `/playlists/playlist/${playlistId}`
  return await fetchHandler(url, 'GET')
}

/**
 * Retrieves items within a specified playlist by playlist ID.
 *
 * This function sends a GET request to the `/playlists/playlist-lessons` endpoint to fetch items in the given playlist.
 * The server combines data from the playlist and additional metadata from Sanity to enhance item details.
 *
 * @param {string|number} playlistId - The unique identifier of the playlist whose items are to be fetched.
 *
 * @returns {Promise<Array<Object>>} - A promise that resolves to an array of playlist items
 *
 * @example
 * fetchPlaylistItems(12345)
 *   .then(items => console.log(items))
 *   .catch(error => console.error('Error fetching playlist items:', error));
 */
export async function fetchPlaylistItems(playlistId, { sort } = {}) {
  const sortString = sort ? `&sort=${sort}` : ''
  const url = `/playlists/playlist-lessons?playlist_id=${playlistId}${sortString}`
  return await fetchHandler(url, 'GET')
}

/**
 * Updates a playlist item with the provided data.
 *
 * @param {Object} updatedData - The data to update the playlist item with.
 * @param {number} updatedData.user_playlist_item_id - The ID of the playlist item to update.
 * @param {number} [updatedData.start_second] - (Optional) The start time in seconds for the item.
 * @param {number} [updatedData.end_second] - (Optional) The end time in seconds for the item.
 * @param {string} [updatedData.playlist_item_name] - (Optional) The new name for the playlist item.
 * @param {number} [updatedData.position] - (Optional) The new position for the playlist item within the playlist.
 * @returns {Promise<Object|null>} - A promise that resolves to an object containing:
 *  - `success` (boolean): Indicates if the update was successful (`true` for success).
 *  - `data` (Object): The updated playlist item data.
 *
 * Resolves to `null` if the request fails.
 * @throws {Error} - Throws an error if the request fails.
 *
 * @example
 * const updatedData = {
 *   user_playlist_item_id: 123,
 *   start_second: 30,
 *   end_second: 120,
 *   playlist_item_name: "Updated Playlist Item Name",
 *   position: 2
 * };
 *
 * updatePlaylistItem(updatedData)
 *   .then(response => {
 *     if (response.success) {
 *       console.log("Playlist item updated successfully:", response.data);
 *     }
 *   })
 *   .catch(error => {
 *     console.error("Error updating playlist item:", error);
 *   });
 */
export async function updatePlaylistItem(updatedData) {
  const url = `/playlists/item`
  return await fetchHandler(url, 'POST', null, updatedData)
}

/**
 * Deletes a playlist item and repositions other items in the playlist if necessary.
 *
 * @param {Object} payload - The data required to delete the playlist item.
 * @param {number} payload.user_playlist_item_id - The ID of the playlist item to delete.
 * @returns {Promise<Object|null>} - A promise that resolves to an object containing:
 *  - `success` (boolean): Indicates if the deletion was successful (`true` for success).
 *  - `message` (string): A success message if the item is deleted successfully.
 *  - `error` (string): An error message if the deletion fails.
 *
 * Resolves to `null` if the request fails.
 * @throws {Error} - Throws an error if the request fails.
 *
 * @example
 * const payload = {
 *   user_playlist_item_id: 123
 * };
 *
 * deletePlaylistItem(payload)
 *   .then(response => {
 *     if (response.success) {
 *       console.log("Playlist item deleted successfully:", response.message);
 *     } else {
 *       console.error("Error:", response.error);
 *     }
 *   })
 *   .catch(error => {
 *     console.error("Error deleting playlist item:", error);
 *   });
 */
export async function deletePlaylistItem(payload) {
  const url = `/playlists/item`
  return await fetchHandler(url, 'DELETE', null, payload)
}

/**
 * Fetches detailed data for a specific playlist item, including associated Sanity and Assignment information if available.
 *
 * @param {Object} payload - The request payload containing necessary parameters.
 * @param {number} payload.user_playlist_item_id - The unique ID of the playlist item to fetch.
 * @returns {Promise<Object|null>} - A promise that resolves to an object with the fetched playlist item data, including:
 *  - `success` (boolean): Indicates if the data retrieval was successful (`true` on success).
 *  - `data` (Object): Contains the detailed playlist item data enriched with Sanity and Assignment details, if available.
 *
 * Resolves to `null` if the request fails.
 * @throws {Error} - Throws an error if the request encounters issues during retrieval.
 *
 * @example
 * const payload = { user_playlist_item_id: 123 };
 *
 * fetchPlaylistItem(payload)
 *   .then(response => {
 *     if (response?.success) {
 *       console.log("Fetched playlist item data:", response.data);
 *     } else {
 *       console.log("Failed to fetch playlist item data.");
 *     }
 *   })
 *   .catch(error => {
 *     console.error("Error fetching playlist item:", error);
 *   });
 */
export async function fetchPlaylistItem(payload) {
  const playlistItemId = payload.user_playlist_item_id
  const url = `/playlists/item/${playlistItemId}`
  return await fetchHandler(url)
}

export async function postContentCompleted(contentId) {
  let url = `/content/user/progress/complete`
  return postDataHandler(url, { contentId: contentId })
}

export async function postContentReset(contentId) {
  let url = `/content/user/progress/reset`
  return postDataHandler(url, { contentId: contentId })
}

/**
 * Adds an item to one or more playlists by making a POST request to the `/playlists/add-item` endpoint.
 *
 * @param {Object} payload - The request payload containing necessary parameters.
 * @param {number} payload.content_id - The ID of the content to add to the playlist(s).
 * @param {Array<number>} payload.playlist_id - An array of playlist IDs where the content should be added.
 * @param {boolean} [payload.import_full_soundslice_assignment=false] - Flag to include full Soundslice assignments.
 * @param {boolean} [payload.import_instrumentless_soundslice_assignment=false] - Flag to include instrumentless Soundslice assignments.
 * @param {boolean} [payload.import_high_routine=false] - Flag to include high routine content.
 * @param {boolean} [payload.import_low_routine=false] - Flag to include low routine content.
 * @param {boolean} [payload.import_all_assignments=false] - Flag to include all Soundslice assignments if true.
 *
 * @returns {Promise<Object|null>} - A promise that resolves to an object with the response data, including:
 *  - `success` (boolean): Indicates if the items were added successfully (`true` on success).
 *  - `limit_excedeed` (Array): A list of playlists where the item limit was exceeded, if any.
 *  - `successful` (Array): A list of successfully added items (empty if none).
 *
 * Resolves to `null` if the request fails.
 * @throws {Error} - Throws an error if the request encounters issues during the operation.
 *
 * @example
 * const payload = {
 *     content_id: 123,
 *     playlist_id: [1, 2, 3],
 *     import_all_assignments: true
 * };
 *
 * addItemToPlaylist(payload)
 *   .then(response => {
 *     if (response?.success) {
 *       console.log("Item(s) added to playlist successfully");
 *     }
 *     if (response?.limit_excedeed) {
 *       console.warn("Some playlists exceeded the item limit:", response.limit_excedeed);
 *     }
 *   })
 *   .catch(error => {
 *     console.error("Error adding item to playlist:", error);
 *   });
 */
export async function addItemToPlaylist(payload) {
  const url = `/playlists/add-item`
  return await fetchHandler(url, 'POST', null, payload)
}

/**
 * Retrieves the count of lessons and assignments associated with a specific content ID.
 *
 * @param {number} contentId - The ID of the content for which to count lessons and assignments.
 *
 * @returns {Promise<Object|null>} - A promise that resolves to an object containing the counts:
 *  - `lessons_count` (number): The number of lessons associated with the content.
 *  - `soundslice_assignments_count` (number): The number of Soundslice assignments associated with the content.
 *
 * @example
 * const contentId = 123;
 *
 * countAssignmentsAndLessons(contentId)
 *   .then(response => {
 *     if (response) {
 *       console.log("Lessons count:", response.lessons_count);
 *       console.log("Soundslice assignments count:", response.soundslice_assignments_count);
 *     } else {
 *       console.log("Failed to retrieve counts.");
 *     }
 *   })
 *   .catch(error => {
 *     console.error("Error fetching assignments and lessons count:", error);
 *   });
 */
export async function countAssignmentsAndLessons(contentId) {
  const url = `/playlists/count-lessons-and-assignments/${contentId}`
  return await fetchHandler(url)
}

/**
 * Pins a playlist to the user's playlist menu.
 *
 * @param {number} playlistId - The ID of the playlist to pin.
 * @returns {Promise<Object>} - A promise that resolves to an object containing:
 *  - `success` (boolean): Indicates if the pinning operation was successful (`true` for success).
 *  - `message` (string): A success message if the playlist was pinned successfully.
 *  - `error` (string): An error message if the pinning operation fails.
 *
 * Resolves to an error message if the request fails.
 * @throws {Error} - Throws an error if the request fails.
 *
 * @example
 * const playlistId = 123;
 *
 * pinPlaylist(playlistId)
 *   .then(response => {
 *     if (response.success) {
 *       console.log("Playlist pinned successfully:", response.message);
 *     } else {
 *       console.error("Error:", response.error);
 *     }
 *   })
 *   .catch(error => {
 *     console.error("Error pinning playlist:", error);
 *   });
 */
export async function pinPlaylist(playlistId) {
  const url = `/playlists/pin/${playlistId}`
  return await fetchHandler(url, 'PUT')
}

/**
 * Unpins a playlist
 *
 * @param {number} playlistId - The ID of the playlist to unpin.
 * @returns {Promise<Object>} - A promise that resolves to an object containing:
 *  - `success` (boolean): Indicates if the unpinning operation was successful (`true` for success).
 *  - `message` (string): A success message if the playlist was unpinned successfully.
 *  - `error` (string): An error message if the unpinning operation fails.
 *
 * Resolves to an error message if the request fails.
 * @throws {Error} - Throws an error if the request fails.
 *
 * @example
 * const playlistId = 123;
 *
 * unpinPlaylist(playlistId)
 *   .then(response => {
 *     if (response.success) {
 *       console.log("Playlist unpinned successfully:", response.message);
 *     } else {
 *       console.error("Error:", response.error);
 *     }
 *   })
 *   .catch(error => {
 *     console.error("Error unpinning playlist:", error);
 *   });
 */
export async function unpinPlaylist(playlistId) {
  const url = `/playlists/unpin/${playlistId}`
  return await fetchHandler(url, 'PUT')
}

/**
 * Fetches the list of pinned playlists for the authenticated user.
 *
 * @param {string} brand - The brand associated with the playlists to fetch.
 * @returns {Promise<Object>} - A promise that resolves to an object containing:
 *  - `success` (boolean): Indicates if the fetching operation was successful (`true` for success).
 *  - `data` (Array): An array of pinned playlists.
 *  - `error` (string): An error message if the fetching operation fails.
 *
 * Resolves to an error message if the request fails.
 * @throws {Error} - Throws an error if the request fails.
 *
 * @example
 * const brand = "drumeo";
 *
 * fetchPinnedPlaylists(brand)
 *   .then(response => {
 *     if (response.success) {
 *       console.log("Pinned playlists:", response.data);
 *     } else {
 *       console.error("Error:", response.error);
 *     }
 *   })
 *   .catch(error => {
 *     console.error("Error fetching pinned playlists:", error);
 *   });
 */
export async function fetchPinnedPlaylists(brand) {
  const url = `/playlists/my-pinned-playlists?brand=${brand}`
  return await fetchHandler(url, 'GET')
}

/**
 * Report playlist endpoint
 *
 * @param playlistId
 * @param issue
 * @returns {Promise<any|null>}
 */
export async function reportPlaylist(playlistId, { issue } = {}) {
  const issueString = issue ? `?issue=${issue}` : ''
  const url = `/playlists/report/${playlistId}${issueString}`
  return await fetchHandler(url, 'PUT')
}

export async function playback(playlistId) {
  const url = `/playlists/play/${playlistId}`
  return await fetchHandler(url, 'GET')
}

/**
 * Set a user's StudentView Flag
 *
 * @param {int|string} userId - id of the user (must be currently authenticated)
 * @param {bool} enable - truthsy value to enable student view
 * @returns {Promise<any|null>}
 */
export async function setStudentViewForUser(userId, enable) {
  let url = `/user-management-system/user/update/${userId}`
  let data = { use_student_view: enable ? 1 : 0 }
  return await patchDataHandler(url, data)
}

/**
 * Fetch the top comment for a given content
 *
 * @param {int} railcontentId - The railcontent id to fetch.
 * @returns {Promise<Object|null>} - A promise that resolves to an comment object
 */
export async function fetchTopComment(railcontentId) {
  const url = `/api/content/v1/comments/${railcontentId}/top`
  return await fetchHandler(url)
}

/**
 *
 * @param railcontentId
 * @param page
 * @param limit
 * @returns {Promise<*|null>}
 */
export async function fetchComments(railcontentId, page = 1, limit = 20) {
  const url = `/api/content/v1/comments/${railcontentId}/all?page=${page}&limit=${limit}`
  return await fetchHandler(url)
}

/**
 *
 * @param commentId
 * @param page
 * @param limit
 * @returns {Promise<*|null>}
 */
export async function fetchCommentRelies(commentId, page = 1, limit = 20) {
  const url = `/api/content/v1/comments/${commentId}/replies?page=${page}&limit=${limit}`
  return await fetchHandler(url)
}

function fetchAbsolute(url, params) {
  if (globalConfig.railcontentConfig.authToken) {
    params.headers['Authorization'] = `Bearer ${globalConfig.railcontentConfig.authToken}`
  }

  if (globalConfig.railcontentConfig.baseUrl) {
    if (url.startsWith('/')) {
      return fetch(globalConfig.railcontentConfig.baseUrl + url, params)
    }
  }
  return fetch(url, params)
}