source_utils_chapterItems.bs

import "pkg:/source/translationKeys.bs"
import "pkg:/source/utils/misc.bs"
import "pkg:/source/utils/translate.bs"

' Build JellyfinBaseItem nodes for the chapters row.
' Uses real chapters from API when available, otherwise generates synthetic chapters
' at adaptive intervals for videos >= 10 minutes.
' @param data - JellyfinBaseItem node for the current video
' @returns Array of JellyfinBaseItem nodes, or empty array if no chapters to show
function buildChapterItems(data as object) as object
  chapters = data.chapters
  runTimeTicks = data.runTimeTicks

  ' If no real chapters, generate synthetic ones for videos that display as ≥ 10 minutes.
  ' ItemDetails.getRuntime() uses round() to the nearest minute, so a 9:30 video shows as
  ' "10 mins" on screen. Match that rounding here so the Chapters row presence is consistent
  ' with the displayed runtime — otherwise the user sees "10 mins" but no row, which feels
  ' broken. Threshold: 9:30 = 5.7B ticks (equivalent to round(minutes) >= 10).
  if not isValidAndNotEmpty(chapters)
    MIN_CHAPTER_RUNTIME_TICKS = 5700000000&
    if not isValid(runTimeTicks) or runTimeTicks < MIN_CHAPTER_RUNTIME_TICKS then return []

    chapters = generateSyntheticChapters(runTimeTicks)
  end if

  if not isValidAndNotEmpty(chapters) then return []

  items = []
  for i = 0 to chapters.count() - 1
    chapter = chapters[i]
    item = CreateObject("roSGNode", "JellyfinBaseItem")
    item.type = "Chapter"
    item.parentType = data.type
    item.id = data.id
    item.indexNumber = i

    ' Use chapter name from API, fall back to "Chapter N" if null/empty
    if isValidAndNotEmpty(chapter.Name)
      item.name = chapter.Name
    else
      item.name = translate(translationKeys.LabelChapter, [stri(i + 1).trim()])
    end if
    item.subtitle = ticksToHuman(chapter.StartPositionTicks)
    item.playbackPositionTicks = chapter.StartPositionTicks

    ' Chapter thumbnail tag (may be empty for synthetic chapters)
    if isValidAndNotEmpty(chapter.ImageTag)
      item.primaryImageTag = chapter.ImageTag
    end if

    items.push(item)
  end for

  return items
end function

' Generate evenly-spaced synthetic chapters for videos without chapter metadata.
' Adaptive interval: 5 min for <30 min, 10 min for 30-<120 min, 15 min for >=2 hours.
' @param runTimeTicks - Video duration in ticks (100-nanosecond units)
' @returns Array of chapter-like AAs with Name, StartPositionTicks, and ImageTag fields
function generateSyntheticChapters(runTimeTicks as longinteger) as object
  THIRTY_MINUTES_TICKS = 18000000000&
  TWO_HOURS_TICKS = 72000000000&

  if runTimeTicks < THIRTY_MINUTES_TICKS
    intervalTicks = 3000000000& ' 5 minutes
  else if runTimeTicks < TWO_HOURS_TICKS
    intervalTicks = 6000000000& ' 10 minutes
  else
    intervalTicks = 9000000000& ' 15 minutes
  end if

  chapters = []
  position = 0&
  chapterNumber = 1
  while position < runTimeTicks
    chapters.push({
      Name: translate(translationKeys.LabelChapter, [stri(chapterNumber).trim()]),
      StartPositionTicks: position,
      ImageTag: ""
    })
    position += intervalTicks
    chapterNumber++
  end while

  return chapters
end function