-- Settings.
local MAX_SEND_ATTACHMENTS = MAIL_MAX_ATTACHED_ITEMS
local EMPTY_ATTACHMENT_ICON = "EsoUI/Art/Mail/Gamepad/gp_mailMenu_attachItem.dds"
local SEND_GOLD_ICON = "EsoUI/Art/Mail/Gamepad/gp_mailMenu_sendGold.dds"
local REQUEST_GOLD_ICON = "EsoUI/Art/Mail/Gamepad/gp_mailMenu_requestGold.dds"
local ATTACHING_GOLD = "attach"
local REQUESTING_GOLD = "request"

-- For a given inventory item, return the attachment slot index, or nil if the item is not attached.
local function GetItemAttachedIndex(bagId, slotIndex)
    for i = 1, MAIL_MAX_ATTACHED_ITEMS do
        local queuedBagId, queuedSlotIndex = GetQueuedItemAttachmentInfo(i)
        if queuedBagId == bagId and queuedSlotIndex == slotIndex then
            return i
        end
    end
    return nil -- Item not attached.
end

-- Returns whether the specified inventory item is attached.
local function IsItemAttached(bagId, slotIndex)
    if GetItemAttachedIndex(bagId, slotIndex) then
        return true
    else
        return false
    end
end

-- Returns the next open attachment slot index, or nil if all attachment slots are full.
local function GetNextOpenAttachIndex()
    for i = 1, MAIL_MAX_ATTACHED_ITEMS do
        local queuedFromBag = GetQueuedItemAttachmentInfo(i)
        if queuedFromBag == 0 then -- Slot is open.
            return i
        end
    end
    return nil -- No open slots.
end

-- Returns whether there is any item attached.
local function IsAnyItemAttached(bagId, slotIndex)
    for i = 1, MAIL_MAX_ATTACHED_ITEMS do
        local queuedFromBag = GetQueuedItemAttachmentInfo(i)
        if queuedFromBag ~= 0 then -- Slot is filled.
            return true
        end
    end
    return false
end

-- Removes the item attached in the specified slot, behaving as design requested.
local function RemoveQueuedAttachment(attachmentIndex)
    RemoveQueuedItemAttachment(attachmentIndex)
    for i = attachmentIndex + 1, MAIL_MAX_ATTACHED_ITEMS do
        local queuedBagId, queuedSlotIndex = GetQueuedItemAttachmentInfo(i)
        if queuedBagId ~= 0 then -- Slot is filled.
            RemoveQueuedItemAttachment(i)
            QueueItemAttachment(queuedBagId, queuedSlotIndex, i-1)
        end 
    end
end

-- Returns the proper platform-dependent text for the "To" field
local function GetDefaultAddresseeText()
    local addresseeText

    if IsConsoleUI() then
        addresseeText = zo_strformat(GetString(SI_GAMEPAD_MAIL_DEFAULT_ADDRESSEE), ZO_GetPlatformAccountLabel())
    else
        addresseeText = GetString(SI_REQUEST_NAME_DEFAULT_TEXT)
    end

    return addresseeText
end

-- The main class.
ZO_MailSend_Gamepad = ZO_InitializingObject:Subclass()

function ZO_MailSend_Gamepad:Initialize(control)
    self.control = control
    self.sendControl = self.control:GetNamedChild("Send")
    self:InitializeFragment()
end

function ZO_MailSend_Gamepad:OnShowing()
    self:PerformDeferredInitialization()

    local TRIGGER_CALLBACK = true
    self.inventoryList:RefreshList(TRIGGER_CALLBACK)
    self:PopulateMainList()
    self:ConnectShownEvents()

    self:EnterOutbox()
    self:UpdateMoneyAttachment()
    ZO_MailSend_Shared.RestorePendingMail(self)

    if self.initialContact or self.initialSubject then
        self:ClearFields()
        self.mailView:Display(nil, nil, self.initialContact, self.initialSubject)
        self.initialContact = nil
        self.initialSubject = nil
    end

    if self.initialBodyInsertText then
        self.mailView.bodyEdit.edit:InsertText(self.initialBodyInsertText)
        self.initialBodyInsertText = nil
    end

    self:HighlightActiveTextField()
end

function ZO_MailSend_Gamepad:OnHidden()
    self:Reset()
    self:DisconnectShownEvent()
    MAIL_GAMEPAD:DeactivateTextSearch()
end

function ZO_MailSend_Gamepad:PerformDeferredInitialization()
    if self.initialized then return end
    self.initialized = true

    self:InitializeControls()
    self:InitializeInventoryList()
    self:InitializeHeader()
    self:InitializeMainList()
    self:InitializeContactsList()
    self:InitializeEvents()
    self:InitializeKeybindDescriptors()
end

function ZO_MailSend_Gamepad:InitializeControls()
    -- Sending Mail
    self.loadingBox = self.control:GetNamedChild("Loading")
    self.loadingLabel = self.loadingBox:GetNamedChild("ContainerText")

    -- Mail View
    self.mailView = self.sendControl:GetNamedChild("RightPane"):GetNamedChild("Container"):GetNamedChild("MailView")
    local IS_OUTBOX = true
    self.mailView:Initialize(GetString(SI_GAMEPAD_MAIL_SEND_TO), EMPTY_ATTACHMENT_ICON, IS_OUTBOX, ZO_MAIL_COD_MONEY_OPTIONS_GAMEPAD, ZO_MAIL_ATTACHED_MONEY_OPTIONS_GAMEPAD, MAX_SEND_ATTACHMENTS)
    self.mailView:Clear()
    self.mailView.subjectEdit.edit:SetMaxInputChars(MAIL_MAX_SUBJECT_CHARACTERS)
    self.mailView.bodyEdit.edit:SetMaxInputChars(MAIL_MAX_BODY_CHARACTERS)

    -- Gold Slider
    self.goldSliderControl = self.sendControl:GetNamedChild("GoldSliderBox")
    self.goldSlider = ZO_CurrencySelector_Gamepad:New(self.goldSliderControl:GetNamedChild("Selector"))
    self.goldSlider:SetCurrencyType(CURT_MONEY)
    self.goldSlider:SetClampValues(true)
    self.goldSlider:RegisterCallback("OnValueChanged", function() MAIL_GAMEPAD:RefreshKeybind() end)
end

function ZO_MailSend_Gamepad:InitializeFragment()
    GAMEPAD_MAIL_SEND_FRAGMENT = ZO_FadeSceneFragment:New(ZO_Mail_Gamepad_TopLevelSend)
    GAMEPAD_MAIL_SEND_FRAGMENT:RegisterCallback("StateChange", function(oldState, newState)
        if newState == SCENE_FRAGMENT_SHOWING then
            self:OnShowing()
        elseif newState == SCENE_SHOWN then
            if self.pendingMailChanged then
                ZO_Dialogs_ShowGamepadDialog("MAIL_ATTACHMENTS_CHANGED")
                self.pendingMailChanged = nil
            end
        elseif newState == SCENE_FRAGMENT_HIDDEN then
            ZO_MailSend_Shared.SavePendingMail()
            self:OnHidden()
        end
    end)
end

function ZO_MailSend_Gamepad:ConnectShownEvents()
    self.control:RegisterForEvent(EVENT_MAIL_COD_CHANGED, function() self:UpdateMoneyAttachment() end)
    self.control:RegisterForEvent(EVENT_MAIL_ATTACHED_MONEY_CHANGED, function() self:UpdateMoneyAttachment() end)
    self.control:RegisterForEvent(EVENT_MONEY_UPDATE, function() self:UpdatePostageMoney() end)

    local function HandleInventoryChanged()
        self:PopulateMainList()
    end

    self.control:RegisterForEvent(EVENT_INVENTORY_FULL_UPDATE, HandleInventoryChanged)
    self.control:RegisterForEvent(EVENT_INVENTORY_SINGLE_SLOT_UPDATE, HandleInventoryChanged)
end

function ZO_MailSend_Gamepad:DisconnectShownEvent()
    self.control:UnregisterForEvent(EVENT_MAIL_COD_CHANGED)
    self.control:UnregisterForEvent(EVENT_MAIL_ATTACHED_MONEY_CHANGED)
    self.control:UnregisterForEvent(EVENT_MONEY_UPDATE)
    self.control:UnregisterForEvent(EVENT_INVENTORY_FULL_UPDATE)
    self.control:UnregisterForEvent(EVENT_INVENTORY_SINGLE_SLOT_UPDATE)
end

function ZO_MailSend_Gamepad:InitializeEvents()
    self.control:RegisterForEvent(EVENT_MAIL_ATTACHMENT_ADDED, function(_, attachSlot) self:OnMailAttachmentAdded(attachSlot) end)
    self.control:RegisterForEvent(EVENT_MAIL_ATTACHMENT_REMOVED, function(_, attachSlot) self:OnMailAttachmentRemoved(attachSlot) end)
    self.control:RegisterForEvent(EVENT_MAIL_SEND_SUCCESS, function() self:OnMailSendSuccess() end)
    self.control:RegisterForEvent(EVENT_MAIL_SEND_FAILED, function(_, ...) self:OnMailSendFailure(...) end)
end

--Global API

function ZO_MailSend_Gamepad:ComposeMailTo(address, subject)
    self.initialContact = address
    self.initialSubject = subject
    local PUSH_SCENE = true
    MAIL_GAMEPAD:ShowTab(ZO_MAIL_TAB_INDEX.SEND, PUSH_SCENE)
end

function ZO_MailSend_Gamepad:InsertBodyText(text)
    self.initialBodyInsertText = text
    local PUSH_SCENE = true
    MAIL_GAMEPAD:ShowTab(ZO_MAIL_TAB_INDEX.SEND, PUSH_SCENE)
end

function ZO_MailSend_Gamepad:IsMailValid()
    local to = self.mailView:GetAddress()
    if (not to) or (to == "") then
        return false
    end
    local subject = self.mailView:GetSubject()
    local hasSubject = subject and (subject ~= "")
    local body = self.mailView:GetBody()
    local hasBody = body and (body ~= "")
    return hasSubject or hasBody or (GetQueuedMoneyAttachment() > 0) or IsAnyItemAttached()
end

function ZO_MailSend_Gamepad:InitializeKeybindDescriptors()
    -- Main list.
    self.mainKeybindDescriptor =
    {
        alignment = KEYBIND_STRIP_ALIGN_LEFT,

        -- Back
        KEYBIND_STRIP:GetDefaultGamepadBackButtonDescriptor(),

        -- Select
        {
            name = GetString(SI_GAMEPAD_SELECT_OPTION),
            keybind = "UI_SHORTCUT_PRIMARY",
            callback = function()
                local targetData = self.mainList:GetTargetData()
                if targetData and targetData.actionFunction then
                    targetData:actionFunction()
                end
            end,
            enabled = function()
                local isEnabled = true

                local targetData = self.mainList:GetTargetData()
                local validEntry = self:IsMailValid()

                if targetData and targetData.text == GetString(SI_MAIL_SEND_SEND) then
                    isEnabled = validEntry
                end

                for i = 1, self.mainList:GetNumItems() do
                    local itemData = self.mainList:GetDataForDataIndex(i)
                    if itemData.text and itemData.text == GetString(SI_MAIL_SEND_SEND) then
                        itemData.disabled = not validEntry
                        break
                    end
                end

                return isEnabled
            end,
        },

        {
            keybind = "UI_SHORTCUT_SECONDARY",
            name = function()
                local targetData = self.mainList:GetTargetData()
                return targetData.secondaryCallbackName
            end,
            callback = function()
                local targetData = self.mainList:GetTargetData()
                targetData:secondaryCallback()
            end,
            visible = function()
                local targetData = self.mainList:GetTargetData()
                return targetData and targetData.secondaryCallback ~= nil
            end,
        },

        -- Clear
        {
            name = GetString(SI_GAMEPAD_MAIL_SEND_CLEAR),
            keybind = "UI_SHORTCUT_TERTIARY",
            callback = function() ZO_Dialogs_ShowGamepadDialog("CONFIRM_CLEAR_MAIL_COMPOSE", { callback = function() self:ClearFields() end }) end
        },
    }
    ZO_Gamepad_AddListTriggerKeybindDescriptors(self.mainKeybindDescriptor, self.mainList)

    -- Slider Edit
    self.sliderKeybindDescriptor =
    {
        alignment = KEYBIND_STRIP_ALIGN_LEFT,

        -- Accept
        {
            name = GetString(SI_GAMEPAD_MAIL_SEND_ACCEPT_MONEY),
            keybind = "UI_SHORTCUT_PRIMARY",
            callback = function()
                if self.goldMode == ATTACHING_GOLD then
                    QueueCOD(0)
                    QueueMoneyAttachment(self.goldSlider:GetValue())
                elseif self.goldMode == REQUESTING_GOLD then
                    QueueMoneyAttachment(0)
                    QueueCOD(self.goldSlider:GetValue())
                end

                ZO_PlayCurrencyTransactSound(CURT_MONEY)

                self:UpdatePostageMoney()
                self:EnterOutbox()
                local targetControl = self.mainList:GetTargetControl()
                targetControl:SetHidden(false)
            end,
            visible = function() return self.goldSlider:GetValue() <= self.goldSlider:GetMaxValue() end,
        },

        -- Cancel
        KEYBIND_STRIP:GenerateGamepadBackButtonDescriptor(function()
            self.mainList:WhenInactiveSetTargetControlHidden(true)
            self:UpdateMoneyAttachment()
            self:EnterOutbox()
        end),
    }

    -- Contacts List
    self.contactsKeybindDescriptor =
    {
        alignment = KEYBIND_STRIP_ALIGN_LEFT,

        -- Accept
        {
            name = GetString(SI_GAMEPAD_MAIL_SEND_ACCEPT_MONEY),
            keybind = "UI_SHORTCUT_PRIMARY",
            callback = function()
                self:EnterOutbox()
                local selectedItem = self.contactsList:GetTargetData()
                if selectedItem.actionFunction then
                    selectedItem.actionFunction(selectedItem)
                end
                            
            end,
        },

        -- Cancel
        KEYBIND_STRIP:GenerateGamepadBackButtonDescriptor(function()
            self:UpdateMoneyAttachment()
            self:EnterOutbox()
        end),
    }
    ZO_Gamepad_AddListTriggerKeybindDescriptors(self.contactsKeybindDescriptor, self.contactsList)

    -- Inventory
    self.inventoryKeybindDescriptor =
    {
        alignment = KEYBIND_STRIP_ALIGN_LEFT,

        -- Add to Mail/Remove from Mail
        {
            name = function()
                local selectedItem = self.inventoryList:GetTargetData()
                if IsItemAttached(selectedItem.bagId, selectedItem.slotIndex) then
                    return GetString(SI_GAMEPAD_MAIL_SEND_DETACH_ITEM)
                else
                    return GetString(SI_GAMEPAD_MAIL_SEND_ATTACH_ITEM)
                end
            end,
            keybind = "UI_SHORTCUT_PRIMARY",
            callback = function()
                local selectedItem = self.inventoryList:GetTargetData()
                local bagId = selectedItem.bagId
                local slotIndex = selectedItem.slotIndex
                local attachedSlotIndex = GetItemAttachedIndex(bagId, slotIndex)
                if attachedSlotIndex then -- Item is attached, detach it.
                    RemoveQueuedAttachment(attachedSlotIndex)
                    local soundCategory = GetItemSoundCategory(bagId, slotIndex)
                    PlayItemSound(soundCategory, ITEM_SOUND_ACTION_UNEQUIP)
                else -- Item is not attached, attach it.
                    attachedSlotIndex = GetNextOpenAttachIndex()
                    if attachedSlotIndex then
                        QueueItemAttachment(bagId, slotIndex, attachedSlotIndex)
                        local soundCategory = GetItemSoundCategory(bagId, slotIndex)
                        PlayItemSound(soundCategory, ITEM_SOUND_ACTION_EQUIP)
                    end
                end
                SCREEN_NARRATION_MANAGER:QueueParametricListEntry(self.inventoryList.list)
            end,
            visible = function()
                local selectedItem = self.inventoryList:GetTargetData()
                if not selectedItem then
                    return false
                end

                local bagId = selectedItem.bagId
                local slotIndex = selectedItem.slotIndex

                if IsItemAttached(bagId, slotIndex) then
                    return true -- Can always remove an attached item.
                end

                local attachedSlotIndex = GetNextOpenAttachIndex()
                if not attachedSlotIndex then
                    return false
                end

                local canAttach = CanQueueItemAttachment(bagId, slotIndex, attachedSlotIndex)
                return canAttach
            end,
        },

        -- Back
        KEYBIND_STRIP:GenerateGamepadBackButtonDescriptor(function()
            self:OnInventoryListBackButtonClicked()
        end),
    }
end

function ZO_MailSend_Gamepad:OnInventoryListBackButtonClicked()
    self:UpdatePostageMoney()
    self:EnterOutbox()
end

local function UpdatePlayerGold(control)
    ZO_CurrencyControl_SetSimpleCurrency(control, CURT_MONEY, GetCurrencyAmount(CURT_MONEY, CURRENCY_LOCATION_CHARACTER), ZO_MAIL_HEADER_MONEY_OPTIONS_GAMEPAD)
    return true
end

local function UpdatePostage(control)
    ZO_CurrencyControl_SetSimpleCurrency(control, CURT_MONEY, GetQueuedMailPostage(), ZO_MAIL_HEADER_MONEY_OPTIONS_GAMEPAD)
    return true
end

local function GetPostageNarration()
    return ZO_Currency_FormatGamepad(CURT_MONEY, GetQueuedMailPostage(), ZO_CURRENCY_FORMAT_AMOUNT_NAME)
end

function ZO_MailSend_Gamepad:InitializeHeader()
    self.mainHeaderData =
    {
        data1HeaderText = GetString(SI_GAMEPAD_MAIL_INBOX_PLAYER_GOLD),
        data1Text = UpdatePlayerGold,
        data1TextNarration = ZO_Currency_GetPlayerCarriedGoldCurrencyNameNarration,

        data2HeaderText = GetString(SI_GAMEPAD_MAIL_SEND_POSTAGE_LABEL),
        data2Text = UpdatePostage,
        data2TextNarration = GetPostageNarration,

        tabBarEntries = MAIL_GAMEPAD.tabBarEntries,
    }

    self.setFieldHeaderData =
    {
        data1HeaderText = GetString(SI_GAMEPAD_MAIL_INBOX_PLAYER_GOLD),
        data1Text = UpdatePlayerGold,
        data1TextNarration = ZO_Currency_GetPlayerCarriedGoldCurrencyNameNarration,

        data2HeaderText = GetString(SI_GAMEPAD_MAIL_SEND_POSTAGE_LABEL),
        data2Text = UpdatePostage,
        data2TextNarration = GetPostageNarration,
    }
end

local function InventorySetupFunction(entryData)
    entryData.isMailAttached = IsItemAttached(entryData.bagId, entryData.slotIndex)
    entryData:SetIgnoreTraitInformation(true)
end

local function ItemFilterFunction(entryData)
    local bagId = entryData.bagId
    local slotIndex = entryData.slotIndex
    return (CanQueueItemAttachment(bagId, slotIndex) or IsItemAttached(bagId, slotIndex)) and not entryData.isPlayerLocked
end

local SETUP_LOCALLY = true
function ZO_MailSend_Gamepad:InitializeInventoryList()
    local function OnRefreshList(list)
        if list:GetNumItems() == 0 then
            MAIL_GAMEPAD:RequestEnterHeader()
        else
            MAIL_GAMEPAD:RequestLeaveHeader()
        end
    end

    self.inventoryList = MAIL_GAMEPAD:AddList("Inventory", SETUP_LOCALLY, ZO_GamepadInventoryList, BAG_BACKPACK, SLOT_TYPE_ITEM, function(...) self:InventorySelectionChanged(...) end, InventorySetupFunction)
    self.inventoryList:SetOnRefreshListCallback(OnRefreshList)
    self.inventoryList:SetSearchContext("mailTextSearch")
    self.inventoryList:SetItemFilterFunction(ItemFilterFunction)
    self.inventoryList:SetNoItemText(GetString(SI_GAMEPAD_INVENTORY_EMPTY))

    MAIL_GAMEPAD:SetOnBackButtonCallback(function() self:OnInventoryListBackButtonClicked() end)
    MAIL_GAMEPAD:SetTextSearchEntryHidden(true)
    self.inventoryListControl = self.inventoryList:GetControl()
end

function ZO_MailSend_Gamepad:AddMainListEntry(text, header, icon, callback, secondaryCallbackName, secondaryCallback, narrationText)
    local newEntry = ZO_GamepadEntryData:New(text, icon)
    newEntry.actionFunction = callback
    newEntry.secondaryCallbackName = secondaryCallbackName
    newEntry.secondaryCallback = secondaryCallback
    newEntry.narrationText = narrationText

    local template
    if header then
        newEntry:SetHeader(header)
        template = "ZO_GamepadMenuEntryTemplateWithHeader"
    else
        template = "ZO_GamepadMenuEntryTemplate"
    end

    newEntry:SetIconTintOnSelection(true)
    self.mainList:AddEntry(template, newEntry)
end

function ZO_MailSend_Gamepad:PopulateMainList()
    local function RefreshKeybind()
        MAIL_GAMEPAD:RefreshKeybind()
    end

    self.mainList:Clear()

    self.onUserListDialogIdSelectedForMailTo = function(hasResult, displayName, consoleId)
        local editControl = self.mailView.addressEdit.edit
        if hasResult then
            editControl:SetText(displayName)
        end
    end

    local NO_HEADER = nil
    local NO_ICON = nil
    local NO_SECONDARY_CALLBACK_NAME = nil
    local NO_SECONDARY_CALLBACK = nil

    do
        local userListCallback = function()
            local INCLUDE_ONLINE_FRIENDS = true
            local INCLUDE_OFFLINE_FRIENDS = true
            PLAYER_CONSOLE_INFO_REQUEST_MANAGER:RequestIdFromUserListDialog(self.onUserListDialogIdSelectedForMailTo, GetString(SI_GAMEPAD_CONSOLE_SELECT_FOR_MAIL), INCLUDE_ONLINE_FRIENDS, INCLUDE_OFFLINE_FRIENDS)
        end

        local editBoxCallback = function()
            self.mailView.addressEdit.edit:TakeFocus()
        end

        local mailSendNarrationText = function(entryData, entryControl)
            return ZO_FormatEditBoxNarrationText(self.mailView.addressEdit.edit, entryData.text)
        end

        if ZO_IsPlaystationPlatform() then
            self:AddMainListEntry(GetString(SI_GAMEPAD_MAIL_SEND_TO), NO_HEADER, NO_ICON, userListCallback, NO_SECONDARY_CALLBACK_NAME, NO_SECONDARY_CALLBACK, mailSendNarrationText)
        elseif GetUIPlatform() == UI_PLATFORM_XBOX then
            if GetNumberConsoleFriends() > 0 then
                self:AddMainListEntry(GetString(SI_GAMEPAD_MAIL_SEND_TO), NO_HEADER, NO_ICON, editBoxCallback, GetString(SI_GAMEPAD_CONSOLE_CHOOSE_FRIEND), userListCallback, mailSendNarrationText)
            else
                self:AddMainListEntry(GetString(SI_GAMEPAD_MAIL_SEND_TO), NO_HEADER, NO_ICON, editBoxCallback, GetString(SI_GAMEPAD_CONSOLE_CHOOSE_FRIEND), NO_SECONDARY_CALLBACK, mailSendNarrationText)
            end
        else
            self:AddMainListEntry(GetString(SI_GAMEPAD_MAIL_SEND_TO), NO_HEADER, NO_ICON, editBoxCallback, NO_SECONDARY_CALLBACK_NAME, NO_SECONDARY_CALLBACK, mailSendNarrationText)
        end
    end

    local mailSubjectNarrationText = function(entryData, entryControl)
        return ZO_FormatEditBoxNarrationText(self.mailView.subjectEdit.edit, entryData.text)
    end

    local mailBodyNarrationText = function(entryData, entryControl)
        return ZO_FormatEditBoxNarrationText(self.mailView.bodyEdit.edit, entryData.text)
    end

    local mailGoldNarrationText = function(entryData, entryControl)
        local narrations = {}
        local queuedCOD = GetQueuedCOD()
        local queuedMoney = GetQueuedMoneyAttachment()
        local moneyHeader
        local moneyText
        if queuedMoney > 0 then
            moneyHeader = GetString(SI_MAIL_READ_SENT_GOLD_LABEL)
            moneyText = ZO_Currency_FormatGamepad(CURT_MONEY, queuedMoney, ZO_CURRENCY_FORMAT_AMOUNT_NAME)
        elseif queuedCOD > 0 then
            moneyHeader = GetString(SI_MAIL_READ_COD_LABEL)
            moneyText = ZO_Currency_FormatGamepad(CURT_MONEY, queuedCOD, ZO_CURRENCY_FORMAT_AMOUNT_NAME)
        else
            moneyHeader = GetString(SI_MAIL_READ_SENT_GOLD_LABEL)
            moneyText = GetString(SI_GAMEPAD_MAIL_INBOX_NO_ATTACHED_GOLD)
        end

        ZO_AppendNarration(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(entryData.text))
        ZO_AppendNarration(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(moneyHeader))
        ZO_AppendNarration(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(moneyText))

        return narrations
    end

    self:AddMainListEntry(GetString(SI_GAMEPAD_MAIL_SUBJECT_LABEL), NO_HEADER, NO_ICON, function() self.mailView.subjectEdit.edit:TakeFocus() end, NO_SECONDARY_CALLBACK_NAME, NO_SECONDARY_CALLBACK, mailSubjectNarrationText)
    self:AddMainListEntry(GetString(SI_GAMEPAD_MAIL_BODY_LABEL), NO_HEADER, NO_ICON, function() self.mailView.bodyEdit.edit:TakeFocus() end, NO_SECONDARY_CALLBACK_NAME, NO_SECONDARY_CALLBACK, mailBodyNarrationText)
    self:AddMainListEntry(GetString(SI_MAIL_SEND_ATTACH_MONEY), GetString(SI_GAMEPAD_MAIL_SEND_GOLD_HEADER), SEND_GOLD_ICON, function() self:ShowSliderControl(ATTACHING_GOLD, GetQueuedMoneyAttachment(), GetCurrencyAmount(CURT_MONEY, CURRENCY_LOCATION_CHARACTER)) end, NO_SECONDARY_CALLBACK_NAME, NO_SECONDARY_CALLBACK, mailGoldNarrationText)
    self:AddMainListEntry(GetString(SI_GAMEPAD_MAIL_SEND_COD), NO_HEADER, REQUEST_GOLD_ICON, function() self:ShowSliderControl(REQUESTING_GOLD, GetQueuedCOD(), MAX_PLAYER_CURRENCY) end, NO_SECONDARY_CALLBACK_NAME, NO_SECONDARY_CALLBACK, mailGoldNarrationText)

    self.mailView.subjectEdit.edit:SetHandler("OnFocusLost", function(editBox)
        RefreshKeybind()
        ZO_GamepadEditBox_FocusLost(editBox)
        --Re-narrate the edit box when we are done typing in it
        SCREEN_NARRATION_MANAGER:QueueParametricListEntry(MAIL_GAMEPAD:GetCurrentList())
    end)

    self.mailView.addressEdit.edit:SetHandler("OnFocusLost", function(editBox)
        RefreshKeybind()
        ZO_GamepadEditBox_FocusLost(editBox)
        --Re-narrate the edit box when we are done typing in it
        SCREEN_NARRATION_MANAGER:QueueParametricListEntry(MAIL_GAMEPAD:GetCurrentList())
    end)

    self.mailView.bodyEdit.edit:SetHandler("OnFocusLost", function(editBox)
        RefreshKeybind()
        ZO_GamepadEditBox_FocusLost(editBox)
        --Re-narrate the edit box when we are done typing in it
        SCREEN_NARRATION_MANAGER:QueueParametricListEntry(MAIL_GAMEPAD:GetCurrentList())
    end)

    if not self.inventoryList:IsEmpty() then
        local mailAttachedItemsNarrationText = function(entryData, entryControl)
            local narrations = {}

            ZO_AppendNarration(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(entryData.text))

            local totalAttachments = 0
            for i = 1, MAIL_MAX_ATTACHED_ITEMS do
                local queuedFromBag, slotIndex, icon, stack = GetQueuedItemAttachmentInfo(i)
                if queuedFromBag ~= 0 then -- Slot is filled.
                    totalAttachments = totalAttachments + stack
                else
                    break
                end
            end

            ZO_AppendNarration(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(GetString(SI_MAIL_ATTACHMENTS_HEADER)))

            if totalAttachments > 0 then
                ZO_AppendNarration(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(totalAttachments))
            else
                ZO_AppendNarration(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(GetString(SI_GAMEPAD_MAIL_INBOX_NO_ATTACHMENTS)))
            end

            return narrations
        end

        self:AddMainListEntry(GetString(SI_GAMEPAD_MAIL_SEND_ATTACH), GetString(SI_GAMEPAD_MAIL_SEND_ITEMS_HEADER), EMPTY_ATTACHMENT_ICON, function() self:EnterInventoryList() end, NO_SECONDARY_CALLBACK_NAME, NO_SECONDARY_CALLBACK, mailAttachedItemsNarrationText)
    end

    local function AttemptSendMail()
        local to = self.mailView:GetAddress()
        local subject = self.mailView:GetSubject()
        local body = self.mailView:GetBody()
        if IsConsoleUI() then
            ZO_ConsoleAttemptCommunicateOrError(function(success)
                if success then
                    SendMail(to, subject, body)
                    self:EnterSending()
                end
            end, to, ZO_PLAYER_CONSOLE_INFO_REQUEST_BLOCK, ZO_CONSOLE_CAN_COMMUNICATE_ERROR_DIALOG, ZO_ID_REQUEST_TYPE_DISPLAY_NAME, to)
        else
            SendMail(to, subject, body)
            self:EnterSending()
        end
    end
    self:AddMainListEntry(GetString(SI_MAIL_SEND_SEND), GetString(SI_MAIL_SEND_SEND), ZO_GAMEPAD_SUBMIT_ENTRY_ICON, AttemptSendMail)

    self.mainList:Commit()
end

function ZO_MailSend_Gamepad:InitializeMainList()
    self.mainList = MAIL_GAMEPAD:GetMainList()
    self.mainList:SetOnSelectedDataChangedCallback(function(...) self:OnListMovement(...) end)
    self:PopulateMainList()
end

function ZO_MailSend_Gamepad:OnListMovement(list, isMoving)
    self.mailView.addressEdit.edit:LoseFocus()
    self.mailView.subjectEdit.edit:LoseFocus()
    self.mailView.bodyEdit.edit:LoseFocus()

    self:HighlightActiveTextField()
    KEYBIND_STRIP:UpdateKeybindButtonGroup(self.mainKeybindDescriptor)
end

function ZO_MailSend_Gamepad:HighlightActiveTextField()
    local textFieldToControl =
    {
        [GetString(SI_GAMEPAD_MAIL_SEND_TO)] = self.mailView.addressEdit,
        [GetString(SI_GAMEPAD_MAIL_SUBJECT_LABEL)] = self.mailView.subjectEdit,
        [GetString(SI_GAMEPAD_MAIL_BODY_LABEL)] = self.mailView.bodyEdit
    }

    for _, control in pairs(textFieldToControl) do
        control.highlight:SetHidden(true)
    end

    local textField = self.mainList:GetTargetData().text
    local currentEditControl = textFieldToControl[textField]
    if currentEditControl then
        currentEditControl.highlight:SetHidden(false)
    end

    KEYBIND_STRIP:UpdateKeybindButtonGroup(self.mainKeybindDescriptor)
end

function ZO_MailSend_Gamepad:InitializeContactsList()
    self.contactsList = MAIL_GAMEPAD:AddList("Contacts")
    self.contactsListControl = self.contactsList:GetControl()
end

function ZO_MailSend_Gamepad:ClearFields()
    self:Clear()
    self:EnterOutbox()
end

function ZO_MailSend_Gamepad:Reset()
    if (not self.inventoryListControl:IsHidden()) or (not self.goldSliderControl:IsHidden()) or (not self.contactsListControl:IsHidden()) then
        PlaySound(SOUNDS.GAMEPAD_MENU_BACK)
    end

    self.goldSliderControl:SetHidden(true)
    self.mainList:WhenInactiveSetTargetControlHidden(false)

    GAMEPAD_TOOLTIPS:ClearTooltip(GAMEPAD_LEFT_TOOLTIP)
    self.loadingBox:SetHidden(true)

    self.goldSlider:Deactivate()

    self.goldMode = nil
    self.inSendMode = false

    MAIL_GAMEPAD:RequestLeaveHeader()
end

function ZO_MailSend_Gamepad:SwitchToSendTab()
    MAIL_GAMEPAD:SwitchToHeader(self.mainHeaderData, ZO_MAIL_TAB_INDEX.SEND)
end

function ZO_MailSend_Gamepad:EnterSending()
    self.inSendMode = true
    self:Reset()

    MAIL_GAMEPAD:SwitchToKeybind(nil) -- Remove keybinds as they are invaild when sending.
    self.loadingLabel:SetText(GetString(SI_GAMEPAD_MAIL_SEND_SENDING))
    self.loadingBox:SetHidden(false)
    MAIL_GAMEPAD:DeactivateTextSearch()
    MAIL_GAMEPAD:SetCurrentList(nil)
    self:EnableMailEditboxes(false)
end

function ZO_MailSend_Gamepad:EnterOutbox()
    self:Reset()

    if self.inSendMode then
        self:EnterSending()
    else
        self:SwitchToSendTab()
        MAIL_GAMEPAD:DeactivateTextSearch()
        MAIL_GAMEPAD:SetCurrentList(self.mainList)
        MAIL_GAMEPAD:SwitchToKeybind(self.mainKeybindDescriptor)
        self:EnableMailEditboxes(true)
    end
end

function ZO_MailSend_Gamepad:EnableMailEditboxes(enabled)
    self.mailView.addressEdit.edit:SetMouseEnabled(enabled)
    self.mailView.subjectEdit.edit:SetMouseEnabled(enabled)
    self.mailView.bodyEdit.edit:SetMouseEnabled(enabled)
end

function ZO_MailSend_Gamepad:AddContact(text, header, callback)
    local newEntry = ZO_GamepadEntryData:New(text)
    newEntry.actionFunction = callback

    local template
    if header then
        newEntry:SetHeader(header)
        template = "ZO_GamepadMenuEntryTemplateWithHeader"
    else
        template = "ZO_GamepadMenuEntryTemplate"
    end

    self.contactsList:AddEntry(template, newEntry)
end

local RECIPIENT_HEADER_TEXT = GetString(SI_GAMEPAD_MAIL_SEND_RECIPIENT)

function ZO_MailSend_Gamepad:EnterContactsList()
    self.contactsList:Clear()

    -- Text edit
    self:AddContact(GetString(SI_GAMEPAD_MAIL_SEND_ENTER_NAME), nil, function() self.mailView.addressEdit.edit:TakeFocus() end)
    self.contactsList:Commit()

    self.setFieldHeaderData.titleText = RECIPIENT_HEADER_TEXT
    MAIL_GAMEPAD:SwitchToHeader(self.setFieldHeaderData)
    MAIL_GAMEPAD:SwitchToKeybind(self.contactsKeybindDescriptor)
    MAIL_GAMEPAD:DeactivateTextSearch()
    MAIL_GAMEPAD:SetCurrentList(self.contactsList)
    self:EnableMailEditboxes(true)

    PlaySound(SOUNDS.GAMEPAD_MENU_FORWARD)
end

function ZO_MailSend_Gamepad:ShowSliderControl(mode, value, maxValue)
    MAIL_GAMEPAD:DeactivateCurrentList()
    self.mainList:WhenInactiveSetTargetControlHidden(true)

    self.goldSlider:SetMaxValue(maxValue)
    self.goldSlider:SetValue(value)

    MAIL_GAMEPAD:SwitchToKeybind(self.sliderKeybindDescriptor)
    self.goldSlider:Activate()
    self.goldMode = mode
    self.goldSliderControl:SetHidden(false)

    PlaySound(SOUNDS.GAMEPAD_MENU_FORWARD)
end

local ATTACHMENT_HEADER_TEXT = GetString(SI_GAMEPAD_MAIL_SEND_ATTACH)

function ZO_MailSend_Gamepad:EnterInventoryList()
    self:Reset()

    self.setFieldHeaderData.titleText = ATTACHMENT_HEADER_TEXT
    MAIL_GAMEPAD:SwitchToHeader(self.setFieldHeaderData)
    MAIL_GAMEPAD:SwitchToKeybind(self.inventoryKeybindDescriptor)
    MAIL_GAMEPAD:SetCurrentList(self.inventoryList)
    MAIL_GAMEPAD:ActivateTextSearch()
    self:EnableMailEditboxes(true)

    if self.inventoryList and self.inventoryList:GetNumItems() == 0 then
        MAIL_GAMEPAD:RequestEnterHeader()
    end

    PlaySound(SOUNDS.GAMEPAD_MENU_FORWARD)
end

function ZO_MailSend_Gamepad:InventorySelectionChanged(list, selectedData)
    if MAIL_GAMEPAD:GetCurrentList() == self.inventoryList then
        GAMEPAD_TOOLTIPS:ClearLines(GAMEPAD_LEFT_TOOLTIP)
        if selectedData then
            GAMEPAD_TOOLTIPS:LayoutBagItem(GAMEPAD_LEFT_TOOLTIP, selectedData.bagId, selectedData.slotIndex)
        end
        MAIL_GAMEPAD:RefreshKeybind()
    end
end

function ZO_MailSend_Gamepad:Clear()
    ClearQueuedMail()

    self.mailView:Clear()

    self:UpdateMoneyAttachment()
end

function ZO_MailSend_Gamepad:UpdateMoneyAttachment()
    local queuedCOD = GetQueuedCOD()
    local queuedMoney = GetQueuedMoneyAttachment()
    self.mailView:Display(queuedCOD, queuedMoney)

    self:UpdatePostageMoney()
end

function ZO_MailSend_Gamepad:IsAttachingItems()
    return not self.sendControl:IsHidden()
end

function ZO_MailSend_Gamepad:UpdatePostageMoney()
    MAIL_GAMEPAD:RefreshHeader()
end

function ZO_MailSend_Gamepad:OnMailAttachmentAdded(attachSlot)
    local _, _, icon, stack = GetQueuedItemAttachmentInfo(attachSlot)
    self.mailView:SetAttachment(attachSlot, stack, icon)
    self:UpdatePostageMoney()
    MAIL_GAMEPAD:RefreshKeybind()
    self.inventoryList:RefreshList()
end

function ZO_MailSend_Gamepad:OnMailAttachmentRemoved(attachSlot)
    self.mailView:ClearAttachment(attachSlot)
    self:UpdatePostageMoney()
    MAIL_GAMEPAD:RefreshKeybind()
    self.inventoryList:RefreshList()
end

function ZO_MailSend_Gamepad:OnMailSendSuccess()
    PlaySound(SOUNDS.MAIL_SENT)
    self.inSendMode = false
    self:Clear()
    if not self.sendControl:IsHidden() then
        self:EnterOutbox()
    end
end

function ZO_MailSend_Gamepad:OnMailSendFailure(failureReason)
    self.inSendMode = false
    if not self.sendControl:IsHidden() then
        self:EnterOutbox()
    end
end

-- XML Handlers

function ZO_MailView_Initialize_Send_Fields_Gamepad(control)
    control.addressEdit.edit:SetDefaultText(GetDefaultAddresseeText())
    control.subjectEdit.edit:SetDefaultText(GetString(SI_MAIL_SUBJECT_DEFAULT_TEXT))
end
