ZO_GAMEPAD_PROVISIONER_INGREDIENTS_BAR_OFFSET_X = (ZO_GAMEPAD_QUADRANT_1_RIGHT_OFFSET + ZO_GAMEPAD_UI_REFERENCE_WIDTH) / 2

local GAMEPAD_PROVISIONER_ROOT_SCENE_NAME = "gamepad_provisioner_root"
local GAMEPAD_PROVISIONER_FILLET_SCENE_NAME = "gamepad_provisioner_fillet"
local GAMEPAD_PROVISIONER_CREATION_SCENE_NAME = "gamepad_provisioner_creation"

PROVISIONER_MODE_ROOT = 0
PROVISIONER_MODE_FILLET = 1
PROVISIONER_MODE_CREATION = 2

local GAMEPAD_PROVISIONER_OPTION_FILTER_INGREDIENTS = 1
local GAMEPAD_PROVISIONER_OPTION_FILTER_SKILLS = 2
local GAMEPAD_PROVISIONER_OPTION_FILTER_QUESTS = 3

local optionFilterIngredients =
{
    filterName = GetString(SI_PROVISIONER_HAVE_INGREDIENTS),
    filterTooltip = GetString(SI_CRAFTING_HAVE_INGREDIENTS_TOOLTIP),
    checked = false,
}
local optionFilterSkills =
{
    filterName = GetString(SI_PROVISIONER_HAVE_SKILLS),
    filterTooltip = GetString(SI_CRAFTING_HAVE_SKILLS_TOOLTIP),
    checked = false,
}
local optionFilterQuests =
{
    filterName = GetString(SI_SMITHING_IS_QUEST_ITEM),
    filterTooltip = GetString(SI_CRAFTING_IS_QUEST_ITEM_TOOLTIP),
    checked = false,
}

ZO_GamepadProvisioner = ZO_SharedProvisioner:Subclass()

function ZO_GamepadProvisioner:Initialize(control, scene)
    self.mainSceneName = GAMEPAD_PROVISIONER_ROOT_SCENE_NAME
    ZO_SharedProvisioner.Initialize(self, control)

    self.skillInfoBar = ZO_GamepadProvisionerTopLevelSkillInfo
    local skillLineXPBarFragment = ZO_FadeSceneFragment:New(self.skillInfoBar)
    local function MakeScene(name, mode)
        local scene = self:CreateInteractScene(name)
        scene:AddFragment(skillLineXPBarFragment)
        scene:RegisterCallback("StateChange", function(oldState, newState)
            -- TODO: Determine if any action is needed here
        end)

        return scene
    end

    GAMEPAD_PROVISIONER_ROOT_SCENE = MakeScene(GAMEPAD_PROVISIONER_ROOT_SCENE_NAME, PROVISIONER_MODE_ROOT)
    GAMEPAD_PROVISIONER_ROOT_SCENE:SetInputPreferredMode(INPUT_PREFERRED_MODE_ALWAYS_GAMEPAD)
    GAMEPAD_PROVISIONER_FILLET_SCENE = MakeScene(GAMEPAD_PROVISIONER_FILLET_SCENE_NAME, PROVISIONER_MODE_FILLET)
    GAMEPAD_PROVISIONER_FILLET_SCENE:SetInputPreferredMode(INPUT_PREFERRED_MODE_ALWAYS_GAMEPAD)
    GAMEPAD_PROVISIONER_CREATION_SCENE = MakeScene(GAMEPAD_PROVISIONER_CREATION_SCENE_NAME, PROVISIONER_MODE_CREATION)
    GAMEPAD_PROVISIONER_CREATION_SCENE:SetInputPreferredMode(INPUT_PREFERRED_MODE_ALWAYS_GAMEPAD)

    GAMEPAD_PROVISIONER_ROOT_SCENE:RegisterCallback("StateChange", function(oldState, newState)
        if newState == SCENE_SHOWING then
            local craftingType = GetCraftingInteractionType()

            self:RefreshModeList(craftingType)

            self.resetUIs = self.resetUIs or self.oldCraftingType ~= craftingType

            self.filletPanel:SetCraftingType(craftingType, self.oldCraftingType, self.resetUIs)
            self.oldCraftingType = craftingType

            self:ResetMode()
            if self.resetUIs then
                self.modeList:SetSelectedIndexWithoutAnimation(PROVISIONER_MODE_FILLET)
            end

            self:SetEnableSkillBar(true)

            self.control:GetNamedChild("IngredientsBar"):SetHidden(true)
            self.resultTooltip:SetHidden(true)

            KEYBIND_STRIP:AddKeybindButtonGroup(self.keybindStripDescriptor)
            self.modeList.control:SetHidden(false)
            self.modeList:Activate()

            local titleString = ZO_GamepadCraftingUtils_GetLineNameForCraftingType(craftingType)

            ZO_GamepadCraftingUtils_SetupGenericHeader(self, titleString)
            ZO_GamepadCraftingUtils_RefreshGenericHeader(self)

            local NARRATE_HEADER = true
            SCREEN_NARRATION_MANAGER:QueueParametricListEntry(self.modeList, NARRATE_HEADER)

            self:RefreshAnimationSounds()

            self.resetUIs = nil
        elseif newState == SCENE_HIDDEN then
            ZO_InventorySlot_RemoveMouseOverKeybinds()
            KEYBIND_STRIP:RemoveKeybindButtonGroup(self.keybindStripDescriptor)
            self.modeList:Deactivate()
            self.modeList.control:SetHidden(true)

            self:SetEnableSkillBar(false)
        end
    end)

    GAMEPAD_PROVISIONER_CREATION_SCENE:AddFragment(skillLineXPBarFragment)
    GAMEPAD_PROVISIONER_CREATION_SCENE:RegisterCallback("StateChange", function(oldState, newState)
        if newState == SCENE_SHOWING then
            ZO_Skills_TieSkillInfoHeaderToCraftingSkill(self.control:GetNamedChild("SkillInfo"), GetCraftingInteractionType())

            if GetCraftingInteractionType() == CRAFTING_TYPE_PROVISIONING and self.filterType ~= PROVISIONER_SPECIAL_INGREDIENT_TYPE_FURNISHING then
                TriggerTutorial(TUTORIAL_TRIGGER_PROVISIONING_OPENED)
            end

            -- Reset provisioning heading within provisioning station
            if self.mode == PROVISIONER_MODE_CREATION and self.settings then
                self.settings = nil
                self:SetDefaultProvisioningSettings()
            end
            self.control:GetNamedChild("IngredientsBar"):SetHidden(false)
            self.resultTooltip:SetHidden(false)

            SYSTEMS:GetObject("craftingResults"):SetCraftingTooltip(self.resultTooltip)
            self.recipeList.control:SetHidden(false)
            self.recipeList:Activate()

            KEYBIND_STRIP:RemoveDefaultExit()
            KEYBIND_STRIP:AddKeybindButtonGroup(self.mainKeybindStripDescriptor)

            ZO_GamepadCraftingUtils_RefreshGenericHeader(self)
            ZO_GamepadGenericHeader_Activate(self.header)

            -- refresh the recipe details on show, since they were cleared/hidden when the scene hid
            -- and we may not have had a change in our list to trigger a refresh
            self:RefreshRecipeDetails(self:GetRecipeData())
        elseif newState == SCENE_HIDDEN then
            self:SetDefaultProvisioningSettings()
            ZO_Skills_UntieSkillInfoHeaderToCraftingSkill(self.control:GetNamedChild("SkillInfo"))
            SYSTEMS:GetObject("craftingResults"):SetCraftingTooltip(nil)
            ZO_GamepadGenericHeader_Deactivate(self.header)
            self.recipeList:Deactivate()
            self.recipeList.control:SetHidden(true)

            -- refresh the recipe details passing nil in to appropriately hide/clear the tooltip and ingredient list
            local NO_RECIPE = nil
            self:RefreshRecipeDetails(NO_RECIPE)

            self.control:GetNamedChild("IngredientsBar"):SetHidden(true)
            self.resultTooltip:SetHidden(true)

            self:EndRecipePreview()

            KEYBIND_STRIP:RemoveKeybindButtonGroup(self.mainKeybindStripDescriptor)
            KEYBIND_STRIP:RestoreDefaultExit()
        end
    end)

    local maskControl = control:GetNamedChild("Mask")
    self.filletPanel = ZO_FishFillet_Gamepad:New(maskControl:GetNamedChild("Fillet"), control:GetNamedChild("Fillet"), self, GAMEPAD_PROVISIONER_FILLET_SCENE)

    self:InitializeSettings()

    local function OnAddOnLoaded(event, name)
        if name == "ZO_Ingame" then
            self:SetupSavedVars()
        end
    end

    self.control:RegisterForEvent(EVENT_ADD_ON_LOADED, OnAddOnLoaded)

    self:InitializeRecipeList()
    self:InitializeModeList()
    self:InitializeKeybindStripDescriptors()
    self:InitializeDetails()

    ZO_GamepadCraftingUtils_InitializeGenericHeader(self, ZO_GAMEPAD_HEADER_TABBAR_CREATE)

    CALLBACK_MANAGER:RegisterCallback("CraftingAnimationsStarted", function() 
        if SCENE_MANAGER:IsShowing(GAMEPAD_PROVISIONER_CREATION_SCENE_NAME) then
            self.recipeList:SetActive(false)
        end
    end)

    CALLBACK_MANAGER:RegisterCallback("CraftingAnimationsStopped", function() 
        if SCENE_MANAGER:IsShowing(GAMEPAD_PROVISIONER_CREATION_SCENE_NAME) then
            self.recipeList:SetActive(true)
        end
    end)

    self:SetDefaultProvisioningSettings()
end

function ZO_GamepadProvisioner:ShouldShowForControlScheme()
    return IsInGamepadPreferredMode()
end

--Settings

ZO_GamepadProvisioner.PROVISIONING_SETTINGS = {}
ZO_GamepadProvisioner.EMBEDDED_SETTINGS = {}

function ZO_GamepadProvisioner:InitializeSettings()
    local function GenerateTab(filterType)
        return {
            text = GetString("SI_PROVISIONERSPECIALINGREDIENTTYPE", filterType),
            callback = function()
                self:OnTabFilterChanged(filterType)
                local NARRATE_HEADER = true
                SCREEN_NARRATION_MANAGER:QueueParametricListEntry(self.recipeList, NARRATE_HEADER)
            end,
        }
    end

    local foodTab = GenerateTab(PROVISIONER_SPECIAL_INGREDIENT_TYPE_SPICES)
    local drinkTab = GenerateTab(PROVISIONER_SPECIAL_INGREDIENT_TYPE_FLAVORING)
    local furnishingsTab = GenerateTab(PROVISIONER_SPECIAL_INGREDIENT_TYPE_FURNISHING)

    local provisioningSettings = ZO_GamepadProvisioner.PROVISIONING_SETTINGS
    provisioningSettings.tabs = { foodTab, drinkTab, furnishingsTab }

    local embeddedSettings = ZO_GamepadProvisioner.EMBEDDED_SETTINGS
    embeddedSettings.tabs = { furnishingsTab }
end

function ZO_GamepadProvisioner:ResetMode()
    self.mode = PROVISIONER_MODE_ROOT
end

function ZO_GamepadProvisioner:SetMode(mode)
    if self.mode ~= mode then
        self.mode = mode
        if mode == PROVISIONER_MODE_CREATION then
            SCENE_MANAGER:Push(GAMEPAD_PROVISIONER_CREATION_SCENE_NAME)
        else
            SCENE_MANAGER:Push(GAMEPAD_PROVISIONER_FILLET_SCENE_NAME)
        end
        self:UpdateKeybindStrip()
    end

    -- Always refresh the audio to handle scene state changes.
    self:RefreshAnimationSounds()
end

function ZO_GamepadProvisioner:RefreshAnimationSounds()
    local mode = self.mode
    if mode == PROVISIONER_MODE_CREATION then
        local recipeData = self:GetRecipeData()
        local soundId = recipeData and recipeData.createSound or nil
        GAMEPAD_CRAFTING_RESULTS:SetTooltipAnimationSounds(soundId)
    elseif mode == PROVISIONER_MODE_FILLET then
        GAMEPAD_CRAFTING_RESULTS:SetTooltipAnimationSounds(SOUNDS.PROVISIONING_FILLET)
    end
end

function ZO_GamepadProvisioner:SetEnableSkillBar(enable)
    if enable then
        local craftingType = GetCraftingInteractionType()
        ZO_Skills_TieSkillInfoHeaderToCraftingSkill(self.skillInfoBar, craftingType)
    else
        ZO_Skills_UntieSkillInfoHeaderToCraftingSkill(self.skillInfoBar)
    end
end

function ZO_GamepadProvisioner:UpdateKeybindStrip()
    KEYBIND_STRIP:UpdateKeybindButtonGroup(self.keybindStripDescriptor)
end

function ZO_GamepadProvisioner:AddModeEntry(entry)
    self.modeList:AddEntry("ZO_GamepadItemEntryTemplate", entry)
end

function ZO_GamepadProvisioner:InitializeModeList()
    self.modeList = ZO_GamepadVerticalItemParametricScrollList:New(self.control:GetNamedChild("MaskContainerList"))
    self.modeList:SetAlignToScreenCenter(true)
    self.modeList:AddDataTemplate("ZO_GamepadItemEntryTemplate", ZO_SharedGamepadEntry_OnSetup, ZO_GamepadMenuEntryTemplateParametricListFunction)

    self.filletModeEntry = self:CreateModeEntry(SI_GAMEPAD_PROVISIONING_TAB_FILLET, PROVISIONER_MODE_FILLET, "EsoUI/Art/Crafting/Gamepad/gp_crafting_menuIcon_fillet.dds")

    local narrationInfo =
    {
        canNarrate = function()
            return GAMEPAD_PROVISIONER_ROOT_SCENE:IsShowing()
        end,
        headerNarrationFunction = function()
            return ZO_GamepadGenericHeader_GetNarrationText(self.header, self.headerData)
        end,
        footerNarrationFunction = function()
            local narrations = {}
            ZO_AppendNarration(narrations, ZO_WRIT_ADVISOR_GAMEPAD:GetNarrationText())
            return narrations
        end,
    }
    SCREEN_NARRATION_MANAGER:RegisterParametricList(self.modeList, narrationInfo)
end

function ZO_GamepadProvisioner:AddInventoryAdditionalFilterBySpecialIngredientType(specialIngredientType, additionalFilterFunction)
    if specialIngredientType == PROVISIONER_SPECIAL_INGREDIENT_TYPE_FILLET then
        if self.filletPanel.inventory then
            self.filletPanel.inventory.additionalFilter = additionalFilterFunction
        end
    end
end

function ZO_GamepadProvisioner:CreateModeEntry(name, mode, icon)
    local data = ZO_GamepadEntryData:New(GetString(name), icon)
    data:SetIconTintOnSelection(true)
    data.mode = mode
    return data
end

function ZO_GamepadProvisioner:IsSceneShowing()
    return SCENE_MANAGER:IsShowing(GAMEPAD_PROVISIONER_ROOT_SCENE_NAME) or SCENE_MANAGER:IsShowing(GAMEPAD_PROVISIONER_FILLET_SCENE_NAME) or SCENE_MANAGER:IsShowing(GAMEPAD_PROVISIONER_CREATION_SCENE_NAME)
end

function ZO_GamepadProvisioner:RefreshModeList(craftingType)
    self.modeList:Clear()
    self:AddModeEntry(self.filletModeEntry)

    local recipeCraftingSystem = GetTradeskillRecipeCraftingSystem(craftingType)
    local recipeCraftingSystemNameStringId = _G["SI_RECIPECRAFTINGSYSTEM"..recipeCraftingSystem]
    local recipeModeEntry = self:CreateModeEntry(recipeCraftingSystemNameStringId, PROVISIONER_MODE_CREATION, ZO_GetGamepadRecipeCraftingSystemMenuTextures(recipeCraftingSystem))
    self:AddModeEntry(recipeModeEntry)
    self.modeList:Commit()
end

function ZO_GamepadProvisioner:ConfigureFromSettings(settings)
    if self.settings ~= settings then
        self.settings = settings

        ZO_GamepadCraftingUtils_SetupGenericHeader(self, nil, settings.tabs)
        ZO_GamepadCraftingUtils_RefreshGenericHeader(self)

        self:DirtyRecipeList()
    end
end

function ZO_GamepadProvisioner:SetDefaultProvisioningSettings()
    self:ConfigureFromSettings(ZO_GamepadProvisioner.PROVISIONING_SETTINGS)
    GAMEPAD_PROVISIONER_CREATION_SCENE:SetInteractionInfo(self.provisionerStationInteraction)
end

function ZO_GamepadProvisioner:EmbedInCraftingScene(interactionInfo)
    --Set the provisioner interact scenes to have the interaction of the current crafting station so it doesn't terminate the crafting interaction
    --when we go into the provisioning UI
    GAMEPAD_PROVISIONER_CREATION_SCENE:SetInteractionInfo(interactionInfo)
    SCENE_MANAGER:Push(GAMEPAD_PROVISIONER_CREATION_SCENE_NAME)

    self:ConfigureFromSettings(ZO_GamepadProvisioner.EMBEDDED_SETTINGS)
end

function ZO_GamepadProvisioner:InitializeKeybindStripDescriptors()
    -- back descriptors for screen / options screen
    local startButton =
    {
        --Ethereal binds show no text, the name field is used to help identify the keybind when debugging. This text does not have to be localized.
        name = "Gamepad Provisioner Default Exit",
        alignment = KEYBIND_STRIP_ALIGN_LEFT,
        keybind = "UI_SHORTCUT_EXIT",
        order = -10000,
        callback = function()
            SCENE_MANAGER:ShowBaseScene()
        end,
        visible = function()
            return not ZO_CraftingUtils_IsPerformingCraftProcess()
        end,
        ethereal = true,
    }

    local backButton =
    {
        alignment = KEYBIND_STRIP_ALIGN_LEFT,
        name = GetString(SI_GAMEPAD_BACK_OPTION),
        keybind = "UI_SHORTCUT_NEGATIVE",
        order = -10000,
        callback = function()
            if ITEM_PREVIEW_GAMEPAD:IsInteractionCameraPreviewEnabled() then
                self:TogglePreviewMode()
            else
                SCENE_MANAGER:HideCurrentScene()
            end
        end,
        visible = function()
            return not ZO_CraftingUtils_IsPerformingCraftProcess()
        end,
    }

    -- recipe list keybinds
    self.mainKeybindStripDescriptor =
    {
        alignment = KEYBIND_STRIP_ALIGN_LEFT,

        -- Perform craft
        {
            name = function()
                local cost = 0
                local recipeData = self:GetRecipeData()
                if recipeData then
                    cost = GetCostToCraftProvisionerItem(recipeData.recipeListIndex, recipeData.recipeIndex)
                end
                return ZO_CraftingUtils_GetCostToCraftString(cost)
            end,
            keybind = "UI_SHORTCUT_SECONDARY",
            gamepadOrder = 1000,
            callback = function()
                self:Create(1)
            end,
            enabled = function()
                return self:ShouldCraftButtonBeEnabled()
            end,
        },

        -- Craft multiple
        {
            name = GetString(SI_GAMEPAD_CRAFT_MULTIPLE),
            keybind = "UI_SHORTCUT_QUATERNARY",
            gamepadOrder = 1010,
            callback = function()
                local itemLink = GetRecipeResultItemLink(self:GetRecipeIndices())
                ZO_GamepadCraftingUtils_ShowMultiCraftDialog(self, itemLink)
            end,
            enabled = function()
                return self:ShouldMultiCraftButtonBeEnabled()
            end,
        },

        -- Options (filtering)
        {
            name = GetString(SI_GAMEPAD_CRAFTING_OPTIONS),
            keybind = "UI_SHORTCUT_TERTIARY",
            gamepadOrder = 1020,
            callback = function()
                self:ShowOptionsMenu()
            end,
            visible = function()
                return not ZO_CraftingUtils_IsPerformingCraftProcess()
            end,
        },

        --Toggle Preview
        {
            name = function()
                if not ITEM_PREVIEW_GAMEPAD:IsInteractionCameraPreviewEnabled() then
                    return GetString(SI_CRAFTING_ENTER_PREVIEW_MODE)
                else
                    return GetString(SI_CRAFTING_EXIT_PREVIEW_MODE)
                end
            end,

            keybind = "UI_SHORTCUT_RIGHT_STICK",
            gamepadOrder = 1030,

            callback = function()
                self:TogglePreviewMode()
            end,

            visible = function()
                local recipeData = self:GetRecipeData()
                if recipeData then
                    return self:CanPreviewRecipe(recipeData)
                else
                    return false
                end
            end,
        },
    }

    ZO_GamepadCraftingUtils_AddListTriggerKeybindDescriptors(self.mainKeybindStripDescriptor, self.recipeList)
    table.insert(self.mainKeybindStripDescriptor, startButton)
    table.insert(self.mainKeybindStripDescriptor, backButton)

    ZO_CraftingUtils_ConnectKeybindButtonGroupToCraftingProcess(self.mainKeybindStripDescriptor)

    self.keybindStripDescriptor =
    {
        -- Select mode
        {
            keybind = "UI_SHORTCUT_PRIMARY",
            alignment = KEYBIND_STRIP_ALIGN_LEFT,

            name = function()
                return GetString(SI_GAMEPAD_SELECT_OPTION)
            end,

            callback = function()
                local targetData = self.modeList:GetTargetData()
                self:SetMode(targetData.mode)
            end,
        },
    }

    ZO_Gamepad_AddBackNavigationKeybindDescriptors(self.keybindStripDescriptor, GAME_NAVIGATION_TYPE_BUTTON)
    ZO_Gamepad_AddListTriggerKeybindDescriptors(self.keybindStripDescriptor, self.modeList)
end

function ZO_GamepadProvisioner:OnFilletSlotChanged()
    -- Needs to be overridden
end

function ZO_GamepadProvisioner:ShowOptionsMenu()
    local dialogData =
    {
        targetData = self.recipeList:GetTargetData(),
        filters =
        {
            [GAMEPAD_PROVISIONER_OPTION_FILTER_INGREDIENTS] = optionFilterIngredients,
            [GAMEPAD_PROVISIONER_OPTION_FILTER_SKILLS] = optionFilterSkills,
            [GAMEPAD_PROVISIONER_OPTION_FILTER_QUESTS] = optionFilterQuests,
        },
        finishedCallback = function()
            self:SaveFilters()
            GAMEPAD_TOOLTIPS:ClearTooltip(GAMEPAD_LEFT_TOOLTIP)
        end
    }
    if not self.craftingOptionsDialogGamepad then
        self.craftingOptionsDialogGamepad = ZO_CraftingOptionsDialogGamepad:New()
    end
    self.craftingOptionsDialogGamepad:ShowOptionsDialog(dialogData)
end

function ZO_GamepadProvisioner:SetupSavedVars()
    local defaults =
    {
        haveIngredientsChecked = false, 
        haveSkillsChecked = false, 
        questsOnlyChecked = false,
    }
    self.savedVars = ZO_SavedVars:New("ZO_Ingame_SavedVariables", 3, "GamepadProvisioner", defaults)

    optionFilterIngredients.checked = self.savedVars.haveIngredientsChecked
    optionFilterSkills.checked = self.savedVars.haveSkillsChecked
    optionFilterQuests.checked = self.savedVars.questsOnlyChecked

    self.control:UnregisterForEvent(EVENT_ADD_ON_LOADED)
end

function ZO_GamepadProvisioner:InitializeRecipeList()
    local listContainer = self.control:GetNamedChild("ContainerRecipe")
    self.recipeList = ZO_GamepadVerticalItemParametricScrollList:New(listContainer:GetNamedChild("List"))
    self.recipeList:SetAlignToScreenCenter(true)

    self.recipeList:SetNoItemText(GetString(SI_PROVISIONER_NO_RECIPES))

    local function MenuEntryTemplateEquality(left, right)
        return left.recipeListIndex == right.recipeListIndex and left.recipeIndex == right.recipeIndex and left.name == right.name
    end

    local function MenuEntryTemplateSetup(control, data, selected, reselectingDuringRebuild, enabled, active)
        ZO_SharedGamepadEntry_OnSetup(control, data, selected, reselectingDuringRebuild, enabled, active)
        local shouldHide = self.questRecipes[data.dataSource.resultItemId] ~= true
        control.questPin:SetHidden(shouldHide)
    end

    self.recipeList:AddDataTemplate("ZO_Provisioner_GamepadItemSubEntryTemplate", MenuEntryTemplateSetup, ZO_GamepadMenuEntryTemplateParametricListFunction, MenuEntryTemplateEquality)
    self.recipeList:AddDataTemplateWithHeader("ZO_Provisioner_GamepadItemSubEntryTemplate", MenuEntryTemplateSetup, ZO_GamepadMenuEntryTemplateParametricListFunction, MenuEntryTemplateEquality, "ZO_GamepadMenuEntryHeaderTemplate")

    self.recipeList:SetOnSelectedDataChangedCallback(function(list, selectedData)
        self:RefreshRecipeDetails(selectedData)
    end)

    local narrationInfo =
    {
        canNarrate = function()
            return GAMEPAD_PROVISIONER_CREATION_SCENE:IsShowing()
        end,
        headerNarrationFunction = function()
            return ZO_GamepadGenericHeader_GetNarrationText(self.header, self.headerData)
        end,
        footerNarrationFunction = function()
            local narrations = {}
            local skillInfoNarration = ZO_Skills_GetSkillInfoHeaderNarrationText(self.control:GetNamedChild("SkillInfo"))
            ZO_AppendNarration(narrations, skillInfoNarration)
            ZO_AppendNarration(narrations, ZO_WRIT_ADVISOR_GAMEPAD:GetNarrationText())
            return narrations
        end,
    }
    SCREEN_NARRATION_MANAGER:RegisterParametricList(self.recipeList, narrationInfo)

    ZO_WRIT_ADVISOR_GAMEPAD:RegisterCallback("CycleActiveQuest", function()
        if GAMEPAD_PROVISIONER_CREATION_SCENE:IsShowing() then
            if self.mode == PROVISIONER_MODE_CREATION or self.settings == ZO_GamepadProvisioner.EMBEDDED_SETTINGS then
                SCREEN_NARRATION_MANAGER:QueueParametricListEntry(self.recipeList)
            else
                SCREEN_NARRATION_MANAGER:QueueParametricListEntry(self.modeList)
            end
        end
    end)

    self.recipeList.control:SetHidden(true)
end

function ZO_GamepadProvisioner:SaveFilters()
    local filterChanged = self.savedVars.haveIngredientsChecked ~= optionFilterIngredients.checked or
                          self.savedVars.haveSkillsChecked ~= optionFilterSkills.checked or
                          self.savedVars.questsOnlyChecked ~= optionFilterQuests.checked
    if filterChanged then
        self.savedVars.haveIngredientsChecked = optionFilterIngredients.checked
        self.savedVars.haveSkillsChecked = optionFilterSkills.checked
        self.savedVars.questsOnlyChecked = optionFilterQuests.checked
        self:DirtyRecipeList()
        ZO_SavePlayerConsoleProfile()
    end
end

function ZO_GamepadProvisioner:InitializeDetails()
    self.ingredientsBar = ZO_GamepadCraftingIngredientBar:New(self.control:GetNamedChild("IngredientsBar"))

    self.ingredientsBar:AddDataTemplate("ZO_ProvisionerIngredientBarSlotTemplate", ZO_ProvisionerIngredientBarSlotTemplateSetup, ZO_ProvisionerIngredientBarSlotTemplateGetNarration)

    --Narration info for the tooltip for the item we are crafting
    local tooltipNarrationInfo =
    {
        canNarrate = function()
            return not self.resultTooltip:IsHidden()
        end,
        tooltipNarrationFunction = function()
            return self.resultTooltip.tip:GetNarrationText()
        end,
    }
    --Order matters. We register this BEFORE we register narration for the ingredients, so this gets narrated first.
    GAMEPAD_TOOLTIPS:RegisterCustomTooltipNarration(tooltipNarrationInfo)

    --Narration info for the ingredients for the item we are crafting
    local ingredientsNarrationInfo =
    {
        canNarrate = function()
            -- We don't use the visibility of ingredients bar here, as it seems to sometimes rely on the visibility of parent controls. 
            -- Instead, we use the result tooltip, as it should always be visible if the ingredients are
            return not self.resultTooltip:IsHidden()
        end,
        tooltipNarrationFunction = function()
            return { GetString(SI_GAMEPAD_PROVISIONER_INGREDIENT_BAR_HEADER_NARRATION), self.ingredientsBar:GetNarrationText() }
        end,
    }
    GAMEPAD_TOOLTIPS:RegisterCustomTooltipNarration(ingredientsNarrationInfo)
end

function ZO_GamepadProvisioner:SetDetailsEnabled(enabled)
    for i, data in ipairs(self.ingredientsBar.dataList) do
        local control = data.control
        ZO_ItemSlot_SetupTextUsableAndLockedColor(control.nameLabel, true, not enabled)
        ZO_ItemSlot_SetupTextUsableAndLockedColor(control.countLabel, true, not enabled)
        ZO_ItemSlot_SetupIconUsableAndLockedColor(control.iconControl, true, not enabled)
    end
end

function ZO_GamepadProvisioner:OnTabFilterChanged(filterType)
    if self.filterType ~= filterType then
        if self.filterType == PROVISIONER_SPECIAL_INGREDIENT_TYPE_FURNISHING then
            if SYSTEMS:GetObject("itemPreview"):IsInteractionCameraPreviewEnabled() then
                self:TogglePreviewMode()
            end
        end
        self.filterType = filterType
        self:DirtyRecipeList()
    end
end

function ZO_GamepadProvisioner:RefreshRecipeList()
    -- This function is called from inventory update events, which is when we need to update the header with the player's current and total inventory slots
    ZO_GamepadCraftingUtils_RefreshGenericHeaderData(self)
    self.recipeList:Clear()

    -- first construct the full table of filtered recipes
    local recipeDataEntries = {}

    local requireIngredients = optionFilterIngredients.checked
    local requireSkills = optionFilterSkills.checked
    local requireQuests = optionFilterQuests.checked
    local craftingInteractionType = GetCraftingInteractionType()
    local hasKnownRecipesInCurrentFilter = false

    local recipeLists = PROVISIONER_MANAGER:GetRecipeListData(craftingInteractionType)
    for _, recipeList in pairs(recipeLists) do
        for _, recipe in ipairs(recipeList.recipes) do
            if recipe.requiredCraftingStationType == craftingInteractionType and self.filterType == recipe.specialIngredientType then
                hasKnownRecipesInCurrentFilter = true
                if self:DoesRecipePassFilter(recipe.specialIngredientType, requireIngredients, recipe.maxIterationsForIngredients, requireSkills, recipe.tradeskillsLevelReqs, recipe.qualityReq, craftingInteractionType, recipe.requiredCraftingStationType, requireQuests, recipe.resultItemId) then
                    local dataEntry = ZO_GamepadEntryData:New(zo_strformat(SI_PROVISIONER_RECIPE_NAME_COUNT_NONE, recipe.name), recipe.iconFile, recipe.iconFile)
                    dataEntry:SetDataSource(recipe)
                    -- recipe.quality is deprecated, included here for addon backwards compatibility
                    dataEntry:SetNameColors(dataEntry:GetColorsBasedOnQuality(recipe.displayQuality or recipe.quality))
                    dataEntry:SetSubLabelColors(ZO_ERROR_COLOR, ZO_ERROR_COLOR)
                    dataEntry:SetStackCount(recipe.maxIterationsForIngredients)
                    dataEntry:SetSubLabelTemplate("ZO_ProvisioningSubLabelTemplate")

                    if not recipe.passesTradeskillLevelReqs or not recipe.passesQualityLevelReq or recipe.maxIterationsForIngredients == 0 then
                        dataEntry:SetIconTint(ZO_ERROR_COLOR, ZO_ERROR_COLOR)
                    end

                    if not recipe.passesTradeskillLevelReqs then
                        for tradeskill, levelReq in pairs(recipe.tradeskillsLevelReqs) do
                            local level = GetNonCombatBonus(GetNonCombatBonusLevelTypeForTradeskillType(tradeskill))
                            if level < levelReq then
                                local levelPassiveAbilityId = GetTradeskillLevelPassiveAbilityId(tradeskill)
                                local levelPassiveAbilityName = GetAbilityName(levelPassiveAbilityId)
                                dataEntry:AddSubLabel(zo_strformat(SI_RECIPE_REQUIRES_LEVEL_PASSIVE, levelPassiveAbilityName, levelReq))
                            end
                        end
                    end
                    --Only items that require only provisioning have a quality check
                    if not recipe.passesQualityLevelReq then
                        dataEntry:AddSubLabel(zo_strformat(SI_PROVISIONER_REQUIRES_RECIPE_QUALITY, recipe.qualityReq))
                    end

                    table.insert(recipeDataEntries, dataEntry)
                end
            end
        end
    end

    -- now iterate through the table so we can properly identify header breaks
    local lastRecipeListName = ""

    if hasKnownRecipesInCurrentFilter then
        self.recipeList:SetNoItemText(GetString(SI_PROVISIONER_NONE_MATCHING_FILTER))
    else
        self.recipeList:SetNoItemText(GetString(SI_PROVISIONER_NO_RECIPES))
    end

    for i, recipeData in ipairs(recipeDataEntries) do
        local nextRecipeData = recipeDataEntries[i + 1]
        local isNextEntryAHeader = nextRecipeData and nextRecipeData.recipeListName ~= recipeData.recipeListName

        local postSelectedOffsetAdditionalPadding = 0
        if isNextEntryAHeader then
            postSelectedOffsetAdditionalPadding = GAMEPAD_HEADER_SELECTED_PADDING
        end

        if recipeData.recipeListName ~= lastRecipeListName then
            lastRecipeListName = recipeData.recipeListName
            recipeData.header = lastRecipeListName

            self.recipeList:AddEntry("ZO_Provisioner_GamepadItemSubEntryTemplateWithHeader", recipeData, nil, isNextEntryAHeader and GAMEPAD_HEADER_DEFAULT_PADDING, GAMEPAD_HEADER_SELECTED_PADDING, postSelectedOffsetAdditionalPadding)
        else
            self.recipeList:AddEntry("ZO_Provisioner_GamepadItemSubEntryTemplate", recipeData, nil, isNextEntryAHeader and GAMEPAD_HEADER_DEFAULT_PADDING, nil, postSelectedOffsetAdditionalPadding)
        end
    end

    self.recipeList:Commit()
end

function ZO_GamepadProvisioner:TogglePreviewMode()
    ITEM_PREVIEW_GAMEPAD:ToggleInteractionCameraPreview(FRAME_TARGET_CRAFTING_GAMEPAD_FRAGMENT, FRAME_PLAYER_ON_SCENE_HIDDEN_FRAGMENT, GAMEPAD_NAV_QUADRANT_2_3_4_FURNITURE_ITEM_PREVIEW_OPTIONS_FRAGMENT)
    if ITEM_PREVIEW_GAMEPAD:IsInteractionCameraPreviewEnabled() then
        self.control:GetNamedChild("IngredientsBar"):SetHidden(true)
    elseif self.settings == ZO_GamepadProvisioner.PROVISIONING_SETTINGS and self.mode ~= PROVISIONER_MODE_CREATION then
        self.control:GetNamedChild("IngredientsBar"):SetHidden(true)
    else
        self.control:GetNamedChild("IngredientsBar"):SetHidden(false)
    end
    self:RefreshRecipeDetails(self:GetRecipeData())
    KEYBIND_STRIP:UpdateKeybindButtonGroup(self.mainKeybindStripDescriptor)
end

function ZO_GamepadProvisioner:RefreshRecipeDetails(selectedData)
    if selectedData then
        if self.mode == PROVISIONER_MODE_CREATION or self.settings == ZO_GamepadProvisioner.EMBEDDED_SETTINGS then
            if ITEM_PREVIEW_GAMEPAD:IsInteractionCameraPreviewEnabled() then
                if self:CanPreviewRecipe(selectedData) then
                    self:PreviewRecipe(selectedData)
                end
                self.resultTooltip:SetHidden(true)
            else
                self.resultTooltip:SetHidden(false)
            end
        else
            self.resultTooltip:SetHidden(true)
        end

        --update recipe tooltip
        local recipeListIndex, recipeIndex = selectedData.recipeListIndex, selectedData.recipeIndex
        self.resultTooltip.tip:ClearLines()
        self.resultTooltip.tip:SetProvisionerResultItem(recipeListIndex, recipeIndex)

        -- populate ingredients bar
        local numIngredients = selectedData.numIngredients

        self.ingredientsBar:Clear()
        for i = 1, numIngredients do
            local newData =
            {
                recipeListIndex = recipeListIndex,
                recipeIndex = recipeIndex,
                ingredientIndex = i,
            }
            self.ingredientsBar:AddEntry("ZO_ProvisionerIngredientBarSlotTemplate", newData)
        end

        self.ingredientsBar:Commit()
    else
        self.resultTooltip:SetHidden(true)

        self.ingredientsBar:Clear()
    end

    self:RefreshAnimationSounds()
    KEYBIND_STRIP:UpdateKeybindButtonGroup(self.mainKeybindStripDescriptor)
end

function ZO_GamepadProvisioner:GetRecipeData()
    return self.recipeList:GetTargetData()
end

--Overridden from base
function ZO_GamepadProvisioner:StartHide()
    self:ResetMode()
end

function ZO_GamepadProvisioner_Initialize(control)
    GAMEPAD_PROVISIONER = ZO_GamepadProvisioner:New(control)
end

--[[ Provision Templates ]]--

function ZO_ProvisionerIngredientBarSlotTemplate_OnInitialized(control)
    control.iconControl = control:GetNamedChild("Icon")
    control.nameLabel = control:GetNamedChild("IngredientName")
    control.countControl = control:GetNamedChild("Count")
    local DIVIDER_THICKNESS = 2
    control.countFractionDisplay = ZO_FractionDisplay:New(control.countControl, "ZoFontGamepad27", DIVIDER_THICKNESS)
    control.countFractionDisplay:SetHorizontalAlignment(TEXT_ALIGN_CENTER)
end

function ZO_ProvisionerIngredientBarSlotTemplateSetup(control, data)
    local name, icon, requiredQuantity, _, displayQuality = GetRecipeIngredientItemInfo(data.recipeListIndex, data.recipeIndex, data.ingredientIndex)
    local ingredientCount = GetCurrentRecipeIngredientCount(data.recipeListIndex, data.recipeIndex, data.ingredientIndex)

    control.nameLabel:SetText(zo_strformat(SI_TOOLTIP_ITEM_NAME, name))
    local r, g, b = GetInterfaceColor(INTERFACE_COLOR_TYPE_ITEM_QUALITY_COLORS, displayQuality)
    control.nameLabel:SetColor(r, g, b, 1)

    control.iconControl:SetTexture(icon)

    control.countFractionDisplay:SetValues(ingredientCount, requiredQuantity)

    local NOT_LOCKED = false
    ZO_ItemSlot_SetupIconUsableAndLockedColor(control.countLabel, ingredientCount >= requiredQuantity, NOT_LOCKED)
    ZO_ItemSlot_SetupIconUsableAndLockedColor(control.iconControl, ingredientCount >= requiredQuantity, NOT_LOCKED)
    ZO_ItemSlot_SetupIconUsableAndLockedColor(control.nameLabel, ingredientCount >= requiredQuantity, NOT_LOCKED)
end

function ZO_ProvisionerIngredientBarSlotTemplateGetNarration(control, data)
    local name, _, requiredQuantity = GetRecipeIngredientItemInfo(data.recipeListIndex, data.recipeIndex, data.ingredientIndex)
    local ingredientCount = GetCurrentRecipeIngredientCount(data.recipeListIndex, data.recipeIndex, data.ingredientIndex)
    return zo_strformat(SI_GAMEPAD_PROVISIONER_INGREDIENT_BAR_SLOT_NARRATION, requiredQuantity, ingredientCount, name)
end