--------------------------------------------------------------------------------
-- Nanami-UI: Mail UI (Mail.lua)
-- Replaces MailFrame with Nanami-UI styled interface
-- Tabs: Inbox / Send
-- Inbox: select-all / per-item checkboxes, batch collect
-- Send: multi-item queue, split into individual sends
--------------------------------------------------------------------------------

SFrames = SFrames or {}
SFrames.Mail = {}
local ML = SFrames.Mail
SFramesDB = SFramesDB or {}

-- Save original Blizzard functions BEFORE other addons (TurtleMail etc.) hook them.
-- File-level locals are captured at load time, which is before later-alphabetical addons.
local _ClickSendMailItemButton = ClickSendMailItemButton
local _PickupContainerItem = PickupContainerItem
local _ClearCursor = ClearCursor
local _SendMail = SendMail

--------------------------------------------------------------------------------
-- Theme
--------------------------------------------------------------------------------
local T = SFrames.Theme:Extend({
    moneyGold   = { 1, 0.84, 0.0 },
    moneySilver = { 0.78, 0.78, 0.78 },
    moneyCopper = { 0.71, 0.43, 0.18 },
    codColor    = { 1.0, 0.35, 0.35 },
    unreadMark  = { 1.0, 0.85, 0.3 },
    successText = { 0.3, 1.0, 0.4 },
    errorText   = { 1.0, 0.3, 0.3 },
    friendColor = { 0.3, 1.0, 0.5 },
    guildColor  = { 0.4, 0.8, 1.0 },
    whoColor    = { 0.85, 0.85, 0.85 },
})

--------------------------------------------------------------------------------
-- Shared state table (avoids excessive upvalues)
--------------------------------------------------------------------------------
local S = {
    frame       = nil,   -- MainFrame
    inboxRows   = {},
    currentTab  = 1,
    inboxPage   = 1,
    inboxChecked = {},
    collectQueue = {},
    collectTimer = nil,
    isCollecting = false,
    sendQueue    = {},
    isSending    = false,
    collectElapsed = 0,
    multiSend    = nil,   -- active multi-send state table
}

local L = {
    W = 360, H = 480, HEADER = 34, PAD = 12, TAB_H = 28,
    BOTTOM = 46, ROWS = 8, ROW_H = 38, ICON = 30, MAX_SEND = 12,
}

--------------------------------------------------------------------------------
-- Helpers
--------------------------------------------------------------------------------
local function GetFont()
    if SFrames and SFrames.GetFont then return SFrames:GetFont() end
    return "Fonts\\ARIALN.TTF"
end

local function SetRoundBackdrop(frame)
    frame:SetBackdrop({
        bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 14,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    frame:SetBackdropColor(T.panelBg[1], T.panelBg[2], T.panelBg[3], T.panelBg[4])
    frame:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], T.panelBorder[4])
end

local function CreateShadow(parent)
    local s = CreateFrame("Frame", nil, parent)
    s:SetPoint("TOPLEFT", parent, "TOPLEFT", -4, 4)
    s:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 4, -4)
    s:SetBackdrop({
        bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 16,
        insets = { left = 4, right = 4, top = 4, bottom = 4 },
    })
    s:SetBackdropColor(0, 0, 0, 0.45)
    s:SetBackdropBorderColor(0, 0, 0, 0.6)
    s:SetFrameLevel(math.max(0, parent:GetFrameLevel() - 1))
    return s
end

local function FormatMoneyString(copper)
    if not copper or copper <= 0 then return "0|cFFB87333c|r" end
    local g = math.floor(copper / 10000)
    local sv = math.floor(math.mod(copper, 10000) / 100)
    local c = math.mod(copper, 100)
    local parts = ""
    if g > 0 then parts = parts .. "|cFFFFD700" .. g .. "g|r " end
    if sv > 0 then parts = parts .. "|cFFC7C7CF" .. sv .. "s|r " end
    if c > 0 then parts = parts .. "|cFFB87333" .. c .. "c|r" end
    return parts
end

local function CreateMoneyIcons(parent, fontSize, iconSize)
    fontSize = fontSize or 10
    iconSize = iconSize or 11
    local font = GetFont()
    local mf = CreateFrame("Frame", nil, parent)
    mf:SetWidth(100); mf:SetHeight(iconSize + 2)

    local function MakePair(mfRef, r, g, b, tcL, tcR)
        local txt = mfRef:CreateFontString(nil, "OVERLAY")
        txt:SetFont(font, fontSize, "OUTLINE"); txt:SetTextColor(r, g, b)
        local tex = mfRef:CreateTexture(nil, "ARTWORK")
        tex:SetTexture("Interface\\MoneyFrame\\UI-MoneyIcons")
        tex:SetTexCoord(tcL, tcR, 0, 1); tex:SetWidth(iconSize); tex:SetHeight(iconSize)
        return txt, tex
    end
    mf.gTxt, mf.gTex = MakePair(mf, 1, 0.84, 0, 0, 0.25)
    mf.sTxt, mf.sTex = MakePair(mf, 0.78, 0.78, 0.81, 0.25, 0.5)
    mf.cTxt, mf.cTex = MakePair(mf, 0.72, 0.45, 0.2, 0.5, 0.75)

    function mf:SetMoney(copper)
        copper = copper or 0
        local gv = math.floor(copper / 10000)
        local sv = math.floor(math.mod(copper, 10000) / 100)
        local cv = math.mod(copper, 100)
        self.gTxt:Hide(); self.gTex:Hide(); self.gTxt:ClearAllPoints()
        self.sTxt:Hide(); self.sTex:Hide(); self.sTxt:ClearAllPoints()
        self.cTxt:Hide(); self.cTex:Hide(); self.cTxt:ClearAllPoints()
        self.gTex:ClearAllPoints(); self.sTex:ClearAllPoints(); self.cTex:ClearAllPoints()
        if copper <= 0 then return end
        local anchor = nil
        local function Attach(txt, tex, val)
            txt:SetText(val)
            txt:ClearAllPoints(); tex:ClearAllPoints()
            if anchor then txt:SetPoint("LEFT", anchor, "RIGHT", 3, 0)
            else txt:SetPoint("LEFT", self, "LEFT", 0, 0) end
            tex:SetPoint("LEFT", txt, "RIGHT", 1, 0)
            txt:Show(); tex:Show()
            anchor = tex
        end
        if gv > 0 then Attach(self.gTxt, self.gTex, gv) end
        if sv > 0 then Attach(self.sTxt, self.sTex, sv) end
        if cv > 0 then Attach(self.cTxt, self.cTex, cv) end
    end
    return mf
end

local function FormatExpiry(daysLeft)
    if not daysLeft then return "" end
    local d = math.floor(daysLeft)
    if d <= 0 then return "|cFFFF3333即将过期|r" end
    if d == 1 then return "|cFFFF6633剩余 1 天|r" end
    if d <= 3 then return "|cFFFFAA33剩余 " .. d .. " 天|r" end
    return "|cFF88FF88" .. d .. " 天|r"
end

--------------------------------------------------------------------------------
-- Action Button Factory
--------------------------------------------------------------------------------
local function CreateActionBtn(parent, text, w)
    local btn = CreateFrame("Button", nil, parent)
    btn:SetWidth(w or 100)
    btn:SetHeight(26)
    btn:SetBackdrop({
        bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 14,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    btn:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
    btn:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])

    local fs = btn:CreateFontString(nil, "OVERLAY")
    fs:SetFont(GetFont(), 11, "OUTLINE")
    fs:SetPoint("CENTER", 0, 0)
    fs:SetText(text)
    fs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
    btn.label = fs

    btn.disabled = false
    function btn:SetDisabled(flag)
        self.disabled = flag
        if flag then
            self.label:SetTextColor(T.btnDisabledText[1], T.btnDisabledText[2], T.btnDisabledText[3])
            self:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], 0.5)
        else
            self.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
            self:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
        end
    end

    btn:SetScript("OnEnter", function()
        if not this.disabled then
            this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
            this:SetBackdropBorderColor(T.btnHoverBd[1], T.btnHoverBd[2], T.btnHoverBd[3], T.btnHoverBd[4])
            this.label:SetTextColor(T.btnActiveText[1], T.btnActiveText[2], T.btnActiveText[3])
        end
    end)
    btn:SetScript("OnLeave", function()
        if not this.disabled then
            this:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
            this:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])
            this.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
        end
    end)
    btn:SetScript("OnMouseDown", function()
        if not this.disabled then
            this:SetBackdropColor(T.btnDownBg[1], T.btnDownBg[2], T.btnDownBg[3], T.btnDownBg[4])
        end
    end)
    btn:SetScript("OnMouseUp", function()
        if not this.disabled then
            this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
        end
    end)

    return btn
end

--------------------------------------------------------------------------------
-- Tab Button Factory
--------------------------------------------------------------------------------
local function CreateTabBtn(parent, text, w)
    local btn = CreateFrame("Button", nil, parent)
    btn:SetWidth(w or 70)
    btn:SetHeight(22)
    btn:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        tile = false, tileSize = 0, edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 },
    })
    btn:SetBackdropColor(T.tabBg[1], T.tabBg[2], T.tabBg[3], T.tabBg[4])
    btn:SetBackdropBorderColor(T.tabBorder[1], T.tabBorder[2], T.tabBorder[3], 0.5)

    local fs = btn:CreateFontString(nil, "OVERLAY")
    fs:SetFont(GetFont(), 12, "OUTLINE")
    fs:SetPoint("CENTER", 0, 0)
    fs:SetText(text)
    fs:SetTextColor(T.tabText[1], T.tabText[2], T.tabText[3])
    btn.label = fs

    btn:SetScript("OnEnter", function()
        if not this.active then
            this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
            this:SetBackdropBorderColor(T.btnHoverBd[1], T.btnHoverBd[2], T.btnHoverBd[3], T.btnHoverBd[4])
        end
    end)
    btn:SetScript("OnLeave", function()
        if this.active then
            this:SetBackdropColor(T.tabActiveBg[1], T.tabActiveBg[2], T.tabActiveBg[3], T.tabActiveBg[4])
            this:SetBackdropBorderColor(T.tabActiveBorder[1], T.tabActiveBorder[2], T.tabActiveBorder[3], T.tabActiveBorder[4])
        else
            this:SetBackdropColor(T.tabBg[1], T.tabBg[2], T.tabBg[3], T.tabBg[4])
            this:SetBackdropBorderColor(T.tabBorder[1], T.tabBorder[2], T.tabBorder[3], 0.5)
        end
    end)

    function btn:SetActive(flag)
        self.active = flag
        if flag then
            self:SetBackdropColor(T.tabActiveBg[1], T.tabActiveBg[2], T.tabActiveBg[3], T.tabActiveBg[4])
            self:SetBackdropBorderColor(T.tabActiveBorder[1], T.tabActiveBorder[2], T.tabActiveBorder[3], T.tabActiveBorder[4])
            self.label:SetTextColor(T.tabActiveText[1], T.tabActiveText[2], T.tabActiveText[3])
        else
            self:SetBackdropColor(T.tabBg[1], T.tabBg[2], T.tabBg[3], T.tabBg[4])
            self:SetBackdropBorderColor(T.tabBorder[1], T.tabBorder[2], T.tabBorder[3], 0.5)
            self.label:SetTextColor(T.tabText[1], T.tabText[2], T.tabText[3])
        end
    end

    return btn
end

--------------------------------------------------------------------------------
-- Checkbox Factory
--------------------------------------------------------------------------------
local function CreateSmallCheckbox(parent, size)
    local sz = size or 16
    local cb = CreateFrame("Button", nil, parent)
    cb:SetWidth(sz); cb:SetHeight(sz)
    cb:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8",
        tile = false, tileSize = 0, edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 },
    })
    cb:SetBackdropColor(T.checkBg[1], T.checkBg[2], T.checkBg[3], T.checkBg[4])
    cb:SetBackdropBorderColor(T.checkBorder[1], T.checkBorder[2], T.checkBorder[3], T.checkBorder[4])
    local mark = cb:CreateTexture(nil, "OVERLAY")
    mark:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
    mark:SetAllPoints(cb); mark:Hide()
    cb.mark = mark; cb.checked = false
    function cb:SetChecked(flag)
        self.checked = flag
        if flag then self.mark:Show(); self:SetBackdropColor(T.checkFill[1], T.checkFill[2], T.checkFill[3], 0.3)
        else self.mark:Hide(); self:SetBackdropColor(T.checkBg[1], T.checkBg[2], T.checkBg[3], T.checkBg[4]) end
    end
    function cb:IsChecked() return self.checked end
    cb:SetScript("OnClick", function()
        this:SetChecked(not this.checked)
        if this.onToggle then this.onToggle(this.checked) end
    end)
    cb:SetScript("OnEnter", function() this:SetBackdropBorderColor(T.checkHoverBorder[1], T.checkHoverBorder[2], T.checkHoverBorder[3], T.checkHoverBorder[4]) end)
    cb:SetScript("OnLeave", function() this:SetBackdropBorderColor(T.checkBorder[1], T.checkBorder[2], T.checkBorder[3], T.checkBorder[4]) end)
    return cb
end

--------------------------------------------------------------------------------
-- Styled EditBox Factory
--------------------------------------------------------------------------------
local function CreateStyledEditBox(parent, width, height, numeric)
    local eb = CreateFrame("EditBox", nil, parent)
    eb:SetWidth(width or 120); eb:SetHeight(height or 22)
    eb:SetBackdrop({
        bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 12, insets = { left = 2, right = 2, top = 2, bottom = 2 },
    })
    eb:SetBackdropColor(T.inputBg[1], T.inputBg[2], T.inputBg[3], T.inputBg[4] or 0.95)
    eb:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], 0.8)
    eb:SetFont(GetFont(), 11, "OUTLINE")
    eb:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
    eb:SetJustifyH("LEFT"); eb:SetAutoFocus(false); eb:SetTextInsets(6, 6, 2, 2)
    if numeric then eb:SetNumeric(true) end
    return eb
end

--------------------------------------------------------------------------------
-- INBOX: Row Factory
--------------------------------------------------------------------------------
local function CreateInboxRow(parent, index)
    local row = CreateFrame("Frame", "SFramesMailInboxRow" .. index, parent)
    row:SetWidth(L.W - L.PAD * 2); row:SetHeight(L.ROW_H)
    local bg = row:CreateTexture(nil, "BACKGROUND")
    bg:SetTexture("Interface\\Buttons\\WHITE8X8"); bg:SetAllPoints(row)
    local normalR, normalG, normalB, normalA
    if math.mod(index, 2) == 0 then
        normalR, normalG, normalB, normalA = T.rowNormal[1], T.rowNormal[2], T.rowNormal[3], T.rowNormal[4]
    else
        normalR, normalG, normalB, normalA = T.panelBg[1], T.panelBg[2], T.panelBg[3], 0.3
    end
    bg:SetVertexColor(normalR, normalG, normalB, normalA)
    row.bg = bg

    local hl = row:CreateTexture(nil, "ARTWORK")
    hl:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
    hl:SetBlendMode("ADD"); hl:SetAllPoints(row); hl:SetAlpha(0); hl:Hide()
    row.highlight = hl

    row:EnableMouse(false)

    local cb = CreateSmallCheckbox(row, 16)
    cb:SetPoint("LEFT", row, "LEFT", 4, 0)
    cb:SetFrameLevel(row:GetFrameLevel() + 10)
    row.checkbox = cb

    local iconFrame = CreateFrame("Frame", nil, row)
    iconFrame:SetWidth(L.ICON); iconFrame:SetHeight(L.ICON)
    iconFrame:SetPoint("LEFT", cb, "RIGHT", 6, 0)
    iconFrame:SetBackdrop({
        bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 12, insets = { left = 2, right = 2, top = 2, bottom = 2 },
    })
    iconFrame:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4])
    iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
    iconFrame:EnableMouse(true)
    iconFrame:SetScript("OnEnter", function()
        if not row.mailIndex then return end
        row.highlight:SetAlpha(0.1); row.highlight:Show()
        GameTooltip:SetOwner(iconFrame, "ANCHOR_RIGHT")
        pcall(GameTooltip.SetInboxItem, GameTooltip, row.mailIndex)
        GameTooltip:Show()
    end)
    iconFrame:SetScript("OnLeave", function()
        row.highlight:SetAlpha(0); row.highlight:Hide()
        GameTooltip:Hide()
    end)
    iconFrame:SetScript("OnMouseUp", function()
        if row.mailIndex then ML:ShowMailDetail(row.mailIndex) end
    end)
    row.iconFrame = iconFrame

    local icon = iconFrame:CreateTexture(nil, "ARTWORK")
    icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
    icon:SetPoint("TOPLEFT", 3, -3); icon:SetPoint("BOTTOMRIGHT", -3, 3)
    row.icon = icon

    local font = GetFont()
    local senderFS = row:CreateFontString(nil, "OVERLAY")
    senderFS:SetFont(font, 11, "OUTLINE")
    senderFS:SetPoint("TOPLEFT", iconFrame, "TOPRIGHT", 6, -2)
    senderFS:SetWidth(110); senderFS:SetJustifyH("LEFT")
    senderFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
    row.senderFS = senderFS

    local subjectFS = row:CreateFontString(nil, "OVERLAY")
    subjectFS:SetFont(font, 9, "OUTLINE")
    subjectFS:SetPoint("TOPLEFT", senderFS, "BOTTOMLEFT", 0, -1)
    subjectFS:SetWidth(110); subjectFS:SetJustifyH("LEFT")
    subjectFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
    row.subjectFS = subjectFS

    local moneyFrame = CreateMoneyIcons(row, 9, 10)
    moneyFrame:SetPoint("RIGHT", row, "RIGHT", -52, 6)
    row.moneyFrame = moneyFrame

    local codFS = row:CreateFontString(nil, "OVERLAY")
    codFS:SetFont(font, 9, "OUTLINE"); codFS:SetTextColor(1, 0.33, 0.33)
    codFS:SetPoint("RIGHT", moneyFrame, "LEFT", -2, 0)
    codFS:Hide()
    row.codFS = codFS

    local expiryFS = row:CreateFontString(nil, "OVERLAY")
    expiryFS:SetFont(font, 9, "OUTLINE")
    expiryFS:SetPoint("RIGHT", row, "RIGHT", -52, -6); expiryFS:SetJustifyH("RIGHT")
    row.expiryFS = expiryFS

    local takeBtn = CreateActionBtn(row, "收取", 40)
    takeBtn:SetHeight(17); takeBtn:SetPoint("TOPRIGHT", row, "TOPRIGHT", -6, -3)
    takeBtn:SetFrameLevel(row:GetFrameLevel() + 10)
    row.takeBtn = takeBtn

    local returnBtn = CreateActionBtn(row, "退回", 40)
    returnBtn:SetHeight(17); returnBtn:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", -6, 3)
    returnBtn:SetFrameLevel(row:GetFrameLevel() + 10)
    row.returnBtn = returnBtn

    row.mailIndex = nil
    return row
end

--------------------------------------------------------------------------------
-- INBOX: Update
--------------------------------------------------------------------------------
local function UpdateInbox()
    if not S.frame or not S.frame:IsVisible() or S.currentTab ~= 1 then return end
    local numItems = GetInboxNumItems()
    local totalPages = math.max(1, math.ceil(numItems / L.ROWS))
    if S.inboxPage > totalPages then S.inboxPage = totalPages end
    if S.inboxPage < 1 then S.inboxPage = 1 end

    S.frame.inboxPageText:SetText(string.format("第 %d / %d 页  (%d 封)", S.inboxPage, totalPages, numItems))
    S.frame.inboxPrevBtn:SetDisabled(S.inboxPage <= 1)
    S.frame.inboxNextBtn:SetDisabled(S.inboxPage >= totalPages)

    for i = 1, L.ROWS do
        local row = S.inboxRows[i]
        local mi = (S.inboxPage - 1) * L.ROWS + i
        if mi <= numItems then
            local _, _, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead = GetInboxHeaderInfo(mi)
            row.mailIndex = mi
            if hasItem then
                local _, itemTex = GetInboxItem(mi)
                row.icon:SetTexture(itemTex or "Interface\\Icons\\INV_Misc_Note_01")
            elseif money and money > 0 then
                row.icon:SetTexture("Interface\\Icons\\INV_Misc_Coin_01")
            else
                row.icon:SetTexture("Interface\\Icons\\INV_Misc_Note_01")
            end
            row.senderFS:SetText(sender or "未知")
            if not wasRead then
                row.senderFS:SetTextColor(T.unreadMark[1], T.unreadMark[2], T.unreadMark[3])
            else
                row.senderFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
            end
            row.subjectFS:SetText(subject or "")
            row.codFS:Hide(); row.codFS:SetText("")
            if money and money > 0 then
                row.moneyFrame:SetMoney(money)
            elseif CODAmount and CODAmount > 0 then
                row.codFS:SetText("COD:"); row.codFS:Show()
                row.moneyFrame:SetMoney(CODAmount)
            else
                row.moneyFrame:SetMoney(0)
            end
            row.expiryFS:SetText(FormatExpiry(daysLeft))
            local canTake = (hasItem or (money and money > 0)) and (not CODAmount or CODAmount == 0)
            row.takeBtn:SetDisabled(not canTake)
            row.takeBtn:SetScript("OnClick", function()
                if row.mailIndex then
                    if hasItem then
                        TakeInboxItem(row.mailIndex)
                    elseif money and money > 0 then
                        local idx = row.mailIndex
                        TakeInboxMoney(idx)
                        if not S.deleteTimer then S.deleteTimer = CreateFrame("Frame") end
                        S.deleteElapsed = 0
                        S.deleteTimer:SetScript("OnUpdate", function()
                            S.deleteElapsed = S.deleteElapsed + arg1
                            if S.deleteElapsed >= 0.5 then
                                this:SetScript("OnUpdate", nil)
                                if idx <= GetInboxNumItems() then DeleteInboxItem(idx) end
                            end
                        end)
                    end
                end
            end)
            row.returnBtn:SetScript("OnClick", function()
                if row.mailIndex then ReturnInboxItem(row.mailIndex) end
            end)
            row.checkbox:SetChecked(S.inboxChecked[mi] == true)
            row.checkbox.onToggle = function(checked)
                S.inboxChecked[mi] = checked or nil
                local any = false
                for _, v in pairs(S.inboxChecked) do if v then any = true; break end end
                S.frame.collectSelectedBtn:SetDisabled(not any)
            end
            if CODAmount and CODAmount > 0 then
                row.checkbox:SetChecked(false); row.checkbox.disabled = true; S.inboxChecked[mi] = nil
            else
                row.checkbox.disabled = false
            end
            row:Show()
        else
            row.mailIndex = nil; row:Hide()
        end
    end

    local hasChecked = false
    for _, v in pairs(S.inboxChecked) do if v then hasChecked = true; break end end
    S.frame.collectSelectedBtn:SetDisabled(not hasChecked and not S.isCollecting)
    S.frame.collectAllBtn:SetDisabled(numItems == 0 and not S.isCollecting)
    if S.isCollecting then
        S.frame.collectAllBtn.label:SetText("收取中...")
        S.frame.collectSelectedBtn.label:SetText("收取中...")
    else
        S.frame.collectAllBtn.label:SetText("全部收取")
        S.frame.collectSelectedBtn.label:SetText("收取选中")
    end
end

--------------------------------------------------------------------------------
-- INBOX: Batch Collect
--------------------------------------------------------------------------------
local function StopCollecting()
    S.isCollecting = false; S.collectQueue = {}; S.collectPendingDelete = nil
    if S.collectTimer then S.collectTimer:SetScript("OnUpdate", nil) end
    UpdateInbox()
end

local function ProcessCollectQueue()
    if S.collectPendingDelete then
        local mi = S.collectPendingDelete
        S.collectPendingDelete = nil
        if mi <= GetInboxNumItems() then DeleteInboxItem(mi) end
        return
    end
    if table.getn(S.collectQueue) == 0 then StopCollecting(); return end
    local mi = table.remove(S.collectQueue, 1)
    if mi > GetInboxNumItems() then ProcessCollectQueue(); return end
    local _, _, _, _, money, CODAmount, _, hasItem = GetInboxHeaderInfo(mi)
    if CODAmount and CODAmount > 0 then ProcessCollectQueue(); return end
    if hasItem then TakeInboxItem(mi)
    elseif money and money > 0 then
        TakeInboxMoney(mi)
        S.collectPendingDelete = mi
    else DeleteInboxItem(mi) end
end

local function StartCollecting(indices)
    if S.isCollecting then return end
    S.collectQueue = {}
    for i = table.getn(indices), 1, -1 do table.insert(S.collectQueue, indices[i]) end
    if table.getn(S.collectQueue) == 0 then return end
    S.isCollecting = true; S.collectElapsed = 0
    if not S.collectTimer then S.collectTimer = CreateFrame("Frame") end
    local nextDelay = 0
    S.collectTimer:SetScript("OnUpdate", function()
        if not S.isCollecting then this:SetScript("OnUpdate", nil); return end
        S.collectElapsed = S.collectElapsed + arg1
        if S.collectElapsed >= nextDelay then
            S.collectElapsed = 0; nextDelay = 0.5; ProcessCollectQueue()
        end
    end)
    UpdateInbox()
end

local function CollectAll()
    local n = GetInboxNumItems()
    if n == 0 then return end
    local idx = {}
    for i = 1, n do
        local _, _, _, _, _, COD = GetInboxHeaderInfo(i)
        if not COD or COD == 0 then table.insert(idx, i) end
    end
    StartCollecting(idx)
end

local function CollectSelected()
    local idx = {}
    for k, v in pairs(S.inboxChecked) do if v then table.insert(idx, k) end end
    table.sort(idx)
    if table.getn(idx) == 0 then return end
    S.inboxChecked = {}
    StartCollecting(idx)
end

local function SelectAllInbox()
    local n = GetInboxNumItems()
    for i = 1, n do
        local _, _, _, _, _, COD = GetInboxHeaderInfo(i)
        if not COD or COD == 0 then S.inboxChecked[i] = true end
    end
    UpdateInbox()
end

local function DeselectAllInbox()
    S.inboxChecked = {}; UpdateInbox()
end

--------------------------------------------------------------------------------
-- SEND: Item Queue
--------------------------------------------------------------------------------
local function AddSendItem(bag, slot)
    if table.getn(S.sendQueue) >= L.MAX_SEND then
        DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r 发送队列已满 (" .. L.MAX_SEND .. " 件)")
        return
    end
    local link = GetContainerItemLink(bag, slot)
    if not link then return end
    local texture, count = GetContainerItemInfo(bag, slot)
    for i = 1, table.getn(S.sendQueue) do
        if S.sendQueue[i].bag == bag and S.sendQueue[i].slot == slot then
            DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r 该物品已在发送列表中"); return
        end
    end
    table.insert(S.sendQueue, { bag = bag, slot = slot, link = link, texture = texture, count = count or 1 })
end

--------------------------------------------------------------------------------
-- SEND: Accept cursor drag (find the locked bag slot the cursor picked up)
--------------------------------------------------------------------------------
local function AcceptCursorItem()
    if not CursorHasItem() then return false end
    for bag = 0, 4 do
        local slots = GetContainerNumSlots(bag)
        for slot = 1, slots do
            local _, _, locked = GetContainerItemInfo(bag, slot)
            if locked then
                AddSendItem(bag, slot)
                ClearCursor()
                ML:UpdateSendPanel()
                return true
            end
        end
    end
    ClearCursor()
    return false
end

local function RemoveSendItem(index)
    if index >= 1 and index <= table.getn(S.sendQueue) then table.remove(S.sendQueue, index) end
end

local function ClearSendItems() S.sendQueue = {} end

local function FlashStatus(text, color, duration)
    if not S.frame or not S.frame.sendStatus then return end
    S.frame.sendStatus:SetText(text)
    S.frame.sendStatus:SetTextColor(color[1], color[2], color[3])
    S.statusFadeElapsed = 0
    if not S.statusFadeTimer then S.statusFadeTimer = CreateFrame("Frame") end
    S.statusFadeTimer:SetScript("OnUpdate", function()
        S.statusFadeElapsed = S.statusFadeElapsed + arg1
        if S.statusFadeElapsed >= (duration or 3) then
            S.frame.sendStatus:SetText("")
            this:SetScript("OnUpdate", nil)
        end
    end)
end

--------------------------------------------------------------------------------
-- SEND: Multi-Send Logic
--------------------------------------------------------------------------------
local function ResetSendForm()
    if not S.frame then return end
    ClearSendItems()
    if S.frame.toEditBox then S.frame.toEditBox:SetText("") end
    if S.frame.subjectEditBox then S.frame.subjectEditBox:SetText("") end
    if S.frame.bodyEditBox then S.frame.bodyEditBox:SetText("") end
    if S.frame.goldEB then S.frame.goldEB:SetText("0") end
    if S.frame.silverEB then S.frame.silverEB:SetText("0") end
    if S.frame.copperEB then S.frame.copperEB:SetText("0") end
    ML:UpdateSendPanel()
end

local function FinishMultiSend()
    local ms = S.multiSend
    S.multiSend = nil
    S.isSending = false
    if S.updateFrame then S.updateFrame:SetScript("OnUpdate", nil) end
    ResetSendForm()
    if S.frame and S.frame.sendBtn then
        S.frame.sendBtn.label:SetText("发送"); S.frame.sendBtn:SetDisabled(false)
    end
    if ms then
        DEFAULT_CHAT_FRAME:AddMessage("|cFF88FF88[Nanami-Mail]|r 所有 " .. ms.total .. " 封邮件已发送完成")
        FlashStatus("全部发送完成!", T.successText, 3)
    end
end

local function AbortMultiSend(reason)
    S.multiSend = nil
    S.isSending = false
    if S.updateFrame then S.updateFrame:SetScript("OnUpdate", nil) end
    if S.frame and S.frame.sendBtn then
        S.frame.sendBtn.label:SetText("发送"); S.frame.sendBtn:SetDisabled(false)
    end
    if S.frame and S.frame.sendStatus then S.frame.sendStatus:SetText("") end
    if reason then
        DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r " .. reason)
        FlashStatus("发送失败!", T.errorText, 5)
    end
end

local function DoMultiSend(recipient, subject, body, money)
    if S.isSending then return end
    if not recipient or recipient == "" then
        DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r 请输入收件人名字"); return
    end
    if not subject or subject == "" then subject = "Mail" end
    if S.frame and S.frame.sendBtn then
        S.frame.sendBtn.label:SetText("发送中..."); S.frame.sendBtn:SetDisabled(true)
    end
    local items = {}
    for i = 1, table.getn(S.sendQueue) do table.insert(items, S.sendQueue[i]) end

    -- No attachments: plain text / money mail
    if table.getn(items) == 0 then
        if money and money > 0 then SetSendMailMoney(money) end
        SendMail(recipient, subject, body or "")
        return
    end

    S.multiSend = {
        items       = items,
        recipient   = recipient,
        subject     = subject or "",
        body        = body or "",
        money       = money,
        total       = table.getn(items),
        sentCount   = 0,
        phase       = "attach",  -- "attach" → "wait_send" → "cooldown" → "attach" ...
        elapsed     = 0,
    }
    S.isSending = true

    if not S.updateFrame then S.updateFrame = CreateFrame("Frame") end
    S.updateFrame:SetScript("OnUpdate", function()
        local ms = S.multiSend
        if not ms then this:SetScript("OnUpdate", nil); return end
        ms.elapsed = ms.elapsed + arg1

        ---------------------------------------------------------------
        -- Phase: attach — pick next item, attach to Blizzard mail slot, then send
        ---------------------------------------------------------------
        if ms.phase == "attach" then
            ms.sentCount = ms.sentCount + 1
            if ms.sentCount > ms.total then
                FinishMultiSend()
                return
            end

            local item = ms.items[ms.sentCount]
            if not item then FinishMultiSend(); return end

            if S.frame and S.frame.sendStatus then
                if S.statusFadeTimer then S.statusFadeTimer:SetScript("OnUpdate", nil) end
                S.frame.sendStatus:SetText("发送中 " .. ms.sentCount .. "/" .. ms.total)
                S.frame.sendStatus:SetTextColor(T.successText[1], T.successText[2], T.successText[3])
            end

            -- Ensure Blizzard send tab is active
            if MailFrameTab_OnClick then MailFrameTab_OnClick(2) end

            -- Check item still in bag
            if not GetContainerItemLink(item.bag, item.slot) then
                DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r 第" .. ms.sentCount .. "件物品已不在背包，跳过")
                ms.elapsed = 0
                return
            end

            -- Attach: clear → pick up → place
            _ClearCursor()
            _ClickSendMailItemButton()
            _ClearCursor()
            _PickupContainerItem(item.bag, item.slot)
            _ClickSendMailItemButton()

            -- Verify
            if not GetSendMailItem() then
                DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r 第" .. ms.sentCount .. "件附件挂载失败，跳过")
                _ClearCursor()
                ms.elapsed = 0
                return
            end

            -- Money only on first mail
            if ms.sentCount == 1 and ms.money and ms.money > 0 then
                SetSendMailMoney(ms.money)
            end

            -- Send this single-attachment mail
            ms.sendOk = false
            ms.phase = "wait_send"
            ms.elapsed = 0
            SendMail(ms.recipient, ms.subject, ms.body)

        ---------------------------------------------------------------
        -- Phase: wait_send — waiting for MAIL_SEND_SUCCESS
        ---------------------------------------------------------------
        elseif ms.phase == "wait_send" then
            if ms.sendOk then
                ms.phase = "cooldown"
                ms.elapsed = 0
            elseif ms.elapsed >= 15 then
                AbortMultiSend("发送超时，已停止")
            end

        ---------------------------------------------------------------
        -- Phase: cooldown — let server & Blizzard UI fully reset
        ---------------------------------------------------------------
        elseif ms.phase == "cooldown" then
            if ms.elapsed >= 0.6 then
                ms.phase = "attach"
                ms.elapsed = 0
            end
        end
    end)
end

--------------------------------------------------------------------------------
-- SEND: Panel Update
--------------------------------------------------------------------------------
function ML:UpdateSendPanel()
    if not S.frame or S.currentTab ~= 2 then return end
    if not S.frame.sendItemSlots then return end
    for i = 1, L.MAX_SEND do
        local slot = S.frame.sendItemSlots[i]
        if not slot then break end
        local entry = S.sendQueue[i]
        if entry then
            slot.icon:SetTexture(entry.texture); slot.icon:Show()
            slot.countFS:SetText(entry.count > 1 and entry.count or "")
            slot.removeBtn:Show(); slot.hasItem = true
        else
            slot.icon:SetTexture(nil); slot.icon:Hide()
            slot.countFS:SetText(""); slot.removeBtn:Hide(); slot.hasItem = false
        end
    end
end

--------------------------------------------------------------------------------
-- BUILD: Main frame + header + tabs
--------------------------------------------------------------------------------
local function BuildMainFrame()
    local f = CreateFrame("Frame", "SFramesMailFrame", UIParent)
    f:SetWidth(L.W); f:SetHeight(L.H)
    f:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 16, -104)
    f:SetFrameStrata("HIGH"); f:SetToplevel(true)
    f:EnableMouse(true); f:SetMovable(true); f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", function() this:StartMoving() end)
    f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
    SetRoundBackdrop(f); CreateShadow(f)
    S.frame = f

    local font = GetFont()
    local header = CreateFrame("Frame", nil, f)
    header:SetPoint("TOPLEFT", 0, 0); header:SetPoint("TOPRIGHT", 0, 0); header:SetHeight(L.HEADER)
    header:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" })
    header:SetBackdropColor(T.headerBg[1], T.headerBg[2], T.headerBg[3], T.headerBg[4])

    local titleIco = SFrames:CreateIcon(header, "mail", 16)
    titleIco:SetDrawLayer("OVERLAY")
    titleIco:SetPoint("LEFT", header, "LEFT", L.PAD, 0)
    titleIco:SetVertexColor(T.gold[1], T.gold[2], T.gold[3])

    local titleFS = header:CreateFontString(nil, "OVERLAY")
    titleFS:SetFont(font, 14, "OUTLINE"); titleFS:SetPoint("LEFT", titleIco, "RIGHT", 5, 0)
    titleFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3]); titleFS:SetText("邮箱")

    local closeBtn = CreateFrame("Button", nil, header)
    closeBtn:SetWidth(20); closeBtn:SetHeight(20); closeBtn:SetPoint("RIGHT", header, "RIGHT", -8, 0)
    local closeTex = closeBtn:CreateTexture(nil, "ARTWORK")
    closeTex:SetTexture("Interface\\AddOns\\Nanami-UI\\img\\icon")
    closeTex:SetTexCoord(0.25, 0.375, 0, 0.125); closeTex:SetAllPoints()
    closeTex:SetVertexColor(T.dimText[1], T.dimText[2], T.dimText[3])
    closeBtn:SetScript("OnClick", function() S.frame:Hide() end)
    closeBtn:SetScript("OnEnter", function() closeTex:SetVertexColor(1, 0.6, 0.7) end)
    closeBtn:SetScript("OnLeave", function() closeTex:SetVertexColor(T.dimText[1], T.dimText[2], T.dimText[3]) end)

    local sep = f:CreateTexture(nil, "ARTWORK")
    sep:SetTexture("Interface\\Buttons\\WHITE8X8"); sep:SetHeight(1)
    sep:SetPoint("TOPLEFT", f, "TOPLEFT", 6, -L.HEADER)
    sep:SetPoint("TOPRIGHT", f, "TOPRIGHT", -6, -L.HEADER)
    sep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])

    local tabInbox = CreateTabBtn(f, "收件箱", 70)
    tabInbox:SetPoint("TOPLEFT", f, "TOPLEFT", L.PAD, -(L.HEADER + 6))
    tabInbox:SetScript("OnClick", function() S.currentTab = 1; ML:ShowInboxPanel() end)
    f.tabInbox = tabInbox

    local tabSend = CreateTabBtn(f, "发送", 70)
    tabSend:SetPoint("LEFT", tabInbox, "RIGHT", 4, 0)
    tabSend:SetScript("OnClick", function() S.currentTab = 2; ML:ShowSendPanel() end)
    f.tabSend = tabSend

    f:Hide()
end

--------------------------------------------------------------------------------
-- BUILD: Inbox panel
--------------------------------------------------------------------------------
local function BuildInboxPanel()
    local f = S.frame
    local panelTop = L.HEADER + 6 + L.TAB_H + 4
    local ip = CreateFrame("Frame", nil, f)
    ip:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -panelTop)
    ip:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
    f.inboxPanel = ip

    local font = GetFont()

    local selAll = CreateActionBtn(ip, "全选", 50)
    selAll:SetHeight(20); selAll:SetPoint("TOPLEFT", ip, "TOPLEFT", L.PAD, 0)
    selAll:SetScript("OnClick", function() SelectAllInbox() end)

    local desel = CreateActionBtn(ip, "取消全选", 66)
    desel:SetHeight(20); desel:SetPoint("LEFT", selAll, "RIGHT", 4, 0)
    desel:SetScript("OnClick", function() DeselectAllInbox() end)

    local pageFS = ip:CreateFontString(nil, "OVERLAY")
    pageFS:SetFont(font, 10, "OUTLINE"); pageFS:SetPoint("LEFT", desel, "RIGHT", 12, 0)
    pageFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
    f.inboxPageText = pageFS

    for i = 1, L.ROWS do
        local row = CreateInboxRow(ip, i)
        row:SetPoint("TOPLEFT", ip, "TOPLEFT", L.PAD, -(26 + (i - 1) * L.ROW_H))
        S.inboxRows[i] = row
    end

    local bsep = ip:CreateTexture(nil, "ARTWORK")
    bsep:SetTexture("Interface\\Buttons\\WHITE8X8"); bsep:SetHeight(1)
    bsep:SetPoint("BOTTOMLEFT", ip, "BOTTOMLEFT", 6, L.BOTTOM)
    bsep:SetPoint("BOTTOMRIGHT", ip, "BOTTOMRIGHT", -6, L.BOTTOM)
    bsep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])

    local prev = CreateActionBtn(ip, "<", 28)
    prev:SetHeight(22); prev:SetPoint("BOTTOMLEFT", ip, "BOTTOMLEFT", L.PAD, 12)
    prev:SetScript("OnClick", function() S.inboxPage = S.inboxPage - 1; UpdateInbox() end)
    f.inboxPrevBtn = prev

    local nxt = CreateActionBtn(ip, ">", 28)
    nxt:SetHeight(22); nxt:SetPoint("LEFT", prev, "RIGHT", 4, 0)
    nxt:SetScript("OnClick", function() S.inboxPage = S.inboxPage + 1; UpdateInbox() end)
    f.inboxNextBtn = nxt

    local colSel = CreateActionBtn(ip, "收取选中", 80)
    colSel:SetHeight(24); colSel:SetPoint("BOTTOMRIGHT", ip, "BOTTOMRIGHT", -L.PAD, 10)
    colSel:SetScript("OnClick", function()
        if S.isCollecting then StopCollecting() else CollectSelected() end
    end)
    f.collectSelectedBtn = colSel

    local colAll = CreateActionBtn(ip, "全部收取", 80)
    colAll:SetHeight(24); colAll:SetPoint("RIGHT", colSel, "LEFT", -6, 0)
    colAll:SetScript("OnClick", function()
        if S.isCollecting then StopCollecting() else CollectAll() end
    end)
    f.collectAllBtn = colAll
end

--------------------------------------------------------------------------------
-- BUILD: Mail detail panel (overlays inbox when a mail is clicked)
--------------------------------------------------------------------------------
local function BuildDetailPanel()
    local f = S.frame
    local panelTop = L.HEADER + 6 + L.TAB_H + 4
    local dp = CreateFrame("Frame", nil, f)
    dp:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -panelTop)
    dp:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
    dp:Hide()
    f.detailPanel = dp

    local font = GetFont()
    local pad = L.PAD

    local backBtn = CreateActionBtn(dp, "< 返回", 60)
    backBtn:SetHeight(20); backBtn:SetPoint("TOPLEFT", dp, "TOPLEFT", pad, 0)
    backBtn:SetScript("OnClick", function() ML:HideMailDetail() end)

    local senderFS = dp:CreateFontString(nil, "OVERLAY")
    senderFS:SetFont(font, 11, "OUTLINE"); senderFS:SetJustifyH("LEFT")
    senderFS:SetPoint("TOPLEFT", dp, "TOPLEFT", pad, -26)
    senderFS:SetWidth(L.W - pad * 2)
    senderFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
    dp.senderFS = senderFS

    local subjectFS = dp:CreateFontString(nil, "OVERLAY")
    subjectFS:SetFont(font, 11, "OUTLINE"); subjectFS:SetJustifyH("LEFT")
    subjectFS:SetPoint("TOPLEFT", senderFS, "BOTTOMLEFT", 0, -4)
    subjectFS:SetWidth(L.W - pad * 2)
    subjectFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
    dp.subjectFS = subjectFS

    local sep1 = dp:CreateTexture(nil, "ARTWORK")
    sep1:SetTexture("Interface\\Buttons\\WHITE8X8"); sep1:SetHeight(1)
    sep1:SetPoint("TOPLEFT", subjectFS, "BOTTOMLEFT", 0, -6)
    sep1:SetPoint("RIGHT", dp, "RIGHT", -pad, 0)
    sep1:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])

    -- Body scroll frame
    local bsf = CreateFrame("ScrollFrame", "SFramesMailDetailScroll", dp, "UIPanelScrollFrameTemplate")
    bsf:SetPoint("TOPLEFT", sep1, "BOTTOMLEFT", 0, -4)
    bsf:SetWidth(L.W - pad * 2 - 24); bsf:SetHeight(180)

    local bodyFS = CreateFrame("Frame", nil, bsf)
    bodyFS:SetWidth(L.W - pad * 2 - 36); bodyFS:SetHeight(400)
    local bodyText = bodyFS:CreateFontString(nil, "OVERLAY")
    bodyText:SetFont(font, 11, "OUTLINE")
    bodyText:SetPoint("TOPLEFT", 0, 0)
    bodyText:SetWidth(L.W - pad * 2 - 36); bodyText:SetJustifyH("LEFT"); bodyText:SetJustifyV("TOP")
    bodyText:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
    bsf:SetScrollChild(bodyFS)
    dp.bodyText = bodyText
    dp.bodyFrame = bodyFS

    local sep2 = dp:CreateTexture(nil, "ARTWORK")
    sep2:SetTexture("Interface\\Buttons\\WHITE8X8"); sep2:SetHeight(1)
    sep2:SetPoint("TOPLEFT", bsf, "BOTTOMLEFT", 0, -4)
    sep2:SetPoint("RIGHT", dp, "RIGHT", -pad, 0)
    sep2:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])

    -- Item + money info line
    local infoFS = dp:CreateFontString(nil, "OVERLAY")
    infoFS:SetFont(font, 10, "OUTLINE"); infoFS:SetJustifyH("LEFT")
    infoFS:SetPoint("TOPLEFT", sep2, "BOTTOMLEFT", 0, -6)
    infoFS:SetWidth(L.W - pad * 2)
    infoFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
    dp.infoFS = infoFS

    local moneyLabel = dp:CreateFontString(nil, "OVERLAY")
    moneyLabel:SetFont(font, 10, "OUTLINE"); moneyLabel:SetTextColor(1, 0.84, 0)
    moneyLabel:SetPoint("TOPLEFT", infoFS, "BOTTOMLEFT", 0, -3)
    moneyLabel:Hide()
    dp.detailMoneyLabel = moneyLabel

    local detailMoney = CreateMoneyIcons(dp, 10, 11)
    detailMoney:SetPoint("LEFT", moneyLabel, "RIGHT", 4, 0)
    detailMoney:Hide()
    dp.detailMoney = detailMoney

    local codLabel = dp:CreateFontString(nil, "OVERLAY")
    codLabel:SetFont(font, 10, "OUTLINE"); codLabel:SetTextColor(1, 0.33, 0.33)
    codLabel:SetPoint("LEFT", detailMoney, "RIGHT", 8, 0)
    codLabel:Hide()
    dp.detailCodLabel = codLabel

    local detailCod = CreateMoneyIcons(dp, 10, 11)
    detailCod:SetPoint("LEFT", codLabel, "RIGHT", 4, 0)
    detailCod:Hide()
    dp.detailCod = detailCod

    -- Action buttons at bottom
    local takeItemBtn = CreateActionBtn(dp, "收取物品", 72)
    takeItemBtn:SetHeight(24); takeItemBtn:SetPoint("BOTTOMLEFT", dp, "BOTTOMLEFT", pad, 10)
    dp.takeItemBtn = takeItemBtn

    local takeMoneyBtn = CreateActionBtn(dp, "收取金币", 72)
    takeMoneyBtn:SetHeight(24); takeMoneyBtn:SetPoint("LEFT", takeItemBtn, "RIGHT", 4, 0)
    dp.takeMoneyBtn = takeMoneyBtn

    local returnBtn = CreateActionBtn(dp, "退回", 50)
    returnBtn:SetHeight(24); returnBtn:SetPoint("LEFT", takeMoneyBtn, "RIGHT", 4, 0)
    dp.returnBtn = returnBtn

    local deleteBtn = CreateActionBtn(dp, "删除", 50)
    deleteBtn:SetHeight(24); deleteBtn:SetPoint("LEFT", returnBtn, "RIGHT", 4, 0)
    dp.deleteBtn = deleteBtn

    local replyBtn = CreateActionBtn(dp, "回复", 50)
    replyBtn:SetHeight(24); replyBtn:SetPoint("BOTTOMRIGHT", dp, "BOTTOMRIGHT", -pad, 10)
    dp.replyBtn = replyBtn
end

--------------------------------------------------------------------------------
-- Show / Hide mail detail
--------------------------------------------------------------------------------
function ML:ShowMailDetail(mailIndex)
    if not S.frame or not S.frame.detailPanel then return end
    local dp = S.frame.detailPanel
    S.detailMailIndex = mailIndex

    local _, _, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead = GetInboxHeaderInfo(mailIndex)
    local bodyText = GetInboxText(mailIndex)

    dp.senderFS:SetText("发件人: |cFFFFFFFF" .. (sender or "未知") .. "|r    " .. FormatExpiry(daysLeft))
    dp.subjectFS:SetText("主题: |cFFFFFFFF" .. (subject or "(无主题)") .. "|r")

    dp.bodyText:SetText(bodyText or "(无正文)")
    local ok, textH = pcall(function() return dp.bodyText:GetStringHeight() end)
    if not ok or not textH then textH = 40 end
    dp.bodyFrame:SetHeight(math.max(textH + 10, 40))

    local info = ""
    if hasItem then
        local itemName, itemTex = GetInboxItem(mailIndex)
        if itemName then info = info .. "|cFFFFD700附件:|r " .. itemName end
    end
    if info == "" and (not money or money <= 0) and (not CODAmount or CODAmount <= 0) then
        info = "无附件，无金币"
    end
    dp.infoFS:SetText(info)

    dp.detailMoneyLabel:Hide(); dp.detailMoney:Hide()
    dp.detailCodLabel:Hide(); dp.detailCod:Hide()
    if money and money > 0 then
        dp.detailMoneyLabel:SetText("金额:"); dp.detailMoneyLabel:Show()
        dp.detailMoney:SetMoney(money); dp.detailMoney:Show()
    end
    if CODAmount and CODAmount > 0 then
        dp.detailCodLabel:SetText("COD:"); dp.detailCodLabel:Show()
        dp.detailCod:SetMoney(CODAmount); dp.detailCod:Show()
    end

    -- Take items button
    local canTakeItem = hasItem and (not CODAmount or CODAmount == 0)
    dp.takeItemBtn:SetDisabled(not canTakeItem)
    dp.takeItemBtn:SetScript("OnClick", function()
        if S.detailMailIndex then
            TakeInboxItem(S.detailMailIndex)
        end
    end)

    -- Take money button
    local canTakeMoney = money and money > 0 and (not CODAmount or CODAmount == 0)
    dp.takeMoneyBtn:SetDisabled(not canTakeMoney)
    dp.takeMoneyBtn:SetScript("OnClick", function()
        if S.detailMailIndex then
            local idx = S.detailMailIndex
            local _, _, _, _, _, _, _, hi = GetInboxHeaderInfo(idx)
            TakeInboxMoney(idx)
            if not hi then
                if not S.deleteTimer then S.deleteTimer = CreateFrame("Frame") end
                S.deleteElapsed = 0
                S.deleteTimer:SetScript("OnUpdate", function()
                    S.deleteElapsed = S.deleteElapsed + arg1
                    if S.deleteElapsed >= 0.5 then
                        this:SetScript("OnUpdate", nil)
                        if idx <= GetInboxNumItems() then DeleteInboxItem(idx) end
                    end
                end)
            end
            ML:HideMailDetail()
        end
    end)

    -- Return button
    dp.returnBtn:SetScript("OnClick", function()
        if S.detailMailIndex then
            ReturnInboxItem(S.detailMailIndex)
            ML:HideMailDetail()
        end
    end)

    -- Delete button
    dp.deleteBtn:SetScript("OnClick", function()
        if S.detailMailIndex then
            DeleteInboxItem(S.detailMailIndex)
            ML:HideMailDetail()
        end
    end)

    -- Reply button
    dp.replyBtn:SetScript("OnClick", function()
        if sender then
            S.currentTab = 2; ML:ShowSendPanel()
            if S.frame.toEditBox then S.frame.toEditBox:SetText(sender) end
            if S.frame.subjectEditBox then
                local re = subject or ""
                if string.sub(re, 1, 4) ~= "Re: " then re = "Re: " .. re end
                S.frame.subjectEditBox:SetText(re)
            end
        end
    end)

    S.frame.inboxPanel:Hide()
    dp:Show()
end

function ML:HideMailDetail()
    if not S.frame then return end
    S.detailMailIndex = nil
    if S.frame.detailPanel then S.frame.detailPanel:Hide() end
    S.frame.inboxPanel:Show()
    UpdateInbox()
end

--------------------------------------------------------------------------------
-- BUILD: Send panel
--------------------------------------------------------------------------------
local function BuildSendPanel()
    local f = S.frame
    local panelTop = L.HEADER + 6 + L.TAB_H + 4
    local sp = CreateFrame("Frame", "SFramesMailSendPanel", f)
    sp:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -panelTop)
    sp:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
    sp:EnableMouse(true)
    sp:SetScript("OnReceiveDrag", function() AcceptCursorItem() end)
    sp:SetScript("OnMouseUp", function()
        if CursorHasItem() then AcceptCursorItem() end
    end)
    sp:Hide()
    f.sendPanel = sp

    local font = GetFont()

    -- Recipient
    local labelW = 50
    local ebW = L.W - L.PAD * 2 - labelW - 6
    local toLabel = sp:CreateFontString(nil, "OVERLAY")
    toLabel:SetFont(font, 11, "OUTLINE"); toLabel:SetPoint("TOPLEFT", sp, "TOPLEFT", L.PAD, -6)
    toLabel:SetWidth(labelW); toLabel:SetJustifyH("RIGHT")
    toLabel:SetText("收件人:"); toLabel:SetTextColor(T.labelText[1], T.labelText[2], T.labelText[3])
    local toEB = CreateStyledEditBox(sp, ebW, 22)
    toEB:SetPoint("LEFT", toLabel, "RIGHT", 6, 0)
    f.toEditBox = toEB

    -- Autocomplete dropdown for recipient
    local AC_MAX = 8
    local acBox = CreateFrame("Frame", "SFramesMailAutoComplete", f)
    acBox:SetWidth(ebW); acBox:SetFrameStrata("TOOLTIP")
    acBox:SetBackdrop({
        bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 12,
        insets = { left = 2, right = 2, top = 2, bottom = 2 },
    })
    acBox:SetBackdropColor(0.05, 0.05, 0.1, 0.95)
    acBox:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], 0.9)
    acBox:SetPoint("TOPLEFT", toEB, "BOTTOMLEFT", 0, -2)
    acBox:Hide()
    acBox:EnableMouse(true)
    f.acBox = acBox

    local acButtons = {}
    for ai = 1, AC_MAX do
        local btn = CreateFrame("Button", nil, acBox)
        btn:SetHeight(18); btn:SetPoint("TOPLEFT", acBox, "TOPLEFT", 4, -4 - (ai - 1) * 18)
        btn:SetPoint("RIGHT", acBox, "RIGHT", -4, 0)
        btn:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
        local bfs = btn:CreateFontString(nil, "OVERLAY")
        bfs:SetFont(font, 11, "OUTLINE"); bfs:SetAllPoints(btn)
        bfs:SetJustifyH("LEFT")
        btn.label = bfs
        btn.sourceName = nil
        btn:SetScript("OnClick", function()
            if btn.sourceName then
                toEB:SetText(btn.sourceName)
            end
            acBox:Hide()
            f.subjectEditBox:SetFocus()
        end)
        btn:Hide()
        acButtons[ai] = btn
    end
    f.acButtons = acButtons

    local friendCache = {}
    local function BuildFriendCache()
        friendCache = {}
        for fi = 1, GetNumFriends() do
            local name = GetFriendInfo(fi)
            if name then friendCache[name] = true end
        end
    end

    local function ShowSuggestions()
        local input = toEB:GetText()
        if not input or input == "" then acBox:Hide(); return end
        local upper = string.upper(input)

        BuildFriendCache()

        local seen = { [UnitName("player")] = true }
        local results = {}

        local function addName(name, source)
            if not name or name == "" or seen[name] then return end
            if string.find(string.upper(name), upper, 1, true) == 1 then
                table.insert(results, { name = name, source = source })
            end
            seen[name] = true
        end

        for fi = 1, GetNumFriends() do
            local name = GetFriendInfo(fi)
            addName(name, "friend")
        end
        if GetNumGuildMembers then
            for gi = 1, GetNumGuildMembers(true) do
                local name = GetGuildRosterInfo(gi)
                addName(name, "guild")
            end
        end

        local count = math.min(table.getn(results), AC_MAX)
        if count == 0 then acBox:Hide(); return end
        if count == 1 and results[1].name == input then acBox:Hide(); return end

        for ai = 1, AC_MAX do
            local btn = acButtons[ai]
            if ai <= count then
                local r = results[ai]
                local col = T.whoColor
                local tag = ""
                if r.source == "friend" then
                    col = T.friendColor; tag = " |cFF66FF88[好友]|r"
                elseif r.source == "guild" then
                    col = T.guildColor; tag = " |cFF66CCFF[公会]|r"
                end
                btn.label:SetText("|cFF" .. string.format("%02x%02x%02x",
                    col[1] * 255, col[2] * 255, col[3] * 255) .. r.name .. "|r" .. tag)
                btn.sourceName = r.name
                btn:Show()
            else
                btn:Hide()
            end
        end
        acBox:SetHeight(count * 18 + 8)
        acBox:Show()
    end

    toEB:SetScript("OnTextChanged", function() ShowSuggestions() end)
    toEB:SetScript("OnEscapePressed", function() acBox:Hide(); this:ClearFocus() end)
    toEB:SetScript("OnEnterPressed", function()
        acBox:Hide(); f.subjectEditBox:SetFocus()
    end)

    -- Subject (second line, aligned with recipient)
    local subLabel = sp:CreateFontString(nil, "OVERLAY")
    subLabel:SetFont(font, 11, "OUTLINE"); subLabel:SetPoint("TOPLEFT", sp, "TOPLEFT", L.PAD, -32)
    subLabel:SetWidth(labelW); subLabel:SetJustifyH("RIGHT")
    subLabel:SetText("主题:"); subLabel:SetTextColor(T.labelText[1], T.labelText[2], T.labelText[3])
    local subEB = CreateStyledEditBox(sp, ebW, 22)
    subEB:SetPoint("LEFT", subLabel, "RIGHT", 6, 0)
    f.subjectEditBox = subEB

    toEB:SetScript("OnTabPressed", function() acBox:Hide(); f.subjectEditBox:SetFocus() end)
    subEB:SetScript("OnTabPressed", function() f.toEditBox:SetFocus() end)

    -- Body
    local bodyLabel = sp:CreateFontString(nil, "OVERLAY")
    bodyLabel:SetFont(font, 11, "OUTLINE"); bodyLabel:SetPoint("TOPLEFT", sp, "TOPLEFT", L.PAD, -58)
    bodyLabel:SetText("正文:"); bodyLabel:SetTextColor(T.labelText[1], T.labelText[2], T.labelText[3])

    local bsf = CreateFrame("ScrollFrame", "SFramesMailBodyScroll", sp, "UIPanelScrollFrameTemplate")
    bsf:SetPoint("TOPLEFT", bodyLabel, "BOTTOMLEFT", 0, -4)
    bsf:SetWidth(L.W - L.PAD * 2 - 28); bsf:SetHeight(100)
    bsf:SetBackdrop({
        bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 12, insets = { left = 2, right = 2, top = 2, bottom = 2 },
    })
    bsf:SetBackdropColor(T.inputBg[1], T.inputBg[2], T.inputBg[3], T.inputBg[4] or 0.95)
    bsf:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], 0.8)

    local bodyEB = CreateFrame("EditBox", "SFramesMailBodyEditBox", bsf)
    bodyEB:SetWidth(L.W - L.PAD * 2 - 40); bodyEB:SetHeight(200)
    bodyEB:SetFont(font, 11, "OUTLINE"); bodyEB:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
    bodyEB:SetAutoFocus(false); bodyEB:SetMultiLine(true); bodyEB:SetMaxLetters(500)
    bodyEB:SetTextInsets(6, 6, 4, 4)
    bsf:SetScrollChild(bodyEB)
    f.bodyEditBox = bodyEB

    -- Money row
    local mLabel = sp:CreateFontString(nil, "OVERLAY")
    mLabel:SetFont(font, 11, "OUTLINE"); mLabel:SetPoint("TOPLEFT", bsf, "BOTTOMLEFT", 0, -10)
    mLabel:SetText("附加金币:"); mLabel:SetTextColor(T.labelText[1], T.labelText[2], T.labelText[3])

    local gL = sp:CreateFontString(nil, "OVERLAY")
    gL:SetFont(font, 10, "OUTLINE"); gL:SetPoint("LEFT", mLabel, "RIGHT", 6, 0)
    gL:SetText("金"); gL:SetTextColor(T.moneyGold[1], T.moneyGold[2], T.moneyGold[3])
    local gEB = CreateStyledEditBox(sp, 60, 20, true); gEB:SetPoint("LEFT", gL, "RIGHT", 4, 0); gEB:SetText("0"); f.goldEB = gEB

    local sL = sp:CreateFontString(nil, "OVERLAY")
    sL:SetFont(font, 10, "OUTLINE"); sL:SetPoint("LEFT", gEB, "RIGHT", 6, 0)
    sL:SetText("银"); sL:SetTextColor(T.moneySilver[1], T.moneySilver[2], T.moneySilver[3])
    local sEB = CreateStyledEditBox(sp, 40, 20, true); sEB:SetPoint("LEFT", sL, "RIGHT", 4, 0); sEB:SetText("0"); f.silverEB = sEB

    local cL = sp:CreateFontString(nil, "OVERLAY")
    cL:SetFont(font, 10, "OUTLINE"); cL:SetPoint("LEFT", sEB, "RIGHT", 6, 0)
    cL:SetText("铜"); cL:SetTextColor(T.moneyCopper[1], T.moneyCopper[2], T.moneyCopper[3])
    local cEB = CreateStyledEditBox(sp, 40, 20, true); cEB:SetPoint("LEFT", cL, "RIGHT", 4, 0); cEB:SetText("0"); f.copperEB = cEB

    -- Attachments
    local aLabel = sp:CreateFontString(nil, "OVERLAY")
    aLabel:SetFont(font, 11, "OUTLINE"); aLabel:SetPoint("TOPLEFT", mLabel, "TOPLEFT", 0, -28)
    aLabel:SetText("附件 (右击/拖放背包物品添加):"); aLabel:SetTextColor(T.labelText[1], T.labelText[2], T.labelText[3])

    local clrBtn = CreateActionBtn(sp, "清空", 50)
    clrBtn:SetHeight(20); clrBtn:SetPoint("LEFT", aLabel, "RIGHT", 8, 0)
    clrBtn:SetScript("OnClick", function() ClearSendItems(); ML:UpdateSendPanel() end)

    -- Item slot grid
    f.sendItemSlots = {}
    BuildSendSlots(sp, aLabel, f)

    -- Send button & status
    local status = sp:CreateFontString(nil, "OVERLAY")
    status:SetFont(font, 11, "OUTLINE"); status:SetPoint("BOTTOMLEFT", sp, "BOTTOMLEFT", L.PAD, 14)
    status:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
    f.sendStatus = status

    local sendBtn = CreateActionBtn(sp, "发送", 80)
    sendBtn:SetHeight(26); sendBtn:SetPoint("BOTTOMRIGHT", sp, "BOTTOMRIGHT", -L.PAD, 10)
    sendBtn:SetScript("OnClick", function()
        local r = f.toEditBox:GetText()
        local sub = f.subjectEditBox:GetText()
        local bd = f.bodyEditBox:GetText()
        local g = tonumber(f.goldEB:GetText()) or 0
        local sv = tonumber(f.silverEB:GetText()) or 0
        local c = tonumber(f.copperEB:GetText()) or 0
        DoMultiSend(r, sub, bd, g * 10000 + sv * 100 + c)
    end)
    f.sendBtn = sendBtn
end

--------------------------------------------------------------------------------
-- BUILD: Send item slots (separate function to stay under upvalue limit)
--------------------------------------------------------------------------------
function BuildSendSlots(parent, anchor, f)
    local slotSize, slotGap, perRow = 34, 4, 6
    for i = 1, L.MAX_SEND do
        local row = math.floor((i - 1) / perRow)
        local col = math.mod((i - 1), perRow)
        local sf = CreateFrame("Button", "SFramesMailSendSlot" .. i, parent)
        sf:SetWidth(slotSize); sf:SetHeight(slotSize)
        sf:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", col * (slotSize + slotGap), -(4 + row * (slotSize + slotGap)))
        sf:SetBackdrop({
            bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            tile = true, tileSize = 16, edgeSize = 12, insets = { left = 2, right = 2, top = 2, bottom = 2 },
        })
        sf:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4])
        sf:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])

        local ico = sf:CreateTexture(nil, "ARTWORK")
        ico:SetTexCoord(0.08, 0.92, 0.08, 0.92)
        ico:SetPoint("TOPLEFT", 3, -3); ico:SetPoint("BOTTOMRIGHT", -3, 3); ico:Hide()
        sf.icon = ico

        local cnt = sf:CreateFontString(nil, "OVERLAY")
        cnt:SetFont("Fonts\\ARIALN.TTF", 11, "OUTLINE")
        cnt:SetPoint("BOTTOMRIGHT", sf, "BOTTOMRIGHT", -2, 2); cnt:SetJustifyH("RIGHT")
        sf.countFS = cnt

        local rb = CreateFrame("Button", nil, sf)
        rb:SetWidth(14); rb:SetHeight(14); rb:SetPoint("TOPRIGHT", sf, "TOPRIGHT", 2, 2)
        local rtx = rb:CreateTexture(nil, "OVERLAY")
        rtx:SetTexture("Interface\\AddOns\\Nanami-UI\\img\\icon")
        rtx:SetTexCoord(0.25, 0.375, 0, 0.125); rtx:SetAllPoints(); rtx:SetVertexColor(1, 0.4, 0.4)
        rb:Hide(); sf.removeBtn = rb; sf.hasItem = false

        local si = i
        rb:SetScript("OnClick", function() RemoveSendItem(si); ML:UpdateSendPanel() end)
        sf:SetScript("OnReceiveDrag", function() AcceptCursorItem() end)
        sf:SetScript("OnClick", function()
            if CursorHasItem() then AcceptCursorItem(); return end
            if arg1 == "RightButton" and sf.hasItem then RemoveSendItem(si); ML:UpdateSendPanel() end
        end)
        sf:RegisterForClicks("LeftButtonUp", "RightButtonUp")
        sf:SetScript("OnEnter", function()
            this:SetBackdropBorderColor(T.slotHover[1], T.slotHover[2], T.slotHover[3], T.slotHover[4])
            local e = S.sendQueue[si]
            if e and e.link then
                GameTooltip:SetOwner(this, "ANCHOR_RIGHT"); pcall(GameTooltip.SetHyperlink, GameTooltip, e.link); GameTooltip:Show()
            end
        end)
        sf:SetScript("OnLeave", function()
            this:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
            GameTooltip:Hide()
        end)
        f.sendItemSlots[i] = sf
    end
end

--------------------------------------------------------------------------------
-- BUILD: Events & hooks
--------------------------------------------------------------------------------
local function SetupEvents()
    local f = S.frame

    f:SetScript("OnHide", function()
        StopCollecting(); if S.multiSend then AbortMultiSend() end; pcall(CloseMail)
    end)

    f:SetScript("OnEvent", function()
        if event == "MAIL_SHOW" then
            if SFramesDB and SFramesDB.enableMail == false then return end
            S.currentTab = 1; S.inboxPage = 1; S.inboxChecked = {}
            CheckInbox(); f:Show(); ML:ShowInboxPanel()
        elseif event == "MAIL_INBOX_UPDATE" then
            if f:IsVisible() then
                if S.detailMailIndex and f.detailPanel and f.detailPanel:IsVisible() then
                    if S.detailMailIndex <= GetInboxNumItems() then
                        ML:ShowMailDetail(S.detailMailIndex)
                    else
                        ML:HideMailDetail()
                    end
                else
                    UpdateInbox()
                end
            end
        elseif event == "MAIL_CLOSED" then
            if S.multiSend then AbortMultiSend("邮箱已关闭") end
            f:Hide()
        elseif event == "MAIL_SEND_SUCCESS" then
            if S.multiSend then
                S.multiSend.sendOk = true
                S.multiSend.phase = "cooldown"
                S.multiSend.elapsed = 0
            else
                FlashStatus("发送成功!", T.successText, 3)
                ResetSendForm()
                if f.sendBtn then f.sendBtn.label:SetText("发送"); f.sendBtn:SetDisabled(false) end
            end
        elseif event == "MAIL_SEND_INFO_UPDATE" then
            -- noop
        elseif event == "MAIL_FAILED" then
            if S.multiSend and S.multiSend.phase == "wait_send" and not S.multiSend.sendOk then
                AbortMultiSend("发送失败")
            elseif not S.multiSend then
                if f.sendBtn then f.sendBtn.label:SetText("发送"); f.sendBtn:SetDisabled(false) end
                FlashStatus("发送失败!", T.errorText, 5)
            end
        end
    end)

    f:RegisterEvent("MAIL_SHOW"); f:RegisterEvent("MAIL_INBOX_UPDATE")
    f:RegisterEvent("MAIL_CLOSED"); f:RegisterEvent("MAIL_SEND_SUCCESS")
    f:RegisterEvent("MAIL_SEND_INFO_UPDATE"); f:RegisterEvent("MAIL_FAILED")

    if MailFrame then
        local origMailOnShow = MailFrame:GetScript("OnShow")
        MailFrame:SetScript("OnShow", function()
            if origMailOnShow then origMailOnShow() end
            this:ClearAllPoints()
            this:SetPoint("TOPLEFT", UIParent, "TOPLEFT", -10000, 10000)
            this:EnableMouse(false)
        end)
        for i = table.getn(UISpecialFrames), 1, -1 do
            if UISpecialFrames[i] == "MailFrame" then
                table.remove(UISpecialFrames, i)
            end
        end
    end
    if OpenMailFrame then
        OpenMailFrame:UnregisterAllEvents(); OpenMailFrame:Hide()
    end

    tinsert(UISpecialFrames, "SFramesMailFrame")

    SFrames.Mail.TryAddItemFromBag = function(bag, slot)
        if S.frame and S.frame:IsVisible() and not S.isSending then
            if bag and slot then
                if S.currentTab ~= 2 then S.currentTab = 2; ML:ShowSendPanel() end
                AddSendItem(bag, slot); ML:UpdateSendPanel()
                return true
            end
        end
        return false
    end

    if ContainerFrameItemButton_OnClick then
        local orig = ContainerFrameItemButton_OnClick
        ContainerFrameItemButton_OnClick = function(button, ignoreShift)
            if arg1 == "RightButton" then
                if SFrames.Mail.TryAddItemFromBag(this:GetParent():GetID(), this:GetID()) then return end
            end
            orig(button, ignoreShift)
        end
    end
end

--------------------------------------------------------------------------------
-- Initialize (calls sub-builders)
--------------------------------------------------------------------------------
function ML:Initialize()
    if S.frame then return end
    BuildMainFrame()
    BuildInboxPanel()
    BuildDetailPanel()
    BuildSendPanel()
    SetupEvents()
end

--------------------------------------------------------------------------------
-- Panel Switching
--------------------------------------------------------------------------------
function ML:ShowInboxPanel()
    if not S.frame then return end
    S.frame.tabInbox:SetActive(true); S.frame.tabSend:SetActive(false)
    if S.frame.detailPanel then S.frame.detailPanel:Hide() end
    S.frame.inboxPanel:Show(); S.frame.sendPanel:Hide()
    S.detailMailIndex = nil
    UpdateInbox()
end

function ML:ShowSendPanel()
    if not S.frame then return end
    S.frame.tabInbox:SetActive(false); S.frame.tabSend:SetActive(true)
    if S.frame.detailPanel then S.frame.detailPanel:Hide() end
    S.detailMailIndex = nil
    S.frame.inboxPanel:Hide(); S.frame.sendPanel:Show()
    if S.frame.sendStatus then S.frame.sendStatus:SetText("") end
    if S.statusFadeTimer then S.statusFadeTimer:SetScript("OnUpdate", nil) end
    ML:UpdateSendPanel()
end

--------------------------------------------------------------------------------
-- Bootstrap
--------------------------------------------------------------------------------
local bootstrap = CreateFrame("Frame")
bootstrap:RegisterEvent("PLAYER_LOGIN")
bootstrap:SetScript("OnEvent", function()
    if event == "PLAYER_LOGIN" then
        if SFramesDB.enableMail == nil then SFramesDB.enableMail = true end
        if SFramesDB.enableMail ~= false then ML:Initialize() end
    end
end)
