components_ui_poster_JRPoster.bs

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

' VideoProgressBar's own height: the strip the bar occupies along the poster's bottom edge.
const PROGRESS_BAR_STRIP = 9

' Gap between the versions badge and the bar. Without it the badge's bottom edge IS the bar's
' top edge, and the two read as one welded element on a partly-watched tile.
const VERSIONS_BADGE_GAP = 6


sub init()
  m.top.width = 464
  m.top.height = 261
  m.top.loadDisplayMode = "scaleToZoom"
  m.disableWatchBadge = false

  if m.global.user.settings.uiTvShowsDisableUnwatchedCount
    m.disableWatchBadge = true
  end if

  m.watchBadgeBackground = m.top.findNode("watchBadgeBackground")
  m.unplayedVideosCount = m.top.findNode("unplayedVideosCount")
  m.checkmark = m.top.findNode("checkmark")
  m.versionsBadgeBackground = m.top.findNode("versionsBadgeBackground")
  ' Theme-coloured glyph on a dark scrim. colorSecondary is the constant for a NON-focusable
  ' highlight (components/CLAUDE.md), which is what a badge is, so the badge re-themes with
  ' the rest of the app instead of being pinned to white. The scrim carries the legibility:
  ' at alpha85 the composite is #262626 even over a white poster, so what is underneath stops
  ' mattering and the glyph reads against a near-constant backdrop.
  m.versionsBadgeBackground.color = m.global.constants.colorBlack + m.global.constants.alpha85
  m.top.findNode("versionsIcon").blendColor = m.global.constants.colorSecondary

  ' Observed once for the life of the poster. A recycled cell's resetBadge() must not
  ' re-render while it clears, so it raises this instead of toggling the observers.
  m.isResettingBadge = false
  m.top.observeField("playedPercentage", "onPlayedPercentageChanged")
  ' Not gated on uiTvShowsDisableUnwatchedCount: that setting hides the WATCHED badge, and
  ' widening it to mean "all poster badges" would break what its name and docs promise.
  m.top.observeField("mediaSourceCount", "onMediaSourceCountChanged")
  if not m.disableWatchBadge
    m.top.observeField("unplayedCount", "onUnplayedCountChanged")
    m.top.observeField("isWatched", "onIsWatchedChanged")
  end if
end sub

sub onUnplayedCountChanged()
  if m.isResettingBadge or m.disableWatchBadge then return
  if m.top.isWatched then return

  if m.top.unplayedCount > 0
    m.checkmark.visible = false
    m.unplayedVideosCount.text = m.top.unplayedCount.ToStr()
    m.unplayedVideosCount.visible = true
  else
    m.unplayedVideosCount.visible = false
    m.unplayedVideosCount.text = ""
    m.checkmark.visible = true
  end if

  updateBadgeSize()
end sub

sub onIsWatchedChanged()
  if m.isResettingBadge or m.disableWatchBadge then return
  if m.top.unplayedCount > 0 then return

  if m.top.isWatched
    m.unplayedVideosCount.visible = false
    m.unplayedVideosCount.text = ""
    m.checkmark.visible = true
  else
    m.checkmark.visible = false
  end if

  updateBadgeSize()
end sub

' Marks a tile whose item has more than one version, so the choice is visible while browsing
' instead of only on the details screen.
'
' Pinned to the BOTTOM-LEFT, clear of the progress bar's strip plus a gap. The bar is added
' and removed on demand, so this badge cannot depend on whether it is there; the offset is
' computed from the constants, not from a bar node that may not exist yet.
'
' Still flush to the LEFT edge, matching the watched badge's flush top-right anchoring. If the
' badge system later adopts a uniform inset, both move together (see
' `poster-badge-system-undesigned`).
sub onMediaSourceCountChanged()
  if m.isResettingBadge then return

  if m.top.mediaSourceCount > 1
    m.versionsBadgeBackground.translation = [0, m.top.height - m.versionsBadgeBackground.height - PROGRESS_BAR_STRIP - VERSIONS_BADGE_GAP]
    m.versionsBadgeBackground.visible = true
  else
    m.versionsBadgeBackground.visible = false
  end if
end sub

sub onPlayedPercentageChanged()
  if m.isResettingBadge then return

  progressBar = m.top.findNode("progressBar")

  if not isValid(progressBar)
    if m.top.playedPercentage = 0
      return
    else
      progressBar = createProgressBar()
    end if
  end if

  if m.top.playedPercentage = 0
    m.top.removeChild(progressBar)
  else if m.top.playedPercentage > 0
    progressBar.width = m.top.width
    progressBar.playedPercentage = m.top.playedPercentage
    m.top.appendChild(progressBar)
  end if
end sub

sub resetBadge()
  ' Handlers run synchronously on these writes, so the flag only has to span them.
  m.isResettingBadge = true
  m.top.unplayedCount = 0
  m.top.isWatched = false
  m.top.playedPercentage = 0
  m.top.mediaSourceCount = 0
  m.isResettingBadge = false

  m.watchBadgeBackground.visible = false
  m.versionsBadgeBackground.visible = false
  m.unplayedVideosCount.text = ""
  m.checkmark.visible = false
  m.watchBadgeBackground.width = 90
  m.checkmark.translation = [0, 0]
  m.unplayedVideosCount.translation = [0, 0]

  progressBar = m.top.findNode("progressBar")
  if isValid(progressBar)
    m.top.removeChild(progressBar)
  end if
end sub

sub updateBadgeSize()
  padding = 12

  if m.top.isWatched
    padding = 15
    dataWidth = m.checkmark.localBoundingRect().width
    m.checkmark.translation = [padding, 3]
  else
    textLength = len(m.unplayedVideosCount.text)
    fontSize = m.unplayedVideosCount.font.size

    charWidth = fontSize * 0.64
    dataWidth = textLength * charWidth

    if dataWidth < 27 then dataWidth = 27

    m.unplayedVideosCount.width = dataWidth
    m.unplayedVideosCount.translation = [padding, 3]
  end if

  badgeWidth = dataWidth + (padding * 2)
  m.watchBadgeBackground.width = badgeWidth
  m.watchBadgeBackground.translation = [m.top.width - badgeWidth, 0]
  m.watchBadgeBackground.visible = true
end sub

function createProgressBar() as object
  progressBar = CreateObject("roSGNode", "VideoProgressBar")
  progressBar.id = "progressBar"
  progressBar.width = m.top.width
  progressBar.translation = [0, m.top.height - progressBar.height]

  return progressBar
end function