local classlib = require("core.class")
local tablex = require("core.tablex")
local timer = require("level.timer")
local animationUtil = require("ui.animationUtil")
local button = require("ui.button")
local buttonUtil = require("ui.buttonUtil")
local consts = require("ui.consts")
local hooks = require("ui.hooks")
local lamsConsts = require("ui.lamsConsts")
local meter = require("ui.meter")
local tutorialUtil = require("ui.tutorialUtil")
local resourceConsts = require("ui.resourceConsts")
local util = require("ui.util")
local Audio = game.Audio
local UI = game.UI
local List = classlib.Class("List")
local HasReleaseEvents = function(list)
  return #list._previousReleaseEvents > 0 or 0 < #list._nextReleaseEvents
end
local DefaultHighlightOn = function(button, animateImmediately)
  local goButtonChild = button:GetInstancedChildObject()
  if goButtonChild ~= nil then
    local startTime = 0
    local endTime = 0.5
    local animRate = consts.DEFAULT_BUTTON_ANIM_RATE
    if animateImmediately == true then
      animRate = 0
      UI.Anim(goButtonChild, consts.AS_Forward, "", animRate, endTime)
    else
      UI.Anim(goButtonChild, consts.AS_Forward, "", animRate, startTime, endTime)
    end
  end
end
local DefaultHighlightOff = function(button, animateImmediately)
  local goButtonChild = button:GetInstancedChildObject()
  if goButtonChild ~= nil then
    local startTime = 0.5
    local endTime = 1
    local animRate = consts.DEFAULT_BUTTON_ANIM_RATE
    if animateImmediately == true then
      animRate = 0
      UI.Anim(goButtonChild, consts.AS_Forward, "", animRate, endTime)
    else
      UI.Anim(goButtonChild, consts.AS_Reverse, "", animRate, startTime, endTime)
    end
  end
end
local DefaultItemCompare = function(item, otherItem)
  return item == otherItem
end
local DefaultGetSortItem = function(button)
  return button:get_item()
end
local SetAccelerationMultiplier = function(list, mult)
  local minMult = 1
  local maxMult = 15
  list._accelerationMultiplier = util.Clamp(mult, minMult, maxMult)
end
local UpdatePadRepeatRate = function(list)
  local multiplier = 1
  if list._isScrolling then
    local frameTime = UI.GetFrameTime()
    local multIncrement = 30 * frameTime
    multiplier = list._accelerationMultiplier + multIncrement
  end
  SetAccelerationMultiplier(list, multiplier)
  list._state:WantPadEvents(false)
  list._state:WantPadEvents(true)
  list._state:PadRepeat(list._padRepeatRate * list._accelerationMultiplier)
end
local StepIncToFocusableButton = function(list, stepDirection, wrap, initialIndex, offset)
  local direction = stepDirection == consts.STEP_NEXT and 1 or -1
  local endIndex = stepDirection == consts.STEP_NEXT and list:GetButtonCount() or 1
  local increment = 1 * direction
  local totalStep = offset
  for index = initialIndex + totalStep, endIndex, increment do
    local currButton = list:GetButton(index)
    if currButton == nil then
      break
    end
    if currButton:get_focusable() then
      return totalStep
    end
    totalStep = totalStep + increment
  end
  if wrap and not list._isScrolling then
    local startIndex = stepDirection == consts.STEP_NEXT and 1 or list:GetButtonCount()
    for index = startIndex, initialIndex - 1, increment do
      local currButton = list:GetButton(index)
      if currButton == nil then
        break
      end
      if currButton:get_focusable() then
        return totalStep
      end
      totalStep = totalStep + increment
    end
  end
  if list._isScrolling and not list._hasDoneScrollingGC then
    collectgarbage()
    list._hasDoneScrollingGC = true
  end
  return 0
end
local NextEventHandler = function(list)
  list:PlaySelectionChangeSound()
  UpdatePadRepeatRate(list)
  local stepFromCurrButtonToNextFocusable = StepIncToFocusableButton(list, consts.STEP_NEXT, list._wrap, list._selectedButtonIndex, 1)
  list:ChangeToNextButton(stepFromCurrButtonToNextFocusable, false, true, false)
  if list._OnNext ~= nil then
    list._OnNext()
  end
  list._isScrolling = HasReleaseEvents(list)
end
local NextReleaseEventHandler = function(list)
  list._isScrolling = false
  list._hasDoneScrollingGC = false
  UpdatePadRepeatRate(list)
end
local PreviousEventHandler = function(list)
  list:PlaySelectionChangeSound()
  UpdatePadRepeatRate(list)
  local stepFromCurrButtonToPrevFocusableButton = StepIncToFocusableButton(list, consts.STEP_PREV, list._wrap, list._selectedButtonIndex, -1)
  list:ChangeToPrevButton(stepFromCurrButtonToPrevFocusableButton, false, true, false)
  if list._OnPrevious ~= nil then
    list._OnPrevious()
  end
  list._isScrolling = HasReleaseEvents(list)
end
local PreviousReleaseEventHandler = function(list)
  list._isScrolling = false
  list._hasDoneScrollingGC = false
  UpdatePadRepeatRate(list)
end
local GetPageCountAtIndex = function(list, indexOfFirstButtonOnPage)
  local maxPageButtonCount = list._maxFocusableObjectCount
  local numberOfButtonsBeforeStartOfPage = indexOfFirstButtonOnPage - 1
  local numberOfButtonsLeftAfterAFullPage = list:GetButtonCount() - (numberOfButtonsBeforeStartOfPage + maxPageButtonCount)
  local pageCount = maxPageButtonCount
  if numberOfButtonsLeftAfterAFullPage < 0 then
    pageCount = maxPageButtonCount + numberOfButtonsLeftAfterAFullPage
  end
  return pageCount
end
local GetPageCountBeforeIndex = function(list, indexOfFirstButtonOnPage)
  local maxPageButtonCount = list._maxFocusableObjectCount
  local numberOfButtonsBeforeStartOfPage = indexOfFirstButtonOnPage - 1
  local numberOfButtonsAFullPageBeforeStartOfPage = numberOfButtonsBeforeStartOfPage - maxPageButtonCount
  local pageCount = maxPageButtonCount
  if numberOfButtonsAFullPageBeforeStartOfPage < 0 then
    pageCount = maxPageButtonCount + numberOfButtonsAFullPageBeforeStartOfPage
  end
  return pageCount
end
local GetLastPageCount = function(list)
  local lastPageCount = list:GetButtonCount() % list._maxFocusableObjectCount
  if lastPageCount == 0 then
    lastPageCount = list._maxFocusableObjectCount
  end
  return lastPageCount
end
local PageDownEventHandler = function(list)
  list:PlaySelectionChangeSound()
  UpdatePadRepeatRate(list)
  local buttonCount = list:GetButtonCount()
  local stepFromLastButtonToLastFocusableButton = StepIncToFocusableButton(list, consts.STEP_PREV, false, buttonCount, 0)
  local lastFocusableButtonIndex = buttonCount + stepFromLastButtonToLastFocusableButton
  local offset = 0
  if lastFocusableButtonIndex > list._selectedButtonIndex then
    local pageCountAtSelectedIndex = GetPageCountAtIndex(list, list._selectedButtonIndex)
    local desiredSelectedIndex = list._selectedButtonIndex + pageCountAtSelectedIndex
    if buttonCount < desiredSelectedIndex then
      offset = buttonCount - list._selectedButtonIndex
    elseif pageCountAtSelectedIndex < list._maxFocusableObjectCount then
      offset = pageCountAtSelectedIndex - 1
    else
      offset = pageCountAtSelectedIndex
    end
  end
  local stepFromCurrButtonToNextFocusableButtonAPageAfter = StepIncToFocusableButton(list, consts.STEP_NEXT, list._wrap, list._selectedButtonIndex, offset)
  list:ChangeToNextButton(stepFromCurrButtonToNextFocusableButtonAPageAfter, true, true, false)
  if list._OnPageDown ~= nil then
    list._OnPageDown()
  end
end
local PageUpEventHandler = function(list)
  list:PlaySelectionChangeSound()
  UpdatePadRepeatRate(list)
  local stepFromStartToFirstFocusableButton = StepIncToFocusableButton(list, consts.STEP_NEXT, false, 0, 1)
  local firstFocusableButtonIndex = stepFromStartToFirstFocusableButton
  local offset = 0
  if firstFocusableButtonIndex < list._selectedButtonIndex then
    local pageCountBeforeSelectedIndex = GetPageCountBeforeIndex(list, list._selectedButtonIndex)
    local desiredSelectedIndex = list._selectedButtonIndex - pageCountBeforeSelectedIndex
    if desiredSelectedIndex < 1 then
      offset = 1 - list._selectedButtonIndex
    else
      offset = pageCountBeforeSelectedIndex * -1
    end
  end
  local stepFromCurrentButtonToPrevFocusableAPageBefore = StepIncToFocusableButton(list, consts.STEP_PREV, list._wrap, list._selectedButtonIndex, offset)
  list:ChangeToPrevButton(stepFromCurrentButtonToPrevFocusableAPageBefore, true, true, false)
  if list._OnPageUp ~= nil then
    list._OnPageUp()
  end
end
local AfterDelayHandler = function(list)
  if list._AfterDelay ~= nil then
    list._AfterDelay(list, list._delayStartButtonIndex, list._selectedButtonIndex)
  end
  list:StopDelayTimer()
end
local GetButtonByFocusableObjectIndex = function(list, focusableObjectIndex)
  local button
  if focusableObjectIndex ~= nil then
    local buttonIndex = list._firstFocusableButtonIndex + focusableObjectIndex - 1
    button = list:GetButton(buttonIndex)
  end
  return button
end
local GetButtonIndexOfItem = function(list, item)
  local index = 1
  for key, button in ipairs(list._buttonArray) do
    if button:HasItem(item) then
      index = key
      break
    end
  end
  return index
end
function List:init(state, args)
  assert(state ~= nil, "Attempted to create new List without state!")
  assert(args ~= nil, "Attempted to create new List without args!")
  self._state = state
  self._active = false
  self._focusedObjectIndex = 1
  self._selectedButtonIndex = 1
  self._firstFocusableButtonIndex = 1
  self._delayStartButtonIndex = 1
  self._scrollBarVisible = false
  self._indicatorPrevVisible = false
  self._indicatorNextVisible = false
  self._isScrolling = false
  self._accelerationMultiplier = 1
  self._hasDoneScrollingGC = false
  self._isActivating = false
  self._name = args.Name
  self._listObjectName = args.ListObjectName
  self._iconObjectName = args.IconObjectName
  self._button_onGainFocus_sound = args.Button_OnGainFocus_Sound
  self._maxFocusableObjectCount = util.GetValueWithDefault(args.MaxFocusableObjectCount, 1)
  self._scrollPoint = math.ceil(self._maxFocusableObjectCount / 2)
  self._scroll = util.GetValueWithDefault(args.Scroll, true)
  self._wrap = util.GetValueWithDefault(args.Wrap, true)
  self._padRepeatRate = util.GetValueWithDefault(args.PadRepeatRate, 5)
  self._selectionChangeSound = util.GetValueWithDefault(args.SelectionChangeSound, "SND_UX_Menu_Tick_Vertical")
  self._delayTime = util.GetValueWithDefault(args.DelayTime, 0.5)
  self._emptyTextLamsID = util.GetValueWithDefault(args.EmptyTextLamsID, lamsConsts.NoItemsAvailable)
  self._backingAnimFrameCount = util.GetValueWithDefault(args.BackingAnimFrameCount, consts.DEFAULT_ANIM_FRAME_COUNT)
  self._hoverToSelect = util.GetValueWithDefault(args.HoverToSelect, false)
  self._allowScrollIndicatorAnim = util.GetValueWithDefault(args.AllowScrollIndicatorAnim, false)
  self._enableLabel = util.GetValueWithDefault(args.EnableLabel, true)
  self._goBacking = nil
  self._goBackingBlur = nil
  self._goHeader = nil
  self._goLabelContainer = nil
  self._goLabel = nil
  self._goIndicatorNext = nil
  self._goIndicatorPrev = nil
  self._thHeaderText = nil
  self._thLabelText = nil
  self._scrollBarMeter = nil
  self._delayTimer = nil
  self._dummyButton = nil
  self._hookTokens = {}
  self._buttonArray = {}
  self._allButtonGOs = {}
  self._eventHandlers = util.GetValueWithDefault(args.EventHandlers, {})
  self._pageUpEvents = util.GetValueWithDefault(args.PageUpEvents, {})
  self._pageDownEvents = util.GetValueWithDefault(args.PageDownEvents, {})
  self._previousReleaseEvents = {}
  self._nextReleaseEvents = {}
  assert(self._eventHandlers ~= nil and type(self._eventHandlers) == "table", "Attempted to create Button with invalid EventHandlers table!")
  assert(self._pageUpEvents ~= nil and type(self._pageUpEvents) == "table", "Attempted to create Button with invalid PageUpEvents table!")
  assert(self._pageDownEvents ~= nil and type(self._pageDownEvents) == "table", "Attempted to create Button with invalid PageDownEvents table!")
  if args.PreviousEvents ~= nil then
    self._previousEvents = args.PreviousEvents
  else
    local dpadEvent = self._padRepeatRate == 0 and "EVT_Up_Release" or "EVT_Up_Press"
    self._previousEvents = {
      dpadEvent,
      "EVT_LeftStick_Up"
    }
  end
  for _, eventName in ipairs(self._previousEvents) do
    if consts.RELEASE_EVENTS[eventName] ~= nil then
      tablex.FastInsert(self._previousReleaseEvents, consts.RELEASE_EVENTS[eventName], #self._previousReleaseEvents + 1)
    end
  end
  if args.NextEvents ~= nil then
    self._nextEvents = args.NextEvents
  else
    local dpadEvent = self._padRepeatRate == 0 and "EVT_Down_Release" or "EVT_Down_Press"
    self._nextEvents = {
      dpadEvent,
      "EVT_LeftStick_Down"
    }
  end
  for _, eventName in ipairs(self._nextEvents) do
    if consts.RELEASE_EVENTS[eventName] ~= nil then
      tablex.FastInsert(self._nextReleaseEvents, consts.RELEASE_EVENTS[eventName], #self._nextReleaseEvents + 1)
    end
  end
  self._OnPageUp = args.OnPageUp
  self._OnPageDown = args.OnPageDown
  self._OnPrevious = args.OnPrevious
  self._OnNext = args.OnNext
  self._AfterDelay = args.AfterDelay
  self._Button_Update = args.Button_Update
  self._Button_OnGainFocus = args.Button_OnGainFocus
  self._Button_OnLoseFocus = args.Button_OnLoseFocus
  self._Button_HighlightOn = args.Button_HighlightOn or DefaultHighlightOn
  self._Button_HighlightOff = args.Button_HighlightOff or DefaultHighlightOff
  self._Button_ItemCompare = args.Button_ItemCompare or DefaultItemCompare
  self._Button_GetSortItem = args.Button_GetSortItem or DefaultGetSortItem
  self._Sort = args.Sort
  if not util.IsStringNilOrEmpty(self._listObjectName) then
    local goList = util.GetUiObjByName(self._listObjectName)
    assert(goList ~= nil, "List " .. self._listObjectName .. " game object not found!")
    self:SetGameObject(goList)
  end
  if args.ItemArray ~= nil then
    self:CreateButtonArray(args.ItemArray, nil, nil)
  end
  self:SetupDummyButton()
  self:Hide()
end
local SelectedIsButton = function(selected, button)
  local child = button:GetInstancedChildObject()
  if selected == child then
    return true
  end
  for _, activationSet in ipairs(button._activationList) do
    local settingsItem = activationSet.Item
    if settingsItem ~= nil then
      if settingsItem.GetInstancedChildObject ~= nil and selected == settingsItem:GetInstancedChildObject() then
        return true
      end
      if settingsItem.GetDirectionIndicator ~= nil then
        if selected == settingsItem:GetDirectionIndicator(consts.STEP_PREV) then
          return true
        end
        if selected == settingsItem:GetDirectionIndicator(consts.STEP_NEXT) then
          return true
        end
      end
      if settingsItem.CLASSNAME == "Control" then
        for _, buttonGOs in ipairs(settingsItem._allButtonGOs) do
          if selected == buttonGOs.button.button then
            return true
          end
        end
      end
    end
  end
  return false
end
local MouseSelectHandler = function(list)
  local selected = UI.GetEventSenderGameObject()
  if selected ~= nil then
    for focusableObjectIndex = 1, list._maxFocusableObjectCount do
      local button = GetButtonByFocusableObjectIndex(list, focusableObjectIndex)
      local key = focusableObjectIndex + list._firstFocusableButtonIndex - 1
      if button and button:get_focusable() and SelectedIsButton(selected, button) then
        if button ~= list:GetSelectedButton() then
          list:PlaySelectionChangeSound()
          list:SetSelectedButton(key, true, not list._hoverToSelect)
        end
        break
      end
    end
  end
  if selected == list._goIndicatorNext then
    NextEventHandler(list)
    NextReleaseEventHandler(list)
  end
  if selected == list._goIndicatorPrev then
    PreviousEventHandler(list)
    PreviousReleaseEventHandler(list)
  end
end
local MouseDragHandler = function(list)
  if list._scrollBarMeter == nil then
    return
  end
  local selected = UI.GetEventSenderGameObject()
  if selected == nil then
    return
  end
  if selected == list._scrollBarMeter:get_gameObject() then
    local min = 1
    local range = list:GetButtonCount() - 1
    local position = util.Clamp(UI.GetEventArgFloat(2) * 1.2 - 0.1, 0, 1)
    local d = 1
    local value = math.floor((position * range + min) / d + 0.5) * d
    local button = list:GetButton(value)
    if button then
      if not button:get_focusable() then
        value = value + 1
      end
      if list._selectedButtonIndex ~= value then
        list:PlaySelectionChangeSound()
        list:SetSelectedButton(value, true, true)
      end
    end
  end
end
local MouseScrollHandler = function(list)
  local selected = UI.GetEventSenderGameObject()
  if selected == nil then
    return
  end
  for focusableObjectIndex = 1, list._maxFocusableObjectCount do
    local button = GetButtonByFocusableObjectIndex(list, focusableObjectIndex)
    if button and SelectedIsButton(selected, button) then
      local scroll = UI.GetEventArgInt(2)
      if scroll < 0 and list._firstFocusableButtonIndex < list:GetButtonCount() - list._maxFocusableObjectCount + 1 then
        do
          local selected = list._selectedButtonIndex
          local step = StepIncToFocusableButton(list, consts.STEP_NEXT, false, selected, 1)
          if step then
            list:PlaySelectionChangeSound()
            list:Scroll(focusableObjectIndex, step, button:get_focusable())
            if list._OnNext ~= nil then
              list._OnNext()
            end
          end
        end
        break
      end
      if 0 < scroll and 1 < list._firstFocusableButtonIndex then
        local selected = list._selectedButtonIndex
        local step = StepIncToFocusableButton(list, consts.STEP_PREV, false, selected, -1)
        if step then
          list:PlaySelectionChangeSound()
          list:Scroll(focusableObjectIndex, step, button:get_focusable())
          if list._OnPrevious ~= nil then
            list._OnPrevious()
          end
        end
      end
      break
    end
  end
end
function List:Activate(show, useOnGainFocus)
  if not self._active then
    self._isActivating = true
    for _, button in ipairs(self._buttonArray) do
      local child = button:GetInstancedChildObject()
      if child ~= nil then
        UI.SetIsClickable(child)
      end
    end
    if self._hoverToSelect then
      util.InstallEventHooks(hooks, self, {
        "EVT_MOUSE_HOVER"
      }, function(list)
        tutorialUtil.HandleEvent(list, {
          "EVT_MOUSE_HOVER"
        }, MouseSelectHandler)
      end)
    else
      util.InstallEventHooks(hooks, self, {
        "EVT_MOUSE_CLICKED"
      }, function(list)
        tutorialUtil.HandleEvent(list, {
          "EVT_MOUSE_CLICKED"
        }, MouseSelectHandler)
      end)
    end
    util.InstallEventHooks(hooks, self, {
      "EVT_MOUSE_DRAGGED"
    }, function(slider)
      tutorialUtil.HandleEvent(slider, {
        "EVT_MOUSE_DRAGGED"
      }, MouseDragHandler)
    end)
    util.InstallEventHooks(hooks, self, {
      "EVT_MOUSE_SCROLLED"
    }, function(list)
      tutorialUtil.HandleEvent(list, {
        "EVT_MOUSE_SCROLLED"
      }, MouseScrollHandler)
    end)
    util.InstallEventHooks(hooks, self, self._pageUpEvents, function(list)
      tutorialUtil.HandleEvent(list, self._pageUpEvents, PageUpEventHandler)
    end)
    util.InstallEventHooks(hooks, self, self._pageDownEvents, function(list)
      tutorialUtil.HandleEvent(list, self._pageDownEvents, PageDownEventHandler)
    end)
    util.InstallEventHooks(hooks, self, self._previousEvents, function(list)
      tutorialUtil.HandleEvent(list, self._previousEvents, PreviousEventHandler)
    end)
    util.InstallEventHooks(hooks, self, self._nextEvents, function(list)
      tutorialUtil.HandleEvent(list, self._nextEvents, NextEventHandler)
    end)
    if #self._previousReleaseEvents > 0 then
      util.InstallEventHooks(hooks, self, self._previousReleaseEvents, function(list)
        tutorialUtil.HandleEvent(list, self._previousReleaseEvents, PreviousReleaseEventHandler)
      end)
    end
    if 0 < #self._nextReleaseEvents then
      util.InstallEventHooks(hooks, self, self._nextReleaseEvents, function(list)
        tutorialUtil.HandleEvent(list, self._nextReleaseEvents, NextReleaseEventHandler)
      end)
    end
    for _, eventHandler in ipairs(self._eventHandlers) do
      util.InstallEventHooks(hooks, self, eventHandler.Events, function(list)
        tutorialUtil.HandleEvent(list, eventHandler.Events, eventHandler.Handler)
      end)
    end
    self._active = true
    self:SetSelectedButton(self._selectedButtonIndex, useOnGainFocus, false)
    if show == true then
      self:Show()
      if self._goList ~= nil then
        animationUtil.DoDefaultTransitionAnim(self._goList.Child)
      end
    end
    self._isActivating = false
  end
end
function List:Deactivate(hide, clearButtons, allowLoseFocus)
  if self._active then
    for _, button in ipairs(self._buttonArray) do
      local child = button:GetInstancedChildObject()
      if child ~= nil then
        UI.ClearIsClickable(child)
      end
    end
    self._isScrolling = false
    self._hasDoneScrollingGC = false
    self:StopDelayTimer()
    if self._hoverToSelect then
      util.UninstallEventHooks(hooks, self, {
        "EVT_MOUSE_HOVER"
      })
    else
      util.UninstallEventHooks(hooks, self, {
        "EVT_MOUSE_CLICKED"
      })
    end
    util.UninstallEventHooks(hooks, self, {
      "EVT_MOUSE_DRAGGED"
    })
    util.UninstallEventHooks(hooks, self, {
      "EVT_MOUSE_SCROLLED"
    })
    util.UninstallEventHooks(hooks, self, self._pageUpEvents)
    util.UninstallEventHooks(hooks, self, self._pageDownEvents)
    util.UninstallEventHooks(hooks, self, self._previousEvents)
    util.UninstallEventHooks(hooks, self, self._nextEvents)
    if #self._previousReleaseEvents > 0 then
      util.UninstallEventHooks(hooks, self, self._previousReleaseEvents)
    end
    if 0 < #self._nextReleaseEvents then
      util.UninstallEventHooks(hooks, self, self._nextReleaseEvents)
    end
    for _, eventHandler in ipairs(self._eventHandlers) do
      util.UninstallEventHooks(hooks, self, eventHandler.Events)
    end
    if allowLoseFocus == false then
      self._bypassNextSelectionAnimation = true
    else
      local selectedButton = self:GetSelectedButton()
      if selectedButton ~= nil then
        selectedButton:LoseFocus(true)
      end
    end
    self._active = false
    tablex.Clear(self._hookTokens)
  end
  if clearButtons == true then
    tablex.Clear(self._buttonArray)
  end
  if hide == true then
    self:Hide()
  end
end
function List:Show()
  if self._goList ~= nil then
    self._goList:Show()
    if self._goListChild ~= nil then
      self._goListChild:Show()
    end
    self:ShowHeader()
    self:ShowIcon()
    self:ShowLabel()
    self:UpdateBacking()
    self:UpdateScrollDirectionIndicators()
    self:UpdateScrollBar()
  end
end
function List:Hide()
  if self._goList ~= nil then
    self._goList:Hide()
    if self._goListChild ~= nil then
      self._goListChild:Hide()
    end
    self:HideHeader()
    self:HideIcon()
    self:HideLabel()
    self:HideBacking()
    self:HideScrollDirectionIndicators()
    self:HideScrollBar()
  end
end
function List:SelectItem(item, useOnGainFocus)
  local buttonIndex = GetButtonIndexOfItem(self, item)
  self:SetSelectedButton(buttonIndex, useOnGainFocus)
end
function List:ReselectSelectedItem(useOnGainFocus)
  self._bypassNextSelectionAnimation = true
  self:SetSelectedButton(self._selectedButtonIndex, useOnGainFocus, false)
end
function List:SetSelectedButton(targetButtonIndex, useOnGainFocus, updateScrollPosition)
  local buttonCount = self:GetButtonCount()
  if buttonCount < 1 then
    self._selectedButtonIndex = 1
    self._focusedObjectIndex = 1
    self._firstFocusableButtonIndex = 1
    return
  end
  local oldSelectedButtonIndex = self._selectedButtonIndex
  local oldFocusedObjectIndex = self._focusedObjectIndex
  local desiredSelectedIndex = targetButtonIndex + StepIncToFocusableButton(self, consts.STEP_NEXT, self._wrap, targetButtonIndex, 0)
  if self._wrap == true and buttonCount < desiredSelectedIndex then
    desiredSelectedIndex = desiredSelectedIndex - buttonCount
  end
  local desiredFirstFocusableButtonIndex = desiredSelectedIndex - self._scrollPoint + 1
  local indexOfTopButtonWhenAtTheBottomOfTheList = buttonCount - self._maxFocusableObjectCount + 1
  if indexOfTopButtonWhenAtTheBottomOfTheList < 1 then
    indexOfTopButtonWhenAtTheBottomOfTheList = 1
  end
  if desiredFirstFocusableButtonIndex > indexOfTopButtonWhenAtTheBottomOfTheList then
    desiredFirstFocusableButtonIndex = indexOfTopButtonWhenAtTheBottomOfTheList
  end
  if desiredFirstFocusableButtonIndex < 1 then
    desiredFirstFocusableButtonIndex = 1
  end
  self._selectedButtonIndex = desiredSelectedIndex
  self._focusedObjectIndex = desiredSelectedIndex - desiredFirstFocusableButtonIndex + 1
  if updateScrollPosition == nil or updateScrollPosition == true then
    self._firstFocusableButtonIndex = desiredFirstFocusableButtonIndex
  end
  self:ChangeToSelectedButton(oldSelectedButtonIndex, oldFocusedObjectIndex, useOnGainFocus, true, true)
end
function List:ChangeToNextButton(stepIncrement, isPaging, useOnGainFocus, forceChange)
  local buttonCount = self:GetButtonCount()
  if buttonCount < 1 then
    self._selectedButtonIndex = 1
    self._focusedObjectIndex = 1
    self._firstFocusableButtonIndex = 1
    return
  end
  local oldSelectedButtonIndex = self._selectedButtonIndex
  local oldFocusedObjectIndex = self._focusedObjectIndex
  local desiredSelectedButtonIndex = self._selectedButtonIndex + stepIncrement
  local stepFromStartToFirstFocusableButton = StepIncToFocusableButton(self, consts.STEP_NEXT, false, 0, 1)
  local currFocusableButtonCount = buttonCount < self._maxFocusableObjectCount and buttonCount or self._maxFocusableObjectCount
  local indexOfTopButtonWhenAtTheBottomOfTheList = buttonCount - self._maxFocusableObjectCount + 1
  if indexOfTopButtonWhenAtTheBottomOfTheList < 1 then
    indexOfTopButtonWhenAtTheBottomOfTheList = 1
  end
  if desiredSelectedButtonIndex < 1 then
    desiredSelectedButtonIndex = stepFromStartToFirstFocusableButton
  end
  if buttonCount < desiredSelectedButtonIndex then
    if self._wrap == true then
      if isPaging == true then
        local stepFromEndToLastFocusableButton = StepIncToFocusableButton(self, consts.STEP_PREV, false, buttonCount, 0)
        local lastFocusableButtonIndex = buttonCount + stepFromEndToLastFocusableButton
        if oldSelectedButtonIndex == lastFocusableButtonIndex then
          local stepFromStartToFirstFocusableButton = StepIncToFocusableButton(self, consts.STEP_NEXT, false, 0, 1)
          self._selectedButtonIndex = stepFromStartToFirstFocusableButton
          self._focusedObjectIndex = stepFromStartToFirstFocusableButton
          self._firstFocusableButtonIndex = 1
        else
          self._selectedButtonIndex = lastFocusableButtonIndex
          self._focusedObjectIndex = currFocusableButtonCount + stepFromEndToLastFocusableButton
          self._firstFocusableButtonIndex = buttonCount - GetLastPageCount(self) + 1
        end
      else
        self._selectedButtonIndex = stepFromStartToFirstFocusableButton
        self._focusedObjectIndex = stepFromStartToFirstFocusableButton
        self._firstFocusableButtonIndex = 1
      end
    end
  else
    local desiredFirstFocusableButtonIndex = self._firstFocusableButtonIndex + stepIncrement
    local desiredFocusedObjectIndex = self._focusedObjectIndex + stepIncrement
    if desiredFocusedObjectIndex < 1 then
      local stepFromStartToFirstFocusableButton = StepIncToFocusableButton(self, consts.STEP_NEXT, false, 0, 1)
      desiredFocusedObjectIndex = stepFromStartToFirstFocusableButton
    end
    if self._scroll == true and desiredFocusedObjectIndex > self._scrollPoint and indexOfTopButtonWhenAtTheBottomOfTheList >= desiredFirstFocusableButtonIndex then
      self._selectedButtonIndex = desiredSelectedButtonIndex
      self._focusedObjectIndex = self._focusedObjectIndex
      self._firstFocusableButtonIndex = desiredFirstFocusableButtonIndex
    elseif 1 > self._focusedObjectIndex or 1 > self._selectedButtonIndex then
      self._selectedButtonIndex = stepFromStartToFirstFocusableButton
      self._focusedObjectIndex = stepFromStartToFirstFocusableButton
      self._firstFocusableButtonIndex = 1
    elseif indexOfTopButtonWhenAtTheBottomOfTheList <= desiredFirstFocusableButtonIndex and buttonCount > desiredFirstFocusableButtonIndex + currFocusableButtonCount then
      self._selectedButtonIndex = desiredSelectedButtonIndex
      self._focusedObjectIndex = desiredFocusedObjectIndex
      self._firstFocusableButtonIndex = indexOfTopButtonWhenAtTheBottomOfTheList
    elseif desiredFocusedObjectIndex <= self._maxFocusableObjectCount then
      self._selectedButtonIndex = desiredSelectedButtonIndex
      self._focusedObjectIndex = desiredFocusedObjectIndex
      self._firstFocusableButtonIndex = self._firstFocusableButtonIndex
    else
      local desiredIndexOfButtonAtTopOfNextPage = self._firstFocusableButtonIndex + self._maxFocusableObjectCount
      while desiredSelectedButtonIndex > desiredIndexOfButtonAtTopOfNextPage do
        desiredIndexOfButtonAtTopOfNextPage = desiredIndexOfButtonAtTopOfNextPage + self._maxFocusableObjectCount
      end
      if indexOfTopButtonWhenAtTheBottomOfTheList < desiredIndexOfButtonAtTopOfNextPage then
        desiredIndexOfButtonAtTopOfNextPage = indexOfTopButtonWhenAtTheBottomOfTheList
      end
      local indexOfButtonBeforeNextPage = desiredIndexOfButtonAtTopOfNextPage - 1
      local stepFromButtonBeforeNextPageToDesiredSelectedButton = StepIncToFocusableButton(self, consts.STEP_NEXT, false, indexOfButtonBeforeNextPage, desiredSelectedButtonIndex - indexOfButtonBeforeNextPage)
      self._selectedButtonIndex = indexOfButtonBeforeNextPage + stepFromButtonBeforeNextPageToDesiredSelectedButton
      self._focusedObjectIndex = stepFromButtonBeforeNextPageToDesiredSelectedButton
      self._firstFocusableButtonIndex = desiredIndexOfButtonAtTopOfNextPage
    end
  end
  self:ChangeToSelectedButton(oldSelectedButtonIndex, oldFocusedObjectIndex, useOnGainFocus, forceChange, false)
  self:AnimateScrollDirectionIndicator(consts.STEP_NEXT)
end
function List:ChangeToPrevButton(stepIncrement, isPaging, useOnGainFocus, forceChange)
  local buttonCount = self:GetButtonCount()
  if buttonCount < 1 then
    self._selectedButtonIndex = 1
    self._focusedObjectIndex = 1
    self._firstFocusableButtonIndex = 1
    return
  end
  local oldSelectedButtonIndex = self._selectedButtonIndex
  local oldFocusedObjectIndex = self._focusedObjectIndex
  local desiredSelectedButtonIndex = self._selectedButtonIndex + stepIncrement
  local stepFromEndToLastFocusableButton = StepIncToFocusableButton(self, consts.STEP_PREV, false, buttonCount, 0)
  local lastFocusableButtonIndex = buttonCount + stepFromEndToLastFocusableButton
  local currFocusableButtonCount = buttonCount < self._maxFocusableObjectCount and buttonCount or self._maxFocusableObjectCount
  local indexOfTopButtonWhenAtTheBottomOfTheList = buttonCount - self._maxFocusableObjectCount + 1
  if indexOfTopButtonWhenAtTheBottomOfTheList < 1 then
    indexOfTopButtonWhenAtTheBottomOfTheList = 1
  end
  if buttonCount < desiredSelectedButtonIndex then
    desiredSelectedButtonIndex = buttonCount
  end
  if desiredSelectedButtonIndex < 1 then
    if self._wrap == true then
      if isPaging == true then
        local stepFromStartToFirstFocusableButton = StepIncToFocusableButton(self, consts.STEP_NEXT, false, 0, 1)
        if oldSelectedButtonIndex == stepFromStartToFirstFocusableButton then
          self._selectedButtonIndex = lastFocusableButtonIndex
          self._focusedObjectIndex = currFocusableButtonCount + stepFromEndToLastFocusableButton
          self._firstFocusableButtonIndex = buttonCount - GetLastPageCount(self) + 1
        else
          self._selectedButtonIndex = stepFromStartToFirstFocusableButton
          self._focusedObjectIndex = stepFromStartToFirstFocusableButton
          self._firstFocusableButtonIndex = 1
        end
      else
        self._selectedButtonIndex = lastFocusableButtonIndex
        self._focusedObjectIndex = currFocusableButtonCount + stepFromEndToLastFocusableButton
        self._firstFocusableButtonIndex = indexOfTopButtonWhenAtTheBottomOfTheList
      end
    end
  else
    local desiredFocusedObjectIndex = self._focusedObjectIndex + stepIncrement
    local desiredFirstFocusableButtonIndex = self._firstFocusableButtonIndex + stepIncrement
    local indexOfButtonAtTopOfFirstPage = 1
    if self._scroll == true and desiredFocusedObjectIndex < self._scrollPoint and desiredFirstFocusableButtonIndex >= indexOfButtonAtTopOfFirstPage then
      self._selectedButtonIndex = desiredSelectedButtonIndex
      self._focusedObjectIndex = self._focusedObjectIndex
      self._firstFocusableButtonIndex = desiredFirstFocusableButtonIndex
    elseif buttonCount < self._focusedObjectIndex or buttonCount < self._selectedButtonIndex then
      self._selectedButtonIndex = lastFocusableButtonIndex
      self._focusedObjectIndex = currFocusableButtonCount + stepFromEndToLastFocusableButton
      self._firstFocusableButtonIndex = indexOfTopButtonWhenAtTheBottomOfTheList
    elseif desiredFirstFocusableButtonIndex <= indexOfButtonAtTopOfFirstPage and buttonCount > desiredFirstFocusableButtonIndex + currFocusableButtonCount then
      self._selectedButtonIndex = desiredSelectedButtonIndex
      self._focusedObjectIndex = desiredFocusedObjectIndex
      self._firstFocusableButtonIndex = indexOfButtonAtTopOfFirstPage
    elseif 1 <= desiredFocusedObjectIndex then
      self._selectedButtonIndex = desiredSelectedButtonIndex
      self._focusedObjectIndex = desiredFocusedObjectIndex
      self._firstFocusableButtonIndex = self._firstFocusableButtonIndex
    else
      local desiredIndexOfButtonAtTopOfPrevPage = self._firstFocusableButtonIndex - self._maxFocusableObjectCount
      while desiredSelectedButtonIndex < desiredIndexOfButtonAtTopOfPrevPage do
        desiredIndexOfButtonAtTopOfPrevPage = desiredIndexOfButtonAtTopOfPrevPage - self._maxFocusableObjectCount
      end
      if indexOfButtonAtTopOfFirstPage > desiredIndexOfButtonAtTopOfPrevPage then
        desiredIndexOfButtonAtTopOfPrevPage = indexOfButtonAtTopOfFirstPage
      end
      local pageCountOfPrevPage = GetPageCountAtIndex(self, desiredIndexOfButtonAtTopOfPrevPage)
      local indexOfButtonAtBottomOfPrevPage = desiredIndexOfButtonAtTopOfPrevPage + pageCountOfPrevPage - 1
      local stepFromButtonAtBottomOfPrevPageToDesiredSelectedButton = StepIncToFocusableButton(self, consts.STEP_PREV, false, indexOfButtonAtBottomOfPrevPage, desiredSelectedButtonIndex - indexOfButtonAtBottomOfPrevPage)
      self._selectedButtonIndex = indexOfButtonAtBottomOfPrevPage + stepFromButtonAtBottomOfPrevPageToDesiredSelectedButton
      self._focusedObjectIndex = self._maxFocusableObjectCount + stepFromButtonAtBottomOfPrevPageToDesiredSelectedButton
      self._firstFocusableButtonIndex = desiredIndexOfButtonAtTopOfPrevPage
    end
  end
  self:ChangeToSelectedButton(oldSelectedButtonIndex, oldFocusedObjectIndex, useOnGainFocus, forceChange, false)
  self:AnimateScrollDirectionIndicator(consts.STEP_PREV)
end
function List:ChangeToSelectedButton(oldSelectedButtonIndex, oldFocusedObjectIndex, useOnGainFocus, forceChange, animateImmediately)
  local selectedButtonChanged = self._selectedButtonIndex ~= oldSelectedButtonIndex
  local oldButton = self:GetButton(oldSelectedButtonIndex)
  local currButton = self:GetSelectedButton()
  if selectedButtonChanged or forceChange == true then
    local focusedObjectIndexChanged = self._focusedObjectIndex ~= oldFocusedObjectIndex
    local bypassAnimation = self._bypassNextSelectionAnimation and not focusedObjectIndexChanged
    if oldButton ~= nil and self._isActivating == false and (selectedButtonChanged or focusedObjectIndexChanged) then
      oldButton:LoseFocus(animateImmediately, bypassAnimation)
    end
    self:Update()
    if currButton ~= nil then
      currButton:GainFocus(false, useOnGainFocus, bypassAnimation)
    end
    if self:get_active() == true then
      self:UpdateDelayTimer(oldSelectedButtonIndex)
    end
    self._bypassNextSelectionAnimation = false
  end
  self:UpdateScrollDirectionIndicators()
  self:UpdateScrollBar()
end
function List:Scroll(focusableObjectIndex, stepIncrement, focused)
  local oldSelectedButtonIndex = self._selectedButtonIndex
  local oldFocusedObjectIndex = self._focusedObjectIndex
  local desiredSelectedButtonIndex = self._selectedButtonIndex + stepIncrement
  local cursor = focusableObjectIndex + self._firstFocusableButtonIndex - 1
  if stepIncrement == 1 and not focused then
    self._focusedObjectIndex = oldFocusedObjectIndex - 1
    desiredSelectedButtonIndex = oldSelectedButtonIndex
  end
  if stepIncrement == 2 and not focused then
    if oldSelectedButtonIndex > cursor then
      stepIncrement = 3
    else
      stepIncrement = 1
    end
    self._focusedObjectIndex = oldFocusedObjectIndex - 1
  end
  if stepIncrement == -1 and not focused then
    self._focusedObjectIndex = oldFocusedObjectIndex + 1
    desiredSelectedButtonIndex = oldSelectedButtonIndex
  end
  if stepIncrement == -2 and not focused then
    stepIncrement = -1
    self._focusedObjectIndex = oldFocusedObjectIndex + 1
  end
  local desiredFirstFocusableButtonIndex = self._firstFocusableButtonIndex + stepIncrement
  if desiredFirstFocusableButtonIndex < 1 then
    desiredFirstFocusableButtonIndex = 1
  end
  local lastButton = self:GetButtonCount() - self._maxFocusableObjectCount + 1
  if desiredFirstFocusableButtonIndex > lastButton then
    desiredFirstFocusableButtonIndex = lastButton
  end
  self._selectedButtonIndex = desiredSelectedButtonIndex
  self._firstFocusableButtonIndex = desiredFirstFocusableButtonIndex
  self:ChangeToSelectedButton(oldSelectedButtonIndex, oldFocusedObjectIndex, true, true, false)
  self:AnimateScrollDirectionIndicator(consts.STEP_PREV)
end
function List:CreateButton(item, itemDetermineFocusabilityFunc, itemGetTextFunc)
  local focusable = true
  local text = ""
  local activationList, eventHandlers, buttonToolTip, toggleToolTip, toggle, advance
  if itemDetermineFocusabilityFunc ~= nil then
    focusable = itemDetermineFocusabilityFunc(item)
  end
  if item ~= nil then
    if itemGetTextFunc == nil then
      text = tostring(item)
    else
      text = itemGetTextFunc(item)
    end
  end
  if type(item) == "table" then
    activationList = item.ActivationList
    eventHandlers = item.EventHandlers
    buttonToolTip = item.ToolTip
    toggleToolTip = item.ToggleTip
    toggle = item.Toggle
    advance = item.Advance
  end
  return button.Button.New(self, {
    Focusable = focusable,
    Item = item,
    ButtonType = type(item) == "table" and item.ButtonType or nil,
    Checked = type(item) == "table" and item.Checked or false,
    Text = text,
    OnGainFocus_Sound = self._button_onGainFocus_sound,
    ButtonToolTip = buttonToolTip,
    ToggleToolTip = toggleToolTip,
    Toggle = toggle,
    EventHandlers = eventHandlers,
    ActivationList = activationList,
    Update = self._Button_Update,
    OnGainFocus = self._Button_OnGainFocus,
    OnLoseFocus = self._Button_OnLoseFocus,
    Advance = advance
  })
end
function List:CreateButtonArray(newItemArray, itemDetermineFocusabilityFunc, itemGetTextFunc)
  assert(newItemArray ~= nil and type(newItemArray) == "table", "Attempted to call CreateButtonArray without newItemArray table.")
  tablex.Clear(self._buttonArray)
  for _, item in ipairs(newItemArray) do
    local newButton = self:CreateButton(item, itemDetermineFocusabilityFunc, itemGetTextFunc)
    tablex.FastInsert(self._buttonArray, newButton, self:GetButtonCount() + 1)
  end
end
function List:Refresh(newItemArray, showList, useOnGainFocus, itemDetermineFocusabilityFunc, itemGetTextFunc)
  self:CreateButtonArray(newItemArray, itemDetermineFocusabilityFunc, itemGetTextFunc)
  local buttonCount = self:GetButtonCount()
  if buttonCount < self._firstFocusableButtonIndex then
    self._firstFocusableButtonIndex = 1
  end
  self:Update()
  if buttonCount == 0 then
    local hideList = not showList
    local clearButtons = true
    self:Deactivate(hideList, clearButtons)
    if showList == true then
      self:Show()
    end
    local emptyText = util.StyleText(util.GetLAMSMsg(self._emptyTextLamsID), "Color_Gray50")
    self._dummyButton:SetGameObjects(1)
    self._dummyButton:Update()
    self._dummyButton:SetText("text", emptyText)
    self._dummyButton:Show()
    if self._dummyButton._OnGainFocus ~= nil then
      self._dummyButton._OnGainFocus(self._dummyButton)
    end
  else
    if buttonCount < self._focusedObjectIndex or buttonCount < self._selectedButtonIndex then
      self:SetSelectedButton(buttonCount, false)
    end
    if showList == true then
      self:Activate(showList, useOnGainFocus)
    end
  end
end
function List:Update()
  self:UpdateButtons()
  self:UpdateBacking()
  self:UpdateScrollDirectionIndicators()
  self:UpdateScrollBar()
end
function List:UpdateButtons()
  for _, button in ipairs(self._buttonArray) do
    button:SetGameObjects(nil)
  end
  for focusableObjectIndex = 1, self._maxFocusableObjectCount do
    local currButton = GetButtonByFocusableObjectIndex(self, focusableObjectIndex)
    if currButton == nil then
      currButton = self._dummyButton
    end
    currButton:SetGameObjects(focusableObjectIndex)
    local child = currButton:GetInstancedChildObject()
    if child ~= nil then
      UI.SetIsClickable(child)
    end
    currButton:Update()
  end
end
function List:UpdateButton(buttonIndex)
  local currButton = self:GetButton(buttonIndex)
  assert(currButton ~= nil, "List " .. self._listObjectName .. " attempted to update non-existant button at index " .. tostring(buttonIndex))
  currButton:Update()
end
function List:UpdateButtonByItem(item)
  local buttonIndex = GetButtonIndexOfItem(self, item)
  self:UpdateButton(buttonIndex)
end
function List:UpdateSelectedButton()
  self:UpdateButton(self._selectedButtonIndex)
end
function List:Sort(sortingGroup, selectedItem, useOnGainFocus, bypassAnimation)
  if self._Sort ~= nil then
    local sortItems = self:GetSortItems()
    if selectedItem == nil then
      selectedItem = self:GetSelectedItem()
    end
    if sortingGroup ~= resourceConsts.POWER_LEVEL_SORTING then
      self._Sort(sortItems, resourceConsts.POWER_LEVEL_SORTING)
    end
    self._Sort(sortItems, resourceConsts.RARITY_SORTING)
    if sortingGroup ~= resourceConsts.RARITY_SORTING then
      self._Sort(sortItems, sortingGroup)
    end
    local sortedArray = {}
    for moveToIndex, sortedItem in ipairs(sortItems) do
      for index, button in ipairs(self._buttonArray) do
        if button:HasItem(sortedItem) then
          local moveFromIndex = index
          sortedArray[moveToIndex] = table.remove(self._buttonArray, moveFromIndex)
        end
      end
    end
    assert(tablex.IsEmpty(self._buttonArray), "Something went wrong in the sorting")
    self._buttonArray = sortedArray
    self._bypassNextSelectionAnimation = bypassAnimation
    self:Update()
    self:SelectItem(selectedItem, useOnGainFocus)
  end
end
function List:get_active()
  return self._active
end
function List:get_LabelGameObject()
  return self._goLabel
end
function List:set_sort(newSortFunc)
  self._Sort = newSortFunc
end
function List:PlaySelectionChangeSound()
  if not util.IsStringNilOrEmpty(self._selectionChangeSound) and not self._hasDoneScrollingGC then
    Audio.PlaySound(self._selectionChangeSound)
  end
end
function List:GetItemByButtonIndex(buttonIndex)
  local button = self:GetButton(buttonIndex)
  local item
  if button ~= nil then
    item = button:get_item()
  end
  return item
end
function List:GetSelectedItem()
  return self:GetItemByButtonIndex(self._selectedButtonIndex)
end
function List:GetItems()
  local itemArray = {}
  local itemIndex = 1
  for _, button in ipairs(self._buttonArray) do
    local item = button:get_item()
    if item ~= nil then
      tablex.FastInsert(itemArray, item, itemIndex)
      itemIndex = itemIndex + 1
    end
  end
  return itemArray
end
function List:GetSortItems()
  local itemArray = {}
  local itemIndex = 1
  for _, button in ipairs(self._buttonArray) do
    local item = button:GetSortItem()
    if item ~= nil then
      tablex.FastInsert(itemArray, item, itemIndex)
      itemIndex = itemIndex + 1
    end
  end
  return itemArray
end
function List:GetButton(buttonIndex)
  local buttonAtIndex
  if buttonIndex ~= nil then
    buttonAtIndex = self._buttonArray[buttonIndex]
  end
  return buttonAtIndex
end
function List:GetSelectedButton()
  return self:GetButton(self._selectedButtonIndex)
end
function List:GetButtons()
  return self._buttonArray
end
function List:GetButtonCount()
  return #self._buttonArray
end
function List:HideButtons()
  for focusableObjectIndex = 1, self._maxFocusableObjectCount do
    self._dummyButton:SetGameObjects(focusableObjectIndex)
    self._dummyButton:Hide()
  end
end
function List:SetDefaultButtonValues()
  local selButton = self:GetSelectedButton()
  for _, button in ipairs(self._buttonArray) do
    button:SetDefaultValue()
  end
end
function List:SetGameObject(goList)
  assert(goList ~= nil, "List:SetGameObject called with no game object!")
  self._goList = goList
  self._buttonObjectBaseName = self._listObjectName .. "_Button"
  self._goListChild = util.GetGOFromChildrenByName(self._goList, "list")
  if self._goListChild ~= nil then
    self._buttonObjectBaseName = "Button"
  end
  self:SetupAllButtonGameObjects()
  self:SetupHeader()
  self:SetupIcon()
  self:SetupLabel()
  self:SetupBacking()
  self:SetupScrollDirectionIndicators()
  self:SetupScrollbar()
end
function List:GetButtonRootGameObject()
  if self._goListChild ~= nil then
    return self._goListChild
  else
    return self._goList
  end
end
function List:SetupAllButtonGameObjects()
  tablex.Clear(self._allButtonGOs)
  local goButtonRoot = self:GetButtonRootGameObject()
  if goButtonRoot ~= nil then
    for focusableObjectIndex = 1, self._maxFocusableObjectCount do
      local buttonObjectName = self._buttonObjectBaseName .. focusableObjectIndex
      local goButtonParent = util.GetGOFromChildrenByName(goButtonRoot, buttonObjectName)
      local buttonGOs = {buttonParent = goButtonParent}
      local childGONames = buttonUtil.GetButtonChildrenObjectNames()
      for _, childGOName in ipairs(childGONames) do
        local goButtonChild = util.GetGOFromChildrenByName(goButtonParent, childGOName)
        if goButtonChild ~= nil then
          local buttonChildGOs = {
            [childGOName] = goButtonChild
          }
          local childSubGONames = buttonUtil.GetButtonChildrenSubObjectNames(childGOName)
          for _, subGOName in ipairs(childSubGONames) do
            local goButtonChildSub = util.GetGOFromChildrenByName(goButtonChild, subGOName)
            if goButtonChildSub ~= nil then
              buttonChildGOs[subGOName] = goButtonChildSub
            end
          end
          buttonGOs[childGOName] = buttonChildGOs
        end
      end
      self._allButtonGOs[focusableObjectIndex] = buttonGOs
    end
  end
end
function List:SetupDummyButton()
  self._dummyButton = self:CreateButton(nil, nil, nil)
  for focusableObjectIndex = 1, self._maxFocusableObjectCount do
    self._dummyButton:SetGameObjects(focusableObjectIndex)
    self._dummyButton:HighlightOff(true)
  end
end
function List:GetButtonGameObjects(focusableObjectIndex)
  local buttonGOs
  if focusableObjectIndex ~= nil then
    local currButtonGOs = self._allButtonGOs[focusableObjectIndex]
    if currButtonGOs ~= nil then
      buttonGOs = currButtonGOs
    end
  end
  return buttonGOs
end
function List:GetDirectionIndicator(direction)
  local goIndicator
  if direction == consts.STEP_PREV then
    goIndicator = self._goIndicatorPrev
  elseif direction == consts.STEP_NEXT then
    goIndicator = self._goIndicatorNext
  end
  return goIndicator
end
function List:StopDelayTimer()
  if self._delayTimer ~= nil then
    self._delayTimer:Stop()
  end
  self._delayTimer = nil
  self._delayStartButtonIndex = self._selectedButtonIndex
end
function List:UpdateDelayTimer(oldSelectedButtonIndex)
  if self._AfterDelay ~= nil and self._delayTime ~= nil then
    if self._delayTimer == nil then
      self._delayStartButtonIndex = oldSelectedButtonIndex
      self._delayTimer = timer.StartLevelTimer(self._delayTime, function()
        AfterDelayHandler(self)
      end)
    else
      self._delayTimer:Restart()
    end
  end
end
function List:DelayTimerExists()
  return self._delayTimer ~= nil
end
function List:SetupHeader()
  local goButtonRoot = self:GetButtonRootGameObject()
  if goButtonRoot ~= nil then
    self._goHeader = util.GetGOFromChildrenByName(goButtonRoot, "Header")
    if self._goHeader ~= nil then
      self._thHeaderText = util.GetTextHandle(self._goHeader, "HeaderText")
      self:SetHeaderText("")
    end
  end
end
function List:ShowHeader()
  if self._goHeader ~= nil then
    self._goHeader:Show()
  end
end
function List:HideHeader()
  if self._goHeader ~= nil then
    self._goHeader:Hide()
  end
end
function List:SetHeaderText(text)
  if self._thHeaderText ~= nil then
    UI.SetText(self._thHeaderText, text)
  end
end
function List:SetupIcon()
  if self._iconObjectName ~= nil then
    local goButtonRoot = self:GetButtonRootGameObject()
    if goButtonRoot ~= nil then
      self._goIconObject = goButtonRoot:FindSingleGOByName(self._iconObjectName)
    end
  end
end
function List:ShowIcon()
  if self._goIconObject ~= nil then
    self._goIconObject:Show()
  end
end
function List:HideIcon()
  if self._goIconObject ~= nil then
    self._goIconObject:Hide()
  end
end
function List:SetIcon(materialSwapName)
  if self._goIconObject ~= nil then
    self._goIconObject:SetMaterialSwap(materialSwapName)
  end
end
function List:SetupLabel()
  local goButtonRoot = self:GetButtonRootGameObject()
  if goButtonRoot ~= nil then
    self._goLabelContainer = util.GetGOFromChildrenByName(goButtonRoot, "labelContainer")
    if self._goLabelContainer ~= nil then
      self._goLabel = self._goLabelContainer:FindSingleGOByName("label")
      assert(self._goLabel ~= nil, "Label game object does not exist in list " .. tostring(self._name) .. "'s labelContainer object!")
      if self._goLabel ~= nil then
        self._thLabelText = util.GetTextHandle(self._goLabel)
        self:SetLabelText("")
      end
    end
  end
end
function List:ShowLabel()
  if self._goLabelContainer ~= nil and self._enableLabel then
    self._goLabelContainer:Show()
    self._goLabel:Show()
  end
end
function List:HideLabel()
  if self._goLabelContainer ~= nil then
    self._goLabelContainer:Hide()
    self._goLabel:Hide()
  end
end
function List:SetLabelText(text)
  if self._thLabelText ~= nil then
    UI.SetText(self._thLabelText, text)
  end
end
function List:SetupBacking()
  local goButtonRoot = self:GetButtonRootGameObject()
  if goButtonRoot ~= nil then
    self._goBacking = util.GetGOFromChildrenByName(goButtonRoot, "Backing")
    self._goBackingBlur = util.GetGOFromChildrenByName(goButtonRoot, "BackingBlur")
    self:HideBacking()
  end
end
function List:HideBacking()
  if self._goBacking ~= nil then
    self._goBacking:Hide()
  end
  if self._goBackingBlur ~= nil then
    self._goBackingBlur:Hide()
  end
end
function List:UpdateBacking()
  local buttonCount = self:GetButtonCount()
  local maxCount = self._maxFocusableObjectCount
  local baseUnit = 0.033333335
  local targetAnimPercent = math.min(buttonCount, maxCount) * baseUnit
  local targetTimelinePos = targetAnimPercent
  local animRate = 0
  if self._goBacking ~= nil then
    self._goBacking:Show()
    UI.Anim(self._goBacking, consts.AS_Forward, "", animRate, targetTimelinePos)
  end
  if self._goBackingBlur ~= nil then
    self._goBackingBlur:Show()
    UI.Anim(self._goBackingBlur, consts.AS_Forward, "", animRate, targetTimelinePos)
  end
end
function List:SetupScrollDirectionIndicators()
  local goButtonRoot = self:GetButtonRootGameObject()
  if goButtonRoot ~= nil then
    self._goIndicatorPrev = util.GetGOFromChildrenByName(goButtonRoot, "IndicatorPrev")
    if self._goIndicatorPrev ~= nil then
      UI.SetIsClickable(self._goIndicatorPrev)
      local thPrev = util.GetTextHandle(self._goIndicatorPrev)
      if thPrev ~= nil then
        UI.SetTextIsUIMacro(thPrev)
      end
    end
    self._goIndicatorNext = util.GetGOFromChildrenByName(goButtonRoot, "IndicatorNext")
    if self._goIndicatorNext ~= nil then
      UI.SetIsClickable(self._goIndicatorNext)
      local thNext = util.GetTextHandle(self._goIndicatorNext)
      if thNext ~= nil then
        UI.SetTextIsUIMacro(thNext)
      end
    end
    self:HideScrollDirectionIndicators()
  end
end
function List:HideIndicator(direction)
  assert(direction == consts.STEP_PREV or direction == consts.STEP_NEXT, "Invalid direction passed into HideIndicator!")
  if direction == consts.STEP_PREV then
    self._goIndicatorPrev:Hide()
    self._indicatorPrevVisible = false
  else
    self._goIndicatorNext:Hide()
    self._indicatorNextVisible = false
  end
end
function List:ShowIndicator(direction)
  assert(direction == consts.STEP_PREV or direction == consts.STEP_NEXT, "Invalid direction passed into ShowIndicator!")
  if direction == consts.STEP_PREV then
    self._goIndicatorPrev:Show()
    self._indicatorPrevVisible = true
  else
    self._goIndicatorNext:Show()
    self._indicatorNextVisible = true
  end
end
function List:HideScrollDirectionIndicators()
  if self._goIndicatorPrev ~= nil then
    self:HideIndicator(consts.STEP_PREV)
  end
  if self._goIndicatorNext ~= nil then
    self:HideIndicator(consts.STEP_NEXT)
  end
end
function List:UpdateScrollDirectionIndicators()
  if self._goIndicatorPrev ~= nil and self._goIndicatorNext ~= nil then
    self:HideScrollDirectionIndicators()
    if self._wrap then
      self:ShowIndicator(consts.STEP_PREV)
      self:ShowIndicator(consts.STEP_NEXT)
    else
      local buttonCount = self:GetButtonCount()
      if self._selectedButtonIndex == 1 then
        if buttonCount == 1 then
          self:HideIndicator(consts.STEP_PREV)
          self:HideIndicator(consts.STEP_NEXT)
        else
          self:HideIndicator(consts.STEP_PREV)
          self:ShowIndicator(consts.STEP_NEXT)
        end
      elseif self._selectedButtonIndex == buttonCount then
        self:ShowIndicator(consts.STEP_PREV)
        self:HideIndicator(consts.STEP_NEXT)
      else
        self:ShowIndicator(consts.STEP_PREV)
        self:ShowIndicator(consts.STEP_NEXT)
      end
    end
  end
end
function List:AnimateScrollDirectionIndicator(direction)
  if self._allowScrollIndicatorAnim and self._goIndicatorPrev ~= nil and self._goIndicatorNext ~= nil then
    local goIndicator = direction == consts.STEP_PREV and self._goIndicatorPrev or self._goIndicatorNext
    local isVisible = false
    if direction == consts.STEP_PREV then
      isVisible = self._indicatorPrevVisible
    elseif direction == consts.STEP_NEXT then
      isVisible = self._indicatorNextVisible
    end
    if isVisible then
      UI.Anim(goIndicator, consts.AS_Forward, "", 5, 0, 1)
    end
  end
end
function List:SetupScrollbar()
  local goButtonRoot = self:GetButtonRootGameObject()
  if goButtonRoot ~= nil then
    local scrollbarObject = util.GetGOFromChildrenByName(goButtonRoot, "ScrollBar")
    if scrollbarObject ~= nil then
      self._scrollBarMeter = meter.Meter.New(self._state, {
        Name = self._listObjectName .. "_ScrollBar",
        MeterObject = scrollbarObject,
        MeterInstanceChildName = "meter",
        Percent = 0
      })
      self._scrollBarMeter:Hide()
    end
  end
end
function List:ShowScrollBar()
  if self._scrollBarMeter ~= nil then
    self._scrollBarMeter:Show()
    UI.SetIsClickable(self._scrollBarMeter:get_gameObject())
    self._scrollBarVisible = true
  end
end
function List:HideScrollBar()
  if self._scrollBarMeter ~= nil then
    self._scrollBarMeter:Hide()
    self._scrollBarVisible = false
  end
end
function List:SetScrollBarVisibility()
  if self._scrollBarMeter ~= nil then
    if self:GetButtonCount() > self._maxFocusableObjectCount then
      self:ShowScrollBar()
    else
      self:HideScrollBar()
    end
  end
end
function List:SetScrollBarPosition()
  if self._scrollBarMeter ~= nil and self._scrollBarVisible == true then
    local firstItem = self:GetItemByButtonIndex(1)
    local isFirstButtonCategory = type(firstItem) == "table" and firstItem.ButtonType == "category"
    local scrollMaxCount = self:GetButtonCount() - 1
    local scrollCurrentPosition = self._selectedButtonIndex - 1
    if isFirstButtonCategory then
      scrollMaxCount = scrollMaxCount - 1
      scrollCurrentPosition = scrollCurrentPosition - 1
    end
    if scrollMaxCount <= 0 then
      scrollMaxCount = 1
    end
    if scrollCurrentPosition < 0 then
      scrollCurrentPosition = 0
    end
    local scrollBarPercent = scrollCurrentPosition / scrollMaxCount
    self._scrollBarMeter:SetPercent(scrollBarPercent, nil)
  end
end
function List:UpdateScrollBar()
  self:SetScrollBarVisibility()
  self:SetScrollBarPosition()
end
return {List = List}
