content-org/playlists.js

/**
 * @module Playlists
 */
import { GET, POST, PUT, DELETE } from '../../infrastructure/http/HttpClient.ts'
import { getNavigateToForPlaylists } from '../contentAggregator.js'
import './playlists-types.js'

/**
 * Exported functions that are excluded from index generation.
 *
 * @type {string[]}
 */
const excludeFromGeneratedIndex = []

const BASE_PATH = `/api/content-org`

/**
 * 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 {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, content_id } = {}
) {
  const pageString = page ? `?page=${page}` : '?page=1'
  const limitString = limit ? `&limit=${limit}` : ''
  const sortString = sort ? `&sort=${sort}` : ''
  const content = content_id ? `&content_id=${content_id}` : ''
  const brandString = brand ? `&brand=${brand}` : ''
  const url = `${BASE_PATH}/v1/user/playlists${pageString}${brandString}${limitString}${sortString}${content}`
  return await getNavigateToForPlaylists(await GET(url), {dataField: 'data'})
}

/**
 * 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 {CreatePlaylistDTO} 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.
 *  - `private` (boolean): Whether the playlist is private (optional, defaults to false).
 *  - `brand` (string): Brand identifier for the playlist.
 *
 * @returns {Promise<Playlist>} - A promise that resolves to the created playlist data if successful, or an error response if validation fails.
 *
 * @example
 * createPlaylist({ name: "My Playlist", description: "A cool playlist", private: true })
 *   .then(response => console.log(response))
 *   .catch(error => console.error('Error creating playlist:', error));
 */
export async function createPlaylist(playlistData) {
  const url = `${BASE_PATH}/v1/user/playlists`
  return await POST(url, playlistData)
}

/**
 * Soft deletes a playlist. Will cascade and also soft delete entries in other playlist tables (pinned, reported, liked,
 * playlist content) and last engaged that have this playlist id
 * @param {id} playlist - the id of the playlist you want to soft delete.
 * @returns {Promise<any|string|null>}
 */
export async function deletePlaylist(playlist) {
  const url = `${BASE_PATH}/v1/user/playlists/delete/${playlist}`
  return await POST(url, playlist)
}

/**
 * Soft restores a playlist. Will cascade and also soft restore entries in other playlist tables (pinned, reported, liked,
 * playlist content) and last engaged that have this playlist id
 * @param {id} playlist - the id of the playlist you want to soft restore.
 * @returns {Promise<any|string|null>}
 */
export async function undeletePlaylist(playlist) {
  const url = `${BASE_PATH}/v1/user/playlists/undelete/${playlist}`
  return await POST(url, playlist)
}


/**
 * Likes a playlist for the current user.
 *
 * @async
 * @function likePlaylist
 * @param {string|number} playlistId - The unique identifier of the playlist to like.
 *
 * @returns {Promise<Object>}
 *
 * @example
 * // Like playlist with ID '123'
 * try {
 *   const response = await likePlaylist('123');
 *   console.log('Playlist liked successfully:', response);
 * } catch (error) {
 *   console.error('Failed to like playlist:', error);
 * }
 */
export async function likePlaylist(playlistId) {
  const url = `${BASE_PATH}/v1/user/playlists/like/${playlistId}`
  return await PUT(url, null)
}

/**
 * Unlikes a previously liked playlist.
 * @async
 * @function unlikePlaylist
 * @param {string|number} playlistId - The unique identifier of the playlist to unlike.
 *
 * @returns {Promise<Object>}
 *
 *
 * @example
 * // Unlike playlist with ID '123'
 * try {
 *   const response = await unlikePlaylist('123');
 *   console.log('Playlist unliked successfully:', response);
 * } catch (error) {
 *   console.error('Failed to unlike playlist:', error);
 * }
 */
export async function unlikePlaylist(playlistId) {
  const url = `${BASE_PATH}/v1/user/playlists/like/${playlistId}`
  return await DELETE(url)
}

/**
 * Reports a playlist
 *
 * @async
 * @function reportPlaylist
 * @param {string|number} playlistId - The unique identifier of the playlist to report.
 *
 * @returns {Promise<Object>}
 *
 * @example
 * // Report playlist with ID '123'
 * try {
 *   const response = await reportPlaylist('123');
 *   console.log('Playlist reported successfully:', response);
 * } catch (error) {
 *   console.error('Failed to report playlist:', error);
 * }
 */
export async function reportPlaylist(playlistId) {
  const url = `${BASE_PATH}/v1/user/playlists/report/${playlistId}`
  return await POST(url, null)
}

/**
 * Adds an item to one or more playlists
 *
 * @param {AddItemToPlaylistDTO} payload - The request payload containing necessary parameters.
 *
 * @returns {Promise<Object|null>} - A promise that resolves to an object with the response data, including:
 *  - `added` (Array): Playlist ids that we were success
 *  - `limit_exceeded` (Array): A list of playlists where the item limit was exceeded, if any.
 *  - `unauthorized` (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],
 *     position: 2,
 *
 * };
 *
 * addItemToPlaylist(payload)
 *   .then(response => {
 *     if (response?.success) {
 *       console.log("Item(s) added to playlist successfully");
 *     }
 *     if (response?.limit_exceeded) {
 *       console.warn("Some playlists exceeded the item limit:", response.limit_exceeded);
 *     }
 *   })
 *   .catch(error => {
 *     console.error("Error adding item to playlist:", error);
 *   });
 */
export async function addItemToPlaylist(payload) {
  const url = `${BASE_PATH}/v1/user/playlists/items`
  return await POST(url, payload)
}

/**
 * Toggles a playlists public/private state
 *
 * @param {string|number} playlistId
 * @param {Boolean} is_private - flag for private/public

 * @returns {Promise<Playlist>} - A promise that resolves to the updated playlist data if successful, or an error response if validation fails.
 *
 * @example
 * togglePlaylistPrivate(11541, true)
 *   .then(response => console.log(response))
 *   .catch(error => console.error('Error creating playlist:', error));
 */
export async function togglePlaylistPrivate(playlistId, is_private)
{
  return await updatePlaylist(playlistId, {is_private})
}


/**
 * Updates a playlists values
 *
 * @param {string|number} playlistId
 * @param {UpdatePlaylistDTO} updateData  - An object containing fields to update on the playlist:
 *  - `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 (optional).
 *  - `is_private` (boolean): Whether the playlist is private (optional, defaults to false).
 *  - `deleted_items` (array): List of playlist item IDs to delete (optional).
 *  - `item_order` (array): Updated order of playlist items (ids, not railcontent_ids) (optional).
 *
 * @returns {Promise<object>} - A promise that resolves to the created playlist data and lessons if successful, or an error response if validation fails.
 *
 * The server response includes:
 *  - `playlist`: Playlist metadata (same as fetchPlaylist)
 *  - `lessons`: Updated list of plalyist lessons  (same as fetchPlaylistItems)
 *
 * @example
 * updatePlaylist(661113 { name: "My Playlist", description: "A cool playlist", is_private: true, deleted_items : [2189832, 221091] })
 *   .then(response => console.log(response.playlist); console.log(response.lessons))
 *   .catch(error => console.error('Error updating playlist:', error));
 */
export async function updatePlaylist(playlistId, updateData)
{
  const { name, description, category, is_private, item_order, deleted_items } = updateData;
  let data = {
    ...name && { name },
    ...'description' in updateData && { description },
    ...'is_private' in updateData && { private: is_private || false },
    ...'category' in updateData && { category },
    ...deleted_items && { deleted_items },
    ...item_order && { item_order },
  }
  const url = `${BASE_PATH}/v1/user/playlists/${playlistId}`
  return await PUT(url, data);
}

/**
 * Delete Items from playlist
 *
 * @async
 * @function deleteItemsFromPlaylist
 * @param {string|number} playlistId - The unique identifier of the playlist to update.
 * @param {array} deleted_items - list of playlist ids to delete (user_playlist_item_id, not the railcontent_id)
 *
 * @returns {Promise<Object>}
 *
 * @example
 * // Delete items 8462221 and 8462222 from playlist 81111
 * try {
 *   const response = await deleteItemsFromPlaylist(81111, [8462221, 8462222]);
 * } catch (error) {
 *   console.error('Failed to delete playlist items:', error);
 * }
 */
export async function deleteItemsFromPlaylist(playlistId, deleted_items) {
  return await updatePlaylist(playlistId, {deleted_items})
}

/**
 * Restore items
 *
 * @async
 * @function restoreItemFromPlaylist
 * @param {string|number} playlistItemId - The unique identifier of the playlist ite to restore.
 *
 * @returns {Promise<Object>}
 *
 * @example
 * // Restore item 8462221
 * try {
 *   const response = await restoreItemFromPlaylist(8462221);
 * } catch (error) {
 *   console.error('Failed to restore playlist item:', error);
 * }
 */
export async function restoreItemFromPlaylist(playlistItemId) {
  const url = `${BASE_PATH}/v1/user/playlists/items/undelete/${playlistItemId}`
  return await POST(url, null)
}

/**
 * Duplicates a playlist and playlist items for the provided playlistID for the authorized user
 *
 * @param {string|number} playlistId
 * @param {DuplicatePlaylistDTO} 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.
 *  - `private` (boolean): Whether the playlist is private (optional, defaults to false).
 *  - `brand` (string): Brand identifier for the playlist.
 *  - 'items' (array): List of playlist items to duplicate in updated order
 *
 * @returns {Promise<Playlist>}
 * @example
 * duplicatePlaylist(81167, { name: "My Playlist (Duplicate)", description: "A cool playlist", private: true })
 *   .then(response => console.log(response))
 *   .catch(error => console.error('Error creating playlist:', error));
 */
export async function duplicatePlaylist(playlistId, playlistData) {
  const url = `${BASE_PATH}/v1/user/playlists/duplicate/${playlistId}`
  return await POST(url, playlistData)
}

/**
 * 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 = `${BASE_PATH}/v1/user/playlists/${playlistId}`
  return await getNavigateToForPlaylists(await GET(url))
}

/**
 * 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) {
  const url = `${BASE_PATH}/v1/user/playlists/items/${playlistId}`
  return await GET(url)
}