local addonName, BM = ...

local RECIPIENT_PANEL_WIDTH = 324
local ATTACHMENT_COLUMNS = 6
local ATTACHMENT_SIZE = 42
local ATTACHMENT_ICON_FALLBACK = "Interface\\Icons\\INV_Misc_QuestionMark"

local function GetFrameNameSafe(frame)
    local frameType = type(frame)
    if frameType ~= "table" and frameType ~= "userdata" then
        return nil
    end

    local ok, name = pcall(function()
        if frame.GetName then
            return frame:GetName()
        end
    end)
    return ok and name or nil
end

local function ForEachInventoryBagFrame(callback)
    for name, frame in pairs(_G) do
        if type(name) == "string"
            and (name:find("^Baganator_SingleViewBackpackViewFrame") or name:find("^Baganator_CategoryViewBackpackViewFrame"))
        then
            callback(frame)
        end
    end

    if ContainerFrameCombinedBags then
        callback(ContainerFrameCombinedBags)
    end

    local maxFrames = NUM_CONTAINER_FRAMES or 13
    for i = 1, maxFrames do
        local frame = _G["ContainerFrame" .. i]
        if frame then
            callback(frame)
        end
    end
end

local function AreInventoryBagsOpen()
    local isOpen = false
    ForEachInventoryBagFrame(function(frame)
        local ok, shown = pcall(function()
            return frame.IsShown and frame:IsShown()
        end)
        if ok and shown then
            isOpen = true
        end
    end)
    return isOpen
end

local function OpenKnownBaganatorFrames()
    local opened = false
    ForEachInventoryBagFrame(function(frame)
        local name = GetFrameNameSafe(frame) or ""
        if name:find("^Baganator_") then
            local ok = pcall(function()
                if frame.Show then frame:Show() end
                if frame.Raise then frame:Raise() end
            end)
            if ok then
                opened = true
            end
        end
    end)
    return opened
end

local function OpenBaganatorBags()
    local opened = false
    if Baganator and Baganator.CallbackRegistry and Baganator.CallbackRegistry.TriggerEvent then
        Baganator.CallbackRegistry:TriggerEvent("BagShow")
        opened = true
    end
    if OpenKnownBaganatorFrames() then
        opened = true
    end
    return opened
end

local function CloseKnownBaganatorFrames()
    local closed = false
    ForEachInventoryBagFrame(function(frame)
        local name = GetFrameNameSafe(frame) or ""
        if name:find("^Baganator_") then
            local ok = pcall(function()
                if frame.Hide then frame:Hide() end
            end)
            if ok then
                closed = true
            end
        end
    end)
    return closed
end

local function CloseBaganatorBags()
    local closed = false
    if Baganator and Baganator.CallbackRegistry and Baganator.CallbackRegistry.TriggerEvent then
        Baganator.CallbackRegistry:TriggerEvent("BagHide")
        closed = true
    end
    if CloseKnownBaganatorFrames() then
        closed = true
    end
    return closed
end

local function RaiseInventoryBags()
    ForEachInventoryBagFrame(function(frame)
        local ok, shown = pcall(function()
            return frame.IsShown and frame:IsShown()
        end)
        if ok and shown then
            pcall(function()
            if frame.SetFrameStrata then frame:SetFrameStrata("DIALOG") end
            if frame.SetFrameLevel then frame:SetFrameLevel(1000) end
            if frame.Raise then frame:Raise() end
            end)
        end
    end)
end

local function OpenBlizzardBags()
    if RunBinding then
        RunBinding("OPENALLBAGS")
    end

    if OpenAllBags then
        OpenAllBags()
    end

    if not AreInventoryBagsOpen() and OpenBackpack then
        OpenBackpack()
    elseif not AreInventoryBagsOpen() and ToggleBackpack then
        ToggleBackpack()
    end

    if not AreInventoryBagsOpen() and OpenBag then
        local maxBag = NUM_BAG_SLOTS or 4
        for bag = 0, maxBag do
            OpenBag(bag)
        end
    end

    if not AreInventoryBagsOpen() and ToggleBag then
        local maxBag = NUM_BAG_SLOTS or 4
        for bag = 0, maxBag do
            ToggleBag(bag)
        end
    end

    if not AreInventoryBagsOpen() and RunBinding then
        RunBinding("TOGGLEBACKPACK")
    end
end

function BM:OpenInventoryBags()
    if BM.SetStatus then
        BM:SetStatus("BetterMailbox: Taschen werden geoeffnet...", 0.13, 0.72, 1)
    end

    BM.openedBagsForSend = true

    if ToggleAllBags then
        ToggleAllBags()
        if C_Timer then
            C_Timer.After(0, RaiseInventoryBags)
        else
            RaiseInventoryBags()
        end
        return
    end

    if SetSendMailShowing then
        pcall(SetSendMailShowing, true)
    end

    if AreInventoryBagsOpen() then
        RaiseInventoryBags()
        return
    end

    if OpenBaganatorBags() and AreInventoryBagsOpen() then
        RaiseInventoryBags()
        return
    end

    OpenBlizzardBags()

    if C_Timer then
        C_Timer.After(0.05, function()
            if not AreInventoryBagsOpen() then
                OpenBaganatorBags()
            end
            if not AreInventoryBagsOpen() and ToggleAllBags then
                ToggleAllBags()
            end
            if not AreInventoryBagsOpen() then
                OpenBlizzardBags()
            end
            RaiseInventoryBags()
        end)
    else
        RaiseInventoryBags()
    end
end

function BM:CloseInventoryBagsOpenedForSend()
    if not BM.openedBagsForSend then
        return
    end

    BM.openedBagsForSend = nil

    CloseBaganatorBags()

    if CloseAllBags then
        CloseAllBags()
    end

    if ToggleAllBags and AreInventoryBagsOpen() then
        ToggleAllBags()
    end
end

local function ParseMoneyField(editBox, maxValue)
    if not editBox then
        return 0
    end

    local value = tonumber(editBox:GetText() or "") or 0
    value = math.floor(math.max(0, value))
    if maxValue then
        value = math.min(maxValue, value)
    end
    return value
end

local function NormalizeCharacterName(name)
    name = strtrim(name or "")
    if name == "" then
        return ""
    end

    if not name:find("-", 1, true) then
        local realm = GetNormalizedRealmName and GetNormalizedRealmName() or nil
        if realm and realm ~= "" then
            return name .. "-" .. realm
        end
    end

    return name
end

local function InsertUniqueRecipient(list, seen, name)
    name = NormalizeCharacterName(name)
    if name == "" or seen[name] then
        return
    end

    seen[name] = true
    table.insert(list, name)
end

function BM:GetRecipientMatches(prefix)
    prefix = (prefix or ""):lower()
    if prefix == "" then
        return {}
    end

    local matches = {}
    local seen = {}
    for _, name in ipairs(BetterMailboxDB.recipientHistory or {}) do
        if not seen[name] and name:lower():find(prefix, 1, true) == 1 then
            seen[name] = true
            table.insert(matches, name)
        end
    end
    return matches
end

function BM:RememberRecipient(name)
    name = NormalizeCharacterName(name)
    if name == "" then
        return
    end

    BetterMailboxDB.recipientHistory = BetterMailboxDB.recipientHistory or {}
    local nextHistory = { name }
    for _, entry in ipairs(BetterMailboxDB.recipientHistory) do
        if entry ~= name and #nextHistory < 15 then
            table.insert(nextHistory, entry)
        end
    end
    BetterMailboxDB.recipientHistory = nextHistory
end

function BM:RefreshRecipientSuggestion()
    if not BM.RecipientSuggestText or not BM.SendRecipientInput then
        return
    end

    local text = BM.SendRecipientInput:GetText() or ""
    local matches = BM:GetRecipientMatches(text)
    if #matches > 0 and text ~= "" and matches[1] ~= text then
        BM.RecipientSuggestText:SetText(BM:L("recent") .. ": " .. matches[1])
        BM.RecipientSuggestText:Show()
        BM.TopRecipientMatch = matches[1]
    else
        BM.RecipientSuggestText:Hide()
        BM.TopRecipientMatch = nil
    end
end

function BM:ApplySendMoney()
    local gold = ParseMoneyField(BM.SendMoneyGoldInput)
    local silver = ParseMoneyField(BM.SendMoneySilverInput, 99)
    local copper = ParseMoneyField(BM.SendMoneyCopperInput, 99)
    local total = (gold * 10000) + (silver * 100) + copper

    if SetSendMailMoney then
        SetSendMailMoney(total)
    end
end

function BM:ResetSendMoney()
    if BM.SendMoneyGoldInput then BM.SendMoneyGoldInput:SetText("") end
    if BM.SendMoneySilverInput then BM.SendMoneySilverInput:SetText("") end
    if BM.SendMoneyCopperInput then BM.SendMoneyCopperInput:SetText("") end
    if SetSendMailMoney then
        SetSendMailMoney(0)
    end
end

function BM:ApplyRecipientSuggestion()
    if BM.TopRecipientMatch and BM.SendRecipientInput then
        BM.SendRecipientInput:SetText(BM.TopRecipientMatch)
        BM.SendRecipientInput:HighlightText(0, 0)
        BM:RefreshRecipientSuggestion()
    end
end

function BM:GetGuildRecipients()
    local recipients = {}
    local seen = {}

    if IsInGuild and IsInGuild() then
        if C_GuildInfo and C_GuildInfo.GuildRoster then
            C_GuildInfo.GuildRoster()
        elseif GuildRoster then
            GuildRoster()
        end

        local total = GetNumGuildMembers and GetNumGuildMembers() or 0
        for i = 1, total do
            local fullName = GetGuildRosterInfo(i)
            InsertUniqueRecipient(recipients, seen, fullName)
        end
    end

    table.sort(recipients)
    return recipients
end

local function GetBattleNetRecipients(recipients, seen)
    if C_BattleNet and C_BattleNet.GetFriendNumGameAccounts and C_BattleNet.GetFriendAccountInfo then
        local total = BNGetNumFriends and BNGetNumFriends() or 0
        for i = 1, total do
            local accountInfo = C_BattleNet.GetFriendAccountInfo(i)
            if accountInfo and accountInfo.gameAccountInfo then
                local gameInfo = accountInfo.gameAccountInfo
                if gameInfo.clientProgram == BNET_CLIENT_WOW and gameInfo.characterName and gameInfo.characterName ~= "" then
                    local fullName = gameInfo.characterName
                    if gameInfo.realmName and gameInfo.realmName ~= "" then
                        fullName = fullName .. "-" .. gameInfo.realmName:gsub("%s+", "")
                    end
                    InsertUniqueRecipient(recipients, seen, fullName)
                end
            end

            local accountGameCount = C_BattleNet.GetFriendNumGameAccounts(i) or 0
            for gameIndex = 1, accountGameCount do
                local gameInfo = C_BattleNet.GetFriendGameAccountInfo(i, gameIndex)
                if gameInfo and gameInfo.clientProgram == BNET_CLIENT_WOW and gameInfo.characterName and gameInfo.characterName ~= "" then
                    local fullName = gameInfo.characterName
                    if gameInfo.realmName and gameInfo.realmName ~= "" then
                        fullName = fullName .. "-" .. gameInfo.realmName:gsub("%s+", "")
                    end
                    InsertUniqueRecipient(recipients, seen, fullName)
                end
            end
        end
        return
    end

    if BNGetNumFriends and BNGetFriendInfo and BNGetNumFriendGameAccounts and BNGetFriendGameAccountInfo then
        local total = BNGetNumFriends()
        for i = 1, total do
            local gameCount = BNGetNumFriendGameAccounts(i) or 0
            for gameIndex = 1, gameCount do
                local _, characterName, _, realmName, _, clientProgram = BNGetFriendGameAccountInfo(i, gameIndex)
                if clientProgram == BNET_CLIENT_WOW and characterName and characterName ~= "" then
                    local fullName = characterName
                    if realmName and realmName ~= "" then
                        fullName = fullName .. "-" .. realmName:gsub("%s+", "")
                    end
                    InsertUniqueRecipient(recipients, seen, fullName)
                end
            end
        end
    end
end

function BM:GetFriendRecipients()
    local recipients = {}
    local seen = {}

    if C_FriendList and C_FriendList.GetNumFriends then
        if C_FriendList.ShowFriends then
            C_FriendList.ShowFriends()
        end
        local total = C_FriendList.GetNumFriends()
        for i = 1, total do
            local info = C_FriendList.GetFriendInfoByIndex(i)
            if info and info.name then
                InsertUniqueRecipient(recipients, seen, info.name)
            end
        end
    elseif GetNumFriends and GetFriendInfo then
        local total = GetNumFriends()
        for i = 1, total do
            local name = GetFriendInfo(i)
            InsertUniqueRecipient(recipients, seen, name)
        end
    end

    GetBattleNetRecipients(recipients, seen)

    table.sort(recipients)
    return recipients
end

function BM:SetRecipientBrowserMode(mode)
    if not BM.RecipientBrowser then
        return
    end

    if mode ~= "guild" and mode ~= "friends" then
        mode = nil
    end

    BM.RecipientPopupMode = mode

    if not mode then
        BM.RecipientBrowser:Hide()
        if BM.ComposeFrame then
            BM.ComposeFrame:ClearAllPoints()
            BM.ComposeFrame:SetPoint("TOPLEFT", 20, -18)
            BM.ComposeFrame:SetPoint("TOPRIGHT", -20, -18)
            BM.ComposeFrame:SetPoint("BOTTOMRIGHT", -20, 20)
        end
        return
    end

    BM.RecipientPopupList = mode == "guild" and BM:GetGuildRecipients() or BM:GetFriendRecipients()
    BM.RecipientBrowser:Show()

    if BM.ComposeFrame then
        BM.ComposeFrame:ClearAllPoints()
        BM.ComposeFrame:SetPoint("TOPLEFT", 20, -18)
        BM.ComposeFrame:SetPoint("TOPRIGHT", BM.RecipientBrowser, "TOPLEFT", -16, 0)
        BM.ComposeFrame:SetPoint("BOTTOMRIGHT", BM.RecipientBrowser, "BOTTOMLEFT", -16, 0)
    end

    if BM.RecipientBrowserTitle then
        BM.RecipientBrowserTitle:SetText(mode == "guild" and BM:L("guild") or BM:L("friends"))
    end

    if BM.RecipientBrowserSearch then
        BM.RecipientBrowserSearch:SetText("")
    end

    BM:RefreshRecipientPopupList("")
end

function BM:OpenRecipientPopup(mode)
    if BM.RecipientPopupMode == mode and BM.RecipientBrowser and BM.RecipientBrowser:IsShown() then
        BM:SetRecipientBrowserMode(nil)
    else
        BM:SetRecipientBrowserMode(mode)
    end
end

function BM:RefreshRecipientPopupList(query)
    if not BM.RecipientPopupButtons then
        return
    end

    query = (query or ""):lower()
    local filtered = {}
    for _, name in ipairs(BM.RecipientPopupList or {}) do
        if query == "" or name:lower():find(query, 1, true) then
            table.insert(filtered, name)
        end
    end

    local anchor
    for i, button in ipairs(BM.RecipientPopupButtons) do
        local name = filtered[i]
        button:ClearAllPoints()
        if name then
            button.recipientName = name
            button.label:SetText(name)
            if not anchor then
                button:SetPoint("TOPLEFT", 0, 0)
            else
                button:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, -6)
            end
            anchor = button
            button:Show()
        else
            button.recipientName = nil
            button:Hide()
        end
    end

    local buttonHeight = 34
    local spacing = 6
    local viewportHeight = BM.RecipientPopupScrollFrame and (BM.RecipientPopupScrollFrame:GetHeight() or 0) or 0
    local rawHeight = math.max(24, (#filtered * (buttonHeight + spacing)) - spacing)
    local contentHeight = math.max(rawHeight, viewportHeight)
    if BM.RecipientPopupScrollChild then
        BM.RecipientPopupScrollChild:SetHeight(contentHeight)
    end

    if BM.RecipientPopupEmpty then
        BM.RecipientPopupEmpty:SetShown(#filtered == 0)
    end

    if BM.RecipientPopupScrollFrame then
        BM.RecipientPopupScrollFrame:UpdateScrollChildRect()
        BM.RecipientPopupScrollFrame:SetVerticalScroll(0)

        local maxScroll = math.max(0, rawHeight - viewportHeight)
        local canScroll = maxScroll > 0
        local scrollBar = BM.RecipientPopupScrollFrame.ScrollBar
        if scrollBar then
            scrollBar:SetMinMaxValues(0, maxScroll)
            scrollBar:SetValue(0)
            scrollBar:SetShown(canScroll)
        end
    end
end

function BM:CreateSendUI()
    local panel = BM.SendPanel

    local hero = BM:CreatePanel(panel)
    hero:SetPoint("TOPLEFT", 16, -16)
    hero:SetPoint("TOPRIGHT", -16, -16)
    hero:SetHeight(88)
    hero:SetBackdropColor(0.05, 0.065, 0.095, 0.985)

    local heroTitle = hero:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    heroTitle:SetPoint("TOPLEFT", 18, -14)
    heroTitle:SetTextColor(unpack(BM.Theme.text))

    local heroSub = hero:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    heroSub:SetPoint("TOPLEFT", heroTitle, "BOTTOMLEFT", 0, -8)
    heroSub:SetTextColor(unpack(BM.Theme.mutetext))

    local friendsButton = BM:CreateFlatActionButton(hero, 116, 30, "")
    friendsButton:SetPoint("RIGHT", -16, 0)
    friendsButton:SetScript("OnClick", function()
        BM:OpenRecipientPopup("friends")
    end)

    local guildButton = BM:CreateFlatActionButton(hero, 116, 30, "")
    guildButton:SetPoint("RIGHT", friendsButton, "LEFT", -10, 0)
    guildButton:SetScript("OnClick", function()
        BM:OpenRecipientPopup("guild")
    end)

    local form = BM:CreatePanel(panel)
    form:SetPoint("TOPLEFT", 16, -120)
    form:SetPoint("BOTTOMRIGHT", -16, 16)
    form:SetBackdropColor(0.035, 0.045, 0.065, 0.985)

    local compose = CreateFrame("Frame", nil, form)
    compose:SetPoint("TOPLEFT", 20, -18)
    compose:SetPoint("TOPRIGHT", -20, -18)
    compose:SetPoint("BOTTOMRIGHT", -20, 20)

    local recipientLabel = compose:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    recipientLabel:SetPoint("TOPLEFT", 0, 0)
    recipientLabel:SetTextColor(unpack(BM.Theme.mutetext))

    local recipientInput = BM:CreateInput(compose, 120, 40, false)
    recipientInput:SetPoint("TOPLEFT", 0, -22)
    recipientInput:SetPoint("RIGHT", compose, "CENTER", -8, -22)
    recipientInput.EditBox:SetScript("OnTextChanged", function()
        BM:RefreshRecipientSuggestion()
    end)
    recipientInput.EditBox:SetScript("OnTabPressed", function(self)
        BM:ApplyRecipientSuggestion()
        self:ClearFocus()
    end)

    local subjectLabel = compose:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    subjectLabel:SetPoint("TOPLEFT", compose, "TOP", 8, 0)
    subjectLabel:SetTextColor(unpack(BM.Theme.mutetext))

    local subjectInput = BM:CreateInput(compose, 120, 40, false)
    subjectInput:SetPoint("TOPLEFT", compose, "TOP", 8, -22)
    subjectInput:SetPoint("RIGHT", compose, "RIGHT", 0, 0)

    local recipientSuggest = compose:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    recipientSuggest:SetPoint("TOPLEFT", recipientInput, "BOTTOMLEFT", 2, -6)
    recipientSuggest:SetTextColor(unpack(BM.Theme.accent))
    recipientSuggest:Hide()

    local bodyLabel = compose:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    bodyLabel:SetPoint("TOPLEFT", 0, -86)
    bodyLabel:SetTextColor(unpack(BM.Theme.mutetext))

    local bodyInput = BM:CreateInput(compose, 300, 260, true)
    bodyInput:SetPoint("TOPLEFT", 0, -108)
    bodyInput:SetPoint("RIGHT", compose, "RIGHT", 0, 0)

    local attachmentsLabel = compose:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    attachmentsLabel:SetPoint("BOTTOMLEFT", 0, 118)
    attachmentsLabel:SetTextColor(unpack(BM.Theme.mutetext))

    local attachmentPanel = BM:CreatePanel(compose)
    attachmentPanel:SetPoint("BOTTOMLEFT", 0, 0)
    attachmentPanel:SetPoint("RIGHT", compose, "RIGHT", 0, 0)
    attachmentPanel:SetHeight(118)
    attachmentPanel:SetBackdropColor(0.03, 0.04, 0.06, 1)

    local slotAnchor = CreateFrame("Frame", nil, attachmentPanel)
    slotAnchor:SetPoint("LEFT", 14, 0)
    slotAnchor:SetSize(ATTACHMENT_COLUMNS * (ATTACHMENT_SIZE + 8), 92)

    local hint = compose:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    hint:SetPoint("BOTTOMLEFT", attachmentPanel, "BOTTOMLEFT", 14, 12)
    hint:SetTextColor(unpack(BM.Theme.mutetext))

    local actionBlock = CreateFrame("Frame", nil, attachmentPanel)
    actionBlock:SetPoint("LEFT", slotAnchor, "RIGHT", 24, 0)
    actionBlock:SetPoint("RIGHT", attachmentPanel, "RIGHT", -18, 0)
    actionBlock:SetHeight(78)

    local moneyBlock = CreateFrame("Frame", nil, actionBlock)
    moneyBlock:SetPoint("TOPLEFT", 0, -4)
    moneyBlock:SetWidth(250)
    moneyBlock:SetHeight(56)

    local moneyLabel = moneyBlock:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    moneyLabel:SetPoint("TOPLEFT", 0, 0)
    moneyLabel:SetTextColor(unpack(BM.Theme.mutetext))
    moneyLabel:SetJustifyH("LEFT")

    local moneyRow = CreateFrame("Frame", nil, moneyBlock)
    moneyRow:SetPoint("TOPLEFT", moneyLabel, "BOTTOMLEFT", 0, -22)
    moneyRow:SetPoint("TOPRIGHT", moneyBlock, "TOPRIGHT", 0, -10)
    moneyRow:SetHeight(28)

    local goldIcon = moneyRow:CreateTexture(nil, "ARTWORK")
    goldIcon:SetTexture("Interface\\MoneyFrame\\UI-GoldIcon")
    goldIcon:SetSize(14, 14)
    goldIcon:SetPoint("LEFT", 0, 0)

    local moneyGoldInput = BM:CreateInput(moneyRow, 50, 28, false)
    moneyGoldInput:SetPoint("LEFT", goldIcon, "RIGHT", 6, 0)
    moneyGoldInput.EditBox:SetNumeric(true)
    moneyGoldInput.EditBox:SetMaxLetters(6)
    moneyGoldInput.EditBox:SetScript("OnTextChanged", function()
        BM:ApplySendMoney()
    end)

    local silverIcon = moneyRow:CreateTexture(nil, "ARTWORK")
    silverIcon:SetTexture("Interface\\MoneyFrame\\UI-SilverIcon")
    silverIcon:SetSize(14, 14)
    silverIcon:SetPoint("LEFT", moneyGoldInput, "RIGHT", 14, 0)

    local moneySilverInput = BM:CreateInput(moneyRow, 34, 28, false)
    moneySilverInput:SetPoint("LEFT", silverIcon, "RIGHT", 6, 0)
    moneySilverInput.EditBox:SetNumeric(true)
    moneySilverInput.EditBox:SetMaxLetters(2)
    moneySilverInput.EditBox:SetScript("OnTextChanged", function()
        BM:ApplySendMoney()
    end)

    local copperIcon = moneyRow:CreateTexture(nil, "ARTWORK")
    copperIcon:SetTexture("Interface\\MoneyFrame\\UI-CopperIcon")
    copperIcon:SetSize(14, 14)
    copperIcon:SetPoint("LEFT", moneySilverInput, "RIGHT", 14, 0)

    local moneyCopperInput = BM:CreateInput(moneyRow, 34, 28, false)
    moneyCopperInput:SetPoint("LEFT", copperIcon, "RIGHT", 6, 0)
    moneyCopperInput.EditBox:SetNumeric(true)
    moneyCopperInput.EditBox:SetMaxLetters(2)
    moneyCopperInput.EditBox:SetScript("OnTextChanged", function()
        BM:ApplySendMoney()
    end)

    local sendButton = BM:CreateFlatActionButton(actionBlock, 172, 34, "")
    sendButton:SetPoint("RIGHT", actionBlock, "RIGHT", 0, 0)
    sendButton:SetScript("OnClick", function()
        BM:SendCurrentMail()
    end)

    bodyInput:SetPoint("BOTTOM", attachmentsLabel, "TOP", 0, 14)

    local browser = BM:CreatePanel(form)
    browser:SetPoint("TOPRIGHT", -20, -18)
    browser:SetPoint("BOTTOMRIGHT", -20, 20)
    browser:SetWidth(RECIPIENT_PANEL_WIDTH)
    browser:SetBackdropColor(0.042, 0.052, 0.076, 0.99)
    browser:Hide()

    local browserTitle = browser:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    browserTitle:SetPoint("TOPLEFT", 16, -14)
    browserTitle:SetTextColor(unpack(BM.Theme.text))

    local browserClose = BM:CreateFlatActionButton(browser, 28, 24, "X")
    browserClose:SetPoint("TOPRIGHT", -12, -10)
    browserClose:SetScript("OnClick", function()
        BM:SetRecipientBrowserMode(nil)
    end)

    local browserSearch = BM:CreateInput(browser, RECIPIENT_PANEL_WIDTH - 32, 34, false)
    browserSearch:SetPoint("TOPLEFT", 16, -42)
    browserSearch.EditBox:SetScript("OnTextChanged", function(self)
        BM:RefreshRecipientPopupList(self:GetText() or "")
    end)

    local scrollFrame = CreateFrame("ScrollFrame", nil, browser, "UIPanelScrollFrameTemplate")
    scrollFrame:SetPoint("TOPLEFT", 16, -86)
    scrollFrame:SetPoint("BOTTOMRIGHT", -28, 16)

    local scrollChild = CreateFrame("Frame", nil, scrollFrame)
    scrollChild:SetSize(RECIPIENT_PANEL_WIDTH - 52, 1)
    scrollFrame:SetScrollChild(scrollChild)

    local empty = scrollChild:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    empty:SetPoint("TOPLEFT", 8, -8)
    empty:SetTextColor(unpack(BM.Theme.mutetext))
    empty:SetWidth(RECIPIENT_PANEL_WIDTH - 70)
    empty:SetJustifyH("LEFT")
    empty:SetText("")
    empty:Hide()

    local popupButtons = {}
    for i = 1, 20 do
        local button = BM:CreateFlatActionButton(scrollChild, RECIPIENT_PANEL_WIDTH - 52, 34, "")
        button:SetScript("OnClick", function(self)
            if BM.SendRecipientInput and self.recipientName then
                BM.SendRecipientInput:SetText(self.recipientName)
                BM:SetRecipientBrowserMode(nil)
                BM:RefreshRecipientSuggestion()
            end
        end)
        popupButtons[i] = button
    end

    BM.SendHeroTitle = heroTitle
    BM.SendHeroSubtitle = heroSub
    BM.SendGuildButton = guildButton
    BM.SendFriendsButton = friendsButton
    BM.ComposeFrame = compose
    BM.SendRecipientInput = recipientInput.EditBox
    BM.SendSubjectInput = subjectInput.EditBox
    BM.SendBodyInput = bodyInput.EditBox
    BM.SendMailButton = sendButton
    BM.SendRecipientLabel = recipientLabel
    BM.SendSubjectLabel = subjectLabel
    BM.SendBodyLabel = bodyLabel
    BM.SendAttachmentsLabel = attachmentsLabel
    BM.SendHintText = hint
    BM.SendMoneyBlock = moneyBlock
    BM.SendMoneyLabel = moneyLabel
    BM.SendMoneyRow = moneyRow
    BM.SendMoneyGoldIcon = goldIcon
    BM.SendMoneySilverIcon = silverIcon
    BM.SendMoneyCopperIcon = copperIcon
    BM.SendMoneyGoldInput = moneyGoldInput.EditBox
    BM.SendMoneySilverInput = moneySilverInput.EditBox
    BM.SendMoneyCopperInput = moneyCopperInput.EditBox
    BM.RecipientSuggestText = recipientSuggest
    BM.SendAttachmentPanel = attachmentPanel
    BM.SendAttachmentAnchor = slotAnchor
    BM.RecipientBrowser = browser
    BM.RecipientBrowserTitle = browserTitle
    BM.RecipientBrowserSearch = browserSearch.EditBox
    BM.RecipientBrowserClose = browserClose
    BM.RecipientPopup = browser
    BM.RecipientPopupTitle = browserTitle
    BM.RecipientPopupSearch = browserSearch.EditBox
    BM.RecipientPopupButtons = popupButtons
    BM.RecipientPopupScrollFrame = scrollFrame
    BM.RecipientPopupScrollChild = scrollChild
    BM.RecipientPopupEmpty = empty
    BM:UpdateSendTexts()
end

function BM:UpdateSendTexts()
    if BM.SendHeroTitle then BM.SendHeroTitle:SetText(BM:L("send")) end
    if BM.SendHeroSubtitle then BM.SendHeroSubtitle:SetText(BM:L("compose_status")) end
    if BM.SendGuildButton and BM.SendGuildButton.label then BM.SendGuildButton.label:SetText(BM:L("guild")) end
    if BM.SendFriendsButton and BM.SendFriendsButton.label then BM.SendFriendsButton.label:SetText(BM:L("friends")) end
    if BM.SendRecipientLabel then BM.SendRecipientLabel:SetText(BM:L("recipient")) end
    if BM.SendSubjectLabel then BM.SendSubjectLabel:SetText(BM:L("subject")) end
    if BM.SendBodyLabel then BM.SendBodyLabel:SetText(BM:L("message")) end
    if BM.SendAttachmentsLabel then BM.SendAttachmentsLabel:SetText(BM:L("attachments")) end
    if BM.SendHintText then BM.SendHintText:SetText(BM:L("send_hint")) end
    if BM.SendMoneyLabel then BM.SendMoneyLabel:SetText(string.upper(BM:L("send_gold"))) end
    if BM.SendMailButton and BM.SendMailButton.label then BM.SendMailButton.label:SetText(BM:L("send_mail")) end
    if BM.RecipientBrowserTitle then
        local mode = BM.RecipientPopupMode == "guild" and BM:L("guild") or BM:L("friends")
        BM.RecipientBrowserTitle:SetText(BM.RecipientPopupMode and mode or "")
    end
    if BM.RecipientPopupEmpty then
        BM.RecipientPopupEmpty:SetText(BM:L("no_results"))
    end
end

function BM:RefreshAttachmentSlots()
    if not BM.SendAttachmentSlots then
        return
    end

    local maxSlots = ATTACHMENTS_MAX_SEND or 12
    for i = 1, maxSlots do
        local slot = BM.SendAttachmentSlots[i]
        if slot then
            local name, itemID, texture, count, quality = nil, nil, nil, nil, nil
            if GetSendMailItem then
                name, itemID, texture, count, quality = GetSendMailItem(i)
            end

            if texture then
                slot.icon:SetTexture(texture)
                slot.icon:Show()
                slot.icon:SetDesaturated(false)
                slot.icon:SetVertexColor(1, 1, 1, 1)
                if slot.count then
                    slot.count:SetText(count and count > 1 and count or "")
                end
                if quality and quality > 1 and slot.SetBackdropBorderColor then
                    local color = ITEM_QUALITY_COLORS and ITEM_QUALITY_COLORS[quality]
                    if color then
                        slot:SetBackdropBorderColor(color.r, color.g, color.b, 1)
                    end
                else
                    slot:SetBackdropBorderColor(0.12, 0.16, 0.23, 1)
                end
            else
                slot.icon:SetTexture(ATTACHMENT_ICON_FALLBACK)
                slot.icon:SetDesaturated(true)
                slot.icon:SetVertexColor(0.35, 0.42, 0.52, 1)
                slot.icon:Show()
                if slot.count then
                    slot.count:SetText("")
                end
                slot:SetBackdropBorderColor(0.12, 0.16, 0.23, 1)
            end
        end
    end
end

function BM:ClearSendAttachmentSlotVisuals()
    if not BM.SendAttachmentSlots then
        return
    end

    for _, slot in ipairs(BM.SendAttachmentSlots) do
        if slot and slot.icon then
            slot.icon:SetTexture(ATTACHMENT_ICON_FALLBACK)
            slot.icon:SetDesaturated(true)
            slot.icon:SetVertexColor(0.35, 0.42, 0.52, 1)
            slot.icon:Show()
            if slot.count then
                slot.count:SetText("")
            end
            if slot.SetBackdropBorderColor then
                slot:SetBackdropBorderColor(0.12, 0.16, 0.23, 1)
            end
        end
    end
end

function BM:EmbedSendAttachmentSlots()
    if not BM.SendAttachmentAnchor then
        return
    end

    BM.SendAttachmentSlots = BM.SendAttachmentSlots or {}
    local maxSlots = ATTACHMENTS_MAX_SEND or 12

    for i = 1, maxSlots do
        local slot = BM.SendAttachmentSlots[i]

        if not slot then
            slot = CreateFrame("Button", nil, BM.SendAttachmentAnchor, "BackdropTemplate")
            slot:SetSize(ATTACHMENT_SIZE, ATTACHMENT_SIZE)
            slot:SetBackdrop({
                bgFile = "Interface/Buttons/WHITE8X8",
                edgeFile = "Interface/Buttons/WHITE8X8",
                edgeSize = 1,
            })
            slot:SetBackdropColor(0.03, 0.04, 0.06, 1)
            slot:SetBackdropBorderColor(0.12, 0.16, 0.23, 1)

            local row = math.floor((i - 1) / ATTACHMENT_COLUMNS)
            local col = (i - 1) % ATTACHMENT_COLUMNS
            slot:SetPoint("TOPLEFT", col * (ATTACHMENT_SIZE + 8), -row * (ATTACHMENT_SIZE + 8))

            local icon = slot:CreateTexture(nil, "ARTWORK")
            icon:SetPoint("TOPLEFT", 5, -5)
            icon:SetPoint("BOTTOMRIGHT", -5, 5)
            icon:SetTexture(ATTACHMENT_ICON_FALLBACK)
            icon:SetDesaturated(true)
            icon:SetVertexColor(0.35, 0.42, 0.52, 1)
            slot.icon = icon

            local count = slot:CreateFontString(nil, "OVERLAY", "NumberFontNormalSmall")
            count:SetPoint("BOTTOMRIGHT", -4, 4)
            count:SetTextColor(1, 1, 1, 1)
            slot.count = count

            slot.slotIndex = i
            slot:RegisterForClicks("LeftButtonUp", "RightButtonUp")
            slot:RegisterForDrag("LeftButton")
            slot:SetScript("OnReceiveDrag", function(self)
                if CursorHasItem and CursorHasItem() and ClickSendMailItemButton then
                    ClickSendMailItemButton(self.slotIndex)
                    ClearCursor()
                    BM:RefreshAttachmentSlots()
                end
            end)
            slot:SetScript("OnMouseUp", function(self, button)
                if button == "RightButton" then
                    if ClickSendMailItemButton then
                        ClickSendMailItemButton(self.slotIndex, true)
                        BM:RefreshAttachmentSlots()
                    end
                elseif CursorHasItem and CursorHasItem() and ClickSendMailItemButton then
                    ClickSendMailItemButton(self.slotIndex)
                    ClearCursor()
                    BM:RefreshAttachmentSlots()
                else
                    local hasAttachment = GetSendMailItem and GetSendMailItem(self.slotIndex)
                    if not hasAttachment then
                        BM:OpenInventoryBags()
                    end
                end
            end)
            slot:SetScript("OnEnter", function(self)
                if GetSendMailItem and GameTooltip then
                    local name = GetSendMailItem(self.slotIndex)
                    if name then
                        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                        GameTooltip:SetSendMailItem(self.slotIndex)
                        GameTooltip:Show()
                    end
                end
            end)
            slot:SetScript("OnLeave", function()
                GameTooltip_Hide()
            end)

            BM.SendAttachmentSlots[i] = slot
        end

        slot:Show()
    end

    BM:RefreshAttachmentSlots()
end

function BM:SendCurrentMail()
    local recipient = BM.SendRecipientInput and BM.SendRecipientInput:GetText() or ""
    local subject = BM.SendSubjectInput and BM.SendSubjectInput:GetText() or ""
    local body = BM.SendBodyInput and BM.SendBodyInput:GetText() or ""

    recipient = NormalizeCharacterName(recipient)
    if recipient == "" then
        BM:SetStatus(BM:L("recipient_required"), 1, 0.35, 0.35)
        return
    end

    if subject == "" then
        subject = " "
    end

    BM:ApplySendMoney()
    SendMail(recipient, subject, body)
    BM:SetStatus(BM:L("sending_status"), 0.13, 0.72, 1)

    if BM.SendMailButton and BM.SendMailButton.label then
        BM.SendMailButton:Disable()
        BM.SendMailButton.label:SetText(BM:L("sending"))
    end
end

function BM:HandleSendSuccess()
    BM:RememberRecipient(BM.SendRecipientInput and BM.SendRecipientInput:GetText() or "")
    if BM.SendRecipientInput then BM.SendRecipientInput:SetText("") end
    if BM.SendSubjectInput then BM.SendSubjectInput:SetText("") end
    if BM.SendBodyInput then BM.SendBodyInput:SetText("") end
    BM:ResetSendMoney()
    BM:ClearSendAttachmentSlotVisuals()

    if BM.SendMailButton and BM.SendMailButton.label then
        BM.SendMailButton:Enable()
        BM.SendMailButton.label:SetText(BM:L("send_mail"))
    end

    if C_Timer and BM.RefreshAttachmentSlots then
        C_Timer.After(0.05, function()
            BM:RefreshAttachmentSlots()
        end)
    end

    BM:SetStatus(BM:L("sent_success"), unpack(BM.Theme.success))
end

function BM:HandleSendFailure()
    if BM.SendMailButton and BM.SendMailButton.label then
        BM.SendMailButton:Enable()
        BM.SendMailButton.label:SetText(BM:L("send_mail"))
    end

    BM:SetStatus(BM:L("sent_fail"), unpack(BM.Theme.danger))
end

function BM:PrepareReply(recipient, subject)
    recipient = NormalizeCharacterName(recipient or "")
    subject = strtrim(subject or "")

    if recipient == "" then
        BM:SetStatus(BM:L("recipient_required"), unpack(BM.Theme.danger))
        return
    end

    if subject == "" then
        subject = BM:L("no_subject")
    end

    if not subject:lower():find("^re:%s*", 1) then
        subject = "Re: " .. subject
    end

    BM.openMailIndex = nil
    BM:SelectTab("send")

    if BM.SendRecipientInput then
        BM.SendRecipientInput:SetText(recipient)
        BM.SendRecipientInput:HighlightText(0, 0)
    end
    if BM.SendSubjectInput then
        BM.SendSubjectInput:SetText(subject)
        BM.SendSubjectInput:HighlightText(0, 0)
    end
    if BM.SendBodyInput then
        BM.SendBodyInput:SetText("")
    end
    BM:ResetSendMoney()

    BM:RefreshRecipientSuggestion()
    BM:SetStatus(BM:L("reply_prepared"), 0.2, 0.72, 1)
end
