components_home_LoadItemsTask.bs

import "pkg:/source/api/ApiClient.bs"
import "pkg:/source/api/baserequest.bs"
import "pkg:/source/api/Image.bs"
import "pkg:/source/api/Items.bs"
import "pkg:/source/data/JellyfinDataTransformer.bs"
import "pkg:/source/roku_modules/log/LogMixin.brs"
import "pkg:/source/utils/config.bs"
import "pkg:/source/utils/deviceCapabilities.bs"
import "pkg:/source/utils/misc.bs"

sub init()
  m.log = log.Logger("LoadItemsTask")
  m.top.functionName = "loadItems"
end sub

sub loadItems()
  globalUser = m.global.user
  m.transformer = JellyfinDataTransformer()

  results = []

  ' Load Libraries
  if m.top.itemsToLoad = "libraries"

    data = GetApi().GetViews()
    if isValid(data) and isValid(data.Items)
      for each item in data.Items
        ' Skip Books for now as we don't support it (issue #525)
        if item.CollectionType <> "books"
          results.push(m.transformer.transformBaseItem(item))
        end if
      end for
    end if

    ' Load Latest Additions to Libraries
  else if m.top.itemsToLoad = "latest"
    activeUser = globalUser.id
    if isValid(activeUser)
      params = {}
      params["Limit"] = 16
      params["ParentId"] = m.top.itemId
      params["EnableImageTypes"] = "Primary,Backdrop,Thumb"
      params["ImageTypeLimit"] = 1
      params["EnableTotalRecordCount"] = false

      data = GetApi().GetLatestMedia(params)

      if isValid(data)
        for each item in data
          ' Skip Books for now as we don't support it (issue #525)
          if item.Type <> "Book"
            results.push(m.transformer.transformBaseItem(item))
          end if
        end for
      end if
    end if

    ' Load Next Up
  else if m.top.itemsToLoad = "nextUp"
    userSettings = globalUser.settings

    url = "Shows/NextUp"
    params = {}
    params["recursive"] = true
    params["SortBy"] = "DatePlayed"
    params["SortOrder"] = "Descending"
    params["ImageTypeLimit"] = 1
    params["UserId"] = globalUser.id
    params["EnableRewatching"] = userSettings.uiDetailsEnableRewatchingNextUp
    params["DisableFirstEpisode"] = false
    params["limit"] = 69
    params["EnableTotalRecordCount"] = false
    params["EnableResumable"] = false

    maxDaysInNextUp = userSettings.uiDetailsMaxDaysNextUp
    if isValid(maxDaysInNextUp)
      if maxDaysInNextUp > 0
        dateToday = CreateObject("roDateTime")
        dateCutoff = CreateObject("roDateTime")

        dateCutoff.FromSeconds(dateToday.AsSeconds() - (maxDaysInNextUp * 86400))

        params["NextUpDateCutoff"] = dateCutoff.ToISOString()
      end if
    end if

    resp = APIRequest(url, params)
    data = getJson(resp)
    if isValid(data) and isValid(data.Items)
      for each item in data.Items
        results.push(m.transformer.transformBaseItem(item))
      end for
    end if
    ' Load Continue Watching
  else if m.top.itemsToLoad = "continue"
    activeUser = globalUser.id
    if isValid(activeUser)
      params = {}
      params["recursive"] = true
      params["SortBy"] = "DatePlayed"
      params["SortOrder"] = "Descending"
      params["Filters"] = "IsResumable"
      params["EnableTotalRecordCount"] = false

      data = GetApi().GetResumeItems(params)
      if isValid(data) and isValid(data.Items)
        for each item in data.Items
          ' Skip Books for now as we don't support it (issue #558)
          if item.Type <> "Book"
            results.push(m.transformer.transformBaseItem(item))
          end if
        end for
      end if
    end if

  else if m.top.itemsToLoad = "favorites"

    params = {}
    params["Filters"] = "IsFavorite"
    params["Limit"] = 20
    params["recursive"] = true
    params["sortby"] = "random"
    params["EnableTotalRecordCount"] = false

    data = GetApi().GetItemsByQuery(params)
    if isValid(data) and isValid(data.Items)
      for each item in data.Items
        ' Skip Books for now as we don't support it (issue #558)
        ' also skip songs since there is limited space
        if not (item.Type = "Book" or item.Type = "Audio")
          results.push(m.transformer.transformBaseItem(item))
        end if
      end for
    end if

  else if m.top.itemsToLoad = "onNow"
    url = "LiveTv/Programs/Recommended"
    params = {}
    params["userId"] = globalUser.id
    params["isAiring"] = true
    params["limit"] = 16 ' 16 to be consistent with "Latest In"
    params["imageTypeLimit"] = 1
    params["enableImageTypes"] = "Primary,Thumb,Backdrop"
    params["enableTotalRecordCount"] = false
    params["fields"] = "ChannelInfo,PrimaryImageAspectRatio"

    resp = APIRequest(url, params)
    data = getJson(resp)
    if isValid(data) and isValid(data.Items)
      for each item in data.Items
        results.push(m.transformer.transformBaseItem(item))
      end for
    end if

    ' Extract array of persons from Views and download full metadata for each
  else if m.top.itemsToLoad = "people"
    if not isValid(m.top.peopleList)
      m.top.content = results
      return
    end if
    for each person in m.top.peopleList
      node = m.transformer.transformPerson(person)
      if isValid(node) then results.push(node)
    end for
  else if m.top.itemsToLoad = "specialfeatures"
    data = GetApi().GetSpecialFeatures(m.top.itemId)
    if isValid(data) and data.count() > 0
      for each specfeat in data
        node = m.transformer.transformBaseItem(specfeat)
        if isValid(node) then results.push(node)
      end for
    end if
  else if m.top.itemsToLoad = "additionalparts"
    additionalParts = GetApi().GetAdditionalParts(m.top.itemId)
    if isValid(additionalParts)
      for each part in additionalParts.items
        node = m.transformer.transformBaseItem(part)
        if isValid(node) then results.push(node)
      end for
    end if
  else if m.top.itemsToLoad = "likethis"
    params = { "userId": globalUser.id, "limit": 16 }
    url = Substitute("Items/{0}/Similar", m.top.itemId)
    resp = APIRequest(url, params)
    data = getJson(resp)
    if isValid(data) and isValid(data.Items)
      for each item in data.items
        node = m.transformer.transformBaseItem(item)
        if isValid(node) then results.push(node)
      end for
    end if
  else if m.top.itemsToLoad = "personMovies"
    getPersonVideos("Movie", results)
  else if m.top.itemsToLoad = "personTVShows"
    getPersonVideos("Episode", results)
  else if m.top.itemsToLoad = "personSeries"
    getPersonVideos("Series", results)
  else if m.top.itemsToLoad = "seasons"
    data = GetApi().GetSeasons(m.top.itemId, { Fields: "PrimaryImageAspectRatio,Overview", EnableImages: true })
    if isValid(data) and isValid(data.Items)
      for each item in data.Items
        node = m.transformer.transformBaseItem(item)
        if isValid(node) then results.push(node)
      end for
    end if
  else if m.top.itemsToLoad = "seasonEpisodes"
    ' itemId = seriesId, metadata.seasonId = season to load
    if not isValid(m.top.metadata) or not isValidAndNotEmpty(m.top.metadata.seasonId)
      m.log.warn("seasonEpisodes requested but metadata.seasonId is missing or empty; skipping GetEpisodes call")
      m.top.content = results
      return
    end if
    data = GetApi().GetEpisodes(m.top.itemId, {
      UserId: globalUser.id,
      SeasonId: m.top.metadata.seasonId,
      Fields: "PrimaryImageAspectRatio,Overview",
      EnableImages: true
    })
    if isValid(data) and isValid(data.Items)
      for each item in data.Items
        node = m.transformer.transformBaseItem(item)
        if isValid(node) then results.push(node)
      end for
    end if
  else if m.top.itemsToLoad = "seriesFirstEpisode"
    data = GetApi().GetEpisodes(m.top.itemId, { Limit: 1, SortBy: "IndexNumber,SortName", SortOrder: "Ascending" })
    if isValid(data) and isValid(data.Items) and data.Items.count() > 0
      node = m.transformer.transformBaseItem(data.Items[0])
      if isValid(node) then results.push(node)
    end if
  else if m.top.itemsToLoad = "seriesResume"
    ' For the series Resume button, prefer an in-progress (resumable) episode and fall back to the next unstarted episode.
    ' Uses the same endpoint as the Continue Watching home row so the Resume button is consistent with it.
    resumeData = GetApi().GetResumeItems({
      ParentId: m.top.itemId,
      Filters: "IsResumable",
      SortBy: "DatePlayed",
      SortOrder: "Descending",
      Limit: 1,
      recursive: true,
      Fields: "PrimaryImageAspectRatio",
      EnableTotalRecordCount: false
    })
    if isValid(resumeData) and isValid(resumeData.Items) and resumeData.Items.Count() > 0
      node = m.transformer.transformBaseItem(resumeData.Items[0])
      if isValid(node) then results.push(node)
    else
      ' No resumable episode — fall back to next unstarted episode.
      ' DisableFirstEpisode: true so a Resume button never appears for a completely unwatched series.
      nextUpData = GetApi().GetNextUp({ SeriesId: m.top.itemId, Limit: 1, DisableFirstEpisode: true, Fields: "PrimaryImageAspectRatio" })
      if isValid(nextUpData) and isValid(nextUpData.Items) and nextUpData.Items.Count() > 0
        node = m.transformer.transformBaseItem(nextUpData.Items[0])
        if isValid(node) then results.push(node)
      end if
    end if
  else if m.top.itemsToLoad = "metaData"
    results.push(ItemMetaData(m.top.itemId))
  else if m.top.itemsToLoad = "metaDataDetails"
    results.push(ItemDetailsMetaData(m.top.itemId))
  else if m.top.itemsToLoad = "audioStream"
    results.push(AudioStream(m.top.itemId))
  else if m.top.itemsToLoad = "backdropImage"
    results.push(BackdropImage(m.top.itemId))
  else if m.top.itemsToLoad = "boxsetitems"
    data = GetApi().GetItemsByQuery({
      "ParentId": m.top.itemId,
      "SortBy": "SortName",
      "SortOrder": "Ascending",
      "Fields": "PrimaryImageAspectRatio,Overview",
      "EnableTotalRecordCount": false
    })
    if isValid(data) and isValid(data.Items)
      for each item in data.Items
        node = m.transformer.transformBaseItem(item)
        if isValid(node) then results.push(node)
      end for
    end if
  end if

  m.top.content = results

end sub

sub getPersonVideos(videoType as string, dest as object)
  params = { personIds: m.top.itemId, recursive: true, includeItemTypes: videoType, Limit: 50, SortBy: "Random" }
  data = GetApi().GetItemsByQuery(params)
  if isValid(data) and data.count() > 0
    for each item in data.items
      node = m.transformer.transformBaseItem(item)
      if isValid(node) then dest.push(node)
    end for
  end if
end sub