local CFG_THEME = SFrames.ActiveTheme

SFrames.Whisper = SFrames.Whisper or {}
SFrames.Whisper.history = SFrames.Whisper.history or {}
SFrames.Whisper.contacts = SFrames.Whisper.contacts or {}
SFrames.Whisper.unreadCount = SFrames.Whisper.unreadCount or {}
SFrames.Whisper.activeContact = nil

local MAX_WHISPER_CONTACTS = 200
local MAX_MESSAGES_PER_CONTACT = 100

function SFrames.Whisper:SaveCache()
    if not SFramesDB then SFramesDB = {} end
    SFramesDB.whisperContacts = {}
    SFramesDB.whisperHistory = {}
    for _, contact in ipairs(self.contacts) do
        table.insert(SFramesDB.whisperContacts, contact)
        if self.history[contact] then
            local msgs = self.history[contact]
            -- Only persist the last MAX_MESSAGES_PER_CONTACT messages per contact
            local start = math.max(1, table.getn(msgs) - MAX_MESSAGES_PER_CONTACT + 1)
            local trimmed = {}
            for i = start, table.getn(msgs) do
                table.insert(trimmed, { time = msgs[i].time, text = msgs[i].text, isMe = msgs[i].isMe, translated = msgs[i].translated })
            end
            SFramesDB.whisperHistory[contact] = trimmed
        end
    end
end

function SFrames.Whisper:LoadCache()
    if not SFramesDB then return end
    if type(SFramesDB.whisperContacts) ~= "table" or type(SFramesDB.whisperHistory) ~= "table" then return end

    self.contacts = {}
    self.history = {}
    for _, contact in ipairs(SFramesDB.whisperContacts) do
        if type(contact) == "string" and SFramesDB.whisperHistory[contact] and table.getn(SFramesDB.whisperHistory[contact]) > 0 then
            table.insert(self.contacts, contact)
            self.history[contact] = SFramesDB.whisperHistory[contact]
        end
    end
    -- Trim to max contacts (remove oldest = last in list)
    while table.getn(self.contacts) > MAX_WHISPER_CONTACTS do
        local oldest = table.remove(self.contacts)
        if oldest then
            self.history[oldest] = nil
        end
    end
end

function SFrames.Whisper:RemoveContact(contact)
    for i, v in ipairs(self.contacts) do
        if v == contact then
            table.remove(self.contacts, i)
            break
        end
    end
    self.history[contact] = nil
    self.unreadCount[contact] = nil
    if self.activeContact == contact then
        self.activeContact = self.contacts[1]
    end
    self:SaveCache()
    if self.frame and self.frame:IsShown() then
        self:UpdateContacts()
        self:UpdateMessages()
    end
end

function SFrames.Whisper:ClearAllHistory()
    self.history = {}
    self.contacts = {}
    self.unreadCount = {}
    self.activeContact = nil
    self:SaveCache()
    if self.frame and self.frame:IsShown() then
        self:UpdateContacts()
        self:UpdateMessages()
    end
end

local function FormatTime()
    local h, m = GetGameTime()
    return string.format("%02d:%02d", h, m)
end

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

local function SetRoundBackdrop(frame, bgColor, borderColor)
    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 },
    })
    local bg = bgColor or CFG_THEME.panelBg
    local bd = borderColor or CFG_THEME.panelBorder
    frame:SetBackdropColor(bg[1], bg[2], bg[3], bg[4] or 1)
    frame:SetBackdropBorderColor(bd[1], bd[2], bd[3], bd[4] or 1)
end

local function CreateShadow(parent, size)
    local s = CreateFrame("Frame", nil, parent)
    local sz = size or 4
    s:SetPoint("TOPLEFT", parent, "TOPLEFT", -sz, sz)
    s:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", sz, -sz)
    s:SetFrameLevel(math.max(parent:GetFrameLevel() - 1, 0))
    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.55)
    s:SetBackdropBorderColor(0, 0, 0, 0.4)
    return s
end

local function EnsureBackdrop(frame)
    if not frame then return end
    if frame.sfCfgBackdrop then return end
    SetRoundBackdrop(frame)
    frame.sfCfgBackdrop = true
end

local function StyleActionButton(btn, label)
    SetRoundBackdrop(btn, CFG_THEME.buttonBg, CFG_THEME.buttonBorder)
    local fs = btn:CreateFontString(nil, "OVERLAY")
    fs:SetFont(GetFont(), 12, "OUTLINE")
    fs:SetTextColor(CFG_THEME.btnText[1], CFG_THEME.btnText[2], CFG_THEME.btnText[3])
    fs:SetPoint("CENTER", btn, "CENTER", 0, 0)
    if label then fs:SetText(label) end
    btn.nLabel = fs

    btn:SetScript("OnEnter", function()
        this:SetBackdropColor(CFG_THEME.buttonHoverBg[1], CFG_THEME.buttonHoverBg[2], CFG_THEME.buttonHoverBg[3], CFG_THEME.buttonHoverBg[4])
        this:SetBackdropBorderColor(CFG_THEME.btnHoverBorder[1], CFG_THEME.btnHoverBorder[2], CFG_THEME.btnHoverBorder[3], CFG_THEME.btnHoverBorder[4])
        if this.nLabel then this.nLabel:SetTextColor(CFG_THEME.btnActiveText[1], CFG_THEME.btnActiveText[2], CFG_THEME.btnActiveText[3]) end
    end)
    btn:SetScript("OnLeave", function()
        this:SetBackdropColor(CFG_THEME.buttonBg[1], CFG_THEME.buttonBg[2], CFG_THEME.buttonBg[3], CFG_THEME.buttonBg[4])
        this:SetBackdropBorderColor(CFG_THEME.buttonBorder[1], CFG_THEME.buttonBorder[2], CFG_THEME.buttonBorder[3], CFG_THEME.buttonBorder[4])
        if this.nLabel then this.nLabel:SetTextColor(CFG_THEME.btnText[1], CFG_THEME.btnText[2], CFG_THEME.btnText[3]) end
    end)
    btn:SetScript("OnMouseDown", function()
        this:SetBackdropColor(CFG_THEME.buttonDownBg[1], CFG_THEME.buttonDownBg[2], CFG_THEME.buttonDownBg[3], CFG_THEME.buttonDownBg[4])
    end)
    btn:SetScript("OnMouseUp", function()
        this:SetBackdropColor(CFG_THEME.buttonHoverBg[1], CFG_THEME.buttonHoverBg[2], CFG_THEME.buttonHoverBg[3], CFG_THEME.buttonHoverBg[4])
    end)
    return fs
end

local function IsNotFullyChinese(text)
    -- If there's any english letter, we consider it requires translation
    return string.find(text, "[a-zA-Z]") ~= nil
end

local function TranslateMessage(text, callback)
    if SFramesDB and SFramesDB.Chat and SFramesDB.Chat.translateEnabled == false then
        callback(nil)
        return
    end
    if not IsNotFullyChinese(text) then
        callback(nil)
        return
    end

    local cleanText = string.gsub(text, "|c%x%x%x%x%x%x%x%x", "")
    cleanText = string.gsub(cleanText, "|r", "")
    cleanText = string.gsub(cleanText, "|H.-|h(.-)|h", "%1")

    if _G.STranslateAPI and _G.STranslateAPI.IsReady and _G.STranslateAPI.IsReady() then
        _G.STranslateAPI.Translate(cleanText, "auto", "zh", function(result, err, meta)
            if result then
                callback(result)
            end
        end, "Nanami-UI")
    else
        callback(nil)
    end
end

function SFrames.Whisper:AddMessage(sender, text, isMe)
    if not self.history[sender] then
        self.history[sender] = {}
        table.insert(self.contacts, 1, sender)
    else
        -- Move to front
        for i, v in ipairs(self.contacts) do
            if v == sender then
                table.remove(self.contacts, i)
                break
            end
        end
        table.insert(self.contacts, 1, sender)
    end

    local msgData = {
        time = FormatTime(),
        text = text,
        isMe = isMe
    }
    table.insert(self.history[sender], msgData)

    if not isMe then
        PlaySound("TellIncoming")
        if self.activeContact ~= sender or not (self.frame and self.frame:IsShown()) then
            self.unreadCount[sender] = (self.unreadCount[sender] or 0) + 1
            if SFrames.Chat and SFrames.Chat.frame and SFrames.Chat.frame.whisperButton then
                SFrames.Chat.frame.whisperButton.hasUnread = true
                if SFrames.Chat.frame.whisperButton.flashFrame then
                    SFrames.Chat.frame.whisperButton.flashFrame:Show()
                end
            end
        end

        TranslateMessage(text, function(translated)
            if translated and translated ~= "" then
                msgData.translated = "(翻译) " .. translated
                if self.frame and self.frame:IsShown() and self.activeContact == sender then
                    self:UpdateMessages()
                end
            end
        end)
    end

    -- Trim contacts if exceeding max
    while table.getn(self.contacts) > MAX_WHISPER_CONTACTS do
        local oldest = table.remove(self.contacts)
        if oldest then
            self.history[oldest] = nil
            self.unreadCount[oldest] = nil
        end
    end

    if self.frame and self.frame:IsShown() then
        self:UpdateContacts()
        if self.activeContact == sender then
            self:UpdateMessages()
        end
    end

    self:SaveCache()
end

function SFrames.Whisper:SelectContact(contact)
    self.activeContact = contact
    self.unreadCount[contact] = 0
    self:UpdateContacts()
    self:UpdateMessages()
    
    -- Clear global unread state if no more unread
    local totalUnread = 0
    for k, v in pairs(self.unreadCount) do
        totalUnread = totalUnread + v
    end
    if totalUnread == 0 and SFrames.Chat and SFrames.Chat.frame and SFrames.Chat.frame.whisperButton then
        SFrames.Chat.frame.whisperButton.hasUnread = false
        if SFrames.Chat.frame.whisperButton.flashFrame then
            SFrames.Chat.frame.whisperButton.flashFrame:Hide()
        end
    end

    if self.frame and self.frame.editBox then
        self.frame.editBox:SetText("")
        self.frame.editBox:SetFocus()
    end
end

function SFrames.Whisper:UpdateContacts()
    if not self.frame or not self.frame.contactList then return end
    
    local scrollChild = self.frame.contactList.scrollChild
    for _, child in ipairs({scrollChild:GetChildren()}) do
        child:Hide()
    end

    local yOffset = -5
    local fontPath = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"

    for i, contact in ipairs(self.contacts) do
        local btn = self.contactButtons and self.contactButtons[i]
        if not btn then
            btn = CreateFrame("Button", nil, scrollChild)
            btn:SetWidth(120)
            btn:SetHeight(24)
            
            EnsureBackdrop(btn)
            
            local txt = btn:CreateFontString(nil, "OVERLAY")
            txt:SetFont(fontPath, 12, "OUTLINE")
            txt:SetPoint("LEFT", btn, "LEFT", 8, 0)
            btn.txt = txt

            local closeBtn = CreateFrame("Button", nil, btn)
            closeBtn:SetWidth(16)
            closeBtn:SetHeight(16)
            closeBtn:SetPoint("RIGHT", btn, "RIGHT", -2, 0)
            
            local closeTxt = closeBtn:CreateFontString(nil, "OVERLAY")
            closeTxt:SetFont(fontPath, 10, "OUTLINE")
            closeTxt:SetPoint("CENTER", closeBtn, "CENTER", 0, 1)
            closeTxt:SetText("x")
            closeTxt:SetTextColor(CFG_THEME.dimText[1], CFG_THEME.dimText[2], CFG_THEME.dimText[3])
            closeBtn:SetFontString(closeTxt)
            closeBtn:SetScript("OnEnter", function() closeTxt:SetTextColor(1, 0.4, 0.5) end)
            closeBtn:SetScript("OnLeave", function() closeTxt:SetTextColor(CFG_THEME.dimText[1], CFG_THEME.dimText[2], CFG_THEME.dimText[3]) end)
            closeBtn:SetScript("OnClick", function()
                SFrames.Whisper:RemoveContact(this:GetParent().contact)
            end)
            btn.closeBtn = closeBtn

            local unreadTxt = btn:CreateFontString(nil, "OVERLAY")
            unreadTxt:SetFont(fontPath, 10, "OUTLINE")
            unreadTxt:SetPoint("RIGHT", closeBtn, "LEFT", -2, 0)
            unreadTxt:SetTextColor(1, 0.4, 0.4)
            btn.unreadTxt = unreadTxt

            btn:SetScript("OnClick", function()
                SFrames.Whisper:SelectContact(this.contact)
            end)
            
            btn:SetScript("OnEnter", function()
                if this.contact ~= SFrames.Whisper.activeContact then
                    this:SetBackdropColor(CFG_THEME.buttonHoverBg[1], CFG_THEME.buttonHoverBg[2], CFG_THEME.buttonHoverBg[3], CFG_THEME.buttonHoverBg[4])
                end
            end)
            btn:SetScript("OnLeave", function()
                if this.contact ~= SFrames.Whisper.activeContact then
                    this:SetBackdropColor(0,0,0,0)
                end
            end)

            if not self.contactButtons then self.contactButtons = {} end
            self.contactButtons[i] = btn
        end

        btn.contact = contact
        btn:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 5, yOffset)
        btn.txt:SetText(contact)
        
        local unreads = self.unreadCount[contact] or 0
        if unreads > 0 then
            btn.unreadTxt:SetText("("..unreads..")")
            btn.txt:SetTextColor(1, 0.8, 0.2)
        else
            btn.unreadTxt:SetText("")
            btn.txt:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3])
        end

        if contact == self.activeContact then
            btn:SetBackdropColor(CFG_THEME.buttonBg[1], CFG_THEME.buttonBg[2], CFG_THEME.buttonBg[3], CFG_THEME.buttonBg[4])
            btn:SetBackdropBorderColor(CFG_THEME.buttonBorder[1], CFG_THEME.buttonBorder[2], CFG_THEME.buttonBorder[3], CFG_THEME.buttonBorder[4])
        else
            btn:SetBackdropColor(0,0,0,0)
            btn:SetBackdropBorderColor(0,0,0,0)
        end

        btn:Show()
        yOffset = yOffset - 26
    end
    
    scrollChild:SetHeight(math.abs(yOffset))
    self.frame.contactList:UpdateScrollChildRect()
    local maxScroll = math.max(0, math.abs(yOffset) - 330)
    local slider = _G["SFramesWhisperContactScrollScrollBar"]
    if slider then
        slider:SetMinMaxValues(0, maxScroll)
    end
end

function SFrames.Whisper:UpdateMessages()
    if not self.frame or not self.frame.messageScroll then return end
    
    local scrollChild = self.frame.messageScroll.scrollChild
    if self.msgLabels then
        for i, fs in ipairs(self.msgLabels) do
            fs:Hide()
        end
    end
    if self.msgCopyBtns then
        for i, btn in ipairs(self.msgCopyBtns) do
            btn:Hide()
        end
    end

    if not self.activeContact then return end
    local messages = self.history[self.activeContact] or {}
    
    local yOffset = -5
    local fontPath = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"

    if not self.msgLabels then self.msgLabels = {} end
    if not self.msgCopyBtns then self.msgCopyBtns = {} end

    for i, msg in ipairs(messages) do
        local fs = self.msgLabels[i]
        if not fs then
            fs = scrollChild:CreateFontString(nil, "OVERLAY")
            fs:SetFont(fontPath, 13, "OUTLINE")
            fs:SetJustifyH("LEFT")
            fs:SetWidth(380)
            if fs.EnableMouse then fs:EnableMouse(true) end
            self.msgLabels[i] = fs
        end
        
        local btn = self.msgCopyBtns[i]
        if not btn then
            btn = CreateFrame("Button", nil, scrollChild)
            btn:SetWidth(20)
            btn:SetHeight(16)
            local txt = btn:CreateFontString(nil, "OVERLAY")
            txt:SetFont(fontPath, 13, "OUTLINE")
            txt:SetPoint("CENTER", btn, "CENTER", 0, 0)
            txt:SetText("[+]")
            txt:SetTextColor(CFG_THEME.dimText[1], CFG_THEME.dimText[2], CFG_THEME.dimText[3])
            btn:SetFontString(txt)
            
            btn:SetScript("OnEnter", function() txt:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3]) end)
            btn:SetScript("OnLeave", function() txt:SetTextColor(CFG_THEME.dimText[1], CFG_THEME.dimText[2], CFG_THEME.dimText[3]) end)
            btn:SetScript("OnClick", function()
                if SFrames and SFrames.Chat and SFrames.Chat.OpenMessageContextMenu then
                    SFrames.Chat:OpenMessageContextMenu("whisper", this.rawText, this.senderName)
                end
            end)
            self.msgCopyBtns[i] = btn
        end

        local color = msg.isMe and "|cff66ccff" or "|cffffbbee"
        local nameStr = msg.isMe and "我" or self.activeContact
        local textStr = string.format("%s[%s] %s:|r %s", color, msg.time, nameStr, msg.text)

        if msg.translated then
            textStr = textStr .. "\n|cffaaaaaa" .. msg.translated .. "|r"
        end

        btn.rawText = msg.text
        btn.senderName = nameStr
        btn:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 5, yOffset)
        btn:Show()

        fs:SetText(textStr)
        fs:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 28, yOffset)
        fs:Show()
        
        yOffset = yOffset - fs:GetHeight() - 8
    end
    
    scrollChild:SetHeight(math.abs(yOffset))
    self.frame.messageScroll:UpdateScrollChildRect()
    local maxScroll = math.max(0, math.abs(yOffset) - 270)
    local slider = _G["SFramesWhisperMessageScrollScrollBar"]
    if slider then
        slider:SetMinMaxValues(0, maxScroll)
        slider:SetValue(maxScroll)
    end
    self.frame.messageScroll:SetVerticalScroll(maxScroll)
end

function SFrames.Whisper:EnsureFrame()
    if self.frame then return end

    local fontPath = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
    
    local f = CreateFrame("Frame", "SFramesWhisperContainer", UIParent)
    f:SetWidth(580)
    f:SetHeight(380)
    f:SetPoint("CENTER", UIParent, "CENTER", 0, 100)
    f:SetFrameStrata("HIGH")
    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", function() this:StartMoving() end)
    f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
    
    if not self.enterHooked then
        self.enterHooked = true
        local orig_ChatFrame_OpenChat = ChatFrame_OpenChat
        if orig_ChatFrame_OpenChat then
            ChatFrame_OpenChat = function(text, chatFrame)
                if SFrames and SFrames.Whisper and SFrames.Whisper.frame and SFrames.Whisper.frame:IsShown() and (not text or text == "") then
                    SFrames.Whisper.frame.editBox:SetFocus()
                    return
                end
                orig_ChatFrame_OpenChat(text, chatFrame)
            end
        end
    end
    
    table.insert(UISpecialFrames, "SFramesWhisperContainer")

    SetRoundBackdrop(f, CFG_THEME.panelBg, CFG_THEME.panelBorder)
    CreateShadow(f, 5)

    local titleIcon = SFrames:CreateIcon(f, "chat", 14)
    titleIcon:SetDrawLayer("OVERLAY")
    titleIcon:SetPoint("TOPLEFT", f, "TOPLEFT", 15, -12)
    titleIcon:SetVertexColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3])

    local title = f:CreateFontString(nil, "OVERLAY")
    title:SetFont(fontPath, 14, "OUTLINE")
    title:SetPoint("LEFT", titleIcon, "RIGHT", 4, 0)
    title:SetText("私聊对话管理")
    title:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3])

    local close = CreateFrame("Button", nil, f)
    close:SetWidth(18)
    close:SetHeight(18)
    close:SetPoint("TOPRIGHT", f, "TOPRIGHT", -8, -8)
    close:SetFrameLevel(f:GetFrameLevel() + 3)
    SetRoundBackdrop(close, CFG_THEME.buttonDownBg or { 0.35, 0.06, 0.06, 0.85 }, CFG_THEME.buttonBorder or { 0.45, 0.1, 0.1, 0.6 })
    local closeIco = SFrames:CreateIcon(close, "close", 12)
    closeIco:SetDrawLayer("OVERLAY")
    closeIco:SetPoint("CENTER", close, "CENTER", 0, 0)
    closeIco:SetVertexColor(1, 0.7, 0.7)
    close:SetScript("OnClick", function() f:Hide() end)
    close:SetScript("OnEnter", function()
        local h = CFG_THEME.buttonHoverBg or { 0.55, 0.1, 0.1, 0.95 }
        local hb = CFG_THEME.btnHoverBd or { 0.65, 0.15, 0.15, 0.9 }
        this:SetBackdropColor(h[1], h[2], h[3], h[4] or 0.95)
        this:SetBackdropBorderColor(hb[1], hb[2], hb[3], hb[4] or 0.9)
    end)
    close:SetScript("OnLeave", function()
        local d = CFG_THEME.buttonDownBg or { 0.35, 0.06, 0.06, 0.85 }
        local db = CFG_THEME.buttonBorder or { 0.45, 0.1, 0.1, 0.6 }
        this:SetBackdropColor(d[1], d[2], d[3], d[4] or 0.85)
        this:SetBackdropBorderColor(db[1], db[2], db[3], db[4] or 0.6)
    end)

    -- Contact List
    local contactList = CreateFrame("ScrollFrame", "SFramesWhisperContactScroll", f, "UIPanelScrollFrameTemplate")
    contactList:SetWidth(130)
    contactList:SetHeight(330)
    contactList:SetPoint("TOPLEFT", f, "TOPLEFT", 10, -40)
    SetRoundBackdrop(contactList, CFG_THEME.listBg, CFG_THEME.listBorder)
    
    local contactChild = CreateFrame("Frame", nil, contactList)
    contactChild:SetWidth(130)
    contactChild:SetHeight(10)
    contactList:SetScrollChild(contactChild)
    contactList.scrollChild = contactChild
    f.contactList = contactList

    -- Apply Nanami-UI scrollbar styling
    local contactSlider = _G["SFramesWhisperContactScrollScrollBar"]
    if contactSlider then
        contactSlider:SetWidth(12)
        local regions = { contactSlider:GetRegions() }
        for i = 1, table.getn(regions) do
            local region = regions[i]
            if region and region.GetObjectType and region:GetObjectType() == "Texture" then
                region:SetTexture(nil)
            end
        end
        local track = contactSlider:CreateTexture(nil, "BACKGROUND")
        track:SetTexture("Interface\\Buttons\\WHITE8X8")
        track:SetPoint("TOPLEFT", contactSlider, "TOPLEFT", 3, 0)
        track:SetPoint("BOTTOMRIGHT", contactSlider, "BOTTOMRIGHT", -3, 0)
        track:SetVertexColor(0.22, 0.12, 0.18, 0.9)

        if contactSlider.SetThumbTexture then
            contactSlider:SetThumbTexture("Interface\\Buttons\\WHITE8X8")
        end
        local thumb = contactSlider.GetThumbTexture and contactSlider:GetThumbTexture()
        if thumb then
            thumb:SetWidth(8)
            thumb:SetHeight(20)
            thumb:SetVertexColor(0.85, 0.55, 0.70, 0.95)
        end
        
        local upBtn = _G["SFramesWhisperContactScrollScrollBarScrollUpButton"]
        local downBtn = _G["SFramesWhisperContactScrollScrollBarScrollDownButton"]
        if upBtn then upBtn:Hide() end
        if downBtn then downBtn:Hide() end

        contactSlider:ClearAllPoints()
        contactSlider:SetPoint("TOPRIGHT", contactList, "TOPRIGHT", -2, -6)
        contactSlider:SetPoint("BOTTOMRIGHT", contactList, "BOTTOMRIGHT", -2, 6)
    end

    -- Message List
    local messageScroll = CreateFrame("ScrollFrame", "SFramesWhisperMessageScroll", f, "UIPanelScrollFrameTemplate")
    messageScroll:SetWidth(405)
    messageScroll:SetHeight(270)
    messageScroll:SetPoint("TOPLEFT", contactList, "TOPRIGHT", 5, 0)
    SetRoundBackdrop(messageScroll, CFG_THEME.listBg, CFG_THEME.listBorder)
    
    local messageChild = CreateFrame("Frame", nil, messageScroll)
    messageChild:SetWidth(405)
    messageChild:SetHeight(10)
    messageChild:EnableMouse(true)
    -- Hyperlink 脚本仅部分帧类型支持，pcall 防止不支持的客户端报错
    pcall(function()
        messageChild:SetScript("OnHyperlinkClick", function()
            local link = arg1
            if not link then return end
            if IsShiftKeyDown() then
                if ChatFrameEditBox and ChatFrameEditBox:IsShown() then
                    ChatFrameEditBox:Insert(link)
                elseif SFrames.Whisper.frame and SFrames.Whisper.frame.editBox then
                    SFrames.Whisper.frame.editBox:Insert(link)
                end
            else
                pcall(function() SetItemRef(link, arg2, arg3) end)
            end
        end)
    end)
    pcall(function()
        messageChild:SetScript("OnHyperlinkEnter", function()
            local link = arg1
            if not link then return end
            GameTooltip:SetOwner(UIParent, "ANCHOR_CURSOR")
            local ok = pcall(function() GameTooltip:SetHyperlink(link) end)
            if ok then
                GameTooltip:Show()
            else
                GameTooltip:Hide()
            end
        end)
    end)
    pcall(function()
        messageChild:SetScript("OnHyperlinkLeave", function()
            GameTooltip:Hide()
        end)
    end)
    messageScroll:SetScrollChild(messageChild)
    messageScroll.scrollChild = messageChild
    f.messageScroll = messageScroll

    local messageSlider = _G["SFramesWhisperMessageScrollScrollBar"]
    if messageSlider then
        messageSlider:SetWidth(12)
        local regions = { messageSlider:GetRegions() }
        for i = 1, table.getn(regions) do
            local region = regions[i]
            if region and region.GetObjectType and region:GetObjectType() == "Texture" then
                region:SetTexture(nil)
            end
        end
        local track = messageSlider:CreateTexture(nil, "BACKGROUND")
        track:SetTexture("Interface\\Buttons\\WHITE8X8")
        track:SetPoint("TOPLEFT", messageSlider, "TOPLEFT", 3, 0)
        track:SetPoint("BOTTOMRIGHT", messageSlider, "BOTTOMRIGHT", -3, 0)
        track:SetVertexColor(0.22, 0.12, 0.18, 0.9)

        if messageSlider.SetThumbTexture then
            messageSlider:SetThumbTexture("Interface\\Buttons\\WHITE8X8")
        end
        local thumb = messageSlider.GetThumbTexture and messageSlider:GetThumbTexture()
        if thumb then
            thumb:SetWidth(8)
            thumb:SetHeight(20)
            thumb:SetVertexColor(0.85, 0.55, 0.70, 0.95)
        end
        
        local upBtn = _G["SFramesWhisperMessageScrollScrollBarScrollUpButton"]
        local downBtn = _G["SFramesWhisperMessageScrollScrollBarScrollDownButton"]
        if upBtn then upBtn:Hide() end
        if downBtn then downBtn:Hide() end

        messageSlider:ClearAllPoints()
        messageSlider:SetPoint("TOPRIGHT", messageScroll, "TOPRIGHT", -2, -22)
        messageSlider:SetPoint("BOTTOMRIGHT", messageScroll, "BOTTOMRIGHT", -2, 22)

        local topBtn = CreateFrame("Button", nil, messageScroll)
        topBtn:SetWidth(12)
        topBtn:SetHeight(12)
        topBtn:SetPoint("BOTTOM", messageSlider, "TOP", 0, 4)
        local topTxt = topBtn:CreateFontString(nil, "OVERLAY")
        topTxt:SetFont(fontPath, 11, "OUTLINE")
        topTxt:SetPoint("CENTER", topBtn, "CENTER", 0, 1)
        topTxt:SetText("▲")
        topTxt:SetTextColor(CFG_THEME.dimText[1], CFG_THEME.dimText[2], CFG_THEME.dimText[3])
        topBtn:SetFontString(topTxt)
        topBtn:SetScript("OnEnter", function() topTxt:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3]) end)
        topBtn:SetScript("OnLeave", function() topTxt:SetTextColor(CFG_THEME.dimText[1], CFG_THEME.dimText[2], CFG_THEME.dimText[3]) end)
        topBtn:SetScript("OnClick", function()
            messageSlider:SetValue(0)
        end)

        local bottomBtn = CreateFrame("Button", nil, messageScroll)
        bottomBtn:SetWidth(12)
        bottomBtn:SetHeight(12)
        bottomBtn:SetPoint("TOP", messageSlider, "BOTTOM", 0, -4)
        local botTxt = bottomBtn:CreateFontString(nil, "OVERLAY")
        botTxt:SetFont(fontPath, 11, "OUTLINE")
        botTxt:SetPoint("CENTER", bottomBtn, "CENTER", 0, -1)
        botTxt:SetText("▼")
        botTxt:SetTextColor(CFG_THEME.dimText[1], CFG_THEME.dimText[2], CFG_THEME.dimText[3])
        bottomBtn:SetFontString(botTxt)
        bottomBtn:SetScript("OnEnter", function() botTxt:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3]) end)
        bottomBtn:SetScript("OnLeave", function() botTxt:SetTextColor(CFG_THEME.dimText[1], CFG_THEME.dimText[2], CFG_THEME.dimText[3]) end)
        bottomBtn:SetScript("OnClick", function()
            local _, maxVal = messageSlider:GetMinMaxValues()
            messageSlider:SetValue(maxVal)
        end)

        if messageScroll.EnableMouseWheel then
            messageScroll:EnableMouseWheel(true)
            messageScroll:SetScript("OnMouseWheel", function()
                local delta = arg1
                local _, maxVal = messageSlider:GetMinMaxValues()
                local val = messageSlider:GetValue() - delta * 40
                if val < 0 then val = 0 end
                if val > maxVal then val = maxVal end
                messageSlider:SetValue(val)
            end)
        end
    end

    local contactSlider = _G["SFramesWhisperContactScrollScrollBar"]
    if contactList and contactSlider and contactList.EnableMouseWheel then
        contactList:EnableMouseWheel(true)
        contactList:SetScript("OnMouseWheel", function()
            local delta = arg1
            local _, maxVal = contactSlider:GetMinMaxValues()
            local val = contactSlider:GetValue() - delta * 30
            if val < 0 then val = 0 end
            if val > maxVal then val = maxVal end
            contactSlider:SetValue(val)
        end)
    end

    -- EditBox for replying
    local editBox = CreateFrame("EditBox", "SFramesWhisperEditBox", f, "InputBoxTemplate")
    editBox:SetWidth(405)
    editBox:SetHeight(20)
    editBox:SetPoint("TOPLEFT", messageScroll, "BOTTOMLEFT", 0, -10)
    editBox:SetAutoFocus(false)
    editBox:SetFont(fontPath, 13, "OUTLINE")
    
    local translateCheck = CreateFrame("CheckButton", "SFramesWhisperTranslateCheck", f, "UICheckButtonTemplate")
    translateCheck:SetWidth(24)
    translateCheck:SetHeight(24)
    translateCheck:SetPoint("TOPLEFT", editBox, "BOTTOMLEFT", -5, -6)
    
    local txt = translateCheck:CreateFontString(nil, "OVERLAY")
    txt:SetFont(fontPath, 11, "OUTLINE")
    txt:SetPoint("LEFT", translateCheck, "RIGHT", 2, 0)
    txt:SetText("发前自动翻译 (译后可修改再回车)")
    txt:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3])
    
    -- Hide the default text and reskin the button if you have defined StyleCfgCheck inside your environment
    if _G["SFramesWhisperTranslateCheckText"] then
        _G["SFramesWhisperTranslateCheckText"]:Hide()
    end
    -- Reskin checkbox
    local function StyleCheck(cb)
        local box = CreateFrame("Frame", nil, cb)
        box:SetPoint("TOPLEFT", cb, "TOPLEFT", 2, -2)
        box:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", -2, 2)
        local boxLevel = (cb:GetFrameLevel() or 1) - 1
        if boxLevel < 0 then boxLevel = 0 end
        box:SetFrameLevel(boxLevel)
        EnsureBackdrop(box)
        if box.SetBackdropColor then
            box:SetBackdropColor(CFG_THEME.buttonBg[1], CFG_THEME.buttonBg[2], CFG_THEME.buttonBg[3], CFG_THEME.buttonBg[4])
        end
        if box.SetBackdropBorderColor then
            box:SetBackdropBorderColor(CFG_THEME.buttonBorder[1], CFG_THEME.buttonBorder[2], CFG_THEME.buttonBorder[3], CFG_THEME.buttonBorder[4])
        end
        cb.sfBox = box

        if cb.GetNormalTexture and cb:GetNormalTexture() then cb:GetNormalTexture():Hide() end
        if cb.GetPushedTexture and cb:GetPushedTexture() then cb:GetPushedTexture():Hide() end
        if cb.GetHighlightTexture and cb:GetHighlightTexture() then cb:GetHighlightTexture():Hide() end
        
        if cb.SetCheckedTexture then cb:SetCheckedTexture("Interface\\Buttons\\WHITE8X8") end
        local checked = cb.GetCheckedTexture and cb:GetCheckedTexture()
        if checked then
            checked:ClearAllPoints()
            checked:SetPoint("TOPLEFT", cb, "TOPLEFT", 5, -5)
            checked:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", -5, 5)
            checked:SetVertexColor(CFG_THEME.checkFill[1], CFG_THEME.checkFill[2], CFG_THEME.checkFill[3], CFG_THEME.checkFill[4])
        end
    end
    StyleCheck(translateCheck)

    f.translateCheck = translateCheck

    local function SendReply()
        if SFrames.Whisper.isTranslating then return end
        
        local text = editBox:GetText()
        if not text or text == "" or not SFrames.Whisper.activeContact then return end
        
        if f.translateCheck and f.translateCheck:GetChecked() then
            if text == SFrames.Whisper.lastTranslation then
                SendChatMessage(text, "WHISPER", nil, SFrames.Whisper.activeContact)
                editBox:SetText("")
                editBox:ClearFocus()
                SFrames.Whisper.lastTranslation = nil
            else
                local targetLang = "en"
                if not string.find(text, "[\128-\255]") then
                    targetLang = "zh"
                end
                
                if _G.STranslateAPI and _G.STranslateAPI.IsReady and _G.STranslateAPI.IsReady() then
                    SFrames.Whisper.isTranslating = true
                    editBox:SetText("翻译中...")
                    _G.STranslateAPI.Translate(text, "auto", targetLang, function(result, err, meta)
                        SFrames.Whisper.isTranslating = false
                        if result then
                            editBox:SetText(result)
                            SFrames.Whisper.lastTranslation = result
                            editBox:SetFocus()
                        else
                            editBox:SetText(text)
                            DEFAULT_CHAT_FRAME:AddMessage("|cffff3333[私聊翻译失败]|r " .. tostring(err))
                        end
                    end, "Nanami-UI")
                else
                    DEFAULT_CHAT_FRAME:AddMessage("|cffff3333[Nanami-UI] STranslate插件未加载|r")
                    SFrames.Whisper.lastTranslation = text
                end
            end
        else
            SendChatMessage(text, "WHISPER", nil, SFrames.Whisper.activeContact)
            editBox:SetText("")
            editBox:ClearFocus()
        end
    end

    editBox:SetScript("OnEnterPressed", SendReply)
    editBox:SetScript("OnEscapePressed", function() this:ClearFocus() end)
    f.editBox = editBox

    local sendBtn = CreateFrame("Button", nil, f)
    sendBtn:SetWidth(60)
    sendBtn:SetHeight(24)
    sendBtn:SetPoint("TOPRIGHT", editBox, "BOTTOMRIGHT", 0, -5)
    StyleActionButton(sendBtn, "发送")
    sendBtn:SetScript("OnClick", SendReply)
    
    f:Hide()
    self.frame = f
end

function SFrames.Whisper:Toggle()
    self:EnsureFrame()
    if self.frame:IsShown() then
        self.frame:Hide()
    else
        self.frame:Show()
        self:UpdateContacts()
        if self.contacts[1] and not self.activeContact then
            self:SelectContact(self.contacts[1])
        elseif self.activeContact then
            self:SelectContact(self.activeContact)
        end
    end
end

-- Filter out addon sync/data messages that abuse the whisper channel.
-- These are not real conversations and should not appear in the whisper UI.
local ADDON_WHISPER_PATTERNS = {
    "^%[%u+:%u+%]",          -- [ST:HB], [GS:REQ], etc.
    "^%u+:%u+:%d",            -- ADDON:CMD:data
    "^<.->.+",                -- <AddonName>data
    "^%$%u+%$",               -- $ADDON$
    "^##%u+##",               -- ##ADDON##
    "^{.-}",                  -- {json-like data}
    "^LVGS:",                 -- LevelGearSync
    "^EEQ:",                  -- EquipExchange
    "^GS:%d",                 -- GearScore sync
    "^ST:%u",                 -- SpellTimer
}

local function IsAddonWhisper(text)
    if not text or text == "" then return false end
    for _, pat in ipairs(ADDON_WHISPER_PATTERNS) do
        if string.find(text, pat) then return true end
    end
    -- Pure numeric data (e.g. "30343.996") with no real words
    if string.find(text, "^[%d%.%s%-]+$") then return true end
    return false
end

-- Hook Events
local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("CHAT_MSG_WHISPER")
eventFrame:RegisterEvent("CHAT_MSG_WHISPER_INFORM")
eventFrame:RegisterEvent("PLAYER_LOGIN")
eventFrame:SetScript("OnEvent", function()
    if event == "PLAYER_LOGIN" then
        SFrames.Whisper:LoadCache()
        return
    end

    local text = arg1
    local sender = arg2
    if not sender or sender == "" then return end
    if IsAddonWhisper(text) then return end
    
    sender = string.gsub(sender, "-.*", "") -- remove realm name if attached

    if event == "CHAT_MSG_WHISPER" then
        SFrames.Whisper:AddMessage(sender, text, false)
    elseif event == "CHAT_MSG_WHISPER_INFORM" then
        SFrames.Whisper:AddMessage(sender, text, true)
    end
end)
