components_ui_button_ResumeButton.bs

' bsc-disable-file print-locations (legacy print() sites; migration to m.log.* tracked by tech-debt.md#legacy-print-statements)
sub init()
  m.top.observeField("playbackPositionTicks", "onTicksChanged")
  m.top.observeField("runtimeTicks", "onTicksChanged")

  ' The progress bar is drawn only once the icon has loaded (see updateProgressBar())
  m.buttonIcon = m.top.findNode("buttonIcon")
  if isValid(m.buttonIcon)
    m.buttonIcon.observeField("loadStatus", "onIconLoaded")
  end if
end sub

' onIconLoaded: Called when button icon finishes loading
'
sub onIconLoaded()
  ' Only proceed if icon is fully loaded
  if m.buttonIcon.loadStatus <> "ready" then return

  ' Ticks set before the icon loaded were held back; draw them now
  updateProgressBar()

  ' Clean up observer to prevent memory leaks. This is a genuine ONE-SHOT: the icon
  ' URI is set once when the button is built and never reassigned, and a ResumeButton is created and
  ' removed per use (ItemDetails.manageResumeButton) rather than recycled as a list
  ' cell, so `loadStatus` reaches "ready" exactly once for this node and there is
  ' nothing further to hear. Those two facts are what make detaching here safe, and
  ' neither is visible to the gate, which is why this is suppressed rather than
  ' carved out of the rule.
  ' bsc-disable-next-line register-once-observer
  m.buttonIcon.unobserveField("loadStatus")
end sub

' onTicksChanged: Updates progress bar when playback position or runtime changes
'
sub onTicksChanged()
  updateProgressBar()
end sub

' updateProgressBar: Creates/updates the progress bar based on current tick values
'
sub updateProgressBar()
  ' Need valid runtime to calculate percentage
  if m.top.runtimeTicks = 0 then return

  ' The bar appears with the icon, not ahead of it. Asked of the icon itself: the
  ' backdrop is sized from init() (IconButton), so its width no longer says whether the
  ' icon has loaded. onIconLoaded() draws the bar once it has.
  if not isValid(m.buttonIcon) or m.buttonIcon.loadStatus <> "ready" then return
  buttonBg = m.top.findNode("buttonBackground")
  if not isValid(buttonBg) then return

  ' Calculate percentage (0-100) - force float division to avoid integer truncation
  ' Multiply by 100.0 (float literal) first to maintain precision
  percentage = (m.top.playbackPositionTicks * 100.0) / m.top.runtimeTicks

  ' Clamp percentage to valid range and warn if out of bounds
  if percentage < 0 or percentage > 100
    ? "[ResumeButton] Warning: Progress percentage out of range (" ; percentage ; "%), clamping to 0-100"
    if percentage < 0
      percentage = 0
    else if percentage > 100
      percentage = 100
    end if
  end if

  ' Find or create progress bar
  progressBar = m.top.findNode("resumeProgressBar")
  if not isValid(progressBar)
    progressBar = createProgressBar()
  end if

  ' Update progress bar size and percentage
  ' Width: reduce by 12px to account for 6px border on left and right
  barMargin = 3
  progressBar.width = buttonBg.width - (12 + barMargin * 2)
  progressBar.playedPercentage = percentage
  ' Position at bottom of button, inside the 6px focus border
  ' X: 6px (textExtension offset) + 6px (left border) = 12px from left
  ' Y: buttonBg.height - 9px (progress height) - 6px (bottom border) = 15px from bottom
  progressBar.translation = [12 + barMargin, buttonBg.height - (15 + barMargin)]
end sub

' createProgressBar: Creates and appends progress bar to button
'
function createProgressBar() as object
  progressBar = CreateObject("roSGNode", "VideoProgressBar")
  progressBar.id = "resumeProgressBar"
  m.top.appendChild(progressBar)

  return progressBar
end function