local addonName,BM=...

BM.Rows={}
BM.selectedMailIndex = nil
BM.deleteSelection = BM.deleteSelection or {}
BM.openMailIndex = nil

local GOLD_ICON = "Interface\\MoneyFrame\\UI-GoldIcon"
local SILVER_ICON = "Interface\\MoneyFrame\\UI-SilverIcon"
local COPPER_ICON = "Interface\\MoneyFrame\\UI-CopperIcon"
local LETTER_ICON = "Interface\\Icons\\INV_Letter_15"
local ROW_WIDTH = 1028
local COIN_ICON_SIZE = 13
local COIN_GOLD_WIDTH = 104
local COIN_SILVER_WIDTH = 30
local COIN_COPPER_WIDTH = 30
local COIN_DISPLAY_WIDTH = 209
local GOLD_BOX_WIDTH = 220
local EXPIRE_WIDTH = 74
local SENDER_WIDTH = 168

local function CreateCoinColumn(parent, width)
    local frame = CreateFrame("Frame", nil, parent)
    frame:SetSize(width, 16)

    local copperIcon = frame:CreateTexture(nil, "OVERLAY")
    copperIcon:SetTexture(COPPER_ICON)
    copperIcon:SetSize(COIN_ICON_SIZE, COIN_ICON_SIZE)
    copperIcon:SetPoint("RIGHT", 0, -1)

    local copper = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    copper:SetPoint("RIGHT", copperIcon, "LEFT", -2, 0)
    copper:SetWidth(COIN_COPPER_WIDTH)
    copper:SetJustifyH("RIGHT")
    copper:SetWordWrap(false)

    local silverIcon = frame:CreateTexture(nil, "OVERLAY")
    silverIcon:SetTexture(SILVER_ICON)
    silverIcon:SetSize(COIN_ICON_SIZE, COIN_ICON_SIZE)
    silverIcon:SetPoint("RIGHT", copper, "LEFT", -4, -1)

    local silver = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    silver:SetPoint("RIGHT", silverIcon, "LEFT", -2, 0)
    silver:SetWidth(COIN_SILVER_WIDTH)
    silver:SetJustifyH("RIGHT")
    silver:SetWordWrap(false)

    local goldIcon = frame:CreateTexture(nil, "OVERLAY")
    goldIcon:SetTexture(GOLD_ICON)
    goldIcon:SetSize(COIN_ICON_SIZE, COIN_ICON_SIZE)
    goldIcon:SetPoint("RIGHT", silver, "LEFT", -4, -1)

    local gold = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    gold:SetPoint("RIGHT", goldIcon, "LEFT", -2, 0)
    gold:SetWidth(COIN_GOLD_WIDTH)
    gold:SetJustifyH("RIGHT")
    gold:SetWordWrap(false)

    frame.Gold = gold
    frame.Silver = silver
    frame.Copper = copper
    frame.GoldIcon = goldIcon
    frame.SilverIcon = silverIcon
    frame.CopperIcon = copperIcon

    return frame
end

local function SetCoinColumn(display, money)
    if not display then return end
    money = tonumber(money) or 0
    local gold = floor(money / 10000)
    local silver = floor((money % 10000) / 100)
    local copper = money % 100

    display.Gold:SetText(gold > 0 and gold or "")
    display.Silver:SetText((gold > 0 or silver > 0) and silver or "")
    display.Copper:SetText((gold > 0 or silver > 0 or copper > 0) and copper or "")

    display.Gold:Show()
    display.Silver:Show()
    display.Copper:Show()
    display.GoldIcon:Show()
    display.SilverIcon:Show()
    display.CopperIcon:Show()

    if money <= 0 then
        display.Gold:SetText("")
        display.Silver:SetText("")
        display.Copper:SetText("")
    end
end

function BM:GetDeleteSelectionCount()
    local count = 0
    for _, selected in pairs(BM.deleteSelection or {}) do
        if selected then
            count = count + 1
        end
    end
    return count
end

function BM:GetDeleteSelectionIndices()
    local list = {}
    for index, selected in pairs(BM.deleteSelection or {}) do
        if selected then
            table.insert(list, index)
        end
    end
    table.sort(list, function(a, b)
        return a > b
    end)
    return list
end

function BM:ClearDeleteSelection(skipUpdate)
    BM.deleteSelection = {}
    for _, row in ipairs(BM.Rows or {}) do
        if row.UpdateDeleteCheckbox then
            row:UpdateDeleteCheckbox()
        end
        if row:IsShown() and row.UpdateVisualState then
            row:UpdateVisualState(false)
        end
    end
    if not skipUpdate and BM.UpdateActionTexts then
        BM:UpdateActionTexts()
    end
end

function BM:ToggleDeleteSelection(index)
    if not index then return end
    BM.deleteSelection = BM.deleteSelection or {}
    if BM.deleteSelection[index] then
        BM.deleteSelection[index] = nil
    else
        BM.deleteSelection[index] = true
    end

    for _, row in ipairs(BM.Rows or {}) do
        if row.index == index then
            if row.UpdateDeleteCheckbox then
                row:UpdateDeleteCheckbox()
            end
            if row.UpdateVisualState then
                row:UpdateVisualState(false)
            end
            break
        end
    end

    if BM.UpdateActionTexts then
        BM:UpdateActionTexts()
    end
end

local function IsAuctionMail(sender, subject)
    local senderText = (sender or ""):lower()
    local subjectText = (subject or ""):lower()
    return senderText:find("auktionshaus", 1, true)
        or senderText:find("auction house", 1, true)
        or subjectText:find("auktion", 1, true)
        or subjectText:find("auction", 1, true)
end

local function ResolveMailIcon(index, packageIcon, sender, subject, money, hasItem)
    if hasItem and hasItem > 0 then
        local itemName, _, texture = GetInboxItem(index, 1)
        local itemLink = GetInboxItemLink(index, 1)
        local itemIcon = itemLink and GetItemIcon(itemLink) or nil
        return itemIcon or texture or packageIcon or LETTER_ICON, itemName, itemLink
    end

    if money and money > 0 and IsAuctionMail(sender, subject) then
        return GOLD_ICON
    end

    return packageIcon or LETTER_ICON
end

local function GetAttachmentCount(index)
    local count = 0
    local maxAttachments = ATTACHMENTS_MAX_RECEIVE or 16
    for attachmentIndex = 1, maxAttachments do
        local name = GetInboxItem(index, attachmentIndex)
        if name then
            count = count + 1
        end
    end
    return count
end

local function FormatExpireText(daysLeft)
    if not daysLeft then
        return "0.0d"
    end
    if daysLeft < 1 then
        return string.format("%.1fh", daysLeft * 24)
    end
    return string.format("%.1fd", daysLeft)
end

local function CreateRow(parent,i)
  local r=CreateFrame("Button",nil,parent,"BackdropTemplate")
  r:SetSize(ROW_WIDTH, 32)
  r:RegisterForClicks("LeftButtonUp", "LeftButtonDown")
  r.baseColor = {
      i % 2 == 0 and 0.058 or 0.043,
      i % 2 == 0 and 0.062 or 0.047,
      i % 2 == 0 and 0.071 or 0.056,
      0.98
  }
  r.hoverColor = { 0.07, 0.076, 0.086, 0.99 }
  r.selectedColor = { 0.085, 0.115, 0.16, 0.995 }
  r.baseBorderColor = { 0.082, 0.102, 0.125, 0.95 }
  r.selectedBorderColor = { 0.32, 0.58, 0.86, 1 }
  r.baseAccentColor = { 0.11, 0.15, 0.2, 0.7 }
  r.selectedAccentColor = { 0.32, 0.58, 0.86, 1 }

    if i==1 then
        r:SetPoint("TOPLEFT",0,0)
    else
        r:SetPoint("TOPLEFT",BM.Rows[i-1],"BOTTOMLEFT",0,-2)
    end

    r:SetBackdrop({
        bgFile = "Interface/Buttons/WHITE8X8",
        edgeFile = "Interface/Buttons/WHITE8X8",
        edgeSize = 1,
    })
    r:SetBackdropColor(unpack(r.baseColor))
    r:SetBackdropBorderColor(unpack(r.baseBorderColor))

    r.leftAccent = r:CreateTexture(nil, "ARTWORK")
    r.leftAccent:SetTexture("Interface/Buttons/WHITE8X8")
    r.leftAccent:SetPoint("TOPLEFT", 0, 0)
    r.leftAccent:SetPoint("BOTTOMLEFT", 0, 0)
    r.leftAccent:SetWidth(1)
    r.leftAccent:SetVertexColor(unpack(r.baseAccentColor))

    r.topShine = r:CreateTexture(nil, "ARTWORK")
    r.topShine:SetTexture("Interface/Buttons/WHITE8X8")
    r.topShine:SetPoint("TOPLEFT", 1, -1)
    r.topShine:SetPoint("TOPRIGHT", -1, -1)
    r.topShine:SetHeight(1)
    r.topShine:SetVertexColor(0.2, 0.28, 0.36, 0.22)

    r.selectionGlow = r:CreateTexture(nil, "BACKGROUND")
    r.selectionGlow:SetTexture("Interface/Buttons/WHITE8X8")
    r.selectionGlow:SetPoint("TOPLEFT", -2, 2)
    r.selectionGlow:SetPoint("BOTTOMRIGHT", 2, -2)
    r.selectionGlow:SetVertexColor(0.24, 0.5, 0.82, 0.18)

    r.deleteBox = CreateFrame("CheckButton", nil, r, "BackdropTemplate")
    r.deleteBox:SetPoint("LEFT", 10, 0)
    r.deleteBox:SetSize(16, 16)
    r.deleteBox:SetBackdrop({
        bgFile = "Interface/Buttons/WHITE8X8",
        edgeFile = "Interface/Buttons/WHITE8X8",
        edgeSize = 1,
    })
    r.deleteBox:SetBackdropColor(0.03, 0.034, 0.041, 0.96)
    r.deleteBox:SetBackdropBorderColor(unpack(BM.Theme.borderSoft))
    r.deleteBox:SetHitRectInsets(0, 0, 0, 0)

    r.deleteBox.mark = r.deleteBox:CreateTexture(nil, "ARTWORK")
    r.deleteBox.mark:SetTexture("Interface/Buttons/WHITE8X8")
    r.deleteBox.mark:SetPoint("TOPLEFT", 3, -3)
    r.deleteBox.mark:SetPoint("BOTTOMRIGHT", -3, 3)
    r.deleteBox.mark:Hide()

    r.iconBorder = CreateFrame("Frame", nil, r, "BackdropTemplate")
    r.iconBorder:SetPoint("LEFT", 36, 0)
    r.iconBorder:SetSize(24, 24)
    r.iconBorder:SetBackdrop({
        bgFile = "Interface/Buttons/WHITE8X8",
        edgeFile = "Interface/Buttons/WHITE8X8",
        edgeSize = 1,
    })
    r.iconBorder:SetBackdropColor(0.025, 0.03, 0.038, 1)
    r.iconBorder:SetBackdropBorderColor(0.12, 0.17, 0.22, 1)

    r.icon=r.iconBorder:CreateTexture(nil,"ARTWORK")
    r.icon:SetSize(20,20)
    r.icon:SetPoint("CENTER", r.iconBorder, "CENTER")

    r.item=r:CreateFontString(nil,"OVERLAY","GameFontNormal")
    r.item:SetPoint("LEFT",r.iconBorder,"RIGHT",10,0)
    r.item:SetWidth(560)
    r.item:SetJustifyH("LEFT")
    r.item:SetWordWrap(false)

    r.meta=r:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    r.meta:SetPoint("TOPLEFT", r.item, "BOTTOMLEFT", 0, -4)
    r.meta:SetWidth(440)
    r.meta:SetJustifyH("LEFT")
    r.meta:SetTextColor(0.58, 0.64, 0.75, 1)
    r.meta:Hide()

    r.goldBox = CreateFrame("Frame", nil, r, "BackdropTemplate")
    r.goldBox:SetPoint("LEFT", r, "LEFT", 848, 0)
    r.goldBox:SetSize(170, 20)
    r.goldBox:SetBackdrop({
        bgFile = "Interface/Buttons/WHITE8X8",
        edgeFile = "Interface/Buttons/WHITE8X8",
        edgeSize = 1,
    })
    r.goldBox:SetBackdropColor(0.03, 0.034, 0.041, 0.92)
    r.goldBox:SetBackdropBorderColor(0.08, 0.1, 0.12, 0.88)

    r.coinDisplay = CreateCoinColumn(r.goldBox, 158)
    r.coinDisplay:SetPoint("CENTER", 0, 0)

    r.expire=r:CreateFontString(nil,"OVERLAY","GameFontDisableSmall")
    r.expire:SetPoint("LEFT", r, "LEFT", 786, 0)
    r.expire:SetWidth(64)
    r.expire:SetJustifyH("CENTER")
    r.expire:SetWordWrap(false)

    r.sender=r:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    r.sender:SetPoint("LEFT", r, "LEFT", 616, 0)
    r.sender:SetWidth(140)
    r.sender:SetJustifyH("CENTER")
    r.sender:SetWordWrap(false)

    r.badge = CreateFrame("Frame", nil, r, "BackdropTemplate")
    r.badge:SetPoint("RIGHT", -12, 0)
    r.badge:SetSize(18, 18)
    r.badge:SetBackdrop({
        bgFile = "Interface/Buttons/WHITE8X8",
        edgeFile = "Interface/Buttons/WHITE8X8",
        edgeSize = 1,
    })
    r.badge:Hide()

    r.UpdateVisualState = function(self, isHovered)
        local isSelected = BM.selectedMailIndex == self.index
        local isMarked = BM.deleteSelection and BM.deleteSelection[self.index]
        if isSelected then
            self:SetBackdropColor(unpack(self.selectedColor))
            self:SetBackdropBorderColor(unpack(self.selectedBorderColor))
            self.leftAccent:SetVertexColor(unpack(self.selectedAccentColor))
            if self.iconBorder then
                self.iconBorder:SetBackdropBorderColor(0.4, 0.66, 0.94, 1)
            end
            self.selectionGlow:Show()
        elseif isHovered then
            self:SetBackdropColor(unpack(self.hoverColor))
            self:SetBackdropBorderColor(0.18, 0.23, 0.28, 1)
            self.leftAccent:SetVertexColor(0.72, 0.58, 0.16, 0.95)
            if self.iconBorder and self.hasItem then
                self.iconBorder:SetBackdropBorderColor(unpack(BM.Theme.accent))
            elseif self.iconBorder then
                self.iconBorder:SetBackdropBorderColor(unpack(BM.Theme.borderSoft))
            end
            self.selectionGlow:Show()
        elseif isMarked then
            self:SetBackdropColor(unpack(self.baseColor))
            self:SetBackdropBorderColor(BM.Theme.accent[1], BM.Theme.accent[2], BM.Theme.accent[3], 0.9)
            self.leftAccent:SetVertexColor(unpack(self.selectedAccentColor))
            if self.iconBorder and self.hasItem then
                self.iconBorder:SetBackdropBorderColor(unpack(BM.Theme.accent))
            elseif self.iconBorder then
                self.iconBorder:SetBackdropBorderColor(unpack(BM.Theme.borderSoft))
            end
            self.selectionGlow:Show()
        else
            self:SetBackdropColor(unpack(self.baseColor))
            self:SetBackdropBorderColor(unpack(self.baseBorderColor))
            self.leftAccent:SetVertexColor(unpack(self.baseAccentColor))
            if self.iconBorder and self.hasItem then
                self.iconBorder:SetBackdropBorderColor(unpack(BM.Theme.accent))
            elseif self.iconBorder then
                self.iconBorder:SetBackdropBorderColor(unpack(BM.Theme.borderSoft))
            end
            self.selectionGlow:Hide()
        end

        if self.UpdateDeleteCheckbox then
            self:UpdateDeleteCheckbox()
        end
    end

    r:SetScript("OnEnter",function(self)
        self:UpdateVisualState(true)
        GameTooltip:SetOwner(self,"ANCHOR_RIGHT")
        if self.hasItem then
            GameTooltip:SetInboxItem(self.index)
            GameTooltip:Show()
        end
    end)

    r:SetScript("OnLeave",function(self)
        self:UpdateVisualState(false)
        GameTooltip_Hide()
    end)

    r.UpdateDeleteCheckbox = function(self)
        if not self.deleteBox then return end
        local isChecked = BM.deleteSelection and BM.deleteSelection[self.index]
        self.deleteBox.mark:SetShown(isChecked)
        self.deleteBox.mark:SetVertexColor(unpack(BM.Theme.accent))
        if isChecked then
            self.deleteBox:SetBackdropBorderColor(unpack(BM.Theme.accent))
            self.deleteBox:SetBackdropColor(0.08, 0.08, 0.08, 1)
        else
            self.deleteBox:SetBackdropBorderColor(unpack(BM.Theme.borderSoft))
            self.deleteBox:SetBackdropColor(0.03, 0.034, 0.041, 0.96)
        end
    end

    r.deleteBox:SetScript("OnClick", function()
        if BM.ToggleDeleteSelection then
            BM:ToggleDeleteSelection(r.index)
        end
    end)

    r.deleteBox:SetScript("OnEnter", function()
        r:UpdateVisualState(true)
    end)

    r.deleteBox:SetScript("OnLeave", function()
        r:UpdateVisualState(false)
    end)

    r:SetScript("OnClick", function(self)
        if BM.SelectInboxRow then
            BM:SelectInboxRow(self.index)
        end
    end)

    r:SetScript("OnDoubleClick", function(self)
        if BM.SelectInboxRow then
            BM:SelectInboxRow(self.index)
        end
        if BM.OpenMail then
            BM:OpenMail(self.index)
        end
    end)

    return r
end

function BM:ApplyInboxLayout()
    local size = BetterMailboxDB and BetterMailboxDB.fontSize or 12
    local rowHeight = math.max(32, size + 16)
    local rowSpacing = 2
    local iconSize = 24
    local iconBorderSize = 28
    local checkboxSize = math.max(16, size + 2)
    local checkboxLeftX = 12
    local iconLeftX = checkboxLeftX + checkboxSize + 12
    local itemStartX = iconLeftX + iconBorderSize + 10
    local viewportWidth = BM.ScrollFrame and BM.ScrollFrame:GetWidth() or (ROW_WIDTH + 24)
    local rowWidth = math.max(980, math.floor(viewportWidth) - 12)
    local goldBoxWidth = GOLD_BOX_WIDTH
    local goldWidth = COIN_DISPLAY_WIDTH
    local goldLeftX = rowWidth - goldBoxWidth - 10
    local expireWidth = EXPIRE_WIDTH
    local expireX = goldLeftX - expireWidth - 34
    local senderWidth = SENDER_WIDTH
    local senderX = expireX - senderWidth - 38
    local itemWidth = math.max(260, senderX - itemStartX - 24)

    BM.InboxLayoutMetrics = {
        rowHeight = rowHeight,
        rowSpacing = rowSpacing,
        rowWidth = rowWidth,
        senderX = senderX,
        expireX = expireX,
        goldLeftX = goldLeftX,
        goldBoxWidth = goldBoxWidth,
        coinDisplayWidth = goldWidth,
    }

    if BM.HeaderTextByKey then
        local positions = {
            item = iconLeftX + math.floor(iconBorderSize * 0.5),
            subject = itemStartX + 84,
            sender = senderX + math.floor(senderWidth * 0.5),
            expires = expireX + math.floor(expireWidth * 0.5),
            gold = goldLeftX + math.floor(goldBoxWidth * 0.5),
        }
        for key, text in pairs(BM.HeaderTextByKey) do
            text:ClearAllPoints()
            if key == "item" then
                text:SetPoint("CENTER", text:GetParent(), "LEFT", positions[key], 0)
                text:SetJustifyH("CENTER")
            elseif key == "subject" then
                text:SetPoint("LEFT", positions[key] or itemStartX, 0)
                text:SetJustifyH("LEFT")
            else
                text:SetPoint("CENTER", headerBar or text:GetParent(), "LEFT", positions[key], 0)
                text:SetJustifyH("CENTER")
            end
        end
    end

    for _, row in ipairs(BM.Rows or {}) do
        row:SetSize(rowWidth, rowHeight)
        if row.deleteBox then
            row.deleteBox:ClearAllPoints()
            row.deleteBox:SetPoint("LEFT", row, "LEFT", checkboxLeftX, 0)
            row.deleteBox:SetSize(checkboxSize, checkboxSize)
        end
        if row.iconBorder then
            row.iconBorder:ClearAllPoints()
            row.iconBorder:SetPoint("LEFT", row, "LEFT", iconLeftX, 0)
            row.iconBorder:SetSize(iconBorderSize, iconBorderSize)
        end
        if row.icon then
            row.icon:SetSize(iconSize, iconSize)
        end
        if row.item then
            row.item:SetWidth(itemWidth)
        end
        if row.sender then
            row.sender:ClearAllPoints()
            row.sender:SetPoint("LEFT", row, "LEFT", senderX, 0)
            row.sender:SetWidth(senderWidth)
            row.sender:SetJustifyH("CENTER")
        end
        if row.expire then
            row.expire:ClearAllPoints()
            row.expire:SetPoint("LEFT", row, "LEFT", expireX, 0)
            row.expire:SetWidth(expireWidth)
            row.expire:SetJustifyH("CENTER")
        end
        if row.goldBox then
            row.goldBox:ClearAllPoints()
            row.goldBox:SetPoint("LEFT", row, "LEFT", goldLeftX, 0)
            row.goldBox:SetSize(goldBoxWidth, math.max(16, size + 4))
        end
        if row.coinDisplay then
            row.coinDisplay:SetWidth(goldWidth)
            row.coinDisplay:ClearAllPoints()
            row.coinDisplay:SetPoint("CENTER", row.goldBox, "CENTER", 0, 0)
        end
    end
end

function BM:SelectInboxRow(index)
    BM.selectedMailIndex = index

    for _, row in ipairs(BM.Rows) do
        if row:IsShown() and row.UpdateVisualState then
            row:UpdateVisualState(false)
        end
    end

    if BM.UpdateActionTexts then
        BM:UpdateActionTexts()
    end

    if index then
        BM:SetStatus(BM:L("selected_mail_ready"), 0.2, 0.72, 1)
    end
end

function BM:TakeOpenMailAttachment(attachmentIndex)
    local index = BM.openMailIndex
    if not index or not attachmentIndex then
        return
    end

    TakeInboxItem(index, attachmentIndex)
    BM:SetStatus(BM:L("collecting"), 0.13, 0.72, 1)

    C_Timer.After(0.25, function()
        CheckInbox()
        if BM.RefreshInbox then
            BM:RefreshInbox()
        end
        if BM.RefreshOpenMail then
            BM:RefreshOpenMail()
        end
    end)
end

function BM:CloseOpenMail()
    BM.openMailIndex = nil
    if BM.currentTab == "inbox" then
        BM:SelectTab("inbox")
    end
end

function BM:OpenMail(index)
    if not index then
        return
    end

    BM.openMailIndex = index
    if BM.SelectInboxRow then
        BM:SelectInboxRow(index)
    end
    BM:SelectTab("inbox")
    BM:SetStatus(BM:L("open_mail_status"), 0.2, 0.72, 1)
end

function BM:ReplyToOpenMail()
    if not BM.openMailIndex then
        return
    end

    local _, _, sender, subject = GetInboxHeaderInfo(BM.openMailIndex)
    if BM.PrepareReply then
        BM:PrepareReply(sender, subject)
    end
end

function BM:RefreshOpenMail()
    if not BM.OpenMailPanel or not BM.openMailIndex then
        return
    end

    local visible, totalItems = GetInboxNumItems()
    local total = math.max(visible or 0, totalItems or 0)
    if BM.openMailIndex > total or total <= 0 then
        BM:CloseOpenMail()
        return
    end

    local _, _, sender, subject, money, _, daysLeft = GetInboxHeaderInfo(BM.openMailIndex)
    local mailText = GetInboxText and GetInboxText(BM.openMailIndex) or ""
    if BM.OpenMailTitle then
        BM.OpenMailTitle:SetText((subject and subject ~= "") and subject or BM:L("no_subject"))
    end
    if BM.OpenMailSenderValue then
        BM.OpenMailSenderValue:SetText(sender or "")
    end
    if BM.OpenMailExpiresValue then
        BM.OpenMailExpiresValue:SetText(FormatExpireText(daysLeft))
    end
    if BM.OpenMailBodyText then
        local bodyText = (mailText and mailText ~= "") and mailText or BM:L("no_mail_text")
        BM.OpenMailBodyText:SetText(bodyText)
        local width = (BM.OpenMailBodyScrollFrame and BM.OpenMailBodyScrollFrame:GetWidth() or 460) - 20
        BM.OpenMailBodyText:SetWidth(math.max(240, width))
        if BM.OpenMailBodyScrollChild then
            BM.OpenMailBodyScrollChild:SetWidth(math.max(240, width))
        end
        local bodyHeight = math.max(120, math.ceil(BM.OpenMailBodyText:GetStringHeight()) + 20)
        if BM.OpenMailBodyScrollChild then
            BM.OpenMailBodyScrollChild:SetHeight(bodyHeight)
        end
    end

    if BM.OpenMailAttachedGoldBlock then
        BM.OpenMailAttachedGoldBlock:SetShown((money or 0) > 0)
    end
    if BM.OpenMailAttachedGoldValue then
        SetCoinColumn(BM.OpenMailAttachedGoldValue, money or 0)
    end

    if BM.OpenMailReplyButton then
        BM.OpenMailReplyButton:SetEnabled(sender and sender ~= "")
        if BM.OpenMailReplyButton.label then
            BM.OpenMailReplyButton.label:SetAlpha((sender and sender ~= "") and 1 or 0.45)
        end
    end

    local maxAttachments = ATTACHMENTS_MAX_RECEIVE or 16
    local shownAttachments = 0
    if BM.OpenMailAttachmentSlots then
        for attachmentIndex, slot in ipairs(BM.OpenMailAttachmentSlots) do
            if attachmentIndex <= maxAttachments then
                local name, _, texture, count, quality = GetInboxItem(BM.openMailIndex, attachmentIndex)
                if name then
                    shownAttachments = shownAttachments + 1
                    slot.mailIndex = BM.openMailIndex
                    slot.attachmentIndex = attachmentIndex
                    slot.icon:SetTexture(texture or LETTER_ICON)
                    slot.icon:SetDesaturated(false)
                    slot.icon:SetVertexColor(1, 1, 1, 1)
                    slot.count:SetText(count and count > 1 and count or "")
                    if quality and quality > 1 and ITEM_QUALITY_COLORS and ITEM_QUALITY_COLORS[quality] then
                        local color = ITEM_QUALITY_COLORS[quality]
                        slot:SetBackdropBorderColor(color.r, color.g, color.b, 1)
                    else
                        slot:SetBackdropBorderColor(0.12, 0.16, 0.23, 1)
                    end
                    slot:Show()
                else
                    slot.mailIndex = nil
                    slot.attachmentIndex = nil
                    slot.count:SetText("")
                    slot:Hide()
                end
            else
                slot:Hide()
            end
        end
    end

    if BM.OpenMailNoAttachments then
        BM.OpenMailNoAttachments:SetShown(shownAttachments == 0)
    end
end

function BM:CreateInbox()
    local headers={
        {key="item",x=28},
        {key="subject",x=138},
        {key="sender",x=648},
        {key="expires",x=818},
        {key="gold",x=912}
    }

    local headerBar = CreateFrame("Frame", nil, BM.InboxPanel, "BackdropTemplate")
    headerBar:SetPoint("TOPLEFT", BM.InboxPanel, "TOPLEFT", 12, -12)
    headerBar:SetPoint("TOPRIGHT", BM.InboxPanel, "TOPRIGHT", -12, -12)
    headerBar:SetHeight(26)
    headerBar:SetBackdrop({
        bgFile = "Interface/Buttons/WHITE8X8",
        edgeFile = "Interface/Buttons/WHITE8X8",
        edgeSize = 1,
    })
    headerBar:SetBackdropColor(0.03, 0.035, 0.042, 0.98)
    headerBar:SetBackdropBorderColor(unpack(BM.Theme.borderSoft))
    BM.InboxHeaderBar = headerBar

    BM.HeaderTexts = {}
    BM.HeaderTextByKey = {}
    for _,h in ipairs(headers) do
        local t=headerBar:CreateFontString(nil,"OVERLAY","GameFontNormalSmall")
        t:SetPoint("LEFT", h.x, 0)
        t:SetTextColor(unpack(BM.Theme.mutetext))
        t.localeKey = h.key
        t:SetJustifyH(h.key == "item_subject" and "LEFT" or "CENTER")
        table.insert(BM.HeaderTexts, t)
        BM.HeaderTextByKey[h.key] = t
    end

    BM.ScrollFrame:SetParent(BM.InboxPanel)
    BM.ScrollFrame:ClearAllPoints()
    BM.ScrollFrame:SetPoint("TOPLEFT", BM.InboxPanel, "TOPLEFT", 12, -46)
    BM.ScrollFrame:SetPoint("BOTTOMRIGHT", BM.InboxPanel, "BOTTOMRIGHT", -28, 12)

    local empty = BM.InboxPanel:CreateFontString(nil, "OVERLAY", "GameFontDisable")
    empty:SetPoint("CENTER", 0, 8)
    empty:Hide()

    local emptySub = BM.InboxPanel:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    emptySub:SetPoint("TOP", empty, "BOTTOM", 0, -8)
    emptySub:SetTextColor(unpack(BM.Theme.mutetext))
    emptySub:Hide()

    BM.EmptyState = empty
    BM.EmptyStateSub = emptySub

    local openMailPanel = CreateFrame("Frame", nil, BM.ContentInset)
    openMailPanel:SetAllPoints(BM.InboxPanel)
    openMailPanel:Hide()

    local detailHero = BM:CreatePanel(openMailPanel)
    detailHero:SetPoint("TOPLEFT", 16, -16)
    detailHero:SetPoint("TOPRIGHT", -16, -16)
    detailHero:SetHeight(88)
    detailHero.themePalette = "hero"
    BM:ApplyThemeToPanel(detailHero)

    local openMailTitle = detailHero:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    openMailTitle:SetPoint("TOPLEFT", 18, -14)
    openMailTitle:SetTextColor(unpack(BM.Theme.text))
    openMailTitle:SetJustifyH("LEFT")

    local senderLabel = detailHero:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    senderLabel:SetPoint("TOPLEFT", openMailTitle, "BOTTOMLEFT", 0, -10)
    senderLabel:SetTextColor(unpack(BM.Theme.mutetext))

    local senderValue = detailHero:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    senderValue:SetPoint("LEFT", senderLabel, "RIGHT", 8, 0)
    senderValue:SetTextColor(unpack(BM.Theme.text))

    local expiresLabel = detailHero:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    expiresLabel:SetPoint("TOPLEFT", senderLabel, "BOTTOMLEFT", 0, -8)
    expiresLabel:SetTextColor(unpack(BM.Theme.mutetext))
    expiresLabel:SetText(BM:L("expires"))

    local expiresValue = detailHero:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    expiresValue:SetPoint("LEFT", expiresLabel, "RIGHT", 8, 0)
    expiresValue:SetTextColor(unpack(BM.Theme.text))

    local backButton = BM:CreateFlatActionButton(detailHero, 132, 30, "")
    backButton:SetPoint("RIGHT", -18, -12)
    backButton:SetScript("OnClick", function()
        BM:CloseOpenMail()
    end)

    local replyButton = BM:CreateFlatActionButton(detailHero, 132, 30, "")
    replyButton:SetPoint("RIGHT", backButton, "LEFT", -10, 0)
    replyButton:SetScript("OnClick", function()
        BM:ReplyToOpenMail()
    end)

    local bodyLabel = openMailPanel:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    bodyLabel:SetPoint("TOPLEFT", detailHero, "BOTTOMLEFT", 0, -14)
    bodyLabel:SetTextColor(unpack(BM.Theme.mutetext))

    local bodyPanel = BM:CreatePanel(openMailPanel)
    bodyPanel:SetPoint("TOPLEFT", bodyLabel, "BOTTOMLEFT", 0, -8)
    bodyPanel:SetPoint("TOPRIGHT", openMailPanel, "TOPRIGHT", -16, -126)
    bodyPanel:SetPoint("BOTTOMLEFT", openMailPanel, "BOTTOMLEFT", 16, 132)
    bodyPanel.themePalette = "content"
    BM:ApplyThemeToPanel(bodyPanel)

    local bodyScroll = CreateFrame("ScrollFrame", nil, bodyPanel, "UIPanelScrollFrameTemplate")
    bodyScroll:SetPoint("TOPLEFT", 12, -12)
    bodyScroll:SetPoint("BOTTOMRIGHT", -28, 12)

    local bodyScrollChild = CreateFrame("Frame", nil, bodyScroll)
    bodyScrollChild:SetSize(1, 1)
    bodyScroll:SetScrollChild(bodyScrollChild)

    local bodyText = bodyScrollChild:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    bodyText:SetPoint("TOPLEFT", 0, 0)
    bodyText:SetJustifyH("LEFT")
    bodyText:SetJustifyV("TOP")
    bodyText:SetTextColor(unpack(BM.Theme.text))

    local goldBlock = CreateFrame("Frame", nil, openMailPanel)
    goldBlock:SetPoint("BOTTOMLEFT", openMailPanel, "BOTTOMLEFT", 16, 84)
    goldBlock:SetSize(320, 26)

    local attachedGoldLabel = goldBlock:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    attachedGoldLabel:SetPoint("LEFT", 0, 0)
    attachedGoldLabel:SetTextColor(unpack(BM.Theme.mutetext))
    attachedGoldLabel:SetText(BM:L("attached_gold"))

    local attachedGoldValue = CreateCoinColumn(goldBlock, COIN_DISPLAY_WIDTH)
    attachedGoldValue:SetPoint("LEFT", attachedGoldLabel, "RIGHT", 12, 0)

    local attachmentsLabel = openMailPanel:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    attachmentsLabel:SetPoint("BOTTOMLEFT", openMailPanel, "BOTTOMLEFT", 16, 108)
    attachmentsLabel:SetTextColor(unpack(BM.Theme.mutetext))

    local attachmentPanel = BM:CreatePanel(openMailPanel)
    attachmentPanel:SetPoint("BOTTOMLEFT", openMailPanel, "BOTTOMLEFT", 16, 16)
    attachmentPanel:SetPoint("BOTTOMRIGHT", openMailPanel, "BOTTOMRIGHT", -16, 16)
    attachmentPanel:SetHeight(72)
    attachmentPanel.themePalette = "panel"
    BM:ApplyThemeToPanel(attachmentPanel)

    local noAttachments = attachmentPanel:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    noAttachments:SetPoint("LEFT", 14, 0)
    noAttachments:SetTextColor(unpack(BM.Theme.mutetext))
    noAttachments:SetJustifyH("LEFT")

    local attachmentAnchor = CreateFrame("Frame", nil, attachmentPanel)
    attachmentAnchor:SetPoint("LEFT", 12, 0)
    attachmentAnchor:SetSize(6 * 48, 48)

    BM.OpenMailAttachmentSlots = BM.OpenMailAttachmentSlots or {}
    for attachmentIndex = 1, (ATTACHMENTS_MAX_RECEIVE or 16) do
        local slot = CreateFrame("Button", nil, attachmentAnchor, "BackdropTemplate")
        slot:SetSize(42, 42)
        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((attachmentIndex - 1) / 6)
        local col = (attachmentIndex - 1) % 6
        slot:SetPoint("TOPLEFT", col * 48, -row * 48)

        slot.icon = slot:CreateTexture(nil, "ARTWORK")
        slot.icon:SetPoint("TOPLEFT", 5, -5)
        slot.icon:SetPoint("BOTTOMRIGHT", -5, 5)

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

        slot:RegisterForClicks("LeftButtonUp")
        slot:SetScript("OnClick", function(self)
            if self.attachmentIndex then
                BM:TakeOpenMailAttachment(self.attachmentIndex)
            end
        end)
        slot:SetScript("OnEnter", function(self)
            if self.mailIndex and self.attachmentIndex then
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:SetInboxItem(self.mailIndex, self.attachmentIndex)
                GameTooltip:Show()
            end
        end)
        slot:SetScript("OnLeave", function()
            GameTooltip_Hide()
        end)
        slot:Hide()
        BM.OpenMailAttachmentSlots[attachmentIndex] = slot
    end

    BM.OpenMailPanel = openMailPanel
    BM.OpenMailTitle = openMailTitle
    BM.OpenMailSenderLabel = senderLabel
    BM.OpenMailSenderValue = senderValue
    BM.OpenMailExpiresLabel = expiresLabel
    BM.OpenMailExpiresValue = expiresValue
    BM.OpenMailBackButton = backButton
    BM.OpenMailReplyButton = replyButton
    BM.OpenMailBodyLabel = bodyLabel
    BM.OpenMailBodyScrollFrame = bodyScroll
    BM.OpenMailBodyScrollChild = bodyScrollChild
    BM.OpenMailBodyText = bodyText
    BM.OpenMailAttachmentsLabel = attachmentsLabel
    BM.OpenMailAttachmentPanel = attachmentPanel
    BM.OpenMailNoAttachments = noAttachments
    BM.OpenMailAttachedGoldBlock = goldBlock
    BM.OpenMailAttachedGoldLabel = attachedGoldLabel
    BM.OpenMailAttachedGoldValue = attachedGoldValue

    BM:UpdateInboxTexts()
    BM:UpdateOpenMailTexts()
    BM:ApplyInboxLayout()
end

function BM:UpdateInboxTexts()
    if BM.HeaderTexts then
        for _, text in ipairs(BM.HeaderTexts) do
            text:SetText(BM:L(text.localeKey))
        end
    end
    if BM.EmptyState then BM.EmptyState:SetText(BM:L("no_mails")) end
    if BM.EmptyStateSub then BM.EmptyStateSub:SetText(BM:L("no_mails_sub")) end
end

function BM:UpdateOpenMailTexts()
    if BM.OpenMailSenderLabel then BM.OpenMailSenderLabel:SetText(BM:L("mail_from")) end
    if BM.OpenMailExpiresLabel then BM.OpenMailExpiresLabel:SetText(BM:L("expires")) end
    if BM.OpenMailBackButton and BM.OpenMailBackButton.label then BM.OpenMailBackButton.label:SetText(BM:L("back")) end
    if BM.OpenMailReplyButton and BM.OpenMailReplyButton.label then BM.OpenMailReplyButton.label:SetText(BM:L("reply")) end
    if BM.OpenMailBodyLabel then BM.OpenMailBodyLabel:SetText(BM:L("mail_body")) end
    if BM.OpenMailAttachmentsLabel then BM.OpenMailAttachmentsLabel:SetText(BM:L("attachments")) end
    if BM.OpenMailNoAttachments then BM.OpenMailNoAttachments:SetText(BM:L("no_attachments")) end
    if BM.OpenMailAttachedGoldLabel then BM.OpenMailAttachedGoldLabel:SetText(BM:L("attached_gold")) end
end

function BM:RefreshInbox()
    if not BM.uiReady or not BM.MainFrame or not BM.ScrollFrame or not BM.ScrollChild or not BM.InboxPanel then
        return
    end

    local visible, totalItems = GetInboxNumItems()
    local total = math.max(visible or 0, totalItems or 0)
    local lootable = 0
    local totalMoney = 0

    if BM.selectedMailIndex and BM.selectedMailIndex > total then
        BM.selectedMailIndex = nil
    end

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

    for i=1,total do
        if not BM.Rows[i] then
            BM.Rows[i]=CreateRow(BM.ScrollChild,i)
        end

        local packageIcon, _, sender, subject, money, codAmount, daysLeft, hasItem = GetInboxHeaderInfo(i)
        local row=BM.Rows[i]
        row.index=i
        row.hasItem = hasItem and hasItem > 0
        row.isDeleteEligible = (codAmount or 0) <= 0 and (money or 0) <= 0 and not row.hasItem
        totalMoney = totalMoney + (money or 0)
        local resolvedIcon, itemName, itemLink = ResolveMailIcon(i, packageIcon, sender, subject, money, hasItem)
        row.icon:SetTexture(resolvedIcon)

        if row.hasItem then
            local _, _, _, count = GetInboxItem(i,1)
            lootable = lootable + 1
            row.iconBorder:SetBackdropBorderColor(unpack(BM.Theme.accent))
            row.badge:SetBackdropColor(unpack(BM.Theme.accent))
            row.badge:SetBackdropBorderColor(unpack(BM.Theme.accentSoft))

            if itemLink then
                if count and count > 1 then
                    row.item:SetText(string.format("%s x%d", itemLink, count))
                else
                    row.item:SetText(itemLink)
                end
            elseif itemName then
                if count and count > 1 then
                    row.item:SetText(string.format("%s x%d", itemName, count))
                else
                    row.item:SetText(itemName)
                end
            else
                row.item:SetText(subject or "")
            end

            row.meta:SetText(subject and subject ~= "" and subject or BM:L("attached_item"))
        else
            row.iconBorder:SetBackdropBorderColor(unpack(BM.Theme.borderSoft))
            if money and money > 0 then
                row.badge:SetBackdropColor(unpack(BM.Theme.accent))
                row.badge:SetBackdropBorderColor(unpack(BM.Theme.accentSoft))
            else
                row.badge:SetBackdropColor(unpack(BM.Theme.panelAlt))
                row.badge:SetBackdropBorderColor(unpack(BM.Theme.borderSoft))
            end
            row.item:SetText(subject ~= "" and subject or BM:L("no_subject"))
            if money and money > 0 and IsAuctionMail(sender, subject) then
                row.meta:SetText(BM:L("contains_gold"))
            else
                row.meta:SetText(money and money > 0 and BM:L("contains_gold") or BM:L("letter_only"))
            end
        end

        SetCoinColumn(row.coinDisplay, money)

        if daysLeft and daysLeft < 1 then
            row.expire:SetText(string.format("%.1fh", daysLeft * 24))
            row.expire:SetTextColor(1, 0.35, 0.35)
        elseif daysLeft and daysLeft < 3 then
            row.expire:SetText(string.format("%.1fd",daysLeft))
            row.expire:SetTextColor(1, 0.82, 0.24)
        else
            row.expire:SetText(string.format("%.1fd",daysLeft or 0))
            row.expire:SetTextColor(unpack(BM.Theme.mutetext))
        end

        row.sender:SetText(sender or "")
        row.item:SetTextColor(unpack(BM.Theme.text))
        row.meta:SetTextColor(unpack(BM.Theme.mutetext))
        row.sender:SetTextColor(0.78, 0.83, 0.9)
        if row.goldBox then
            row.goldBox:SetBackdropColor(0.03, 0.034, 0.041, 0.92)
            row.goldBox:SetBackdropBorderColor(0.08, 0.1, 0.12, 0.88)
        end
        row.meta:Hide()
        if row.UpdateDeleteCheckbox then
            row:UpdateDeleteCheckbox()
        end
        if row.UpdateVisualState then
            row:UpdateVisualState(false)
        end
        row:Show()
    end

    for i = total + 1, #BM.Rows do
        BM.Rows[i]:Hide()
        BM.Rows[i].hasItem = nil
        BM.Rows[i].isDeleteEligible = nil
    end

    BM:ApplyInboxLayout()
    if BM.ApplyFontSize then
        BM:ApplyFontSize()
    end

    local metrics = BM.InboxLayoutMetrics or { rowHeight = 30, rowSpacing = 2, rowWidth = ROW_WIDTH }
    local height = total > 0 and (total * (metrics.rowHeight + metrics.rowSpacing)) or 1
    BM.ScrollChild:SetSize(metrics.rowWidth, height)

    if BM.EmptyState then
        BM.EmptyState:SetShown(total == 0)
    end

    if BM.EmptyStateSub then
        BM.EmptyStateSub:SetShown(total == 0)
    end

    if BM.UpdateActionTexts then
        BM:UpdateActionTexts()
    end

    if BM.MetricInboxCount then
        BM.MetricInboxCount:SetText(total)
    end

    if BM.MetricLootCount then
        BM.MetricLootCount:SetText(lootable)
    end

    if BM.MetricGoldCoinDisplay then
        SetCoinColumn(BM.MetricGoldCoinDisplay, totalMoney)
    elseif BM.MetricGoldValue then
        BM.MetricGoldValue:SetText(totalMoney > 0 and GetCoinTextureString(totalMoney) or "0")
    end

    if BM.openMailIndex and BM.currentTab == "inbox" and BM.RefreshOpenMail then
        BM:RefreshOpenMail()
    end
end

