-- Globals used by multiple crafting files
ZO_GAMEPAD_CRAFTING_UTILS_INGREDIENT_SLOT_AND_MARGIN_WIDTH = 231
ZO_GAMEPAD_CRAFTING_UTILS_INGREDIENT_SLOT_AND_MARGIN_HEIGHT = 205
ZO_GAMEPAD_CRAFTING_UTILS_FLOATING_PADDING_Y = 50
ZO_GAMEPAD_CRAFTING_UTILS_FLOATING_SLOT_STANDARD_HEIGHT = 190
ZO_GAMEPAD_CRAFTING_UTILS_FLOATING_BOTTOM_OFFSET = ZO_GAMEPAD_QUADRANT_BOTTOM_OFFSET - ZO_GAMEPAD_CRAFTING_UTILS_FLOATING_PADDING_Y

ZO_TRACKED_PIN_STATUS_ICON_OVERRIDE = 
{
    iconTexture = "EsoUI/Art/WritAdvisor/Gamepad/gp_advisor_trackedPin_icon.dds",
    iconNarration = GetString(SI_SCREEN_NARRATION_CRAFTING_QUEST_PIN_ICON_NARRATION),
}

ZO_DISABLED_TRACKED_PIN_STATUS_ICON_OVERRIDE =
{
    iconTexture = "EsoUI/Art/WritAdvisor/Gamepad/gp_advisor_trackedPin_icon_disabled.dds",
    iconNarration = GetString(SI_SCREEN_NARRATION_DISABLED_CRAFTING_QUEST_PIN_ICON_NARRATION),
}

ZO_IS_SLOTTED_STATUS_ICON_OVERRIDE =
{
    iconTexture = "EsoUI/Art/Inventory/Gamepad/gp_inventory_icon_equipped.dds",
    iconNarration = GetString(SI_SCREEN_NARRATION_SELECTED_ICON_NARRATION),
}

ZO_IS_IN_ARMORY_STATUS_ICON_OVERRIDE =
{
    iconTexture = ZO_IN_ARMORY_BUILD_ICON,
    iconNarration = GetString(SI_SCREEN_NARRATION_IN_ARMORY_BUILD_ICON_NARRATION),
}

ZO_IS_ACTIVELY_SCRIBED_STATUS_ICON_OVERRIDE =
{
    iconTexture = "EsoUI/Art/Crafting/Gamepad/gp_crafting_menuIcon_activeScribe.dds",
    iconNarration = GetString(SI_SCREEN_NARRATION_ACTIVE_ICON_NARRATION),
}

ZO_IS_CRAFTED_ABILITY_SCRIBED_STATUS_ICON_OVERRIDE =
{
    iconTexture = "EsoUI/Art/Skills/gamepad/gp_scribing_grimoire_equipped.dds",
    iconNarration = GetString(SI_SCREEN_NARRATION_ACTIVE_ICON_NARRATION),
}

-- Note: call this towards the end of your keybind setup function...if you call this before you do something like self.keybindStripDescriptor = {keybinds} you'll destroy these
function ZO_GamepadCraftingUtils_AddGenericCraftingBackKeybindsToDescriptor(keybindDescriptor)
    if keybindDescriptor == nil then
        keybindDescriptor = {}
    end

    local genericStartButton = {
            --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 Crafting 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 genericBackButton = {
            alignment = KEYBIND_STRIP_ALIGN_LEFT,
            name = GetString(SI_GAMEPAD_BACK_OPTION),
            keybind = "UI_SHORTCUT_NEGATIVE",
            order = -10000,
            callback = function()
                SCENE_MANAGER:HideCurrentScene()
            end,
            visible = function()
                return not ZO_CraftingUtils_IsPerformingCraftProcess()
            end
        }

    table.insert(keybindDescriptor, genericStartButton)
    table.insert(keybindDescriptor, genericBackButton)
end

function ZO_GamepadCraftingUtils_AddListTriggerKeybindDescriptors(descriptor, list, optionalHeaderComparator)
    local leftTrigger, rightTrigger = ZO_Gamepad_CreateListTriggerKeybindDescriptors(list, optionalHeaderComparator)

    local enabledFn = function()
        return not ZO_CraftingUtils_IsPerformingCraftProcess()
    end

    leftTrigger.enabled = enabledFn
    rightTrigger.enabled = enabledFn

    table.insert(descriptor, leftTrigger)
    table.insert(descriptor, rightTrigger)
end

-- Generic crafting header functions
function ZO_GamepadCraftingUtils_GetLineNameForCraftingType(craftingType)
    local craftingSkillLineData = SKILLS_DATA_MANAGER:GetCraftingSkillLineData(craftingType)
    if craftingSkillLineData then
        return craftingSkillLineData:GetFormattedName()
    end
    return ""
end

function ZO_GamepadCraftingUtils_InitializeGenericHeader(craftingObject, createTabBar)
    craftingObject.header = craftingObject.control:GetNamedChild("HeaderContainer"):GetNamedChild("Header")
    ZO_GamepadGenericHeader_Initialize(craftingObject.header, createTabBar)

    local function UpdateCapacity()
        if not craftingObject.control:IsHidden() then
            ZO_GamepadCraftingUtils_RefreshGenericHeader(craftingObject)
        end
    end

    craftingObject.control:RegisterForEvent(EVENT_INVENTORY_FULL_UPDATE, UpdateCapacity)
    craftingObject.control:RegisterForEvent(EVENT_INVENTORY_SINGLE_SLOT_UPDATE, UpdateCapacity)
end

function ZO_GamepadCraftingUtils_SetupGenericHeader(craftingObject, titleString, tabBarEntries, showCapacity, showItemSets)
    if tabBarEntries and #tabBarEntries == 1 then
        local tabBarFirstEntry = tabBarEntries[1]
        if tabBarFirstEntry.text then
            titleString = tabBarFirstEntry.text
        end
        if tabBarFirstEntry.callback then
            tabBarFirstEntry:callback()
        end
    end

    local function GetCapacity()
        return zo_strformat(SI_GAMEPAD_INVENTORY_CAPACITY_FORMAT, GetNumBagUsedSlots(BAG_BACKPACK), GetBagSize(BAG_BACKPACK))
    end

    local function GetUnlockedSets()
        local totalSets = GetNumConsolidatedSmithingSets()
        local unlockedSets = GetNumUnlockedConsolidatedSmithingSets()
        return zo_strformat(SI_SMITHING_CONSOLIDATED_STATION_ITEM_SETS_UNLOCKED_VALUE_FORMATTER, unlockedSets, totalSets)
    end

    craftingObject.headerData = { }

    --If showItemSets is true, we will display that *instead* of capacity
    if showItemSets then
        craftingObject.headerData.data1HeaderText = GetString(SI_SMITHING_CONSOLIDATED_STATION_ITEM_SETS_UNLOCKED_HEADER)
        craftingObject.headerData.data1Text = GetUnlockedSets
    elseif showCapacity or showCapacity == nil then
        craftingObject.headerData.data1HeaderText = GetString(SI_GAMEPAD_INVENTORY_CAPACITY)
        craftingObject.headerData.data1Text = GetCapacity
    end

    if tabBarEntries and #tabBarEntries > 1 then
        craftingObject.headerData.tabBarEntries = ZO_ShallowTableCopy(tabBarEntries)
        craftingObject.headerData.titleText = nil
    else
        craftingObject.headerData.tabBarEntries = nil
        craftingObject.headerData.titleText = titleString
    end
end

function ZO_GamepadCraftingUtils_SetGenericHeaderData2(craftingObject, headerText, text)
    craftingObject.headerData.data2HeaderText = headerText
    craftingObject.headerData.data2Text = text
end

function ZO_GamepadCraftingUtils_RefreshGenericHeader(craftingObject)
    ZO_GamepadGenericHeader_Refresh(craftingObject.header, craftingObject.headerData)
end

function ZO_GamepadCraftingUtils_RefreshGenericHeaderData(craftingObject)
    ZO_GamepadGenericHeader_RefreshData(craftingObject.header, craftingObject.headerData)
end

-- Crafting slot manipulation functions
-- Note: These amounts are scaled on both sides of the slot, so the slot will actually be scaled by twice the amount
local SELECTED_SLOT_X_SCALE_AMOUNT = 20
local SELECTED_SLOT_Y_SCALE_AMOUNT = 36

function ZO_GamepadCraftingUtils_ScaleSlotBackground(slot)
    if slot then
        local bg = slot:GetControl():GetNamedChild("Bg")
        local X_OFFSET_INDEX = 4
        local Y_OFFSET_INDEX = 5

        local _, point, relTo, relPoint, offsX, offsY = bg:GetAnchor(0)
        bg.savedAnchor1 = {point, relTo, relPoint, offsX, offsY}

        _, point, relTo, relPoint, offsX, offsY = bg:GetAnchor(1)
        bg.savedAnchor2 = {point, relTo, relPoint, offsX, offsY}

        bg:ClearAnchors()

        local newAnchor = ZO_Anchor:New(unpack(bg.savedAnchor1))
        newAnchor:SetOffsets(bg.savedAnchor1[X_OFFSET_INDEX] - SELECTED_SLOT_X_SCALE_AMOUNT, bg.savedAnchor1[Y_OFFSET_INDEX] - SELECTED_SLOT_Y_SCALE_AMOUNT)
        newAnchor:AddToControl(bg)

        newAnchor = ZO_Anchor:New(unpack(bg.savedAnchor2))
        newAnchor:SetOffsets(bg.savedAnchor2[X_OFFSET_INDEX] + SELECTED_SLOT_X_SCALE_AMOUNT, bg.savedAnchor2[Y_OFFSET_INDEX] + SELECTED_SLOT_Y_SCALE_AMOUNT)
        newAnchor:AddToControl(bg)
    end
end

function ZO_GamepadCraftingUtils_RestoreSlotBackground(slot)
    if slot then
        local bg = slot:GetControl():GetNamedChild("Bg")

        bg:ClearAnchors()

        local restoredAnchor = ZO_Anchor:New(unpack(bg.savedAnchor1))
        restoredAnchor:AddToControl(bg)

        restoredAnchor = ZO_Anchor:New(unpack(bg.savedAnchor2))
        restoredAnchor:AddToControl(bg)
    end
end

function ZO_GamepadCraftingUtils_PlaySlotBounceAnimation(slot)
    if slot then
        if not slot.control.bounceAnimation then
            slot.control.bounceAnimation = ANIMATION_MANAGER:CreateTimelineFromVirtual("ZO_CraftingSlot_Gamepad_Bounce", slot.control)
        end

        slot.control.bounceAnimation:PlayFromStart()
    end
end

function ZO_GamepadCraftingUtils_SelectOptionFromOptionList(craftingObject)
    if craftingObject.optionList then
        local targetOptionData = craftingObject.optionList:GetTargetData()
        if targetOptionData then
            targetOptionData.currentValue = not targetOptionData.currentValue
            craftingObject:RefreshOptionList()
            PlaySound(SOUNDS.DEFAULT_CLICK)
        end
    end
end

function ZO_GamepadCraftingUtils_CraftingTooltip_StandardFloatingBottomScreenResizeHandler(control)
    local maxHeight = control:GetBottom() - ZO_GAMEPAD_FLOATING_SCROLL_SAFE_TOOLTIP_TOP_OFFSET
    control:SetDimensionConstraints(0, 0, 0, maxHeight)
end

function ZO_GamepadCraftingUtils_CraftingTooltip_StandardFloatingCenterScreenResizeHandler(control)
    local maxHeight = GuiRoot:GetHeight() - ZO_GAMEPAD_PANEL_FLOATING_HEIGHT_DISCOUNT - (ZO_GAMEPAD_CRAFTING_UTILS_FLOATING_PADDING_Y * 2)
    control:SetDimensionConstraints(0, 0, 0, maxHeight)
end

function ZO_GamepadCraftingUtils_CraftingTooltip_Gamepad_Initialize(control, resizeHandler)
    local CRAFTING_TOOLTIP_OFFSET_X = -3
    ZO_ResizingFloatingScrollTooltip_Gamepad_OnInitialized(control, ZO_CRAFTING_TOOLTIP_STYLES, resizeHandler, RIGHT, CRAFTING_TOOLTIP_OFFSET_X)
end

do
    local GAMEPAD_SMITHING_FILTER_TO_ITEM_SLOT_TEXTURE =
    {
       [SMITHING_FILTER_TYPE_RAW_MATERIALS] = "EsoUI/Art/Crafting/Gamepad/gp_smithing_refine_emptySlot.dds",
       [SMITHING_FILTER_TYPE_WEAPONS] = "EsoUI/Art/Crafting/Gamepad/gp_smithing_weaponSlot.dds",
       [SMITHING_FILTER_TYPE_ARMOR] = "EsoUI/Art/Crafting/Gamepad/gp_smithing_apparelSlot.dds",
       [SMITHING_FILTER_TYPE_JEWELRY] = "EsoUI/Art/Crafting/Gamepad/gp_smithing_jewelrySlot.dds",
    }

    function ZO_GamepadCraftingUtils_GetItemSlotTextureFromSmithingFilter(smithingFilter)
        return GAMEPAD_SMITHING_FILTER_TO_ITEM_SLOT_TEXTURE[smithingFilter]
    end

    local GAMEPAD_SMITHING_DECONSTRUCTION_TYPE_TO_MULTIPLE_ITEMS_TEXTURE =
    {
       [SMITHING_DECONSTRUCTION_TYPE_RAW_MATERIALS] = "EsoUI/Art/Crafting/Gamepad/GP_smithing_refine_multiple_emptySlot.dds",
       [SMITHING_DECONSTRUCTION_TYPE_WEAPONS_AND_ARMOR] = "EsoUI/Art/Crafting/Gamepad/GP_smithing_multiple_armorWeaponSlot.dds",
       [SMITHING_DECONSTRUCTION_TYPE_ARMOR] = "EsoUI/Art/Crafting/Gamepad/GP_smithing_multiple_armorSlot.dds",
       [SMITHING_DECONSTRUCTION_TYPE_JEWELRY] = "EsoUI/Art/Crafting/Gamepad/GP_smithing_multiple_jewelrySlot.dds",
    }

    function ZO_GamepadCraftingUtils_GetMultipleItemsTextureFromSmithingDeconstructionType(deconstructionType)
        return internalassert(GAMEPAD_SMITHING_DECONSTRUCTION_TYPE_TO_MULTIPLE_ITEMS_TEXTURE[deconstructionType], "No multiple items texture for deconstruction type")
    end
end

ESO_Dialogs["CRAFTING_CREATE_MULTIPLE_GAMEPAD"] =
{
    gamepadInfo =
    {
        dialogType = GAMEPAD_DIALOGS.PARAMETRIC,
    },
    blockDialogReleaseOnPress = true, -- Don't release on Max items pressed
    setup = function(dialog, data)
        data.maxIterations = data.craftingObject:GetMultiCraftMaxIterations()
        data.minIterations = 1
        data.numIterations = data.minIterations
        local headerData =
        {
            data1 =
            {
                header = GetString(SI_GAMEPAD_INVENTORY_CAPACITY),
                value = zo_strformat(SI_GAMEPAD_INVENTORY_CAPACITY_FORMAT, GetNumBagUsedSlots(BAG_BACKPACK), GetBagSize(BAG_BACKPACK)),
            }
        }
        local NO_LIMIT_NUM_ENTRIES = nil
        dialog:setupFunc(NO_LIMIT_NUM_ENTRIES, headerData)
    end,
    title =
    {
        text = GetString(SI_GAMEPAD_CRAFT_MULTIPLE),
    },
    mainText = 
    {
        text = GetString(SI_GAMEPAD_CRAFT_MULTIPLE_DESCRIPTION),
    },
    parametricList =
    {
        {
            template = "ZO_GamepadMultiCraftSpinnerTemplate",
            templateData = {
                text = GetString(SI_GAMEPAD_QUANTITY_SPINNER_TEMPLATE_LABEL),
                setup = function(control, entryData, selected, selectedDuringRebuild, enabled, active)
                    local dialogData = entryData.dialog.data
                    if control.spinner == nil then
                        control.spinner = ZO_Spinner_Gamepad:New(control:GetNamedChild("Spinner"), 1, 1, GAMEPAD_SPINNER_DIRECTION_HORIZONTAL)
                    end
                    control.spinner:SetActive(selected)
                    control.spinner:SetStep(1)
                    control.spinner:SetMinMax(dialogData.minIterations, dialogData.maxIterations)
                    control.spinner:SetValue(dialogData.numIterations)
                    control.spinner:UnregisterAllCallbacks("OnValueChanged")
                    control.spinner:RegisterCallback("OnValueChanged", function(value)
                        dialogData.numIterations = value
                        ZO_GenericGamepadDialog_RefreshKeybinds(entryData.dialog)
                        SCREEN_NARRATION_MANAGER:QueueDialog(entryData.dialog)
                    end)
                    entryData.additionalInputNarrationFunction = function()
                        --Only narrate directional input if there is more than one possible value
                        if control.spinner:GetMin() ~= control.spinner:GetMax() then
                            return ZO_GetNumericHorizontalDirectionalInputNarrationData(control.spinner:IsDecreaseEnabled(), control.spinner:IsIncreaseEnabled())
                        else
                            return {}
                        end
                    end
                end,
                narrationText = function(entryData, entryControl)
                    return ZO_FormatSpinnerNarrationText(entryData.text, entryData.dialog.data.numIterations)
                end,
            },
        }
    },
    parametricListOnActivatedChangedCallback = function(list, isActive)
        if not isActive then
            local selectedControl = list:GetSelectedControl()
            if selectedControl and selectedControl.spinner then
                selectedControl.spinner:SetActive(false)
            end
        end
    end,
    buttons =
    {
        -- Craft
        {
            text = SI_DIALOG_CONFIRM,
            keybind = "DIALOG_PRIMARY",
            callback = function(dialog)
                dialog.data.craftingObject:Create(dialog.data.numIterations)
                ZO_Dialogs_ReleaseDialogOnButtonPress(dialog.name)
            end,
        },
        -- Cancel
        {
            text = SI_DIALOG_CANCEL,
            keybind = "DIALOG_NEGATIVE",
            callback = function(dialog)
                ZO_Dialogs_ReleaseDialogOnButtonPress(dialog.name)
            end,
        },
        -- Min/Max Quantity
        {
            text = function(dialog)
                if dialog.data.numIterations < dialog.data.maxIterations then
                    return GetString(SI_GAMEPAD_CRAFTING_QUANTITY_MAX)
                else
                    return GetString(SI_GAMEPAD_CRAFTING_QUANTITY_MIN)
                end
            end,
            keybind = "DIALOG_TERTIARY",
            callback = function(dialog)
                if dialog.data.numIterations < dialog.data.maxIterations then
                    dialog.data.numIterations = dialog.data.maxIterations
                else
                    dialog.data.numIterations = dialog.data.minIterations
                end
                ZO_GenericParametricListGamepadDialogTemplate_RefreshVisibleEntries(dialog)
            end,
        },
    }
}

function ZO_GamepadCraftingUtils_ShowMultiCraftDialog(craftingObject, resultItemLink)
    local nameColor = GetItemQualityColor(GetItemLinkDisplayQuality(resultItemLink))
    local itemName = nameColor:Colorize(GetItemLinkName(resultItemLink))

    ZO_Dialogs_ShowGamepadDialog("CRAFTING_CREATE_MULTIPLE_GAMEPAD", { craftingObject = craftingObject }, { mainTextParams = { itemName } })
end

ESO_Dialogs["CRAFTING_DECONSTRUCT_PARTIAL_STACK_GAMEPAD"] =
{
    gamepadInfo =
    {
        dialogType = GAMEPAD_DIALOGS.PARAMETRIC,
    },
    blockDialogReleaseOnPress = true, -- Don't release on Max items pressed
    setup = function(dialog, data)
        data.minIterations = 1
        data.iterations = data.maxIterations
        local headerData =
        {
            data1 =
            {
                header = GetString(SI_GAMEPAD_INVENTORY_CAPACITY),
                value = zo_strformat(SI_GAMEPAD_INVENTORY_CAPACITY_FORMAT, GetNumBagUsedSlots(BAG_BACKPACK), GetBagSize(BAG_BACKPACK)),
            }
        }
        local NO_LIMIT_NUM_ENTRIES = nil
        dialog:setupFunc(NO_LIMIT_NUM_ENTRIES, headerData)
    end,
    title =
    {
        text = function(dialog)
            return GetString("SI_DECONSTRUCTACTIONNAME_PERFORMMULTIPLE", dialog.data.verb)
        end,
    },
    mainText =
    {
        text = function(dialog)
            return GetString("SI_DECONSTRUCTACTIONNAME_CONFIRMSTACK", dialog.data.verb)
        end,
    },
    parametricList =
    {
        {
            template = "ZO_GamepadMultiDeconstructSpinnerTemplate",
            templateData = {
                text = GetString(SI_GAMEPAD_QUANTITY_SPINNER_TEMPLATE_LABEL),
                setup = function(control, entryData, selected, selectedDuringRebuild, enabled, active)
                    local dialogData = entryData.dialog.data
                    if control.spinner == nil then
                        control.spinner = ZO_Spinner_Gamepad:New(control:GetNamedChild("Spinner"), 1, 1, GAMEPAD_SPINNER_DIRECTION_HORIZONTAL)
                    end
                    control.spinner:SetActive(selected)
                    control.spinner:SetMinMax(dialogData.minIterations, dialogData.maxIterations)
                    control.spinner:SetValue(dialogData.iterations)
                    control.spinner:UnregisterAllCallbacks("OnValueChanged")
                    control.spinner:RegisterCallback("OnValueChanged", function(value)
                        dialogData.iterations = value
                        ZO_GenericGamepadDialog_RefreshKeybinds(entryData.dialog)
                        SCREEN_NARRATION_MANAGER:QueueDialog(entryData.dialog)
                    end)
                    entryData.additionalInputNarrationFunction = function()
                        --Only narrate directional input if there is more than one possible value
                        if control.spinner:GetMin() ~= control.spinner:GetMax() then
                            return ZO_GetNumericHorizontalDirectionalInputNarrationData(control.spinner:IsDecreaseEnabled(), control.spinner:IsIncreaseEnabled())
                        else
                            return {}
                        end
                    end
                end,
                narrationText = function(entryData, entryControl)
                    return ZO_FormatSpinnerNarrationText(entryData.text, entryData.dialog.data.iterations)
                end,
            },
        }
    },
    parametricListOnActivatedChangedCallback = function(list, isActive)
        if not isActive then
            local selectedControl = list:GetSelectedControl()
            if selectedControl and selectedControl.spinner then
                selectedControl.spinner:SetActive(false)
            end
        end
    end,
    buttons =
    {
        -- Deconstruct
        {
            text = SI_DIALOG_CONFIRM,
            keybind = "DIALOG_PRIMARY",
            callback = function(dialog)
                dialog.data.deconstructFn(dialog.data.iterations)

                ZO_Dialogs_ReleaseDialogOnButtonPress(dialog.name)
            end,
        },
        -- Cancel
        {
            text = SI_DIALOG_CANCEL,
            keybind = "DIALOG_NEGATIVE",
            callback = function(dialog)
                ZO_Dialogs_ReleaseDialogOnButtonPress(dialog.name)
            end,
        },
        -- Max Quantity
        {
            text = function(dialog)
                if dialog.data.iterations < dialog.data.maxIterations then
                    return GetString(SI_GAMEPAD_CRAFTING_QUANTITY_MAX)
                else
                    return GetString(SI_GAMEPAD_CRAFTING_QUANTITY_MIN)
                end
            end,
            keybind = "DIALOG_TERTIARY",
            callback = function(dialog)
                if dialog.data.iterations < dialog.data.maxIterations then
                    dialog.data.iterations = dialog.data.maxIterations
                else
                    dialog.data.iterations = dialog.data.minIterations
                end
                ZO_GenericParametricListGamepadDialogTemplate_RefreshVisibleEntries(dialog)
            end,
        },
    }
}

function ZO_GamepadCraftingUtils_ShowDeconstructPartialStackDialog(bagId, slotIndex, maxIterations, deconstructFn, verb)
    local displayQuality = GetItemDisplayQuality(bagId, slotIndex)
    local nameColor = GetItemQualityColor(displayQuality)
    local itemName = nameColor:Colorize(GetItemName(bagId, slotIndex))
    local data =
    {
        bagId = bagId,
        slotIndex = slotIndex,
        maxIterations = maxIterations,
        deconstructFn = deconstructFn,
        verb = verb
    }
    ZO_Dialogs_ShowGamepadDialog("CRAFTING_DECONSTRUCT_PARTIAL_STACK_GAMEPAD", data, { mainTextParams = { itemName } })
end

--[[ Gamepad Crafting Ingredient Bar ]]--
ZO_GamepadCraftingIngredientBar = ZO_InitializingObject:Subclass()

function ZO_GamepadCraftingIngredientBar:Initialize(control, isVertical)
    self.control = control

    self.slotCenterControl = self.control:GetNamedChild("SlotCenter")
    self.isVertical = isVertical

    self.dataTypes = {}
    self:Clear()
end

function ZO_GamepadCraftingIngredientBar:Clear()
    self.dataList = {}

    if self.dataTypes then
        for key, dataTypeInfo in pairs(self.dataTypes) do
            dataTypeInfo.pool:ReleaseAllObjects()
        end
    end
end

function ZO_GamepadCraftingIngredientBar:AddDataTemplate(templateName, setupFunction, narrationFunction)
    if not self.dataTypes[templateName] then
        local dataTypeInfo =
        {
            pool = ZO_ControlPool:New(templateName, self.slotCenterControl),
            setupFunction = setupFunction,
            narrationFunction = narrationFunction,
        }
        self.dataTypes[templateName] = dataTypeInfo
    end
end

function ZO_GamepadCraftingIngredientBar:AddEntry(templateName, data)
    local dataTypeInfo = self.dataTypes[templateName]
    if dataTypeInfo then
        table.insert(self.dataList, data)

        local control, key = dataTypeInfo.pool:AcquireObject()
        control.key = key
        control.templateName = templateName

        data.control = control
        data.narrationFunction = dataTypeInfo.narrationFunction

        dataTypeInfo.setupFunction(control, data)
    end
end

function ZO_GamepadCraftingIngredientBar:Commit()
    local numIngredients = #self.dataList
    if self.isVertical then
        -- Start the topmost slot at the height of all slots. This way the collection of slots will be visually centered
        local offsetY = (numIngredients - 1) * -ZO_GAMEPAD_CRAFTING_UTILS_INGREDIENT_SLOT_AND_MARGIN_HEIGHT

        for i, data in ipairs(self.dataList) do
            data.control:SetAnchor(CENTER, self.slotCenterControl, CENTER, 0, offsetY)
            -- Anchor the next slot to the bottom of this one
            offsetY = offsetY + ZO_GAMEPAD_CRAFTING_UTILS_INGREDIENT_SLOT_AND_MARGIN_HEIGHT
        end
    else
        -- Start the leftmost slot at the width of all slots halved. This way the collection of slots will be visually centered
        local offsetX = (numIngredients - 1) * -ZO_GAMEPAD_CRAFTING_UTILS_INGREDIENT_SLOT_AND_MARGIN_WIDTH * 0.5

        for i, data in ipairs(self.dataList) do
            data.control:SetAnchor(CENTER, self.slotCenterControl, CENTER, offsetX, 0)
            -- Anchor the next slot to the right of this one
            offsetX = offsetX + ZO_GAMEPAD_CRAFTING_UTILS_INGREDIENT_SLOT_AND_MARGIN_WIDTH
        end
    end
end

function ZO_GamepadCraftingIngredientBar:GetNarrationText()
    local narrations = {}
    for i, data in ipairs(self.dataList) do
        if data.narrationFunction then
            table.insert(narrations, data.narrationFunction(data.control, data))
        end
    end
    return narrations
end

function ZO_GamepadCraftingUtils_SetEntryDataSlotted(data, isSlotted)
    -- data should be used with ZO_SharedGamepadEntry_OnSetup
    if isSlotted then
        data.overrideStatusIndicatorIcons = { ZO_IS_SLOTTED_STATUS_ICON_OVERRIDE }
    else
        data.overrideStatusIndicatorIcons = nil
    end
end

function ZO_GamepadCraftingUtils_AddOverridesEntryData(data)
    -- Check if item is in armory or is researchable and add icons as appropriate
    if data.overrideStatusIndicatorIcons then
        if data.isInArmory then
            table.insert(data.overrideStatusIndicatorIcons, ZO_IS_IN_ARMORY_STATUS_ICON_OVERRIDE)
        end
        if data.traitInformation ~= ITEM_TRAIT_INFORMATION_NONE and not data.ignoreTraitInformation then
            local traitData =
            {
                iconTexture = ZO_GetPlatformTraitInformationIcon(data.traitInformation),
                iconNarration = GetString("SI_ITEMTRAITINFORMATION", data.traitInformation),
            }
            table.insert(data.overrideStatusIndicatorIcons, traitData)
        end
    end
end
