ZO_SKILL_ABILITY_DISPLAY_INTERACTIVE = 1
ZO_SKILL_ABILITY_DISPLAY_VIEW = 2

ZO_GAMEPAD_SKILLS_SKILL_LIST_BROWSE_MODE = 1
ZO_GAMEPAD_SKILLS_ABILITY_LIST_BROWSE_MODE = 2
ZO_GAMEPAD_SKILLS_SINGLE_ABILITY_ASSIGN_MODE = 3

-------------------------------
-- Gamepad Skills quick menu --
-------------------------------

-- This is used by the action bar to display and select from any current slottable skill, across multiple skill lines
ZO_GamepadAssignableActionBar_PlayerQuickMenu = ZO_GamepadAssignableActionBar_QuickMenu_Base:Subclass()

function ZO_GamepadAssignableActionBar_PlayerQuickMenu:SetupListTemplates()
    local function MenuAbilityEntryTemplateSetup(control, skillEntry, selected, reselectingDuringRebuild, enabled, activated)
        ZO_GamepadSkillEntryTemplate_SetEntryInfoFromAllocator(skillEntry)
        ZO_SharedGamepadEntry_OnSetup(control, skillEntry, selected, reselectingDuringRebuild, enabled, activated)
        ZO_GamepadSkillEntryTemplate_Setup(control, skillEntry, selected, activated, ZO_SKILL_ABILITY_DISPLAY_INTERACTIVE)
    end

    local function MenuEntryHeaderTemplateSetup(control, skillEntry, selected, selectedDuringRebuild, enabled, activated)
        control.header:SetText(skillEntry:GetHeader())
        local skillData = skillEntry.skillData
        control.skillRankHeader:SetText(skillData:GetSkillLineData():GetCurrentRank())
    end

    local function IsSkillEqual(leftSkillEntry, rightSkillEntry)
        return leftSkillEntry.skillData == rightSkillEntry.skillData
    end

    self.list:AddDataTemplate("ZO_GamepadSimpleAbilityEntryTemplate", MenuAbilityEntryTemplateSetup, ZO_GamepadMenuEntryTemplateParametricListFunction, IsSkillEqual)
    self.list:AddDataTemplateWithHeader("ZO_GamepadSimpleAbilityEntryTemplate", MenuAbilityEntryTemplateSetup, ZO_GamepadMenuEntryTemplateParametricListFunction, IsSkillEqual, "ZO_GamepadSimpleAbilityEntryHeaderTemplate", MenuEntryHeaderTemplateSetup)
end

do
    local function IsSkillPurchased(skillData)
        return skillData:GetPointAllocator():IsPurchased()
    end
    local function IsSkillNormalActive(skillData)
        return skillData:IsActive() and not skillData:IsUltimate()
    end
    local function IsSkillUltimate(skillData)
        return skillData:IsUltimate()
    end
    local function IsSkillVisible(skillData)
        return not skillData:IsHidden()
    end
    function ZO_GamepadAssignableActionBar_PlayerQuickMenu:ForEachSlottableSkill(visitor)
        local skillLineFilters = { ZO_SkillLineData.IsAvailable }
        if ACTION_BAR_ASSIGNMENT_MANAGER:GetCurrentHotbarCategory() == HOTBAR_CATEGORY_WEREWOLF then
            table.insert(skillLineFilters, ZO_SkillLineData.IsWerewolf)
        end

        local shouldShowUltimateSkills = self.assignableActionBar:IsUltimateSelected()
        local skillDataFilters = { IsSkillPurchased, shouldShowUltimateSkills and IsSkillUltimate or IsSkillNormalActive, IsSkillVisible }

        for _, skillTypeData in SKILLS_DATA_MANAGER:SkillTypeIterator() do
            for _, skillLineData in skillTypeData:SkillLineIterator(skillLineFilters) do
                for _, skillData in skillLineData:SkillIterator(skillDataFilters) do
                    visitor(skillTypeData, skillLineData, skillData)
                end
            end
        end
    end
end

--------------------
-- Gamepad Skills --
--------------------

ZO_GamepadSkills = ZO_Gamepad_ParametricList_Screen:Subclass()

function ZO_GamepadSkills:Initialize(control)
    ZO_Gamepad_ParametricList_Screen.Initialize(self, control, ZO_GAMEPAD_HEADER_TABBAR_DONT_CREATE)

    self.trySetClearUpdatedAbilityFlagCallback = function(callId)
        self:TrySetClearUpdatedAbilityFlag(callId)
    end

    self.trySetClearNewSkillLineFlagCallback = function(callId)
        self:TrySetClearNewSkillLineFlag(callId)
    end

    -- Used to select a desired skill ability after a lineFilterList refresh
    self.selectSkillData = nil

    self.skillLineXPBarFragment = ZO_FadeSceneFragment:New(ZO_GamepadSkillsTopLevelSkillInfo)

    GAMEPAD_SKILLS_ROOT_SCENE = ZO_InteractScene:New("gamepad_skills_root", SCENE_MANAGER, ZO_SKILL_RESPEC_INTERACT_INFO)
    GAMEPAD_SKILLS_ROOT_SCENE:RegisterCallback("StateChange", function(oldState, newState)
        ZO_Gamepad_ParametricList_Screen.OnStateChanged(self, oldState, newState)
        if newState == SCENE_SHOWING then
            self:SetMode(ZO_GAMEPAD_SKILLS_SKILL_LIST_BROWSE_MODE)
            self:RefreshHeader(GetString(SI_MAIN_MENU_SKILLS))
            self.categoryListRefreshGroup:TryClean()
            KEYBIND_STRIP:AddKeybindButtonGroup(self.categoryKeybindStripDescriptor)

            local previousSceneName = SCENE_MANAGER:GetPreviousSceneName()
            if self.returnToAdvisor then
                if previousSceneName == "gamepad_skills_line_filter" then
                    -- second entry is always the skills advisor
                    self.categoryList:SetSelectedIndexWithoutAnimation(2)
                    self:DeactivateCurrentList()
                    ZO_GAMEPAD_SKILLS_ADVISOR_SUGGESTIONS_WINDOW:Activate()
                end
                self.returnToAdvisor = false
            elseif self.selectSubclassing then
                -- Subclassing follows scribing, skills advisor and three class skill lines
                local SUBCLASSING_INDEX = 6
                self.categoryList:SetSelectedIndexWithoutAnimation(SUBCLASSING_INDEX)
                self.selectSubclassing = false
            elseif self.selectSkillData then
                if previousSceneName == "gamepad_skills_scribing_library_root" then
                    -- first entry is always scribing
                    self.categoryList:SetSelectedIndexWithoutAnimation(1)
                end
            end

            TriggerTutorial(TUTORIAL_TRIGGER_COMBAT_SKILLS_OPENED)
            TriggerTutorial(TUTORIAL_TRIGGER_SKILLS_SKILL_STYLING)

            if self.selectSkillLineBySkillDataOnInitialize then
                self:SelectSkillLineBySkillData(self.selectSkillLineBySkillDataOnInitialize)
                self.selectSkillLineBySkillDataOnInitialize = nil
            elseif self.showAttributeDialog and not self.selectSkillData then
                --Defer dialog call in case we're entering the scene from the base scene. This is to
                --ensure the dialog's keybind layer is added after the other layers, and not before.
                local function ShowDialog()
                    if SCENE_MANAGER:IsShowing("gamepad_skills_root") then
                        ZO_Dialogs_ShowGamepadDialog("GAMEPAD_SKILLS_ATTRIBUTE_PURCHASE", nil, nil)
                    end
                end
                zo_callLater(ShowDialog, 20)
            end
        elseif newState == SCENE_SHOWN then
            --If we entered skills with the action bar selected make sure to activate it. We do this in shown because fragments are set to showing after
            --the scene is which means the action bar is still hidden on showing which prevents activating it.
            if self.mode == ZO_GAMEPAD_SKILLS_SKILL_LIST_BROWSE_MODE then
                if self.assignableActionBar:IsActive() then
                    self:ActivateAssignableActionBarFromList()
                end
            end
            HandleReturningPlayerUISystemShown(UI_SYSTEM_SKILLS)
        elseif newState == SCENE_HIDING then
            --Disable now so it's not possible to change the selected skill live/skills advisor entry as the scene is hiding since the line filter list depends on it being a skill line
            self:DisableCurrentList()
        elseif newState == SCENE_HIDDEN then
            KEYBIND_STRIP:RemoveKeybindButtonGroup(self.categoryKeybindStripDescriptor)
            GAMEPAD_TOOLTIPS:Reset(GAMEPAD_LEFT_TOOLTIP)
            GAMEPAD_TOOLTIPS:Reset(GAMEPAD_RIGHT_TOOLTIP)
        end
    end)

    GAMEPAD_SKILLS_LINE_FILTER_SCENE = ZO_InteractScene:New("gamepad_skills_line_filter", SCENE_MANAGER, ZO_SKILL_RESPEC_INTERACT_INFO)
    GAMEPAD_SKILLS_LINE_FILTER_SCENE:AddFragment(self.skillLineXPBarFragment)
    GAMEPAD_SKILLS_LINE_FILTER_SCENE:RegisterCallback("StateChange", function(oldState, newState)
        ZO_Gamepad_ParametricList_Screen.OnStateChanged(self, oldState, newState)
        if newState == SCENE_SHOWING then
            local targetSkillLineData = self.categoryList:GetTargetData().skillLineData
            self:SetMode(ZO_GAMEPAD_SKILLS_ABILITY_LIST_BROWSE_MODE)
            self:RefreshHeader(targetSkillLineData:GetFormattedName())
            --To pick up the new skill line that was just selected
            self.lineFilterListRefreshGroup:MarkDirty("List")
            self.lineFilterListRefreshGroup:TryClean()

            -- If there was a skill data to select, find it and select it now that the skill list is showing
            local setSelectedIndex = false
            if self.selectSkillData then
                for i = 1, self.lineFilterList:GetNumEntries() do
                    local data = self.lineFilterList:GetDataForDataIndex(i)
                    if data.skillData == self.selectSkillData then
                        self.lineFilterList:SetSelectedIndexWithoutAnimation(i)
                        setSelectedIndex = true
                        break
                    end
                end
            end
            if not setSelectedIndex then
                self.lineFilterList:SetSelectedIndexWithoutAnimation(1)
            end

            ACTION_BAR_ASSIGNMENT_MANAGER:UpdateWerewolfBarStateInCycle(targetSkillLineData)
            KEYBIND_STRIP:AddKeybindButtonGroup(self.lineFilterKeybindStripDescriptor)
        elseif newState == SCENE_HIDDEN then
            local NO_SKILL_LINE_SELECTED = nil
            ACTION_BAR_ASSIGNMENT_MANAGER:UpdateWerewolfBarStateInCycle(NO_SKILL_LINE_SELECTED)
            self:DisableCurrentList()
            self:TryClearSkillUpdatedStatus()
            self:TryClearSkillLineNewStatus()
            self.clearSkillUpdatedStatusCallId = nil
            self.clearSkillUpdatedStatusSkillData = nil
            self.clearSkillLineNewStatusCallId = nil
            self.clearSkillLineNewStatusSkillLineData = nil
            KEYBIND_STRIP:RemoveKeybindButtonGroup(self.lineFilterKeybindStripDescriptor)
            GAMEPAD_TOOLTIPS:Reset(GAMEPAD_LEFT_TOOLTIP)
            GAMEPAD_TOOLTIPS:Reset(GAMEPAD_RIGHT_TOOLTIP)
             if self.mode == ZO_GAMEPAD_SKILLS_SINGLE_ABILITY_ASSIGN_MODE then
                self.actionBarAnimation:PlayInstantlyToStart()
            end
            if self.selectSkillData then
                self.categoryListRefreshGroup:MarkDirty("List")
            end
        end
    end)

    GAMEPAD_SKILLS_SCENE_GROUP = ZO_SceneGroup:New("gamepad_skills_root", "gamepad_skills_line_filter")
    GAMEPAD_SKILLS_SCENE_GROUP:RegisterCallback("StateChange", function(_, newState)
        if newState == SCENE_GROUP_SHOWING then
            self:PerformDeferredInitialization()
            self.showAttributeDialog = GetAttributeUnspentPoints() > 0 and not SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeBatchSave()
        elseif newState == SCENE_GROUP_HIDDEN then
            self.assignableActionBar:OnSkillsHidden()
            SKILLS_AND_ACTION_BAR_MANAGER:ResetInterface()
        end
    end)

    GAMEPAD_SKILLS_ROOT_SCENE:SetHideSceneConfirmationCallback(ZO_GamepadSkills.OnConfirmHideScene)
    GAMEPAD_SKILLS_LINE_FILTER_SCENE:SetHideSceneConfirmationCallback(ZO_GamepadSkills.OnConfirmHideScene)

    --Init the weapon swap descriptors here because OnWeaponSwap is private and cannot be initialized on a scene show if an addon does the show
    self.categoryKeybindStripDescriptor = { alignment = KEYBIND_STRIP_ALIGN_LEFT }
    self:AddWeaponSwapDescriptor(self.categoryKeybindStripDescriptor)
    self:AddSkillsAdvisorDescriptor(self.categoryKeybindStripDescriptor)
    self.lineFilterKeybindStripDescriptor = { alignment = KEYBIND_STRIP_ALIGN_LEFT }
    self:AddWeaponSwapDescriptor(self.lineFilterKeybindStripDescriptor)

    self:InitializeOptionsDialog()
end

function ZO_GamepadSkills.OnConfirmHideScene(scene, nextSceneName, bypassHideSceneConfirmationReason)
    if bypassHideSceneConfirmationReason == nil and 
        SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeBatchSave() and
        (nextSceneName == "gamepad_skills_scribing_library_root" or
            not GAMEPAD_SKILLS_SCENE_GROUP:HasScene(nextSceneName)) then

        ZO_Dialogs_ShowGamepadDialog("CONFIRM_REVERT_CHANGES",
        {
            confirmCallback = function()
                scene:AcceptHideScene()
                SKILLS_DATA_MANAGER:RebuildSkillsData()
                ACTION_BAR_ASSIGNMENT_MANAGER:ResetPlayerHotbars()
                SKILLS_AND_ACTION_BAR_MANAGER:ResetInterface()
            end,
            declineCallback = function() scene:RejectHideScene() end,
        })
    else
        scene:AcceptHideScene()
    end
end

function ZO_GamepadSkills:OnPlayerDeactivated()
    --If we are deactivated we might be jumping somewhere else. We also might be in the respec interaction which will not be valid when we get where we are going. So just clear out the respec here.
    if GAMEPAD_SKILLS_SCENE_GROUP:IsShowing() and SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeBatchSave() then
        SCENE_MANAGER:RequestShowLeaderBaseScene(ZO_BHSCR_SKILLS_PLAYER_DEACTIVATED)
    end
end

function ZO_GamepadSkills:PerformUpdate()
    -- Include update functionality here if the screen uses self.dirty to track needing to update
    self.dirty = false
end

--TODO: Because we do not associate the parametric list screen with a scene/fragment/scene group, we need to override the ZO_Gamepad_ParametricList_Screen.IsShowing function to make sure it returns the proper thing
--We cannot assign a scene to this screen because there are two separate scenes that are managing the state change functionality. At some point, we should refactor this screen so we don't need to do this anymore
function ZO_GamepadSkills:IsShowing()
    return GAMEPAD_SKILLS_ROOT_SCENE:IsShowing() or GAMEPAD_SKILLS_LINE_FILTER_SCENE:IsShowing() or GAMEPAD_SKILLS_SCRIBING_LIBRARY_ROOT_SCENE:IsShowing()
end

function ZO_GamepadSkills:PerformDeferredInitialization()
    if self.fullyInitialized then return end
    self.fullyInitialized = true

    self:InitializeHeader()
    self:InitializeAssignableActionBar()
    self:InitializeCategoryList()
    self:InitializeLineFilterList()
    self:InitializeMorphDialog()
    self:InitializeChangeSkillStyleDialog()
    self:InitializePurchaseAndUpgradeDialog()
    self:InitializeAttributeDialog()
    self:InitializeRespecConfirmationGoldDialog()
    self:InitializeConfirmClearAllDialog()
    self:InitializeQuickMenu()
    self:InitializeCategoryKeybindStrip()
    self:InitializeLineFilterKeybindStrip()

    self:InitializeRefreshGroups()
    self:InitializeEvents()

    self:RefreshPointsDisplay()
    self:RefreshRespecModeBindingsDisplay()
end

function ZO_GamepadSkills:InitializeHeader()
    local selectedSkillBuild = ZO_SKILLS_ADVISOR_SINGLETON:GetAvailableSkillBuildById(ZO_SKILLS_ADVISOR_SINGLETON:GetSelectedSkillBuildId())
    self.headerData =
    {
        titleText = GetString(SI_MAIN_MENU_SKILLS),
        subtitleText = selectedSkillBuild and zo_strformat(SI_SKILLS_ADVISOR_GAMEPAD_SELECTED_BUILD_SUBTITLE, selectedSkillBuild.name) or "",
        data1HeaderText = GetString(SI_GAMEPAD_SKILLS_AVAILABLE_POINTS),
        data2HeaderText = GetString(SI_GAMEPAD_SKILLS_SKY_SHARDS),
    }
    ZO_GamepadGenericHeader_SetDataLayout(self.header, ZO_GAMEPAD_HEADER_LAYOUTS.DATA_PAIRS_TOGETHER)
end

function ZO_GamepadSkills:RefreshHeader(headerTitle)
    self.headerData.titleText = headerTitle

    local selectedSkillBuild = ZO_SKILLS_ADVISOR_SINGLETON:GetAvailableSkillBuildById(ZO_SKILLS_ADVISOR_SINGLETON:GetSelectedSkillBuildId())
    self.headerData.subtitleText = selectedSkillBuild and zo_strformat(SI_SKILLS_ADVISOR_GAMEPAD_SELECTED_BUILD_SUBTITLE, selectedSkillBuild.name) or ""

    ZO_GamepadGenericHeader_RefreshData(self.header, self.headerData)
end

local function IsEntryHeader(data)
    return data.header
end

local function AreSkillLineEntriesEqual(left, right)
    return left.skillLineData == right.skillLineData
end

function ZO_GamepadSkills:GetSkillLineEntryIndex(skillLineData)
    for i = 1, self.categoryList:GetNumEntries() do
        local categoryEntry = self.categoryList:GetDataForDataIndex(i)
        if not (categoryEntry.isSkillsAdvisor or categoryEntry.isScribeLibrary or categoryEntry.isSubclassing) then
            if categoryEntry.skillLineData == skillLineData then
                return i
            end
        end
    end
end

function ZO_GamepadSkills:SelectSkillLineBySkillData(skillData, returnToSkillsAdvisor)
    if not self.fullyInitialized or not self:IsShowing() then
        self.selectSkillLineBySkillDataOnInitialize = skillData
        return
    end

    if skillData then
        self.returnToAdvisor = returnToSkillsAdvisor
        local skillLineData = skillData:GetSkillLineData()
        -- Set the category index to the category where clicked skill ability can be found 
        local categoryIndex = self:GetSkillLineEntryIndex(skillLineData)
        if categoryIndex then
            self.categoryList:SetSelectedIndexWithoutAnimation(categoryIndex)

            if skillLineData:IsAvailable() then
                -- Since the ability we want to select won't be available until after the lineFilterList is refreshed,
                -- store ability we want to select as a member var and use it to select the abilty after list is ready
                self.selectSkillData = skillData

                -- Prevent input while transitioning from skills advisor to selected skill ability in skill line
                self:DisableCurrentList()

                -- Open the skill line filter view
                SCENE_MANAGER:Push("gamepad_skills_line_filter")
            else
                self:SetCurrentList(self.categoryList)
            end
        else
            -- SkillLine not known or yet advised
            if self.returnToAdvisor then
                ZO_GAMEPAD_SKILLS_ADVISOR_SUGGESTIONS_WINDOW:SetSelectSkillData(skillData)
                skillLineData:SetAdvised(true)
            end
            self:SetCurrentList(self.categoryList)
        end
    end
end

function ZO_GamepadSkills:SelectEntryByAllocationMode()
    local allocationMode = SKILLS_AND_ACTION_BAR_MANAGER:GetSkillPointAllocationMode()
    if allocationMode == SKILL_POINT_ALLOCATION_MODE_SUBCLASS_ONLY then
        self.selectSubclassing = true
    end
end

function ZO_GamepadSkills:InitializeCategoryKeybindStrip()
    table.insert(self.categoryKeybindStripDescriptor,
    {
        name = function() --name
            local targetData = self.categoryList:GetTargetData()
            if self.assignableActionBar:IsActive() then
                return GetString(SI_GAMEPAD_SKILLS_BUILD_PLANNER)
            elseif targetData and targetData.isScribeLibrary and not SCRIBING_DATA_MANAGER:IsScribingUnlocked() then
                local collectibleData = SCRIBING_DATA_MANAGER:GetScribingPurchasableCollectibleData()
                if collectibleData:IsCategoryType(COLLECTIBLE_CATEGORY_TYPE_CHAPTER) then
                    return GetString(SI_SCRIBING_ACTION_UPGRADE)
                else
                    return GetString(SI_GAMEPAD_DLC_BOOK_ACTION_OPEN_CROWN_STORE)
                end
            end
            return GetString(SI_GAMEPAD_SELECT_OPTION)
        end,
        keybind = "UI_SHORTCUT_PRIMARY",
        callback =  function()
            --Here we determine what fragment to load, but we're going to wait until it loads to decide how to populate it
            --So we'll prevent any further movement and proceed based on what we expect the selected data to be by the time we need it.
            --We may already be in the process of a scroll, so "current data" isn't reliable.
            local targetData = self.categoryList:GetTargetData()
            if self.assignableActionBar:IsActive() then
                self.selectedTooltipRefreshGroup:MarkDirty("Full")
                self.quickMenu:Show()
                PlaySound(SOUNDS.GAMEPAD_MENU_FORWARD)
            elseif targetData and targetData.isSkillsAdvisor then
                self:DeactivateCurrentList()
                if ZO_SKILLS_ADVISOR_SINGLETON:GetSelectedSkillBuildId() then
                    ZO_GAMEPAD_SKILLS_ADVISOR_SUGGESTIONS_WINDOW:Activate()
                else
                    SCENE_MANAGER:Push("gamepad_skills_advisor_build_selection_root")
                end
            elseif targetData and targetData.isScribeLibrary then
                if SCRIBING_DATA_MANAGER:IsScribingUnlocked() then
                    SCENE_MANAGER:Push("gamepad_skills_scribing_library_root")
                else
                    local collectibleData = SCRIBING_DATA_MANAGER:GetScribingPurchasableCollectibleData()
                    if collectibleData:IsCategoryType(COLLECTIBLE_CATEGORY_TYPE_CHAPTER) then
                        ZO_ShowChapterUpgradePlatformScreen(MARKET_OPEN_OPERATION_SKILLS_SCRIBING_LIBRARY)
                    else
                        local searchTerm = zo_strformat(SI_CROWN_STORE_SEARCH_FORMAT_STRING, collectibleData:GetName())
                        ShowMarketAndSearch(searchTerm, MARKET_OPEN_OPERATION_SKILLS_SCRIBING_LIBRARY)
                    end
                end
            elseif targetData and targetData.isSubclassing then
                SCENE_MANAGER:Push("gamepad_skills_subclassing_root")
            elseif targetData and not targetData.advised then
                self:DeactivateCurrentList()
                SCENE_MANAGER:Push("gamepad_skills_line_filter")
            end
        end,
        enabled = function()
            local targetData = self.categoryList:GetTargetData()
            if targetData then
                if targetData.isSubclassing then
                    return SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeAllowAccessToSubclassing()
                end
                return true
            end
            return false
        end,
    })

    -- Confirm Bind
    table.insert(self.categoryKeybindStripDescriptor,
    {
        name = GetString(SI_SKILL_RESPEC_CONFIRM_KEYBIND),
        keybind = "UI_SHORTCUT_SECONDARY",
        callback = function()
            self:ShowConfirmRespecDialog()
        end,
        visible = function()
            return SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeBatchSave()
        end,
    })

    -- Subclassing Grant Quest Bind
    table.insert(self.categoryKeybindStripDescriptor,
    {
        name = GetString(SI_SKILLS_SUBCLASSING_QUEST_GRANT_TEXT),
        keybind = "UI_SHORTCUT_QUATERNARY",
        callback = function()
            BestowSubclassingQuest()
        end,
        visible = function()
            local targetData = self.categoryList:GetTargetData()
            if targetData and targetData.isSubclassing and not HasAccessToSubclassing() then
                local achievementId = GetSubclassingAchievementId()
                if IsAchievementComplete(achievementId) then
                    local questId = GetSubclassingQuestId()
                    return not HasQuest(questId)
                end
            end
            return false
        end,
    })

    --Clear All Bind and Subclassing Help
    table.insert(self.categoryKeybindStripDescriptor,
    {
        name = function()
            local targetData = self.categoryList:GetTargetData()
            if targetData.isSubclassing then
                return GetString(SI_SKILLS_SUBCLASSING_MORE_INFO_KEYBIND)
            end
            return GetString("SI_SKILLPOINTALLOCATIONMODE_CLEARKEYBIND", SKILLS_AND_ACTION_BAR_MANAGER:GetSkillPointAllocationMode())
        end,
        keybind = "UI_SHORTCUT_RIGHT_STICK",
        callback = function()
            local targetData = self.categoryList:GetTargetData()
            if targetData.isSubclassing then
                local helpCategoryIndex, helpIndex = GetSubclassingHelpIndices()
                HELP_TUTORIALS_ENTRIES_GAMEPAD:Push(helpCategoryIndex, helpIndex)
            else
                self:ShowConfirmClearAllDialog(targetData.skillLineData)
            end
        end,
        visible = function()
            local targetData = self.categoryList:GetTargetData()
            if targetData then
                if targetData.isSubclassing then
                    local helpCategoryIndex, helpIndex = GetSubclassingHelpIndices()
                    return helpCategoryIndex ~= nil
                elseif SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeAllowClear() and not self.assignableActionBar:IsActive() then
                    return not targetData.isSkillsAdvisor
                end
            end
            return false
        end,
    })

    ZO_Gamepad_AddBackNavigationKeybindDescriptors(self.categoryKeybindStripDescriptor, GAME_NAVIGATION_TYPE_BUTTON)
    ZO_Gamepad_AddListTriggerKeybindDescriptors(self.categoryKeybindStripDescriptor, self.categoryList, IsEntryHeader)
end

function ZO_GamepadSkills:InitializeLineFilterKeybindStrip()
    local function IsActionBarEditable()
        local lockedReason = GetActionBarLockedReason()
        if lockedReason == ACTION_BAR_LOCKED_REASON_COMBAT then
            return false, GetString("SI_RESPECRESULT", RESPEC_RESULT_IS_IN_COMBAT)
        elseif lockedReason == ACTION_BAR_LOCKED_REASON_NOT_RESPECCABLE then
            return false, GetString("SI_RESPECRESULT", RESPEC_RESULT_ACTIVE_HOTBAR_NOT_RESPECCABLE)
        end
        return true
    end

    table.insert(self.lineFilterKeybindStripDescriptor,
    {
        name = function()
            --This is confirm when respecing and assign otherwise
            if SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeBatchSave() then
                return GetString(SI_SKILL_RESPEC_CONFIRM_KEYBIND)
            else
                return GetString(SI_GAMEPAD_SKILLS_ASSIGN)
            end
        end,

        keybind = "UI_SHORTCUT_SECONDARY",

        visible = function()
            if self.mode ~= ZO_GAMEPAD_SKILLS_ABILITY_LIST_BROWSE_MODE or self.assignableActionBar:IsActive() then
                return false
            end
            --This is confirm when respecing and assign otherwise
            if SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeBatchSave() then
                return true
            else
                local skillEntry = self.lineFilterList:GetTargetData()
                if skillEntry then
                    local skillData = skillEntry.skillData
                    return skillData:IsActive() and skillData:GetPointAllocator():IsPurchased()
                end
            end
        end,

        enabled = IsActionBarEditable,

        callback = function()
            --This is confirm when respecing and assign otherwise
            if SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeBatchSave() then
                self:ShowConfirmRespecDialog()
            else
                local skillEntry = self.lineFilterList:GetTargetData()
                local skillData = skillEntry.skillData
                self:DeactivateCurrentList()
                self:StartSingleAbilityAssignment(skillData)
                PlaySound(SOUNDS.GAMEPAD_MENU_FORWARD)
            end
        end,
    })

    table.insert(self.lineFilterKeybindStripDescriptor,
    {
        name = GetString(SI_SKILL_STYLE_GAMEPAD_CHANGE_STYLE),

        keybind = "UI_SHORTCUT_QUATERNARY",

        visible = function()
            if self.mode ~= ZO_GAMEPAD_SKILLS_ABILITY_LIST_BROWSE_MODE or self.assignableActionBar:IsActive() then
                return false
            end

            local skillEntry = self.lineFilterList:GetTargetData()
            if skillEntry then
                local skillData = skillEntry.skillData
                local skillPointAllocator = skillData:GetPointAllocator()
                local skillProgressionData = skillPointAllocator:GetProgressionData()
                return skillData:IsActive() and skillProgressionData:HasAnyNonHiddenSkillStyles()
            end
        end,

        callback = function()
            local skillEntry = self.lineFilterList:GetTargetData()
            if skillEntry then
                local skillData = skillEntry.skillData
                ZO_Dialogs_ShowGamepadDialog("GAMEPAD_SKILL_STYLE_SELECTION", { skillData = skillEntry.skillData })
            end
        end,
    })

    table.insert(self.lineFilterKeybindStripDescriptor,
    {
        name = GetString(SI_GAMEPAD_OPTIONS_MENU),

        keybind = "UI_SHORTCUT_QUINARY",

        callback = function()
            local skillEntry = self.lineFilterList:GetTargetData()
            if skillEntry then
                ZO_Dialogs_ShowPlatformDialog("SKILLS_OPTIONS_DIALOG_GAMEPAD", { skillData = skillEntry.skillData })
            end
        end,
    })

    local function IsInSkillPointAllocationMode()
        return not (self.mode == ZO_GAMEPAD_SKILLS_SINGLE_ABILITY_ASSIGN_MODE or self.assignableActionBar:IsActive())
    end

    local function IncreaseSkillKeybindName()
        local skillEntry = self.lineFilterList:GetTargetData()
        local actionType = skillEntry.skillData:GetPointAllocator():GetIncreaseSkillAction()
        if actionType == ZO_SKILL_POINT_ACTION.PURCHASE or actionType == ZO_SKILL_POINT_ACTION.INCREASE_RANK then
            return GetString(SI_GAMEPAD_SKILLS_PURCHASE)
        elseif actionType == ZO_SKILL_POINT_ACTION.MORPH or actionType == ZO_SKILL_POINT_ACTION.REMORPH then
            return GetString(SI_GAMEPAD_SKILLS_MORPH)
        end
    end

    local function IncreaseSkillKeybindVisible()
        local skillEntry = self.lineFilterList:GetTargetData()
        if skillEntry then
            return skillEntry.skillData:GetPointAllocator():GetIncreaseSkillAction() ~= ZO_SKILL_POINT_ACTION.NONE
        else
            return false
        end
    end

    local function IncreaseSkillKeybindCallback()
        local skillEntry = self.lineFilterList:GetTargetData()
        local skillData = skillEntry.skillData
        local skillProgressionData = skillData:GetPointAllocatorProgressionData()

        local actionType = skillData:GetPointAllocator():GetIncreaseSkillAction()
        local availablePoints = GetAvailableSkillPoints()

        local name = skillProgressionData:GetFormattedNameWithRank()

        if actionType == ZO_SKILL_POINT_ACTION.PURCHASE then
            if SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeConfirmOnPurchase() then
                local labelData = { titleParams = { availablePoints }, mainTextParams = { name, skillData:GetSkillPointCostMultiplier() } }
                local dialogData = { purchaseSkillProgressionData = skillProgressionData, }

                ZO_Dialogs_ShowGamepadDialog("GAMEPAD_SKILLS_PURCHASE_CONFIRMATION", dialogData, labelData)
            else
                skillData:GetPointAllocator():Purchase()
            end
        elseif actionType == ZO_SKILL_POINT_ACTION.INCREASE_RANK then
            if SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeConfirmOnIncreaseRank() then
                local labelData = { titleParams = { availablePoints }, mainTextParams = { name, skillData:GetSkillPointCostMultiplier() } }
                local dialogData = { currentSkillProgressionData = skillProgressionData }

                ZO_Dialogs_ShowGamepadDialog("GAMEPAD_SKILLS_UPGRADE_CONFIRMATION", dialogData, labelData)
            else
                skillData:GetPointAllocator():IncreaseRank()
            end
        elseif actionType == ZO_SKILL_POINT_ACTION.MORPH or actionType == ZO_SKILL_POINT_ACTION.REMORPH then
            local morphSkillData = skillProgressionData:GetSkillData()
            local mainTextData = { titleParams = { availablePoints } }
            local dialogData = { morphSkillData = morphSkillData }

            GAMEPAD_TOOLTIPS:ClearTooltip(GAMEPAD_LEFT_TOOLTIP)
            ZO_Dialogs_ShowGamepadDialog("GAMEPAD_SKILLS_MORPH_CONFIRMATION", dialogData, mainTextData)
        end
    end

    local function DecreaseSkillKeybindEnabled()
        local skillEntry = self.lineFilterList:GetTargetData()
        if skillEntry then
            local skillData = skillEntry.skillData
            local skillPointAllocator = skillData:GetPointAllocator()
            local actionType = skillPointAllocator:GetDecreaseSkillAction()
            if actionType ~= ZO_SKILL_POINT_ACTION.NONE then
                return true
            elseif SKILLS_AND_ACTION_BAR_MANAGER:GetSkillPointAllocationMode() == SKILL_POINT_ALLOCATION_MODE_MORPHS_ONLY and not skillData:IsCraftedAbility() and skillData:IsActive() then
                if skillPointAllocator:IsPurchased() and skillPointAllocator:GetMorphSlot() == MORPH_SLOT_BASE and not skillPointAllocator:CanSell() then
                    return false, GetString(SI_SKILL_RESPEC_MORPHS_ONLY_CANNOT_SELL_BASE_ABILITY)
                end
            end
        end
        return false
    end

    local function DecreaseSkillKeybindCallback()
        local skillEntry = self.lineFilterList:GetTargetData()
        local skillData = skillEntry.skillData
        local skillPointAllocator = skillData:GetPointAllocator()

        local actionType = skillPointAllocator:GetDecreaseSkillAction()

        if actionType == ZO_SKILL_POINT_ACTION.SELL then
            skillPointAllocator:Sell()
        elseif actionType == ZO_SKILL_POINT_ACTION.DECREASE_RANK then
            skillPointAllocator:DecreaseRank()
        elseif actionType == ZO_SKILL_POINT_ACTION.UNMORPH then
            skillPointAllocator:Unmorph()
        end
    end

    --Decrease Skill Bind
    table.insert(self.lineFilterKeybindStripDescriptor,
    {
        name = function()
            local skillEntry = self.lineFilterList:GetTargetData()
            local action = skillEntry.skillData:GetPointAllocator():GetDecreaseSkillAction()
            return zo_iconFormat(ZO_Skills_GetGamepadSkillPointActionIcon(action), "100%", "100%")
        end,

        narrationOverrideName = function()
            local skillEntry = self.lineFilterList:GetTargetData()
            local action = skillEntry.skillData:GetPointAllocator():GetDecreaseSkillAction()
            return ZO_Skills_GetGamepadSkillPointActionIconNarrationText(action)
        end,

        keybind = "UI_SHORTCUT_LEFT_SHOULDER",

        visible = function()
            if IsInSkillPointAllocationMode() and SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeBatchSave() then
                return DecreaseSkillKeybindEnabled()
            else
                return false
            end
        end,

        callback = DecreaseSkillKeybindCallback,
    })

    --Increase Skill Only Bind
    table.insert(self.lineFilterKeybindStripDescriptor,
    {
        name = function()
            local skillEntry = self.lineFilterList:GetTargetData()
            local action = skillEntry.skillData:GetPointAllocator():GetIncreaseSkillAction()
            return zo_iconFormat(ZO_Skills_GetGamepadSkillPointActionIcon(action), "100%", "100%")
        end,

        narrationOverrideName = function()
            local skillEntry = self.lineFilterList:GetTargetData()
            local action = skillEntry.skillData:GetPointAllocator():GetIncreaseSkillAction()
            return ZO_Skills_GetGamepadSkillPointActionIconNarrationText(action)
        end,

        keybind = "UI_SHORTCUT_RIGHT_SHOULDER",

        visible = function()
            if IsInSkillPointAllocationMode() and SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeBatchSave() then
                return IncreaseSkillKeybindVisible()
            else
                return false
            end
        end,

        callback = IncreaseSkillKeybindCallback,
    })

    --Single Ability Assign/Assignable Action Bar/Increase Skill Bind
    table.insert(self.lineFilterKeybindStripDescriptor,
    {
        name = function()
            if self.mode == ZO_GAMEPAD_SKILLS_SINGLE_ABILITY_ASSIGN_MODE then
                return GetString(SI_GAMEPAD_SKILLS_ASSIGN)
            elseif self.assignableActionBar:IsActive() then
                return GetString(SI_GAMEPAD_SKILLS_BUILD_PLANNER)
            else
                return IncreaseSkillKeybindName()
            end
        end,

        keybind = "UI_SHORTCUT_PRIMARY",

        visible = function()
            if IsInSkillPointAllocationMode() then
                return not SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeBatchSave() and IncreaseSkillKeybindVisible()
            else
                return true
            end
        end,

        enabled = function()
            local canEdit, lockText = IsActionBarEditable()
            if not canEdit then
                return false, lockText
            end
            if self.assignableActionBar:IsActive() then
                local slotIndex = self.assignableActionBar:GetSelectedSlotIndex()
                local result = ACTION_BAR_ASSIGNMENT_MANAGER:GetCurrentHotbar():GetExpectedSlotEditResult(slotIndex)
                if result ~= HOT_BAR_RESULT_SUCCESS then
                    return false, GetString("SI_HOTBARRESULT", result)
                end
            end
            return true
        end,

        callback = function()
            if self.mode == ZO_GAMEPAD_SKILLS_SINGLE_ABILITY_ASSIGN_MODE then
                self:DeactivateCurrentList()
                self.assignableActionBar:AssignTargetSkill()
                self.actionBarAnimation:PlayBackward()
                GAMEPAD_TOOLTIPS:ClearTooltip(GAMEPAD_RIGHT_TOOLTIP)
                PlaySound(SOUNDS.GAMEPAD_MENU_BACK)
                --Don't set mode back to ZO_GAMEPAD_SKILLS_ABILITY_LIST_BROWSE_MODE til OnAbilityFinalizedCallback says everything worked
            elseif self.assignableActionBar:IsActive() then
                self.quickMenu:Show()
                PlaySound(SOUNDS.GAMEPAD_MENU_FORWARD)
            else
                IncreaseSkillKeybindCallback()
            end
        end,
    })

    ZO_Gamepad_AddBackNavigationKeybindDescriptors(self.lineFilterKeybindStripDescriptor, GAME_NAVIGATION_TYPE_BUTTON, function()
        if self.mode == ZO_GAMEPAD_SKILLS_SINGLE_ABILITY_ASSIGN_MODE then
            self.actionBarAnimation:PlayBackward()
            self.assignableActionBar:Deactivate()
            GAMEPAD_TOOLTIPS:ClearTooltip(GAMEPAD_RIGHT_TOOLTIP)
            self:SetMode(ZO_GAMEPAD_SKILLS_ABILITY_LIST_BROWSE_MODE)
            PlaySound(SOUNDS.GAMEPAD_MENU_BACK)
        else
            local previousSceneName = SCENE_MANAGER:GetPreviousSceneName()
            if previousSceneName == "gamepad_skills_scribing_library_root" then
                ZO_GAMEPAD_SCRIBING_CRAFTED_ABILITY_SKILLS:SetToAutoActivate()
            end
            SCENE_MANAGER:HideCurrentScene()
        end
    end)
    ZO_Gamepad_AddListTriggerKeybindDescriptors(self.lineFilterKeybindStripDescriptor, self.lineFilterList, IsEntryHeader)
end

function ZO_GamepadSkills:AddWeaponSwapDescriptor(descriptor)
    descriptor[#descriptor + 1] =
    {
        alignment = KEYBIND_STRIP_ALIGN_LEFT,
        name = GetString(SI_BINDING_NAME_SPECIAL_MOVE_WEAPON_SWAP),
        keybind = "UI_SHORTCUT_TERTIARY",
        visible = function()
            return ACTION_BAR_ASSIGNMENT_MANAGER:ShouldShowHotbarSwap()
        end,
        enabled = function()
            return ACTION_BAR_ASSIGNMENT_MANAGER:CanCycleHotbars()
        end,
        callback = function()
            ACTION_BAR_ASSIGNMENT_MANAGER:CycleCurrentHotbar()
        end,
    }
end

function ZO_GamepadSkills:AddSkillsAdvisorDescriptor(descriptor)
    descriptor[#descriptor + 1] =
    {
        alignment = KEYBIND_STRIP_ALIGN_RIGHT,
        name = GetString(SI_SKILLS_ADVISOR_GAMEPAD_OPEN_ADVISOR_SETTINGS),
        keybind = "UI_SHORTCUT_LEFT_STICK",
        sound = SOUNDS.SKILLS_ADVISOR_SELECT,
        visible =   function()
                        local targetData = self.categoryList:GetTargetData()
                        return targetData and targetData.isSkillsAdvisor
                    end,
        callback = function()
                        SCENE_MANAGER:Push("gamepad_skills_advisor_build_selection_root")
                    end,
    }
end

function ZO_GamepadSkills:InitializeAssignableActionBar()
    local actionBarControl = self.header:GetNamedChild("AssignableActionBar")
    local actionBarBg = self.control:GetNamedChild("Bg")
    actionBarBg:SetAnchor(TOPLEFT, actionBarControl, TOPLEFT, 0, -12)
    actionBarBg:SetAnchor(BOTTOMRIGHT, actionBarControl, BOTTOMRIGHT, 0, 30)

    self.assignableActionBar = ZO_AssignableActionBar:New(actionBarControl)
    self:SetupHeaderFocus(self.assignableActionBar)

    self.actionBarAnimation = ANIMATION_MANAGER:CreateTimelineFromVirtual("ZO_GamepadSkillsActionBarFocusAnimation")

    self.actionBarAnimation:GetAnimation(1):SetAnimatedControl(actionBarControl)
    self.actionBarAnimation:GetAnimation(2):SetAnimatedControl(actionBarBg)

    self.actionBarAnimation:PlayInstantlyToStart()
    --Narration info for the action bar
    local narrationInfo =
    {
        canNarrate = function()
            return GAMEPAD_SKILLS_SCENE_GROUP:IsShowing() and self.assignableActionBar:IsActive()
        end,
        selectedNarrationFunction = function()
            return self.assignableActionBar:GetNarrationText()
        end,
    }
    SCREEN_NARRATION_MANAGER:RegisterCustomObject("skillAssignableActionBar", narrationInfo)
end

function ZO_GamepadSkills:ActivateAssignableActionBarFromList()
    --Only activate the action bar if it is showing. You could start moving toward it being selected and then close the window
    --before selection happens, then the selection happens when the window is closed an the bar activates (ESO-490544).
    if not self.assignableActionBar:GetControl():IsHidden() then
        self.assignableActionBar:SelectMostRecentlySelectedButton()
        self.assignableActionBar:Activate()
    end
end

function ZO_GamepadSkills:OnEnterHeader()
    self:ActivateAssignableActionBarFromList()
    self:UpdateKeybinds()
    PlaySound(SOUNDS.GAMEPAD_MENU_UP)
end

function ZO_GamepadSkills:OnLeaveHeader()
    self.assignableActionBar:Deactivate()
    self:UpdateKeybinds()
    PlaySound(SOUNDS.GAMEPAD_MENU_DOWN)
end

function ZO_GamepadSkills:UpdateKeybinds()
    KEYBIND_STRIP:UpdateKeybindButtonGroup(self.categoryKeybindStripDescriptor)
    KEYBIND_STRIP:UpdateKeybindButtonGroup(self.lineFilterKeybindStripDescriptor)
    self.quickMenu:RefreshKeybinds()
end

function ZO_GamepadSkills:InitializeCategoryList()
    self.skillInfo = self.control:GetNamedChild("SkillInfo")

    local function SkillLineEntryTemplateSetup(control, data, selected, reselectingDuringRebuild, enabled, activated)
        local skillLineData = data.skillLineData
        if SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeBatchSave() then
            data:SetText(skillLineData:GetFormattedNameWithNumPointsAllocated())
        else
            data:SetText(skillLineData:GetFormattedName())
        end
        ZO_SharedGamepadEntry_OnSetup(control, data, selected, reselectingDuringRebuild, enabled, activated)
        ZO_GamepadSkillLineEntryTemplate_Setup(control, data, selected)
        if selected then
            GAMEPAD_SKILLS_ROOT_SCENE:AddFragment(self.skillLineXPBarFragment)
            ZO_GamepadSkillLineXpBar_Setup(data.skillLineData, self.skillInfo.xpBar, self.skillInfo.name, true)
        end
    end

    local function MenuEntryTemplateSetup(control, data, selected, reselectingDuringRebuild, enabled, activated)
        if data.isSubclassing then
            data.enabled = SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeAllowAccessToSubclassing()
        end
        ZO_SharedGamepadEntry_OnSetup(control, data, selected, reselectingDuringRebuild, enabled, activated)
        if selected then
            GAMEPAD_SKILLS_ROOT_SCENE:RemoveFragment(self.skillLineXPBarFragment)
        end
    end

    local function SetupCategoryList(list)
        list:SetAdditionalBottomSelectedItemOffsets(0, 20)
        list:SetHandleDynamicViewProperties(true)

        list:AddDataTemplate("ZO_GamepadSkillLineEntryTemplate", SkillLineEntryTemplateSetup, ZO_GamepadMenuEntryTemplateParametricListFunction, AreSkillLineEntriesEqual)
        list:AddDataTemplateWithHeader("ZO_GamepadSkillLineEntryTemplate", SkillLineEntryTemplateSetup, ZO_GamepadMenuEntryTemplateParametricListFunction, AreSkillLineEntriesEqual, "ZO_GamepadMenuEntryHeaderTemplate")
        list:AddDataTemplate("ZO_GamepadMenuEntryTemplate", MenuEntryTemplateSetup, ZO_GamepadMenuEntryTemplateParametricListFunction)
    end

    self.categoryList = self:AddList("Category", SetupCategoryList)

    self.categoryList:SetOnSelectedDataChangedCallback(
    function(_, selectedData)
        self:OnSelectedSkillLineChanged(selectedData)
    end)

    self.initialCategoryListIndex = 2
end

function ZO_GamepadSkills:InitializeLineFilterList()
    local function MenuAbilityEntryTemplateSetup(control, skillEntry, selected, reselectingDuringRebuild, enabled, activated)
        ZO_GamepadSkillEntryTemplate_SetEntryInfoFromAllocator(skillEntry)
        ZO_SharedGamepadEntry_OnSetup(control, skillEntry, selected, reselectingDuringRebuild, enabled, activated)
        ZO_GamepadSkillEntryTemplate_Setup(control, skillEntry, selected, activated, ZO_SKILL_ABILITY_DISPLAY_INTERACTIVE)
    end

    local function IsSkillEqual(leftSkillEntry, rightSkillEntry)
        return leftSkillEntry.skillData == rightSkillEntry.skillData
    end

    local function SetupLineFilterList(list)
        list:SetHandleDynamicViewProperties(true)
        list:AddDataTemplate("ZO_GamepadAbilityEntryTemplate", MenuAbilityEntryTemplateSetup, ZO_GamepadMenuEntryTemplateParametricListFunction, IsSkillEqual)
        list:AddDataTemplateWithHeader("ZO_GamepadAbilityEntryTemplate", MenuAbilityEntryTemplateSetup, ZO_GamepadMenuEntryTemplateParametricListFunction, IsSkillEqual, "ZO_GamepadMenuEntryHeaderTemplate")
    end
    self.lineFilterList = self:AddList("LineFilter", SetupLineFilterList)
    self.lineFilterList:SetOnSelectedDataChangedCallback(function(list, selectedData)
        self:OnSelectedSkillChanged(selectedData)
    end)

    local lineFilterListControl = self.lineFilterList:GetControl()
    local lineFilterListContainer = lineFilterListControl:GetParent()
    local lineFilterRespecBindingsControl = WINDOW_MANAGER:CreateControlFromVirtual("$(parent)RespecBindings", lineFilterListContainer, "ZO_GamepadSkills_RespecBindings")
    lineFilterRespecBindingsControl:SetAnchor(BOTTOMLEFT, lineFilterListControl, TOPLEFT, 0, 0)
    lineFilterRespecBindingsControl:SetAnchor(BOTTOMRIGHT, lineFilterListControl, TOPRIGHT, 0, 0)
    self.lineFilterRespecBindingsControl = lineFilterRespecBindingsControl
end

function ZO_GamepadSkills:InitializeQuickMenu()
    local quickMenuControl = self.control:GetNamedChild("QuickMenu")
    self.quickMenu = ZO_GamepadAssignableActionBar_PlayerQuickMenu:New(quickMenuControl, self.assignableActionBar)

    local X_OFFSET = 30
    local Y_OFFSET = 15
    local quickMenuList = self.quickMenu:GetListControl()
    quickMenuList:ClearAnchors()
    quickMenuList:SetAnchor(TOPLEFT, self.header, BOTTOMLEFT, -X_OFFSET, Y_OFFSET)
    quickMenuList:SetAnchor(BOTTOMRIGHT, self.control, BOTTOMRIGHT, X_OFFSET, 0)
end

function ZO_GamepadSkills:InitializeRefreshGroups()
    --Category List
    local categoryListRefreshGroup = ZO_OrderedRefreshGroup:New(ZO_ORDERED_REFRESH_GROUP_AUTO_CLEAN_PER_FRAME)
    categoryListRefreshGroup:AddDirtyState("List", function()
        self:RefreshCategoryList()
    end)
    categoryListRefreshGroup:AddDirtyState("Visible", function()
        self.categoryList:RefreshVisible()
        KEYBIND_STRIP:UpdateKeybindButtonGroup(self.categoryKeybindStripDescriptor)
    end)
    categoryListRefreshGroup:SetActive(function()
        return GAMEPAD_SKILLS_ROOT_SCENE:IsShowing()
    end)
    categoryListRefreshGroup:MarkDirty("List")
    self.categoryListRefreshGroup = categoryListRefreshGroup

    --Line Filter List
    local lineFilterListRefreshGroup = ZO_OrderedRefreshGroup:New(ZO_ORDERED_REFRESH_GROUP_AUTO_CLEAN_PER_FRAME)
    lineFilterListRefreshGroup:AddDirtyState("List", function()
        self:RefreshLineFilterList(false)
    end)
    lineFilterListRefreshGroup:AddDirtyState("Visible", function()
        self.lineFilterList:RefreshVisible()
        KEYBIND_STRIP:UpdateKeybindButtonGroup(self.lineFilterKeybindStripDescriptor)
    end)
    lineFilterListRefreshGroup:SetActive(function()
        return GAMEPAD_SKILLS_LINE_FILTER_SCENE:IsShowing()
    end)
    self.lineFilterListRefreshGroup = lineFilterListRefreshGroup

    --Build Planner List
    local quickMenuRefreshGroup = ZO_OrderedRefreshGroup:New(ZO_ORDERED_REFRESH_GROUP_AUTO_CLEAN_PER_FRAME)
    quickMenuRefreshGroup:AddDirtyState("List", function()
        self.quickMenu:RefreshList()
    end)
    quickMenuRefreshGroup:AddDirtyState("Visible", function()
        self.quickMenu:RefreshVisible()
    end)
    quickMenuRefreshGroup:SetActive(function()
        return self.quickMenu:IsShowing()
    end)
    self.quickMenuRefreshGroup = quickMenuRefreshGroup

    --Selected Tooltip
    local selectedTooltipRefreshGroup = ZO_OrderedRefreshGroup:New(ZO_ORDERED_REFRESH_GROUP_AUTO_CLEAN_PER_FRAME)
    selectedTooltipRefreshGroup:AddDirtyState("Full", function()
        self:RefreshSelectedTooltip()
    end)
    self.selectedTooltipRefreshGroup = selectedTooltipRefreshGroup
end

function ZO_GamepadSkills:InitializeEvents()
    --Skill and Point Updates

    --Systems that could need a refresh:
    --categoryList (RefreshCategoryList)
    --lineFilterList (RefreshLineFilterList(false))
    --buildPlannerList (RefreshQuickMenuList)
    --Selected Tooltip (RefreshSelectedTooltip)
    --Points Display (RefreshPointsDisplay)

    local function FullRebuild()
        self.categoryListRefreshGroup:MarkDirty("List")
        self.lineFilterListRefreshGroup:MarkDirty("List")
        self.quickMenuRefreshGroup:MarkDirty("List")
        self:RefreshPointsDisplay()
        self.selectedTooltipRefreshGroup:MarkDirty("Full")
    end

    local function OnSkillLineUpdated(skillLineData)
        --For the skill line rank on each entry and the skill line XP bar on the selected entry
        self.categoryListRefreshGroup:MarkDirty("Visible")
        --For the locked/unlocked display on a skill
        self.lineFilterListRefreshGroup:MarkDirty("Visible", skillLineData)
        --For the skill line rank in the header of each section
        self.quickMenuRefreshGroup:MarkDirty("Visible")
        self.selectedTooltipRefreshGroup:MarkDirty("Full")
    end

    local function OnSkillLineAdded(skillLineData)
        --To add the new skill line to the list
        self.categoryListRefreshGroup:MarkDirty("List")
        --To add the skills in the new skill line to the combined planner list
        self.quickMenuRefreshGroup:MarkDirty("List")
        self.selectedTooltipRefreshGroup:MarkDirty("Full")
    end

    local function OnSkillProgressionUpdated(skillData)
        local skillLineData = skillData:GetSkillLineData()
        --For the name of an active skill entry
        self.lineFilterListRefreshGroup:MarkDirty("Visible", skillLineData)
        --For the name of an active skill entry
        self.quickMenuRefreshGroup:MarkDirty("Visible")
        self.selectedTooltipRefreshGroup:MarkDirty("Full")
    end

    local function OnPointAllocatorPurchasedChanged(skillPointAllocator)
        local skillLineData = skillPointAllocator:GetSkillData():GetSkillLineData()
        --For the number of allocated points after the skill line name
        self.categoryListRefreshGroup:MarkDirty("Visible")
        --For the purchased state and name of skills
        self.lineFilterListRefreshGroup:MarkDirty("Visible", skillLineData)
        --To add or remove skills from the combined planner list
        self.quickMenuRefreshGroup:MarkDirty("List")
        self.selectedTooltipRefreshGroup:MarkDirty("Full")
    end

    local function OnPointAllocatorProgressionKeyChanged(skillPointAllocator)
        local skillLineData = skillPointAllocator:GetSkillData():GetSkillLineData()
        --For the number of allocated points after the skill line name
        self.categoryListRefreshGroup:MarkDirty("Visible")
        --For the name of skills
        self.lineFilterListRefreshGroup:MarkDirty("Visible", skillLineData)
        self.selectedTooltipRefreshGroup:MarkDirty("Full")
    end

    local function OnSkillsCleared(skillLineData)
        --Skill Line Data is only defined if a single skill line was cleared
        --For the number of allocated points after the skill line name
        self.categoryListRefreshGroup:MarkDirty("Visible")
        --For the purchased state and name of skills
        self.lineFilterListRefreshGroup:MarkDirty("Visible", skillLineData)
        --To add or remove skills from the combined planner list
        self.quickMenuRefreshGroup:MarkDirty("List")
        self.selectedTooltipRefreshGroup:MarkDirty("Full")
    end

    local function OnSkillPointsChanged()
        --For the number of allocated points after the skill line name
        self.categoryListRefreshGroup:MarkDirty("Visible")
        --For the point spending action icons
        self.lineFilterListRefreshGroup:MarkDirty("Visible")
        self.selectedTooltipRefreshGroup:MarkDirty("Full")
        self:RefreshPointsDisplay()
    end

    local function OnSkillPointAllocationModeChanged()
        --For the number of allocated points after the skill line name
        self.categoryListRefreshGroup:MarkDirty("Visible")
        --For the purchased state and name of skills
        self.lineFilterListRefreshGroup:MarkDirty("Visible")
        --To add or remove skills from the combined planner list
        self.quickMenuRefreshGroup:MarkDirty("List")
        --For the number of used skill points changing
        self:RefreshPointsDisplay()
        --To show or hide the respec mode bindings
        self:RefreshRespecModeBindingsDisplay()
        --To refresh showing the commit/clear all binds
        KEYBIND_STRIP:UpdateKeybindButtonGroup(self.categoryKeybindStripDescriptor)
        self.selectedTooltipRefreshGroup:MarkDirty("Full")
    end

    local function OnCurrentHotbarUpdated()
        --To refresh tooltip if an action slot is selected
        self.selectedTooltipRefreshGroup:MarkDirty("Full")
        --Re-narrate when the current hotbar changes (if active)
        if self.assignableActionBar:IsActive() then
            SCREEN_NARRATION_MANAGER:QueueCustomEntry("skillAssignableActionBar")
        end
    end

    local function OnCollectionUpdated(collectionUpdateType, collectiblesByNewUnlockState)
        local scribingCollectibleData = SCRIBING_DATA_MANAGER:GetScribingUnlockCollectibleData()
        for _, unlockStateTable in pairs(collectiblesByNewUnlockState) do
            for _, collectibleData in ipairs(unlockStateTable) do
                if collectibleData == scribingCollectibleData then
                    FullRebuild()
                end
            end
        end
    end

    local function OnQuestsChanged()
        self.categoryListRefreshGroup:MarkDirty("List")
        self.selectedTooltipRefreshGroup:MarkDirty("Full")
    end

    self.control:RegisterForEvent(EVENT_PLAYER_ACTIVATED, FullRebuild)
    self.control:RegisterForEvent(EVENT_QUEST_ADDED, OnQuestsChanged)
    self.control:RegisterForEvent(EVENT_QUEST_REMOVED, OnQuestsChanged)
    SKILLS_DATA_MANAGER:RegisterCallback("FullSystemUpdated", FullRebuild)
    SKILLS_DATA_MANAGER:RegisterCallback("SkillLineUpdated", OnSkillLineUpdated)
    SKILLS_DATA_MANAGER:RegisterCallback("SkillLineAdded", OnSkillLineAdded)
    SKILLS_DATA_MANAGER:RegisterCallback("SkillProgressionUpdated", OnSkillProgressionUpdated)
    SKILL_POINT_ALLOCATION_MANAGER:RegisterCallback("PurchasedChanged", OnPointAllocatorPurchasedChanged)
    SKILL_POINT_ALLOCATION_MANAGER:RegisterCallback("SkillProgressionKeyChanged", OnPointAllocatorProgressionKeyChanged)
    SKILL_POINT_ALLOCATION_MANAGER:RegisterCallback("OnSkillsCleared", OnSkillsCleared)
    SKILL_POINT_ALLOCATION_MANAGER:RegisterCallback("SkillPointsChanged", OnSkillPointsChanged)
    SKILLS_AND_ACTION_BAR_MANAGER:RegisterCallback("SkillPointAllocationModeChanged", OnSkillPointAllocationModeChanged)
    SKILLS_AND_ACTION_BAR_MANAGER:RegisterCallback("RespecStateReset", FullRebuild)
    ACTION_BAR_ASSIGNMENT_MANAGER:RegisterCallback("CurrentHotbarUpdated", OnCurrentHotbarUpdated)
    ZO_COLLECTIBLE_DATA_MANAGER:RegisterCallback("OnCollectionUpdated", OnCollectionUpdated)

    --Weapon Swap
    local function OnHotbarSwapVisibleStateChanged()
        if not self.control:IsHidden() then
            KEYBIND_STRIP:UpdateKeybindButtonGroup(self.categoryKeybindStripDescriptor)
            KEYBIND_STRIP:UpdateKeybindButtonGroup(self.lineFilterKeybindStripDescriptor)
        end
    end
    ACTION_BAR_ASSIGNMENT_MANAGER:RegisterCallback("HotbarSwapVisibleStateChanged", OnHotbarSwapVisibleStateChanged)

    self.control:RegisterForEvent(EVENT_PLAYER_DEACTIVATED, function() self:OnPlayerDeactivated() end)

    local function OnPurchaseLockStateChanged()
        -- Refresh state of purchase/morph/assign keybinds
        KEYBIND_STRIP:UpdateKeybindButtonGroup(self.lineFilterKeybindStripDescriptor)
    end
    self.control:RegisterForEvent(EVENT_ACTION_BAR_LOCKED_REASON_CHANGED, OnPurchaseLockStateChanged)

    --action bar
    local function OnAbilityFinalized()
        if self.mode == ZO_GAMEPAD_SKILLS_SINGLE_ABILITY_ASSIGN_MODE then
            self.assignableActionBar:ClearTargetSkill()
            -- make sur eto deactivate the action bar before changing mode
            -- so the list can be fully activated when we switch to it
            self.assignableActionBar:Deactivate()
            self:SetMode(ZO_GAMEPAD_SKILLS_ABILITY_LIST_BROWSE_MODE)
        end
    end
    self.assignableActionBar:RegisterCallback("AbilityFinalized", OnAbilityFinalized)

    local function OnSelectedActionBarButtonChanged(selectedSlotIndex, didSlotTypeChange)
        self.selectedTooltipRefreshGroup:MarkDirty("Full")
        --Re-narrate when the selected action bar slot changes
        SCREEN_NARRATION_MANAGER:QueueCustomEntry("skillAssignableActionBar")
    end
    self.assignableActionBar:RegisterCallback("SelectedButtonChanged", OnSelectedActionBarButtonChanged)
end

function ZO_GamepadSkills:TryClearSkillUpdatedStatus()
    if self.clearAbilityStatusOnSelectionChanged then
        self.clearAbilityStatusOnSelectionChanged = false
        self.clearSkillUpdatedStatusSkillData:ClearUpdate()
        self.lineFilterList:RefreshVisible()
    end
end

function ZO_GamepadSkills:TryClearSkillLineNewStatus()
    if self.clearSkillLineStatusOnSelectionChanged then
        self.clearSkillLineStatusOnSelectionChanged = false
        self.clearSkillLineNewStatusSkillLineData:ClearNew()
        self.categoryList:RefreshVisible()
    end
end

function ZO_GamepadSkills:TrySetClearUpdatedAbilityFlag(callId)
    if self.clearSkillUpdatedStatusCallId == callId then
        self.clearAbilityStatusOnSelectionChanged = true
    end
end

function ZO_GamepadSkills:TrySetClearNewSkillLineFlag(callId)
    if self.clearSkillLineNewStatusCallId == callId then
        self.clearSkillLineStatusOnSelectionChanged = true
    end
end

do
    local function SkillLineGamepadEntryProgressBarNarrationText(entryData)
        local barMax = entryData.nextRankXP
        if barMax then
            local barMin = entryData.lastRankXP or 0
            local barValue = entryData.currentXP or 0
            return ZO_GetProgressBarNarrationText(barMin, barMax, barValue)
        end
    end
    local function IsSkillLineAvailableOrAdvised(skillLineData)
        return skillLineData:IsAvailable() or skillLineData:IsAdvised()
    end
    local SKILL_LINE_FILTERS = { IsSkillLineAvailableOrAdvised }
    function ZO_GamepadSkills:RefreshCategoryList()
        self.categoryList:Clear()

        local skillLineNarrationText = function(entryData, entryControl)
            local narrations = {}
            local skillLineData = entryData.skillLineData
            ZO_AppendNarration(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(GetString(SI_SKILLS_GAMEPAD_RANK_NARRATION)))
            ZO_AppendNarration(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(skillLineData.currentRank))
            ZO_AppendNarration(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(entryData.text))
            ZO_AppendNarration(narrations, ZO_GetSharedGamepadEntrySubLabelNarrationText(entryData, entryControl))
            ZO_AppendNarration(narrations, SkillLineGamepadEntryProgressBarNarrationText(skillLineData))
            ZO_AppendNarration(narrations, ZO_GetSharedGamepadEntryStackCountNarrationText(entryData, entryControl))
            ZO_AppendNarration(narrations, ZO_GetSharedGamepadEntryStatusIndicatorNarrationText(entryData, entryControl))
            return narrations
        end

        local scribeLibraryEntryData = ZO_GamepadEntryData:New(zo_strformat(SI_SKILLS_ENTRY_NAME_FORMAT, GetString(SI_SCRIBING_TITLE)))
        scribeLibraryEntryData.isScribeLibrary = true
        self.categoryList:AddEntry("ZO_GamepadMenuEntryTemplate", scribeLibraryEntryData)

        local skillsAdvisorEntryData = ZO_GamepadEntryData:New(zo_strformat(SI_SKILLS_ENTRY_NAME_FORMAT, GetString(SI_SKILLS_ADVISOR_TITLE)))
        skillsAdvisorEntryData.isSkillsAdvisor = true
        self.categoryList:AddEntry("ZO_GamepadMenuEntryTemplate", skillsAdvisorEntryData)

        for _, skillTypeData in SKILLS_DATA_MANAGER:SkillTypeIterator() do
            local isHeader = true
            for _, skillLineData in skillTypeData:SkillLineIterator(SKILL_LINE_FILTERS) do
                local function IsSkillLineNew()
                    return skillLineData:IsSkillLineOrAbilitiesNew()
                end

                local data = ZO_GamepadEntryData:New()
                data:SetNew(IsSkillLineNew)
                data.skillLineData = skillLineData
                data.narrationText = skillLineNarrationText

                if isHeader then
                    data:SetHeader(skillTypeData:GetName())
                    self.categoryList:AddEntry("ZO_GamepadSkillLineEntryTemplateWithHeader", data)
                else
                    self.categoryList:AddEntry("ZO_GamepadSkillLineEntryTemplate", data)
                end

                isHeader = false
            end
            if skillTypeData.skillType == SKILL_TYPE_CLASS then
                local data = ZO_GamepadEntryData:New(zo_strformat(SI_SKILLS_ENTRY_NAME_FORMAT, GetString(SI_SKILLS_SUBCLASSING_ENTRY_NAME)))
                data.isSubclassing = true
                self.categoryList:AddEntry("ZO_GamepadMenuEntryTemplate", data)
            end
        end
        self.categoryList:Commit()

        local selectSkillData = ZO_GAMEPAD_SKILLS_ADVISOR_SUGGESTIONS_WINDOW:GetSelectSkillData()
        if selectSkillData then
            local categoryEntryIndex = self:GetSkillLineEntryIndex(selectSkillData:GetSkillLineData())
            if categoryEntryIndex then
                self.categoryList:SetSelectedIndexWithoutAnimation(categoryEntryIndex)
            end
            ZO_GAMEPAD_SKILLS_ADVISOR_SUGGESTIONS_WINDOW:SetSelectSkillData(nil)
        elseif self.initialCategoryListIndex then
            self.categoryList:SetSelectedIndexWithoutAnimation(self.initialCategoryListIndex)
            self.initialCategoryListIndex = nil
        end
    end
end

do
    local g_ShownHeaderTexts = {}

    function ZO_GamepadSkills:RefreshLineFilterList()
        local list = self.lineFilterList
        list:Clear()
        ZO_ClearTable(g_ShownHeaderTexts)

        local skillLineEntry = self.categoryList:GetTargetData()
        if skillLineEntry.isSkillsAdvisor or skillLineEntry.isScribeLibrary or skillLineEntry.isSubclassing then
            return
        end

        local function IsSkillVisible(skillData)
            return not skillData:IsHidden()
        end

        local skillLineData = skillLineEntry.skillLineData
        for _, skillData in skillLineData:SkillIterator({ IsSkillVisible }) do
            local skillEntry = ZO_GamepadEntryData:New()

            skillEntry:SetFontScaleOnSelection(false)
            skillEntry.skillData = skillData

            local headerText = skillData:GetHeaderText()
            if not g_ShownHeaderTexts[headerText] then
                skillEntry:SetHeader(headerText)
                list:AddEntry("ZO_GamepadAbilityEntryTemplateWithHeader", skillEntry)
                g_ShownHeaderTexts[headerText] = true
            else
                list:AddEntry("ZO_GamepadAbilityEntryTemplate", skillEntry)
            end
        end

        list:Commit()
    end
end

function ZO_GamepadSkills:RefreshPointsDisplay()
    local availablePoints = SKILL_POINT_ALLOCATION_MANAGER:GetAvailableSkillPoints()
    local skyShards = GetNumSkyShards()

    self.headerData.data1Text = availablePoints
    self.headerData.data2Text = zo_strformat(SI_GAMEPAD_SKILLS_SKY_SHARDS_FOUND, skyShards, NUM_PARTIAL_SKILL_POINTS_FOR_FULL)

    ZO_GamepadGenericHeader_RefreshData(self.header, self.headerData)
end

function ZO_GamepadSkills:RefreshRespecModeBindingsDisplay()
    local showBindings = SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeBatchSave()
    self.lineFilterRespecBindingsControl:SetHidden(not showBindings)
    local listTopOffsetY = showBindings and 50 or 0
    local lineFilterListControl = self.lineFilterList:GetControl()
    local lineFilterListContainer = lineFilterListControl:GetParent()
    local skillsContainer = lineFilterListContainer:GetParent()
    local headerContainer = skillsContainer:GetNamedChild("HeaderContainer")
    lineFilterListControl:ClearAnchors()
    lineFilterListControl:SetAnchor(TOPLEFT, headerContainer, BOTTOMLEFT, 0, listTopOffsetY)
    lineFilterListControl:SetAnchor(BOTTOMRIGHT, nil, BOTTOMRIGHT, 0, 0)
end

do
    local g_purchaseAndUpgradeHeaderData =
    {
        data1 =
        {
            header = GetString(SI_GAMEPAD_SKILLS_AVAILABLE_POINTS),
        },
    }
    local function SetupFunction(control)
        local availablePoints = GetAvailableSkillPoints()
   
        g_purchaseAndUpgradeHeaderData.data1.value = availablePoints
        control.setupFunc(control, g_purchaseAndUpgradeHeaderData)
    end

    function ZO_GamepadSkills:InitializePurchaseAndUpgradeDialog()
        ZO_Dialogs_RegisterCustomDialog("GAMEPAD_SKILLS_PURCHASE_CONFIRMATION",
        {
            setup = SetupFunction,
            gamepadInfo =
            {
                dialogType = GAMEPAD_DIALOGS.BASIC,
                allowRightStickPassThrough = true,
            },
            title = 
            {
                text = GetString(SI_GAMEPAD_SKILLS_PURCHASE_TITLE),
            },
            mainText = 
            {
                text = GetString(SI_GAMEPAD_SKILLS_PURCHASE_CONFIRM),
            },
            warning = 
            {
                text = function(dialog)
                    if not ZO_SKILLS_ADVISOR_SINGLETON:IsAdvancedModeSelected() and dialog.data.purchaseSkillProgressionData:IsAdvised() then
                        ZO_GenericGamepadDialog_SetDialogWarningColor(dialog, ZO_SKILLS_ADVISOR_ADVISED_COLOR)
                        return GetString(SI_SKILLS_ADVISOR_PURCHASE_ADVISED)
                    end
                    return ""
                end
            },
            buttons =
            {
                [1] =
                {
                    text =      SI_GAMEPAD_SKILLS_PURCHASE,
                    callback =  function(dialog)
                                    local purchaseSkillProgressionData = dialog.data.purchaseSkillProgressionData
                                    local skillData = purchaseSkillProgressionData:GetSkillData()
                                    skillData:GetPointAllocator():Purchase()
                                end,
                },
                [2] =
                {
                    text =      SI_DIALOG_CANCEL,
                },
            },
        })

        ZO_Dialogs_RegisterCustomDialog("GAMEPAD_SKILLS_UPGRADE_CONFIRMATION",
        {
            setup = SetupFunction,
            gamepadInfo =
            {
                dialogType = GAMEPAD_DIALOGS.BASIC,
                allowRightStickPassThrough = true,
            },
            title = 
            {
                text = GetString(SI_GAMEPAD_SKILLS_PURCHASE_TITLE),
            },
            mainText = 
            {
                text = GetString(SI_GAMEPAD_SKILLS_UPGRADE_CONFIRM),
            },
            warning = 
            {
                text = function(dialog)
                    local currentSkillProgressionData = dialog.data.currentSkillProgressionData
                    local upgradeSkillProgressionData = currentSkillProgressionData:GetNextRankData()
                    if not ZO_SKILLS_ADVISOR_SINGLETON:IsAdvancedModeSelected() and upgradeSkillProgressionData:IsAdvised() then
                        ZO_GenericGamepadDialog_SetDialogWarningColor(dialog, ZO_SKILLS_ADVISOR_ADVISED_COLOR)
                        return GetString(SI_SKILLS_ADVISOR_PURCHASE_ADVISED)
                    end
                    return ""
                end
            },
            buttons =
            {
                [1] =
                {
                    text =      SI_GAMEPAD_SKILLS_PURCHASE,
                    callback =  function(dialog)
                                    local currentSkillProgressionData = dialog.data.currentSkillProgressionData
                                    local skillData = currentSkillProgressionData:GetSkillData()
                                    skillData:GetPointAllocator():IncreaseRank()
                                end,
                },
                [2] =
                {
                    text =      SI_DIALOG_CANCEL,
                },
            },
        })
    end

    local parametricDialog = ZO_GenericGamepadDialog_GetControl(GAMEPAD_DIALOGS.PARAMETRIC)

    local function MorphConfirmSetup(control, data, selected, reselectingDuringRebuild, enabled, active)
        local morphSkillData = parametricDialog.data.morphSkillData
        local specificMorphSkillProgressionData = morphSkillData:GetProgressionData(data.morphSlot)

        data:SetText(specificMorphSkillProgressionData:GetFormattedName())
        data:ClearIcons()
        data:AddIcon(specificMorphSkillProgressionData:GetIcon())
        data.skillProgressionData = specificMorphSkillProgressionData
        ZO_SharedGamepadEntry_OnSetup(control, data, selected, reselectingDuringRebuild, enabled, active)
        ZO_GamepadSkillEntryTemplate_Setup(control, data, selected, active, ZO_SKILL_ABILITY_DISPLAY_VIEW)
    end

    local function MorphConfirmCallback(dialog)
        local morphSkillData = dialog.data.morphSkillData
        local morphEntry = dialog.entryList:GetTargetData()
        morphSkillData:GetPointAllocator():Morph(morphEntry.morphSlot)
    end

    local g_morphHeaderData =
    {
        data1 =
        {
            header = GetString(SI_GAMEPAD_SKILLS_AVAILABLE_POINTS),
        },
        data2 =
        {
            header = GetString(SI_GAMEPAD_SKILLS_MORPH_COST_HEADER),
        },
    }

    function ZO_GamepadSkills:InitializeMorphDialog()
        ZO_Dialogs_RegisterCustomDialog("GAMEPAD_SKILLS_MORPH_CONFIRMATION",
        {
            gamepadInfo =
            {
                dialogType = GAMEPAD_DIALOGS.PARAMETRIC,
                allowRightStickPassThrough = true,
            },

            setup = function(dialog)
                local availablePoints = GetAvailableSkillPoints()
                local morphSkillData = dialog.data.morphSkillData

                g_morphHeaderData.data1.value = availablePoints
                g_morphHeaderData.data2.value = morphSkillData:GetSkillPointCostMultiplier()
                ZO_GenericGamepadDialog_ShowTooltip(dialog)
                dialog:setupFunc(nil, g_morphHeaderData)

                --Select the currently chosen morph if a morph is chosen, or pick the first one
                local selectedMorphSkillProgressionData = morphSkillData:GetPointAllocatorProgressionData()
                if selectedMorphSkillProgressionData:GetMorphSlot() == MORPH_SLOT_MORPH_2 then
                    dialog.entryList:SetSelectedIndexWithoutAnimation(2)
                else
                    dialog.entryList:SetSelectedIndexWithoutAnimation(1)
                end
            end,

            title =
            {
                text = GetString(SI_GAMEPAD_SKILLS_MORPH_TITLE),
            },

            parametricList =
            {
                -- Morph 1
                {
                    template = "ZO_GamepadSimpleAbilityEntryTemplate",
                    templateData =
                    {
                        setup = MorphConfirmSetup,
                        morphSlot = MORPH_SLOT_MORPH_1,
                        narrationTooltip = GAMEPAD_LEFT_DIALOG_TOOLTIP,
                    },
                },
                -- Morph 2
                {
                    template = "ZO_GamepadSimpleAbilityEntryTemplate",
                    templateData =
                    {
                        setup = MorphConfirmSetup,
                        morphSlot = MORPH_SLOT_MORPH_2,
                        narrationTooltip = GAMEPAD_LEFT_DIALOG_TOOLTIP,
                    },
                },
            },

            parametricListOnSelectionChangedCallback = function(dialog, list)
                local targetData = list:GetTargetData()
                local morphSkillData = parametricDialog.data.morphSkillData
                local morphSkillProgressionData = morphSkillData:GetMorphData(targetData.morphSlot)
                local SHOW_RANK_NEEDED_LINE = true
                local SHOW_POINT_SPEND_LINE = true
                local SHOW_ADVISED_LINE = true
                local DONT_SHOW_RESPEC_TO_FIX_BAD_MORPH_LINE = false
                local SHOW_UPGRADE_INFO_BLOCK = true
                GAMEPAD_TOOLTIPS:LayoutSkillProgression(GAMEPAD_LEFT_DIALOG_TOOLTIP, morphSkillProgressionData, SHOW_RANK_NEEDED_LINE, SHOW_POINT_SPEND_LINE, SHOW_ADVISED_LINE, DONT_SHOW_RESPEC_TO_FIX_BAD_MORPH_LINE, SHOW_UPGRADE_INFO_BLOCK)
            end,
            buttons =
            {
                {
                    keybind = "DIALOG_PRIMARY",
                    text = SI_GAMEPAD_SKILLS_MORPH,
                    callback =  MorphConfirmCallback,
                },
                {
                    keybind = "DIALOG_NEGATIVE",
                    text = SI_DIALOG_CANCEL,
                    callback =  function(dialog)
                        self.selectedTooltipRefreshGroup:MarkDirty("Full")
                    end,
                },
            },
        })
    end

    function ZO_GamepadSkills:InitializeChangeSkillStyleDialog()
        ZO_Dialogs_RegisterCustomDialog("GAMEPAD_SKILL_STYLE_SELECTION",
        {
            gamepadInfo =
            {
                dialogType = GAMEPAD_DIALOGS.PARAMETRIC,
            },

            setup = function(dialog)
                local progressionId = dialog.data.skillData.progressionId
                local isSkillLinePurchased = dialog.data.skillData.isPurchased
                local parametricListEntries = dialog.info.parametricList
                ZO_ClearNumericallyIndexedTable(parametricListEntries)

                local currentCollectibleId = GetActiveProgressionSkillAbilityFxOverrideCollectibleId(progressionId)

                -- Insert Default Entry
                local defaultEntryData = ZO_GamepadEntryData:New(GetString(SI_SKILL_STYLING_DEFAULT_NAME))
                defaultEntryData.setup = ZO_SharedGamepadEntry_OnSetup
                defaultEntryData:AddIcon("EsoUI/Art/Progression/Gamepad/gp_skillStyleEmpty.dds")
                defaultEntryData.collectibleId = 0
                defaultEntryData.isCurrent = currentCollectibleId == 0
                defaultEntryData:SetSelected(defaultEntryData.isCurrent)
                defaultEntryData.narrationTooltip = GAMEPAD_LEFT_DIALOG_TOOLTIP

                local defaultListItem =
                {
                    template = "ZO_GamepadItemEntryTemplate",
                    entryData = defaultEntryData,
                }
                table.insert(parametricListEntries, defaultListItem)

                -- Insert Available Skill Styles for Progression
                for index = 1, GetNumProgressionSkillAbilityFxOverrides(progressionId) do
                    local collectibleId = GetProgressionSkillAbilityFxOverrideCollectibleIdByIndex(progressionId, index)
                    local collectibleData = ZO_COLLECTIBLE_DATA_MANAGER:GetCollectibleDataById(collectibleId)
                    if collectibleData and not collectibleData:IsHiddenFromCollection() then
                        local entryData = ZO_GamepadEntryData:New(collectibleData:GetFormattedName())
                        entryData.setup = ZO_SharedGamepadEntry_OnSetup
                        entryData:AddIcon(collectibleData:GetIcon())
                        entryData.iconIndex = index
                        entryData.collectibleId = collectibleId
                        entryData.collectibleData = collectibleData
                        entryData.isCurrent = function()
                            return collectibleId == currentCollectibleId
                        end

                        entryData:InitializeCollectibleVisualData(collectibleData, GAMEPLAY_ACTOR_CATEGORY_PLAYER)
                        entryData:SetEnabled(isSkillLinePurchased and collectibleData:IsUnlocked())
                        entryData:SetSelected(entryData.isCurrent())
                        entryData:SetLocked(not collectibleData:IsOwned() or not isSkillLinePurchased)
                        entryData.narrationTooltip = GAMEPAD_LEFT_DIALOG_TOOLTIP

                        local listItem =
                        {
                            template = "ZO_GamepadItemEntryTemplate",
                            entryData = entryData,
                        }
                        table.insert(parametricListEntries, listItem)
                    end
                end

                ZO_GenericGamepadDialog_ShowTooltip(dialog)
                dialog:setupFunc()
            end,

            finishedCallback = function()
                self.lineFilterListRefreshGroup:MarkDirty("List")
                SKILLS_SUBCLASSING_GAMEPAD.skillsListRefreshGroup:MarkDirty("List")
            end,

            title =
            {
                text = function(dialog)
                    local data = dialog.data
                    local skillType = data.skillData.skillLineData.skillTypeData.skillType
                    local skillLineIndex = data.skillData.skillLineData.skillLineIndex
                    local skillIndex = data.skillData.skillIndex
                    return zo_strformat(SI_SKILL_STYLING_DIALOG_TITLE, GetProgressionSkillProgressionName(skillType, skillLineIndex, skillIndex))
                end,
            },
            mainText =
            {
                text = function(dialog)
                    return SKILLS_DATA_MANAGER:GetSkillStyleWarningText(dialog.data)
                end,
            },

            parametricList = {}, -- Generated Dynamically
            parametricListOnSelectionChangedCallback = function(dialog, list)
                local targetData = dialog.entryList:GetTargetData()
                if targetData then
                    if targetData.collectibleId == 0 then
                        GAMEPAD_TOOLTIPS:LayoutTitleAndDescriptionTooltip(GAMEPAD_LEFT_DIALOG_TOOLTIP, GetString(SI_SKILL_STYLING_TOOLTIP_DEFAULT_TITLE), GetString(SI_SKILL_STYLING_TOOLTIP_DEFAULT_DESCRIPTION))
                    else
                        local NO_COOLDOWN = 0
                        local SHOW_VISUAL_LAYER_INFO = true
                        local SHOW_BLOCK_REASON = true
                        GAMEPAD_TOOLTIPS:LayoutCollectibleFromData(GAMEPAD_LEFT_DIALOG_TOOLTIP, targetData.collectibleData, SHOW_VISUAL_LAYER_INFO, NO_COOLDOWN, SHOW_BLOCK_REASON)
                        ZO_GenericGamepadDialog_ShowTooltip(dialog)
                    end
                else
                    ZO_GenericGamepadDialog_HideTooltip(dialog)
                end
            end,
            buttons =
            {
                {
                    keybind = "DIALOG_PRIMARY",
                    text = SI_GAMEPAD_SELECT_OPTION,
                    enabled = function(dialog)
                        local targetData = dialog.entryList:GetTargetData()
                        if targetData then
                            return targetData:IsEnabled()
                        end
                        return false
                    end,
                    callback =  function(dialog)
                        local targetData = dialog.entryList:GetTargetData()
                        if targetData then
                            local isCurrent = targetData.isCurrent
                            if type(isCurrent) == "function" then
                                isCurrent = isCurrent()
                            end
                            if targetData.collectibleData then
                                if not isCurrent then
                                    targetData.collectibleData:Use()
                                end
                            else
                                -- We selected default, unselect current collectible by using it again
                                for i = 1, dialog.entryList:GetNumEntries() do
                                    local entryData = dialog.entryList:GetEntryData(i)
                                    local isCurrent = entryData.isCurrent
                                    if type(isCurrent) == "function" then
                                        isCurrent = isCurrent()
                                    end
                                    if entryData.collectibleData and isCurrent then
                                        entryData.collectibleData:Use()
                                    end
                                end
                            end
                        end
                    end,
                },
                {
                    keybind = "DIALOG_NEGATIVE",
                    text = SI_DIALOG_CANCEL,
                    callback =  function(dialog)
                        self.selectedTooltipRefreshGroup:MarkDirty("Full")
                    end,
                },
            },
        })
    end
end

function ZO_GamepadSkills:InitializeRespecConfirmationGoldDialog()
    local dialogData =
    {
        data1 =
        {
            header = GetString(SI_GAMEPAD_SKILL_RESPEC_CONFIRM_DIALOG_BALANCE_HEADER),
        },
        data2 =
        {
            header = GetString(SI_GAMEPAD_SKILL_RESPEC_CONFIRM_DIALOG_COST_HEADER),
        },
    }

    ZO_Dialogs_RegisterCustomDialog("SKILL_RESPEC_CONFIRM_GOLD_GAMEPAD",
    {
        gamepadInfo =
        {
            dialogType = GAMEPAD_DIALOGS.BASIC,
        },
        title =
        {
            text = SI_SKILL_RESPEC_CONFIRM_DIALOG_TITLE,
        },
        mainText = 
        {
            text = function(dialog)
                local mainTextEntryTable = ZO_Dialogs_GetSkillsRespecMainTextEntryTable()
                return ZO_GenerateParagraphSeparatedList(mainTextEntryTable)
            end,
        },
        setup = function(dialog)
            local balance = GetCurrencyAmount(CURT_MONEY, CURRENCY_LOCATION_CHARACTER)
            local cost = GetSkillRespecCost(SKILLS_AND_ACTION_BAR_MANAGER:GetSkillPointAllocationMode())
            local IS_GAMEPAD = true
            dialogData.data1.value = ZO_Currency_Format(balance, CURT_MONEY, ZO_CURRENCY_FORMAT_AMOUNT_ICON, IS_GAMEPAD)
            dialogData.data2.value = ZO_Currency_Format(cost, CURT_MONEY, balance > cost and ZO_CURRENCY_FORMAT_AMOUNT_ICON or ZO_CURRENCY_FORMAT_ERROR_AMOUNT_ICON, IS_GAMEPAD)
            dialog.setupFunc(dialog, dialogData)
        end,
        buttons =
        {
            {
                keybind = "DIALOG_PRIMARY",
                text = SI_DIALOG_CONFIRM,
                callback = function()
                    SKILLS_AND_ACTION_BAR_MANAGER:ApplyChanges()
                end,
            },
            {
                keybind = "DIALOG_NEGATIVE",
                text = SI_DIALOG_CANCEL,
            },
        },
    })
end

function ZO_GamepadSkills:ShowConfirmRespecDialog()
    if SKILLS_AND_ACTION_BAR_MANAGER:DoPendingChangesIncurCost() then
        if SKILLS_AND_ACTION_BAR_MANAGER:GetSkillRespecPaymentType() == RESPEC_PAYMENT_TYPE_GOLD then
            ZO_Dialogs_ShowGamepadDialog("SKILL_RESPEC_CONFIRM_GOLD_GAMEPAD")
        else
            ZO_Dialogs_ShowGamepadDialog("SKILL_RESPEC_CONFIRM_SCROLL")
        end
    else
        ZO_Dialogs_ShowGamepadDialog("SKILL_RESPEC_CONFIRM_FREE")
    end
end

function ZO_GamepadSkills:InitializeConfirmClearAllDialog()
    local clearSkillLineEntry =
    {
        template = "ZO_GamepadMenuEntryTemplate",
        templateData =
        {
            setup = ZO_SharedGamepadEntry_OnSetup,
        }
    }

    ZO_Dialogs_RegisterCustomDialog("SKILL_RESPEC_CONFIRM_CLEAR_ALL_GAMEPAD",
    {
        gamepadInfo =
        {
            dialogType = GAMEPAD_DIALOGS.PARAMETRIC,
        },
        title =
        {
            text = function()
                return GetString("SI_SKILLPOINTALLOCATIONMODE_CLEARKEYBIND", SKILLS_AND_ACTION_BAR_MANAGER:GetSkillPointAllocationMode())
            end,
        },
        setup = function(dialog, skillLineData)
            clearSkillLineEntry.header = GetString("SI_SKILLPOINTALLOCATIONMODE_CLEARCHOICEHEADERGAMEPAD", SKILLS_AND_ACTION_BAR_MANAGER:GetSkillPointAllocationMode())
            local clearSkillLineEntryTemplateData = clearSkillLineEntry.templateData
            clearSkillLineEntryTemplateData.text = skillLineData:GetFormattedName()
            clearSkillLineEntryTemplateData.skillLineData = skillLineData
            dialog.setupFunc(dialog, nil , skillLineData)
        end,
        parametricList =
        {
            -- Clear Skill Line
            clearSkillLineEntry,
            -- Clear All
            {
                template = "ZO_GamepadMenuEntryTemplate",
                templateData =
                {
                    text = GetString(SI_SKILL_RESPEC_CONFIRM_CLEAR_ALL_DIALOG_ALL_OPTION),
                    setup = ZO_SharedGamepadEntry_OnSetup,
                },
            },
        },
        buttons =
        {
            {
                keybind = "DIALOG_PRIMARY",
                text = SI_DIALOG_CONFIRM,
                callback = function(dialog)
                    local entry = dialog.entryList:GetTargetData()
                    if entry.skillLineData then
                        SKILL_POINT_ALLOCATION_MANAGER:ClearPointsOnSkillLine(entry.skillLineData)
                    else
                        SKILL_POINT_ALLOCATION_MANAGER:ClearPointsOnAllSkillLines()
                    end
                end,
            },
            {
                keybind = "DIALOG_NEGATIVE",
                text = SI_DIALOG_CANCEL,
            },
        },
    })
end

function ZO_GamepadSkills:ShowConfirmClearAllDialog(skillLineData)
    ZO_Dialogs_ShowGamepadDialog("SKILL_RESPEC_CONFIRM_CLEAR_ALL_GAMEPAD", skillLineData)
end

do

    local parametricDialog = ZO_GenericGamepadDialog_GetControl(GAMEPAD_DIALOGS.PARAMETRIC)

    function ZO_GamepadSkills:UpdatePendingStatBonuses(derivedStat, pendingBonus)
        local data = parametricDialog.entryList:GetTargetData()
        local attributeType = data.attributeType
        if not pendingBonus then
            local perPointBonus = GetAttributeDerivedStatPerPointValue(attributeType, STAT_TYPES[attributeType])
            pendingBonus = self.pendingAttributePoints[attributeType] * perPointBonus
        end
        GAMEPAD_TOOLTIPS:LayoutAttributeInfo(GAMEPAD_LEFT_DIALOG_TOOLTIP, attributeType, pendingBonus)
    end

    function ZO_GamepadSkills:SetAddedPoints(attributeType, addedPoints)
        self.pendingAttributePoints[attributeType] = addedPoints
    end

    function ZO_GamepadSkills:GetAddedPoints(attributeType)
        return self.pendingAttributePoints[attributeType]
    end

    function ZO_GamepadSkills:SetAvailablePoints(points)
        parametricDialog.headerData.data1Text = points
        ZO_GamepadGenericHeader_Refresh(parametricDialog.header, parametricDialog.headerData)
    end

    function ZO_GamepadSkills:GetAvailablePoints()
        local numPending = 0
        for i = 1, GetNumAttributes() do
            numPending = numPending + self.pendingAttributePoints[i]
        end
        return GetAttributeUnspentPoints() - numPending
    end

    function ZO_GamepadSkills:SpendAvailablePoints(points)
        self:SetAvailablePoints(self:GetAvailablePoints() - points)
    end

    function ZO_GamepadSkills:ResetAttributeData()
        self.pendingAttributePoints = {}
        for i = 1, GetNumAttributes() do
            self.pendingAttributePoints[i] = 0
        end
    end

    local attributePointData =
    {
        data1 =
        {
            value = 0,
            header = GetString(SI_STATS_GAMEPAD_AVAILABLE_POINTS),
        },
    }

    function ZO_GamepadSkills:InitializeAttributeDialog()
        self:ResetAttributeData()

        local function OnAttributeValueChanged(entryData, entryControl)
            SCREEN_NARRATION_MANAGER:QueueDialog(entryData.dialog)
        end

        local function GetAttributeNarrationText(entryData, entryControl)
            local totalPoints = entryControl.pointLimitedSpinner:GetPoints() + entryControl.pointLimitedSpinner:GetAllocatedPoints()
            return ZO_FormatSpinnerNarrationText(entryData.text, totalPoints)
        end
        
        ZO_Dialogs_RegisterCustomDialog("GAMEPAD_SKILLS_ATTRIBUTE_PURCHASE",
        {
            canQueue = true,
            gamepadInfo = 
            {
                dialogType = GAMEPAD_DIALOGS.PARAMETRIC,
            },

            setup = function(dialog)
                attributePointData.data1.value = self:GetAvailablePoints()
                ZO_GenericGamepadDialog_ShowTooltip(dialog)
                dialog:setupFunc(nil, attributePointData)
            end,
            title =
            {
                text = GetString(SI_STATS_ATTRIBUTES_LEVEL_UP),
            },

            blockDirectionalInput = true,
            parametricList =
            {
                -- magicka
                {
                    template = "ZO_GamepadStatAttributeRow",
                    templateData = 
                    {
                        setup = ZO_GamepadStatAttributeRow_Setup,
                        attributeType = ATTRIBUTE_MAGICKA,
                        screen = self,
                        onValueChangedCallback = OnAttributeValueChanged,
                        narrationText = GetAttributeNarrationText,
                        narrationTooltip = GAMEPAD_LEFT_DIALOG_TOOLTIP,
                    },
                    text = GetString("SI_ATTRIBUTES", ATTRIBUTE_MAGICKA),
                    icon = "/esoui/art/characterwindow/Gamepad/gp_characterSheet_magickaIcon.dds",
                },
                -- health
                {
                    template = "ZO_GamepadStatAttributeRow",
                    templateData = 
                    {
                        setup = ZO_GamepadStatAttributeRow_Setup,
                        attributeType = ATTRIBUTE_HEALTH,
                        screen = self,
                        onValueChangedCallback = OnAttributeValueChanged,
                        narrationText = GetAttributeNarrationText,
                        narrationTooltip = GAMEPAD_LEFT_DIALOG_TOOLTIP,
                    },
                    text = GetString("SI_ATTRIBUTES", ATTRIBUTE_HEALTH),
                    icon = "/esoui/art/characterwindow/Gamepad/gp_characterSheet_healthIcon.dds",
                },
                -- stamina
                {
                    template = "ZO_GamepadStatAttributeRow",
                    templateData = 
                    {
                        setup = ZO_GamepadStatAttributeRow_Setup,
                        attributeType = ATTRIBUTE_STAMINA,
                        screen = self,
                        onValueChangedCallback = OnAttributeValueChanged,
                        narrationText = GetAttributeNarrationText,
                        narrationTooltip = GAMEPAD_LEFT_DIALOG_TOOLTIP,
                    },
                    text = GetString("SI_ATTRIBUTES", ATTRIBUTE_STAMINA),
                    icon = "/esoui/art/characterwindow/Gamepad/gp_characterSheet_staminaIcon.dds",
                },
            },

            parametricListOnSelectionChangedCallback = function()
                self:UpdatePendingStatBonuses()
            end,

            parametricListOnActivatedChangedCallback = function(list, isActive)
                if not isActive then
                    local selectedControl = list:GetSelectedControl()
                    if selectedControl and selectedControl.pointLimitedSpinner then
                        selectedControl.pointLimitedSpinner:SetActive(false)
                    end
                else
                    list:RefreshVisible()
                end
            end,

            buttons =
            {
                {
                    keybind = "DIALOG_PRIMARY",
                    text = SI_GAMEPAD_LEVELUP_DIALOG_CONFIRM,
                    callback = function(dialog)
                        local pendingHealth = self.pendingAttributePoints[ATTRIBUTE_HEALTH]
                        local pendingMagicka = self.pendingAttributePoints[ATTRIBUTE_MAGICKA]
                        local pendingStamina = self.pendingAttributePoints[ATTRIBUTE_STAMINA]
                        if pendingHealth + pendingMagicka + pendingStamina > 0 then
                            PurchaseAttributes(pendingHealth, pendingMagicka, pendingStamina)
                            PlaySound(SOUNDS.GAMEPAD_STATS_SINGLE_PURCHASE)
                        end
                    end,
                },
                {
                    keybind = "DIALOG_NEGATIVE",
                    text = SI_GAMEPAD_BACK_OPTION,
                },
            },

            finishedCallback = function(dialog)
                --Setup Skills Scene
                ZO_GenericGamepadDialog_HideTooltip(dialog)
                self.showAttributeDialog = false
                self.selectedTooltipRefreshGroup:MarkDirty("Full")
                self:ResetAttributeData()
            end,
        })
    end
end

local TIME_NEW_PERSISTS_WHILE_SELECTED = 1000

function ZO_GamepadSkills:OnSelectedSkillChanged(skillEntry)
    self.selectedTooltipRefreshGroup:MarkDirty("Full")
    KEYBIND_STRIP:UpdateKeybindButtonGroup(self.lineFilterKeybindStripDescriptor)

    self:TryClearSkillUpdatedStatus()

    if skillEntry and not self.assignableActionBar:IsActive() then
        local skillData = skillEntry.skillData
        if skillData:HasUpdatedStatus() then
            self.clearSkillUpdatedStatusSkillData = skillData
            self.clearSkillUpdatedStatusCallId = zo_callLater(self.trySetClearUpdatedAbilityFlagCallback, TIME_NEW_PERSISTS_WHILE_SELECTED)
        else
            self.clearSkillUpdatedStatusCallId = nil
        end
    else
        self.clearSkillUpdatedStatusCallId = nil
    end
end

function ZO_GamepadSkills:OnSelectedSkillLineChanged(selectedData)
    self.selectedTooltipRefreshGroup:MarkDirty("Full")
    KEYBIND_STRIP:UpdateKeybindButtonGroup(self.categoryKeybindStripDescriptor)

    self:TryClearSkillLineNewStatus()

    if selectedData and not self.assignableActionBar:IsActive() then
        local skillLineData = selectedData.skillLineData
        if skillLineData and skillLineData:IsSkillLineOrAbilitiesNew() then
            self.clearSkillLineNewStatusSkillLineData = skillLineData
            self.clearSkillLineNewStatusCallId = zo_callLater(self.trySetClearNewSkillLineFlagCallback, TIME_NEW_PERSISTS_WHILE_SELECTED)
        else
            self.clearSkillLineNewStatusCallId = nil
        end
    else
        self.clearSkillLineNewStatusCallId = nil
    end
end

function ZO_GamepadSkills:RefreshSelectedTooltip()
    --don't setup tooltip til dialog is gone.
    if self.showAttributeDialog or (not GAMEPAD_SKILLS_ROOT_SCENE:IsShowing() and not GAMEPAD_SKILLS_LINE_FILTER_SCENE:IsShowing()) then
        return
    end
    GAMEPAD_TOOLTIPS:ClearTooltip(GAMEPAD_LEFT_TOOLTIP)
    SCENE_MANAGER:RemoveFragment(SKILLS_ADVISOR_SUGGESTIONS_GAMEPAD_FRAGMENT)
    SCENE_MANAGER:AddFragment(GAMEPAD_LEFT_TOOLTIP_BACKGROUND_FRAGMENT)

    if self.quickMenu:IsShowing() then
        self.quickMenu:RefreshTooltip()
    elseif self.mode == ZO_GAMEPAD_SKILLS_SKILL_LIST_BROWSE_MODE then
        local selectedData = self.categoryList:GetTargetData()
        if selectedData and self.assignableActionBar:IsActive() then
            self.assignableActionBar:LayoutOrClearSlotTooltip(GAMEPAD_LEFT_TOOLTIP)
        elseif selectedData and selectedData.isSkillsAdvisor then
            if not (ZO_SKILLS_ADVISOR_SINGLETON:GetSelectedSkillBuildId() or ZO_SKILLS_ADVISOR_SINGLETON:IsAdvancedModeSelected()) then
                -- No Skill Build yet selected
                GAMEPAD_TOOLTIPS:LayoutTitleAndMultiSectionDescriptionTooltip(GAMEPAD_LEFT_TOOLTIP, selectedData.text, GetString(SI_SKILLS_ADVISOR_GAMEPAD_DESCRIPTION))
            elseif ZO_SKILLS_ADVISOR_SINGLETON:IsAdvancedModeSelected() then
                -- Advanced Player selected
                GAMEPAD_TOOLTIPS:LayoutTitleAndMultiSectionDescriptionTooltip(GAMEPAD_LEFT_TOOLTIP, GetString(SI_SKILLS_ADVISOR_ADVANCED_PLAYER_NAME), GetString(SI_SKILLS_ADVISOR_GAMEPAD_ADVANCED_SELECTED_DESCRIPTION))
            elseif ZO_SKILLS_ADVISOR_SINGLETON:GetSelectedSkillBuildId() then
                -- Skill Build selected
                SCENE_MANAGER:AddFragment(SKILLS_ADVISOR_SUGGESTIONS_GAMEPAD_FRAGMENT)
            end
        elseif selectedData and selectedData.isScribeLibrary then
            local scribingTitle = GetString(SI_SCRIBING_TITLE)
            local scribingDescription = GetString(SI_SCRIBING_LIBRARY_DESCRIPTION)
            if SCRIBING_DATA_MANAGER:IsScribingUnlocked() then
                GAMEPAD_TOOLTIPS:LayoutTitleAndDescriptionTooltip(GAMEPAD_LEFT_TOOLTIP, scribingTitle, scribingDescription)
            else
                local lockedText = ZO_Tooltip:GetRequiredScribingCollectibleText()
                GAMEPAD_TOOLTIPS:LayoutTitleAndMultiSectionDescriptionTooltip(GAMEPAD_LEFT_TOOLTIP, scribingTitle, scribingDescription, lockedText)
            end
        elseif selectedData and selectedData.isSubclassing then
            local subclassingTitle = GetString(SI_SKILLS_SUBCLASSING_ENTRY_NAME)
            local subclassingDescription = GetString(SI_SKILLS_SUBCLASSING_DESCRIPTION)
            GAMEPAD_TOOLTIPS:LayoutTitleAndDescriptionTooltip(GAMEPAD_LEFT_TOOLTIP, subclassingTitle, subclassingDescription)
            if not SKILLS_AND_ACTION_BAR_MANAGER:DoesSkillPointAllocationModeAllowAccessToSubclassing() then
                local lockedTextTable = {}
                local achievementId = GetSubclassingAchievementId()
                local achievementTextColor = ZO_ERROR_COLOR
                local questId = GetSubclassingQuestId()
                local questNameText = zo_strformat(SI_SKILLS_SUBCLASSING_UNLOCK_QUEST_TEXT, GetQuestName(questId))
                table.insert(lockedTextTable, GetString(SI_SKILLS_SUBCLASSING_UNLOCK_TOOLTIP_HEADER))
                if IsAchievementComplete(achievementId) then
                    achievementTextColor = ZO_NORMAL_TEXT
                end
                table.insert(lockedTextTable, achievementTextColor:Colorize(GetString(SI_SKILLS_SUBCLASSING_UNLOCK_ACHIEVEMENT_TEXT)))
                table.insert(lockedTextTable, ZO_ERROR_COLOR:Colorize(questNameText))
                local lockedText = ZO_GenerateParagraphSeparatedList(lockedTextTable)
                GAMEPAD_TOOLTIPS:LayoutTitleAndMultiSectionDescriptionTooltip(GAMEPAD_LEFT_TOOLTIP, subclassingTitle, subclassingDescription, lockedText)
            end
        elseif selectedData then
            local skillLineData = selectedData.skillLineData

            GAMEPAD_TOOLTIPS:LayoutSkillLinePreview(GAMEPAD_LEFT_TOOLTIP, skillLineData)
        end
    else
        local skillEntry = self.lineFilterList:GetTargetData()
        if skillEntry and not self.assignableActionBar:IsActive() then
            local skillData = skillEntry.skillData
            local SHOW_RANK_NEEDED_LINE = true
            local SHOW_POINT_SPEND_LINE = true
            local SHOW_ADVISED_LINE = true
            local SHOW_RESPEC_TO_FIX_BAD_MORPH_LINE = true
            --Morphs (the only actives with upgrade info) only show that info in the morph dialog tooltip. Passives show it all the time.
            local showUpgradeInfoBlock = skillData:IsPassive()
            GAMEPAD_TOOLTIPS:LayoutSkillProgression(GAMEPAD_LEFT_TOOLTIP, skillData:GetPointAllocatorProgressionData(), SHOW_RANK_NEEDED_LINE, SHOW_POINT_SPEND_LINE, SHOW_ADVISED_LINE, SHOW_RESPEC_TO_FIX_BAD_MORPH_LINE, showUpgradeInfoBlock)
            if self.mode == ZO_GAMEPAD_SKILLS_SINGLE_ABILITY_ASSIGN_MODE then
                self.assignableActionBar:LayoutOrClearSlotTooltip(GAMEPAD_RIGHT_TOOLTIP)
            end 
        elseif self.assignableActionBar:IsActive() then
            self.assignableActionBar:LayoutOrClearSlotTooltip(GAMEPAD_LEFT_TOOLTIP)
        end
    end
end

function ZO_GamepadSkills:SetMode(mode)
    self.mode = mode
    self.selectedTooltipRefreshGroup:MarkDirty("Full")
    if self.mode == ZO_GAMEPAD_SKILLS_SKILL_LIST_BROWSE_MODE then
        if self:IsCurrentList(self.categoryList) then
            if not self.assignableActionBar:IsActive() then
                self:ActivateCurrentList()
            end
        else
            self:SetCurrentList(self.categoryList)
        end
    elseif self.mode == ZO_GAMEPAD_SKILLS_ABILITY_LIST_BROWSE_MODE then
        if self:IsCurrentList(self.lineFilterList) then
            if not self.assignableActionBar:IsActive() then
                self.lineFilterList:Activate()
            end
        else
            self:SetCurrentList(self.lineFilterList)
        end
        self.lineFilterListRefreshGroup:MarkDirty("Visible")
    elseif self.mode == ZO_GAMEPAD_SKILLS_SINGLE_ABILITY_ASSIGN_MODE then
        self:DisableCurrentList()
        self.lineFilterListRefreshGroup:MarkDirty("Visible") -- The action bar reuses line filter list keybinds, so refresh those
    end
end

function ZO_GamepadSkills:StartSingleAbilityAssignment(skillData)
    self.assignableActionBar:SetTargetSkill(skillData)
    self.assignableActionBar:Activate()
    self.actionBarAnimation:PlayForward()
    self:SetMode(ZO_GAMEPAD_SKILLS_SINGLE_ABILITY_ASSIGN_MODE)
    self.selectedTooltipRefreshGroup:MarkDirty("Full")
end

function ZO_GamepadSkills:InitializeOptionsDialog()
    local function ReleaseDialog()
        ZO_Dialogs_ReleaseDialogOnButtonPress("SKILLS_OPTIONS_DIALOG_GAMEPAD")
    end

    local linkInChat = ZO_GamepadEntryData:New(zo_strformat(SI_ITEM_ACTION_LINK_TO_CHAT))
    linkInChat.setup = ZO_SharedGamepadEntry_OnSetup
    linkInChat.callback = function(entryData)
        local skillData = entryData.data.skillData
        local link = skillData:GetCurrentProgressionLink()
        if internalassert(link, "Unable to generate link for skill.") then
            ZO_LinkHandler_InsertLinkAndSubmit(link)
        end
        ReleaseDialog()
    end

    ZO_Dialogs_RegisterCustomDialog("SKILLS_OPTIONS_DIALOG_GAMEPAD",
    {
        blockDialogReleaseOnPress = true,

        canQueue = true,

        gamepadInfo =
        {
            dialogType = GAMEPAD_DIALOGS.PARAMETRIC,
            allowRightStickPassThrough = true,
        },

        setup = function(dialog)
            dialog:setupFunc()
        end,

        title =
        {
            text = SI_GAMEPAD_OPTIONS_MENU,
        },
        parametricList =
        {
            {
                template = "ZO_GamepadMenuEntryTemplate",
                entryData = linkInChat,
            },
        },
        buttons =
        {
            {
                keybind = "DIALOG_PRIMARY",
                text = SI_GAMEPAD_SELECT_OPTION,
                callback = function(dialog)
                    local targetData = dialog.entryList:GetTargetData()
                    if targetData and targetData.callback then
                        targetData.callback(dialog)
                    end
                end,
            },
            {
                keybind = "DIALOG_NEGATIVE",
                text = SI_DIALOG_CANCEL,
                callback = function()
                    ReleaseDialog()
                end,
            },
        }
    })
end

function ZO_GamepadSkills_OnInitialize(control)
    GAMEPAD_SKILLS = ZO_GamepadSkills:New(control)
end

--[[ Skill Templates ]]--

function ZO_GamepadSkills_RespecBindingsBinding_OnInitialized(self, binding)
    ZO_KeybindButtonTemplate_OnInitialized(self)
    ApplyTemplateToControl(self, "ZO_KeybindButton_Gamepad_Template")
    local DONT_SHOW_UNBOUND = false
    self:SetKeybind(binding, DONT_SHOW_UNBOUND, binding)
end