--------------------------------------------------------------------------------
-- Nanami-UI: AFK Screen (AFKScreen.lua)
-- Full-screen idle screen with dancing character model & info panel
--------------------------------------------------------------------------------

SFrames.AFKScreen = {}
local AFK = SFrames.AFKScreen

local FADE_SPEED = 1.25
local PARTICLE_COUNT = 18
local CLOCK_UPDATE_INTERVAL = 0.5

local CLASS_NAMES_ZH = {
    ["WARRIOR"] = "战士", ["MAGE"] = "法师", ["ROGUE"] = "盗贼",
    ["DRUID"] = "德鲁伊", ["HUNTER"] = "猎人", ["SHAMAN"] = "萨满",
    ["PRIEST"] = "牧师", ["WARLOCK"] = "术士", ["PALADIN"] = "圣骑士",
}

local T = SFrames.ActiveTheme

local WORLD_BUFF_DEFS = {
    ["Rallying Cry of the Dragonslayer"] = { display = "屠龙者的咆哮 (龙头)", priority = 1, color = {1, 0.85, 0.30} },
    ["屠龙者的咆哮"]                     = { display = "屠龙者的咆哮 (龙头)", priority = 1, color = {1, 0.85, 0.30} },
    ["巨龙杀手的战吼"]                   = { display = "屠龙者的咆哮 (龙头)", priority = 1, color = {1, 0.85, 0.30} },

    ["Warchief's Blessing"]              = { display = "酋长的祝福",     priority = 2, color = {0.90, 0.55, 0.30} },
    ["酋长的祝福"]                       = { display = "酋长的祝福",     priority = 2, color = {0.90, 0.55, 0.30} },

    ["Spirit of Zandalar"]               = { display = "赞达拉之魂",     priority = 2, color = {0.40, 0.90, 0.45} },
    ["赞达拉之魂"]                       = { display = "赞达拉之魂",     priority = 2, color = {0.40, 0.90, 0.45} },

    ["Songflower Serenade"]              = { display = "歌唱花小夜曲",   priority = 3, color = {0.75, 0.55, 0.90} },
    ["歌唱花小夜曲"]                     = { display = "歌唱花小夜曲",   priority = 3, color = {0.75, 0.55, 0.90} },

    ["Mol'dar's Moxie"]                  = { display = "莫达尔的勇气",   priority = 3, color = {0.50, 0.80, 0.55} },
    ["莫达尔的勇气"]                     = { display = "莫达尔的勇气",   priority = 3, color = {0.50, 0.80, 0.55} },
    ["Fengus' Ferocity"]                 = { display = "芬古斯的凶猛",   priority = 3, color = {0.85, 0.55, 0.40} },
    ["芬古斯的凶猛"]                     = { display = "芬古斯的凶猛",   priority = 3, color = {0.85, 0.55, 0.40} },
    ["Slip'kik's Savvy"]                 = { display = "斯里基克的精明", priority = 3, color = {0.55, 0.70, 0.90} },
    ["斯里基克的精明"]                   = { display = "斯里基克的精明", priority = 3, color = {0.55, 0.70, 0.90} },

    ["Darkmoon Faire"]                   = { display = "暗月马戏团",     priority = 4, color = {0.65, 0.50, 0.80} },
}

--------------------------------------------------------------------------------
-- Helpers
--------------------------------------------------------------------------------

local function NextName(prefix)
    if not AFK._nameCounter then AFK._nameCounter = 0 end
    AFK._nameCounter = AFK._nameCounter + 1
    return "NanamiAFK_" .. prefix .. AFK._nameCounter
end

local function GetFont()
    return SFrames:GetFont()
end

local function GetOutline()
    return SFrames.Media.fontOutline or "OUTLINE"
end

local function GetClassColor(class)
    local c = SFrames.Config.colors.class[class]
    if c then return c.r, c.g, c.b end
    return 1, 1, 1
end

local function FormatDuration(seconds)
    local h = math.floor(seconds / 3600)
    local m = math.floor((seconds - h * 3600) / 60)
    local s = math.floor(seconds - h * 3600 - m * 60)
    if h > 0 then
        return string.format("%d:%02d:%02d", h, m, s)
    else
        return string.format("%02d:%02d", m, s)
    end
end

local function GetDurabilityPercent()
    local current, maximum = 0, 0
    for slot = 1, 18 do
        local ok, cur, mx = pcall(function()
            local hasItem = GetInventoryItemLink("player", slot)
            if hasItem then
                local c, m = GetInventoryItemDurability(slot)
                return c, m
            end
            return nil, nil
        end)
        if ok and cur and mx and mx > 0 then
            current = current + cur
            maximum = maximum + mx
        end
    end
    if maximum == 0 then return nil end
    return math.floor((current / maximum) * 100)
end

local function GetPvPRankName()
    local ok, name = pcall(function() return GetPVPRankInfo(UnitPVPRank("player")) end)
    if ok and name and name ~= "" then return name end
    return nil
end

--------------------------------------------------------------------------------
-- UI Construction
--------------------------------------------------------------------------------

function AFK:Build()
    if self.frame then return end

    local existing = getglobal("NanamiAFKScreen")
    if existing then
        existing:SetScript("OnUpdate", nil)
        existing:SetScript("OnKeyDown", nil)
        existing:SetScript("OnMouseDown", nil)
        existing:EnableKeyboard(false)
        existing:EnableMouse(false)
        existing:Hide()
    end

    local existingWatcher = getglobal("NanamiAFKWatcher")
    if existingWatcher then
        existingWatcher:SetScript("OnUpdate", nil)
        existingWatcher:Hide()
    end

    local f = CreateFrame("Frame", "NanamiAFKScreen", WorldFrame)
    f:SetFrameStrata("FULLSCREEN_DIALOG")
    f:SetFrameLevel(100)
    f:SetAllPoints(UIParent)
    f:EnableMouse(true)
    f:EnableKeyboard(true)
    f:SetAlpha(0)
    f:Hide()
    self.frame = f

    -- Full-screen dark overlay
    f.overlay = f:CreateTexture(nil, "BACKGROUND")
    f.overlay:SetTexture("Interface\\Buttons\\WHITE8X8")
    f.overlay:SetAllPoints(f)
    f.overlay:SetVertexColor(T.overlayBg[1], T.overlayBg[2], T.overlayBg[3])
    f.overlay:SetAlpha(T.overlayBg[4])

    -- Top gradient (subtle vignette)
    f.topGrad = f:CreateTexture(nil, "BACKGROUND", nil, 1)
    f.topGrad:SetTexture("Interface\\Buttons\\WHITE8X8")
    f.topGrad:SetPoint("TOPLEFT", f, "TOPLEFT")
    f.topGrad:SetPoint("TOPRIGHT", f, "TOPRIGHT")
    f.topGrad:SetHeight(200)
    f.topGrad:SetGradientAlpha("VERTICAL", 0, 0, 0, 0, 0.02, 0.005, 0.02, 0.6)

    -- Bottom gradient
    f.botGrad = f:CreateTexture(nil, "BACKGROUND", nil, 1)
    f.botGrad:SetTexture("Interface\\Buttons\\WHITE8X8")
    f.botGrad:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT")
    f.botGrad:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT")
    f.botGrad:SetHeight(250)
    f.botGrad:SetGradientAlpha("VERTICAL", 0.03, 0.01, 0.03, 0.45, 0, 0, 0, 0)

    -- Bottom accent line
    f.accentLine = f:CreateTexture(nil, "ARTWORK")
    f.accentLine:SetTexture("Interface\\Buttons\\WHITE8X8")
    f.accentLine:SetHeight(1)
    f.accentLine:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 0, 60)
    f.accentLine:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 60)
    f.accentLine:SetVertexColor(T.accentLine[1], T.accentLine[2], T.accentLine[3])
    f.accentLine:SetAlpha(T.accentLine[4])

    -- Accent glow (soft line above the accent)
    f.accentGlow = f:CreateTexture(nil, "ARTWORK", nil, -1)
    f.accentGlow:SetTexture("Interface\\Buttons\\WHITE8X8")
    f.accentGlow:SetHeight(30)
    f.accentGlow:SetPoint("BOTTOMLEFT", f.accentLine, "TOPLEFT", 0, 0)
    f.accentGlow:SetPoint("BOTTOMRIGHT", f.accentLine, "TOPRIGHT", 0, 0)
    f.accentGlow:SetGradientAlpha("VERTICAL",
        T.accentLine[1], T.accentLine[2], T.accentLine[3], 0.18,
        T.accentLine[1], T.accentLine[2], T.accentLine[3], 0)

    self:BuildModel(f)
    self:BuildInfoPanel(f)
    self:BuildStatsPanel(f)
    self:BuildSkillsPanel(f)
    self:BuildWorldBuffPanel(f)
    self:BuildClock(f)
    self:BuildParticles(f)
    self:BuildBrand(f)

    -- Exit hint below accent line
    local hintFs = f:CreateFontString(nil, "OVERLAY")
    hintFs:SetFont(GetFont(), 11, GetOutline())
    hintFs:SetPoint("BOTTOM", f, "BOTTOM", 0, 22)
    hintFs:SetJustifyH("CENTER")
    hintFs:SetTextColor(T.dimColor[1], T.dimColor[2], T.dimColor[3])
    hintFs:SetText("- 点击或按任意键退出 -")
    self.hintText = hintFs

    -- Exit AFK on key press or mouse click
    f:SetScript("OnKeyDown", function()
        if AFK.isShowing and not AFK._exiting then
            AFK:RequestExit()
        end
    end)
    f:SetScript("OnMouseDown", function()
        if AFK.isShowing and not AFK._exiting then
            AFK:RequestExit()
        end
    end)

    -- Master OnUpdate
    f:SetScript("OnUpdate", function()
        AFK:OnUpdate(arg1)
    end)
end

--------------------------------------------------------------------------------
-- 3D Character Model
--------------------------------------------------------------------------------

function AFK:BuildModel(parent)
    local model = CreateFrame("PlayerModel", NextName("Model"), parent)
    model:SetWidth(520)
    model:SetHeight(640)
    model:SetPoint("BOTTOMLEFT", parent, "BOTTOMLEFT", 10, 65)
    model:SetFrameLevel(parent:GetFrameLevel() + 1)

    -- Soft darkening on the right side so the info panel is readable
    local modelFade = parent:CreateTexture(nil, "ARTWORK", nil, 0)
    modelFade:SetTexture("Interface\\Buttons\\WHITE8X8")
    modelFade:SetPoint("TOPRIGHT", parent, "TOPRIGHT")
    modelFade:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT")
    modelFade:SetWidth(420)
    modelFade:SetGradientAlpha("HORIZONTAL",
        0, 0, 0, 0,
        T.overlayBg[1], T.overlayBg[2], T.overlayBg[3], 0.65)

    self.model = model
    self.modelFacing = 0
end

--------------------------------------------------------------------------------
-- Info Panel (bottom-right area above accent line)
--------------------------------------------------------------------------------

function AFK:BuildInfoPanel(parent)
    local panel = CreateFrame("Frame", nil, parent)
    panel:SetFrameLevel(parent:GetFrameLevel() + 5)
    panel:SetWidth(210)
    panel:SetHeight(150)
    panel:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", -40, 75)
    self.infoPanel = panel

    panel:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = false, tileSize = 0, edgeSize = 12,
        insets = { left = 3, right = 3, top = 3, bottom = 3 }
    })
    panel:SetBackdropColor(T.panelBg[1], T.panelBg[2], T.panelBg[3], T.panelBg[4])
    panel:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], T.panelBorder[4])

    local yOff = -10
    local xPad = 12

    local classIconFrame = CreateFrame("Frame", nil, panel)
    classIconFrame:SetFrameLevel(panel:GetFrameLevel() + 1)
    classIconFrame:SetWidth(36)
    classIconFrame:SetHeight(36)
    classIconFrame:SetPoint("TOPLEFT", panel, "TOPLEFT", xPad, yOff)
    local classIcon = classIconFrame:CreateTexture(nil, "OVERLAY")
    classIcon:SetTexture("Interface\\AddOns\\Nanami-UI\\img\\UI-Classes-Circles")
    classIcon:SetAllPoints(classIconFrame)
    classIcon:Hide()
    self.classIcon = classIcon
    self.classIconFrame = classIconFrame

    local nameFs = panel:CreateFontString(nil, "OVERLAY")
    nameFs:SetFont(GetFont(), 16, GetOutline())
    nameFs:SetPoint("TOPLEFT", classIconFrame, "TOPRIGHT", 8, 0)
    nameFs:SetJustifyH("LEFT")
    self.nameText = nameFs

    local classFs = panel:CreateFontString(nil, "OVERLAY")
    classFs:SetFont(GetFont(), 10, GetOutline())
    classFs:SetPoint("TOPLEFT", nameFs, "BOTTOMLEFT", 0, -2)
    classFs:SetJustifyH("LEFT")
    classFs:SetTextColor(T.valueColor[1], T.valueColor[2], T.valueColor[3])
    self.classText = classFs

    local sep1 = panel:CreateTexture(nil, "ARTWORK")
    sep1:SetTexture("Interface\\Buttons\\WHITE8X8")
    sep1:SetHeight(1)
    sep1:SetPoint("TOPLEFT", panel, "TOPLEFT", xPad, yOff - 44)
    sep1:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -xPad, yOff - 44)
    sep1:SetVertexColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3])
    sep1:SetAlpha(0.35)

    local rowStart = yOff - 52
    local rowGap = 15

    local function MakeRow(label, yPos, iconKey)
        if iconKey and SFrames and SFrames.CreateIcon then
            local ico = SFrames:CreateIcon(panel, iconKey, 9)
            ico:SetDrawLayer("OVERLAY")
            ico:SetPoint("TOPLEFT", panel, "TOPLEFT", xPad, yPos)
            ico:SetVertexColor(T.labelColor[1], T.labelColor[2], T.labelColor[3])
            ico:SetAlpha(0.85)
        end

        local lbl = panel:CreateFontString(nil, "OVERLAY")
        lbl:SetFont(GetFont(), 9, GetOutline())
        lbl:SetPoint("TOPLEFT", panel, "TOPLEFT", iconKey and (xPad + 13) or xPad, yPos)
        lbl:SetJustifyH("LEFT")
        lbl:SetTextColor(T.labelColor[1], T.labelColor[2], T.labelColor[3])
        lbl:SetText(label)

        local val = panel:CreateFontString(nil, "OVERLAY")
        val:SetFont(GetFont(), 9, GetOutline())
        val:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -xPad, yPos)
        val:SetJustifyH("RIGHT")
        val:SetTextColor(T.valueColor[1], T.valueColor[2], T.valueColor[3])
        return val
    end

    self.guildText    = MakeRow("公会",   rowStart,            "party")
    self.zoneText     = MakeRow("位置",   rowStart - rowGap,   "worldmap")
    self.durText      = MakeRow("耐久度", rowStart - rowGap * 2, "tank")
    self.pvpText      = MakeRow("军衔",   rowStart - rowGap * 3, "honor")
    self.playtimeText = MakeRow("游戏时长", rowStart - rowGap * 4, "hearthstone")
end

--------------------------------------------------------------------------------
-- Stats Panel (top-right area — character attributes)
--------------------------------------------------------------------------------

function AFK:BuildStatsPanel(parent)
    local panel = CreateFrame("Frame", nil, parent)
    panel:SetFrameLevel(parent:GetFrameLevel() + 5)
    panel:SetWidth(210)
    panel:SetHeight(230)
    panel:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -40, -80)
    self.statsPanel = panel

    panel:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = false, tileSize = 0, edgeSize = 12,
        insets = { left = 3, right = 3, top = 3, bottom = 3 }
    })
    panel:SetBackdropColor(T.panelBg[1], T.panelBg[2], T.panelBg[3], 0.72)
    panel:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], 0.45)

    local statsIco = SFrames:CreateIcon(panel, "charsheet", 12)
    statsIco:SetDrawLayer("OVERLAY")
    statsIco:SetPoint("TOP", panel, "TOP", -38, -8)
    statsIco:SetVertexColor(T.titleColor[1], T.titleColor[2], T.titleColor[3])

    local titleFs = panel:CreateFontString(nil, "OVERLAY")
    titleFs:SetFont(GetFont(), 11, GetOutline())
    titleFs:SetPoint("LEFT", statsIco, "RIGHT", 4, 0)
    titleFs:SetJustifyH("CENTER")
    titleFs:SetTextColor(T.titleColor[1], T.titleColor[2], T.titleColor[3])
    titleFs:SetText("- 角色属性 -")

    local sep = panel:CreateTexture(nil, "ARTWORK")
    sep:SetTexture("Interface\\Buttons\\WHITE8X8")
    sep:SetHeight(1)
    sep:SetPoint("TOPLEFT", panel, "TOPLEFT", 12, -22)
    sep:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -12, -22)
    sep:SetVertexColor(T.accentLine[1], T.accentLine[2], T.accentLine[3])
    sep:SetAlpha(0.3)

    local xPad = 14
    local yStart = -30
    local rowGap = 15
    self._statRows = {}

    local statDefs = {
        { key = "hp",      label = "生命值" },
        { key = "mp",      label = "法力值" },
        { key = "armor",   label = "护甲" },
        { divider = true },
        { key = "str",     label = "力量" },
        { key = "agi",     label = "敏捷" },
        { key = "sta",     label = "耐力" },
        { key = "int",     label = "智力" },
        { key = "spi",     label = "精神" },
        { divider = true },
        { key = "melee",   label = "攻击强度" },
        { key = "spell",   label = "法术强度" },
        { key = "crit",    label = "暴击率" },
        { key = "dodge",   label = "躲闪" },
    }

    local curY = yStart
    for _, def in ipairs(statDefs) do
        if def.divider then
            local div = panel:CreateTexture(nil, "ARTWORK")
            div:SetTexture("Interface\\Buttons\\WHITE8X8")
            div:SetHeight(1)
            div:SetPoint("TOPLEFT", panel, "TOPLEFT", xPad + 4, curY - 2)
            div:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -xPad - 4, curY - 2)
            div:SetVertexColor(0.45, 0.28, 0.40)
            div:SetAlpha(0.25)
            curY = curY - 8
        else
            local lbl = panel:CreateFontString(nil, "OVERLAY")
            lbl:SetFont(GetFont(), 9, GetOutline())
            lbl:SetPoint("TOPLEFT", panel, "TOPLEFT", xPad, curY)
            lbl:SetJustifyH("LEFT")
            lbl:SetTextColor(0.58, 0.48, 0.55)
            lbl:SetText(def.label)

            local val = panel:CreateFontString(nil, "OVERLAY")
            val:SetFont(GetFont(), 10, GetOutline())
            val:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -xPad, curY)
            val:SetJustifyH("RIGHT")
            val:SetTextColor(0.92, 0.84, 0.90)

            self._statRows[def.key] = val
            curY = curY - rowGap
        end
    end
end

--------------------------------------------------------------------------------
-- Skills Panel (right side, below stats — professions & weapon skills)
--------------------------------------------------------------------------------

function AFK:BuildSkillsPanel(parent)
    local panel = CreateFrame("Frame", nil, parent)
    panel:SetFrameLevel(parent:GetFrameLevel() + 5)
    panel:SetWidth(210)
    panel:SetHeight(200)
    panel:SetPoint("TOPRIGHT", self.statsPanel, "BOTTOMRIGHT", 0, -6)
    self.skillsPanel = panel

    panel:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = false, tileSize = 0, edgeSize = 12,
        insets = { left = 3, right = 3, top = 3, bottom = 3 }
    })
    panel:SetBackdropColor(T.panelBg[1], T.panelBg[2], T.panelBg[3], 0.72)
    panel:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], 0.45)

    local skillsIco = SFrames:CreateIcon(panel, "profession", 12)
    skillsIco:SetDrawLayer("OVERLAY")
    skillsIco:SetPoint("TOP", panel, "TOP", -38, -8)
    skillsIco:SetVertexColor(T.titleColor[1], T.titleColor[2], T.titleColor[3])

    local titleFs = panel:CreateFontString(nil, "OVERLAY")
    titleFs:SetFont(GetFont(), 11, GetOutline())
    titleFs:SetPoint("LEFT", skillsIco, "RIGHT", 4, 0)
    titleFs:SetJustifyH("CENTER")
    titleFs:SetTextColor(T.titleColor[1], T.titleColor[2], T.titleColor[3])
    titleFs:SetText("- 技能总览 -")

    local sep = panel:CreateTexture(nil, "ARTWORK")
    sep:SetTexture("Interface\\Buttons\\WHITE8X8")
    sep:SetHeight(1)
    sep:SetPoint("TOPLEFT", panel, "TOPLEFT", 12, -22)
    sep:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -12, -22)
    sep:SetVertexColor(T.accentLine[1], T.accentLine[2], T.accentLine[3])
    sep:SetAlpha(0.3)

    self._skillBars = {}
end

function AFK:CreateSkillBar(parent, yOffset, name, rank, maxRank, barColor)
    local xPad = 14
    local barWidth = 182
    local barHeight = 8

    local lbl = parent:CreateFontString(nil, "OVERLAY")
    lbl:SetFont(GetFont(), 8, GetOutline())
    lbl:SetPoint("TOPLEFT", parent, "TOPLEFT", xPad, yOffset)
    lbl:SetJustifyH("LEFT")
    lbl:SetTextColor(0.62, 0.52, 0.58)
    lbl:SetText(name)

    local valFs = parent:CreateFontString(nil, "OVERLAY")
    valFs:SetFont(GetFont(), 8, GetOutline())
    valFs:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -xPad, yOffset)
    valFs:SetJustifyH("RIGHT")
    valFs:SetTextColor(0.82, 0.74, 0.80)
    if maxRank and maxRank > 0 then
        valFs:SetText(rank .. " / " .. maxRank)
    else
        valFs:SetText(rank)
    end

    local bgBar = parent:CreateTexture(nil, "ARTWORK")
    bgBar:SetTexture("Interface\\Buttons\\WHITE8X8")
    bgBar:SetWidth(barWidth)
    bgBar:SetHeight(barHeight)
    bgBar:SetPoint("TOPLEFT", parent, "TOPLEFT", xPad, yOffset - 10)
    bgBar:SetVertexColor(T.panelBg[1], T.panelBg[2], T.panelBg[3])
    bgBar:SetAlpha(0.85)

    -- Bar fill
    local fillBar = parent:CreateTexture(nil, "OVERLAY")
    fillBar:SetTexture("Interface\\Buttons\\WHITE8X8")
    fillBar:SetHeight(barHeight)
    fillBar:SetPoint("TOPLEFT", bgBar, "TOPLEFT", 0, 0)

    local pct = 0
    if maxRank and maxRank > 0 then
        pct = rank / maxRank
    elseif rank and rank > 0 then
        pct = 1
    end
    local fillWidth = math.max(1, barWidth * pct)
    fillBar:SetWidth(fillWidth)

    local cr, cg, cb = barColor[1], barColor[2], barColor[3]
    fillBar:SetVertexColor(cr, cg, cb, 0.75)

    -- Subtle highlight on bar top
    local shine = parent:CreateTexture(nil, "OVERLAY", nil, 1)
    shine:SetTexture("Interface\\Buttons\\WHITE8X8")
    shine:SetHeight(math.floor(barHeight / 2))
    shine:SetPoint("TOPLEFT", fillBar, "TOPLEFT", 0, 0)
    shine:SetPoint("TOPRIGHT", fillBar, "TOPRIGHT", 0, 0)
    shine:SetVertexColor(1, 1, 1, 0.08)

    return { lbl = lbl, val = valFs, bg = bgBar, fill = fillBar, shine = shine }
end

--------------------------------------------------------------------------------
-- World Buff Panel (center — prominent buff reminders)
--------------------------------------------------------------------------------

function AFK:BuildWorldBuffPanel(parent)
    local panel = CreateFrame("Frame", nil, parent)
    panel:SetFrameLevel(parent:GetFrameLevel() + 7)
    panel:SetWidth(320)
    panel:SetHeight(60)
    panel:SetPoint("CENTER", parent, "CENTER", -50, 30)
    self.wbPanel = panel
    panel:Hide()

    panel:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = false, tileSize = 0, edgeSize = 12,
        insets = { left = 3, right = 3, top = 3, bottom = 3 }
    })
    panel:SetBackdropColor(0.06, 0.04, 0.01, 0.82)
    panel:SetBackdropBorderColor(T.wbBorder[1], T.wbBorder[2], T.wbBorder[3], 0.55)

    local glow = CreateFrame("Frame", nil, parent)
    glow:SetFrameLevel(parent:GetFrameLevel() + 6)
    glow:SetWidth(328)
    glow:SetHeight(68)
    glow:SetPoint("CENTER", panel, "CENTER", 0, 0)
    glow:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = false, tileSize = 0, edgeSize = 14,
        insets = { left = 3, right = 3, top = 3, bottom = 3 }
    })
    glow:SetBackdropColor(0, 0, 0, 0)
    glow:SetBackdropBorderColor(1, 0.82, 0.30, 0.20)
    glow:Hide()
    self._wbGlow = glow

    local titleFs = panel:CreateFontString(nil, "OVERLAY")
    titleFs:SetFont(GetFont(), 12, GetOutline())
    titleFs:SetPoint("TOP", panel, "TOP", 0, -10)
    titleFs:SetJustifyH("CENTER")
    titleFs:SetTextColor(T.wbGold[1], T.wbGold[2], T.wbGold[3])
    titleFs:SetText("⚔ 世界增益 ⚔")
    self.wbTitle = titleFs

    local sep = panel:CreateTexture(nil, "ARTWORK")
    sep:SetTexture("Interface\\Buttons\\WHITE8X8")
    sep:SetHeight(1)
    sep:SetPoint("TOPLEFT", panel, "TOPLEFT", 14, -26)
    sep:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -14, -26)
    sep:SetVertexColor(1, 0.78, 0.30, 0.35)

    self._wbRows = {}
    self._wbPulse = 0
end

--------------------------------------------------------------------------------
-- Clock & AFK Timer (top center)
--------------------------------------------------------------------------------

function AFK:BuildClock(parent)
    local clockFrame = CreateFrame("Frame", nil, parent)
    clockFrame:SetFrameLevel(parent:GetFrameLevel() + 5)
    clockFrame:SetWidth(400)
    clockFrame:SetHeight(150)
    clockFrame:SetPoint("TOP", parent, "TOP", 0, -30)
    self.clockFrame = clockFrame

    -- Main clock: local time (large)
    local timeFs = clockFrame:CreateFontString(nil, "OVERLAY")
    timeFs:SetFont(GetFont(), 72, GetOutline())
    timeFs:SetPoint("TOP", clockFrame, "TOP", 0, 0)
    timeFs:SetJustifyH("CENTER")
    timeFs:SetTextColor(T.clockColor[1], T.clockColor[2], T.clockColor[3])
    self.clockText = timeFs

    -- Sub clock: server time (below main)
    local serverFs = clockFrame:CreateFontString(nil, "OVERLAY")
    serverFs:SetFont(GetFont(), 18, GetOutline())
    serverFs:SetPoint("TOP", timeFs, "BOTTOM", 0, -4)
    serverFs:SetJustifyH("CENTER")
    serverFs:SetTextColor(T.dimColor[1], T.dimColor[2], T.dimColor[3])
    self.serverClockText = serverFs

    -- AFK timer
    local timerFs = clockFrame:CreateFontString(nil, "OVERLAY")
    timerFs:SetFont(GetFont(), 18, GetOutline())
    timerFs:SetPoint("TOP", serverFs, "BOTTOM", 0, -8)
    timerFs:SetJustifyH("CENTER")
    timerFs:SetTextColor(T.timerColor[1], T.timerColor[2], T.timerColor[3])
    self.timerText = timerFs

    -- Subtle decorative dots flanking the main clock
    for _, side in ipairs({ -1, 1 }) do
        local dot = clockFrame:CreateTexture(nil, "ARTWORK")
        dot:SetTexture("Interface\\Buttons\\WHITE8X8")
        dot:SetWidth(4)
        dot:SetHeight(4)
        dot:SetPoint("CENTER", timeFs, side > 0 and "RIGHT" or "LEFT", side * 18, 0)
        dot:SetVertexColor(T.accentLine[1], T.accentLine[2], T.accentLine[3])
        dot:SetAlpha(0.5)
    end
end

--------------------------------------------------------------------------------
-- Floating Particles (sakura / firefly effect)
--------------------------------------------------------------------------------

function AFK:BuildParticles(parent)
    self.particles = {}
    for i = 1, PARTICLE_COUNT do
        local p = parent:CreateTexture(nil, "ARTWORK", nil, 2)
        p:SetTexture("Interface\\Buttons\\WHITE8X8")
        local size = math.random(2, 6)
        p:SetWidth(size)
        p:SetHeight(size)
        p:SetVertexColor(
            T.particleColor[1] + math.random() * 0.1 - 0.05,
            T.particleColor[2] + math.random() * 0.2 - 0.1,
            T.particleColor[3] + math.random() * 0.1 - 0.05
        )
        p:SetAlpha(0)

        p._baseAlpha = 0.15 + math.random() * 0.25
        p._speed = 8 + math.random() * 20
        p._drift = (math.random() - 0.5) * 40
        p._phase = math.random() * 6.28
        p._pulseSpeed = 1.5 + math.random() * 2
        p._x = math.random() * 1024
        p._y = math.random() * 768

        p:SetPoint("CENTER", parent, "BOTTOMLEFT", p._x, p._y)
        table.insert(self.particles, p)
    end
end

--------------------------------------------------------------------------------
-- Brand / Logo (bottom-left)
--------------------------------------------------------------------------------

function AFK:BuildBrand(parent)
    local brandIco = SFrames:CreateIcon(parent, "logo", 28)
    brandIco:SetDrawLayer("OVERLAY")
    brandIco:SetPoint("BOTTOMLEFT", parent, "BOTTOMLEFT", 20, 30)
    brandIco:SetVertexColor(T.brandColor[1], T.brandColor[2], T.brandColor[3])
    brandIco:SetAlpha(0.85)
    self.brandIcon = brandIco

    local brandFs = parent:CreateFontString(nil, "OVERLAY")
    brandFs:SetFont(GetFont(), 16, GetOutline())
    brandFs:SetPoint("LEFT", brandIco, "RIGHT", 6, 2)
    brandFs:SetJustifyH("LEFT")
    brandFs:SetTextColor(T.brandColor[1], T.brandColor[2], T.brandColor[3])
    brandFs:SetAlpha(0.85)
    brandFs:SetText("Nanami-UI  v1.0.0")
    self.brandText = brandFs

    local catFs = parent:CreateFontString(nil, "OVERLAY")
    catFs:SetFont(GetFont(), 12, GetOutline())
    catFs:SetPoint("TOPLEFT", brandIco, "BOTTOMLEFT", 0, -3)
    catFs:SetJustifyH("LEFT")
    catFs:SetTextColor(T.dimColor[1], T.dimColor[2], T.dimColor[3])
    catFs:SetAlpha(0.7)
    catFs:SetText("=^_^=  Meow~")
    self.catText = catFs
end

--------------------------------------------------------------------------------
-- Data Refresh
--------------------------------------------------------------------------------

function AFK:RefreshInfo()
    local name = UnitName("player") or "Unknown"
    local level = UnitLevel("player") or 0
    local _, classEn = UnitClass("player")
    classEn = classEn or "WARRIOR"

    local cr, cg, cb = GetClassColor(classEn)
    self.nameText:SetText(name)
    self.nameText:SetTextColor(cr, cg, cb)

    local className = CLASS_NAMES_ZH[classEn] or UnitClass("player") or classEn
    self.classText:SetText("Lv." .. level .. "  " .. className)

    -- Class icon
    local coords = SFrames.CLASS_ICON_TCOORDS and SFrames.CLASS_ICON_TCOORDS[classEn]
    if coords then
        self.classIcon:SetTexCoord(coords[1], coords[2], coords[3], coords[4])
        self.classIcon:Show()
        self.classIconFrame:Show()
    else
        self.classIcon:Hide()
    end

    -- Guild
    local guildName, guildRank = GetGuildInfo("player")
    if guildName then
        self.guildText:SetText("<" .. guildName .. ">  " .. (guildRank or ""))
    else
        self.guildText:SetText("-")
        self.guildText:SetTextColor(T.dimColor[1], T.dimColor[2], T.dimColor[3])
    end

    -- Zone
    local zone = GetZoneText() or ""
    local subZone = GetSubZoneText() or ""
    if subZone ~= "" and subZone ~= zone then
        self.zoneText:SetText(zone .. " - " .. subZone)
    else
        self.zoneText:SetText(zone)
    end

    -- Durability
    local dur = GetDurabilityPercent()
    if dur then
        local dr, dg, db = 0.4, 1, 0.4
        if dur < 25 then
            dr, dg, db = 1, 0.2, 0.2
        elseif dur < 50 then
            dr, dg, db = 1, 0.7, 0.2
        end
        self.durText:SetText(dur .. "%")
        self.durText:SetTextColor(dr, dg, db)
    else
        self.durText:SetText("-")
        self.durText:SetTextColor(T.dimColor[1], T.dimColor[2], T.dimColor[3])
    end

    -- PvP Rank
    local pvpRank = GetPvPRankName()
    if pvpRank then
        self.pvpText:SetText(pvpRank)
    else
        self.pvpText:SetText("-")
        self.pvpText:SetTextColor(T.dimColor[1], T.dimColor[2], T.dimColor[3])
    end

    -- Playtime (request it; the event callback fills it in)
    self.playtimeText:SetText("...")
    self.playtimeText:SetTextColor(T.dimColor[1], T.dimColor[2], T.dimColor[3])
    self._waitingPlaytime = true
    RequestTimePlayed()

    -- Character stats, skills & world buffs
    self:RefreshStats()
    self:RefreshSkills()
    self:RefreshWorldBuffs()
end

function AFK:RefreshStats()
    if not self._statRows then return end
    local R = self._statRows

    -- HP / MP
    local hp = UnitHealthMax("player") or 0
    local mp = UnitManaMax("player") or 0
    R.hp:SetText(hp)
    R.mp:SetText(mp)

    local _, pClass = UnitClass("player")
    if pClass == "WARRIOR" or pClass == "ROGUE" then
        R.mp:SetTextColor(0.50, 0.42, 0.48)
    end

    -- Armor
    local base, effectiveArmor = UnitArmor("player")
    R.armor:SetText(effectiveArmor or base or 0)

    -- Primary stats
    local function GetStat(id)
        local base, stat, posBuff, negBuff = UnitStat("player", id)
        return stat or base or 0
    end
    R.str:SetText(GetStat(1))
    R.agi:SetText(GetStat(2))
    R.sta:SetText(GetStat(3))
    R.int:SetText(GetStat(4))
    R.spi:SetText(GetStat(5))

    -- Melee attack power
    local mBase, mPos, mNeg = UnitAttackPower("player")
    local ap = (mBase or 0) + (mPos or 0) + (mNeg or 0)
    R.melee:SetText(ap)

    -- Spell power (approximate from bonus healing / damage stats if available)
    local sp = 0
    if GetSpellBonusDamage then
        for school = 2, 7 do
            local v = GetSpellBonusDamage(school)
            if v and v > sp then sp = v end
        end
    end
    R.spell:SetText(sp)

    -- Crit chance
    local crit = 0
    if GetCritChance then crit = GetCritChance() or 0
    elseif GetMeleeCritChance then crit = GetMeleeCritChance() or 0
    end
    R.crit:SetText(string.format("%.1f%%", crit))

    -- Dodge
    local dodge = GetDodgeChance and GetDodgeChance() or 0
    R.dodge:SetText(string.format("%.1f%%", dodge))
end

function AFK:RefreshSkills()
    if not self.skillsPanel then return end

    -- Clean up old bars
    if self._skillBars then
        for _, bar in ipairs(self._skillBars) do
            if bar.lbl then bar.lbl:Hide() end
            if bar.val then bar.val:Hide() end
            if bar.bg then bar.bg:Hide() end
            if bar.fill then bar.fill:Hide() end
            if bar.shine then bar.shine:Hide() end
        end
    end
    self._skillBars = {}

    -- Also clean up section labels
    if self._skillLabels then
        for _, fs in ipairs(self._skillLabels) do fs:Hide() end
    end
    self._skillLabels = {}

    local tradeSkills = {}
    local secondarySkills = {}
    local weapons = {}
    local numSkills = GetNumSkillLines and GetNumSkillLines() or 0
    local currentHeader = ""

    for i = 1, numSkills do
        local name, header, isExpanded, skillRank, numTemp, skillMod, skillMax, isAbandonable = GetSkillLineInfo(i)
        if header then
            currentHeader = name or ""
        elseif name then
            local isWeapon = (currentHeader == "Weapon Skills" or currentHeader == "武器技能"
                          or currentHeader == "Weapons" or currentHeader == "Combat"
                          or currentHeader == "武器")
            local isTrade = (currentHeader == "Trade Skills" or currentHeader == "专业技能"
                          or currentHeader == "Professions" or currentHeader == "专业")
            local isSecondary = (currentHeader == "Secondary Skills" or currentHeader == "辅助技能"
                          or currentHeader == "Secondary")

            if isWeapon and skillMax and skillMax > 0 then
                table.insert(weapons, { name = name, rank = skillRank or 0, max = skillMax })
            elseif isTrade and skillMax and skillMax > 0 then
                table.insert(tradeSkills, { name = name, rank = skillRank or 0, max = skillMax })
            elseif isSecondary and skillMax and skillMax > 0 then
                table.insert(secondarySkills, { name = name, rank = skillRank or 0, max = skillMax })
            end
        end
    end

    local professions = {}
    local maxProf = 3
    for _, p in ipairs(tradeSkills) do
        if table.getn(professions) < maxProf then table.insert(professions, p) end
    end
    for _, p in ipairs(secondarySkills) do
        if table.getn(professions) < maxProf then table.insert(professions, p) end
    end

    local panel = self.skillsPanel
    local yOff = -28
    local barSpacing = 22

    local profColor  = { 0.45, 0.75, 0.90 }
    local weapColor  = { 0.85, 0.60, 0.45 }

    local function AddSectionLabel(text, y)
        local fs = panel:CreateFontString(nil, "OVERLAY")
        fs:SetFont(GetFont(), 9, GetOutline())
        fs:SetPoint("TOPLEFT", panel, "TOPLEFT", 14, y)
        fs:SetJustifyH("LEFT")
        fs:SetTextColor(T.titleColor[1], T.titleColor[2], T.titleColor[3])
        fs:SetAlpha(0.85)
        fs:SetText(text)
        table.insert(self._skillLabels, fs)
    end

    if table.getn(professions) > 0 then
        AddSectionLabel("专业技能", yOff)
        yOff = yOff - 14
        for _, p in ipairs(professions) do
            local bar = self:CreateSkillBar(panel, yOff, p.name, p.rank, p.max, profColor)
            table.insert(self._skillBars, bar)
            yOff = yOff - barSpacing
        end
        yOff = yOff - 4
    end

    -- Sort weapons by rank descending; show top 4
    table.sort(weapons, function(a, b) return a.rank > b.rank end)
    local maxWeapons = 4
    if table.getn(weapons) > maxWeapons then
        local trimmed = {}
        for i = 1, maxWeapons do table.insert(trimmed, weapons[i]) end
        weapons = trimmed
    end

    if table.getn(weapons) > 0 then
        AddSectionLabel("武器熟练", yOff)
        yOff = yOff - 14
        for _, w in ipairs(weapons) do
            local bar = self:CreateSkillBar(panel, yOff, w.name, w.rank, w.max, weapColor)
            table.insert(self._skillBars, bar)
            yOff = yOff - barSpacing
        end
    end

    local totalHeight = math.abs(yOff) + 10
    if totalHeight < 50 then totalHeight = 50 end
    panel:SetHeight(totalHeight)
end

--------------------------------------------------------------------------------
-- World Buff Scanning & Refresh
--------------------------------------------------------------------------------

-- Texture-based fallback for world buff detection (server-independent)
local WORLD_BUFF_TEXTURES = {
    ["Interface\\Icons\\Spell_Holy_GreaterHeal"]            = { display = "屠龙者的咆哮 (龙头)", priority = 1, color = {1, 0.85, 0.30} },
    ["Interface\\Icons\\INV_Misc_Head_Dragon_01"]           = { display = "屠龙者的咆哮 (龙头)", priority = 1, color = {1, 0.85, 0.30} },
    ["Interface\\Icons\\Spell_Arcane_TeleportOrgrimmar"]    = { display = "酋长的祝福",     priority = 2, color = {0.90, 0.55, 0.30} },
    ["Interface\\Icons\\Ability_Creature_Poison_05"]        = { display = "赞达拉之魂",     priority = 2, color = {0.40, 0.90, 0.45} },
    ["Interface\\Icons\\Spell_Holy_MindSoothe"]             = { display = "歌唱花小夜曲",   priority = 3, color = {0.75, 0.55, 0.90} },
}

function AFK:ScanWorldBuffs()
    local found = {}
    local matched = {}

    if not self._buffTip then
        self._buffTip = getglobal("NanamiAFKBuffTip") or CreateFrame("GameTooltip", "NanamiAFKBuffTip", UIParent, "GameTooltipTemplate")
    end

    -- Build texture → remaining time mapping
    local texTime = {}
    if GetPlayerBuff then
        for slot = 0, 31 do
            local bi = GetPlayerBuff(slot, "HELPFUL")
            if bi and bi >= 0 then
                local tex = GetPlayerBuffTexture and GetPlayerBuffTexture(bi)
                local tl  = GetPlayerBuffTimeLeft and GetPlayerBuffTimeLeft(bi)
                if tex then texTime[tex] = tl or 0 end
            end
        end
    end

    -- Debug: print all buffs once per AFK session to help identify names
    local debugOnce = not self._wbDebugDone
    if debugOnce then self._wbDebugDone = true end

    for i = 1, 32 do
        local tex = UnitBuff("player", i)
        if not tex then break end

        self._buffTip:SetOwner(WorldFrame, "ANCHOR_NONE")
        self._buffTip:SetUnitBuff("player", i)
        local bName = NanamiAFKBuffTipTextLeft1 and NanamiAFKBuffTipTextLeft1:GetText()
        self._buffTip:Hide()

        if debugOnce and bName then
            local tl = texTime[tex] or 0
            if tl > 300 then
                DEFAULT_CHAT_FRAME:AddMessage("|cff88aacc[NanamiAFK Debug] buff: \"" .. bName .. "\" tex: " .. tex .. " time: " .. math.floor(tl) .. "s|r")
            end
        end

        local def = nil
        -- 1) Exact name match
        if bName then
            def = WORLD_BUFF_DEFS[bName]
        end
        -- 2) Partial name match (substring)
        if not def and bName then
            for pattern, d in pairs(WORLD_BUFF_DEFS) do
                if string.find(bName, pattern, 1, true) then
                    def = d
                    break
                end
            end
        end
        -- 3) Texture-based fallback
        if not def and tex then
            def = WORLD_BUFF_TEXTURES[tex]
        end

        if def and not matched[def.display] then
            matched[def.display] = true
            table.insert(found, {
                name     = def.display,
                timeLeft = texTime[tex] or 0,
                priority = def.priority,
                color    = def.color,
                texture  = tex,
            })
        end
    end

    table.sort(found, function(a, b) return a.priority < b.priority end)
    return found
end

function AFK:RefreshWorldBuffs()
    if not self.wbPanel then return end

    for _, row in ipairs(self._wbRows or {}) do
        if row.icon      then row.icon:Hide() end
        if row.nameFs    then row.nameFs:Hide() end
        if row.timeFs    then row.timeFs:Hide() end
        if row.highlight then row.highlight:Hide() end
    end
    self._wbRows = {}

    local buffs = self:ScanWorldBuffs()

    if table.getn(buffs) == 0 then
        self.wbPanel:Hide()
        if self._wbGlow then self._wbGlow:Hide() end
        return
    end

    local panel = self.wbPanel
    local yOff = -34
    local rowHeight = 28
    local xPad = 16

    for _, buff in ipairs(buffs) do
        local isPrimary   = (buff.priority == 1)
        local nameSize    = isPrimary and 13 or 11
        local timeSize    = isPrimary and 15 or 12

        local icon = panel:CreateFontString(nil, "OVERLAY")
        icon:SetFont(GetFont(), isPrimary and 14 or 11, GetOutline())
        icon:SetPoint("TOPLEFT", panel, "TOPLEFT", xPad, yOff)
        icon:SetTextColor(buff.color[1], buff.color[2], buff.color[3])
        icon:SetText(isPrimary and "★" or "◆")

        local nameFs = panel:CreateFontString(nil, "OVERLAY")
        nameFs:SetFont(GetFont(), nameSize, GetOutline())
        nameFs:SetPoint("TOPLEFT", panel, "TOPLEFT", xPad + 18, yOff)
        nameFs:SetJustifyH("LEFT")
        nameFs:SetTextColor(buff.color[1], buff.color[2], buff.color[3])
        nameFs:SetText(buff.name)

        local timeFs = panel:CreateFontString(nil, "OVERLAY")
        timeFs:SetFont(GetFont(), timeSize, GetOutline())
        timeFs:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -xPad, yOff)
        timeFs:SetJustifyH("RIGHT")
        self:_SetBuffTimeColor(timeFs, buff.timeLeft)

        local highlight = nil
        if isPrimary then
            highlight = panel:CreateTexture(nil, "ARTWORK")
            highlight:SetTexture("Interface\\Buttons\\WHITE8X8")
            highlight:SetHeight(rowHeight - 4)
            highlight:SetPoint("TOPLEFT",  panel, "TOPLEFT",  6, yOff + 3)
            highlight:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -6, yOff + 3)
            highlight:SetVertexColor(1, 0.82, 0.25, 0.06)
        end

        table.insert(self._wbRows, {
            icon = icon, nameFs = nameFs, timeFs = timeFs,
            highlight = highlight, buff = buff,
        })
        yOff = yOff - rowHeight
    end

    local totalH = math.abs(yOff) + 10
    if totalH < 60 then totalH = 60 end
    panel:SetHeight(totalH)
    panel:Show()

    if self._wbGlow then
        self._wbGlow:SetHeight(totalH + 8)
        self._wbGlow:Show()
    end
end

function AFK:_SetBuffTimeColor(fs, timeLeft)
    if timeLeft and timeLeft > 0 then
        fs:SetText(FormatDuration(timeLeft))
        if timeLeft < 600 then
            fs:SetTextColor(1, 0.35, 0.30)
        elseif timeLeft < 1800 then
            fs:SetTextColor(1, 0.72, 0.30)
        else
            fs:SetTextColor(0.92, 0.88, 0.72)
        end
    else
        fs:SetText("--:--")
        fs:SetTextColor(0.50, 0.46, 0.38)
    end
end

function AFK:UpdateWorldBuffTimers()
    if not self._wbRows or table.getn(self._wbRows) == 0 then return end

    local texTime = {}
    if GetPlayerBuff then
        for slot = 0, 31 do
            local bi = GetPlayerBuff(slot, "HELPFUL")
            if bi and bi >= 0 then
                local tex = GetPlayerBuffTexture and GetPlayerBuffTexture(bi)
                local tl  = GetPlayerBuffTimeLeft and GetPlayerBuffTimeLeft(bi)
                if tex then texTime[tex] = tl or 0 end
            end
        end
    end

    for _, row in ipairs(self._wbRows) do
        if row.buff and row.buff.texture then
            local tl = texTime[row.buff.texture]
            if tl and tl > 0 then
                self:_SetBuffTimeColor(row.timeFs, tl)
                if row.nameFs then row.nameFs:SetAlpha(1) end
                if row.icon   then row.icon:SetAlpha(1) end
            elseif tl == nil then
                row.timeFs:SetText("|cff665e50已消失|r")
                row.timeFs:SetTextColor(0.42, 0.38, 0.32)
                if row.nameFs then row.nameFs:SetAlpha(0.45) end
                if row.icon   then row.icon:SetAlpha(0.45) end
            else
                self:_SetBuffTimeColor(row.timeFs, 0)
            end
        end
    end
end

--------------------------------------------------------------------------------
-- OnUpdate (master tick)
--------------------------------------------------------------------------------

function AFK:OnUpdate(elapsed)
    if not elapsed or not self.frame then return end

    -- Fade logic
    if self.fadeDirection then
        local alpha = self.frame:GetAlpha()
        alpha = alpha + self.fadeDirection * FADE_SPEED * elapsed
        if self.fadeDirection > 0 and alpha >= 1 then
            alpha = 1
            self.fadeDirection = nil
        elseif self.fadeDirection < 0 and alpha <= 0 then
            alpha = 0
            self.fadeDirection = nil
            self.frame:Hide()
            self.frame:EnableKeyboard(false)
            self.isShowing = false
            self._exiting = false
            UIParent:Show()
            return
        end
        self.frame:SetAlpha(alpha)
    end

    if not self.isShowing then return end

    -- Dance initialization: apply SetSequence once after model is loaded
    if self._danceWait then
        self._danceWait = self._danceWait - elapsed
        if self._danceWait <= 0 then
            self._danceWait = nil
            if self.model and self.model.SetSequence then
                self.model:SetSequence(69)
            end
        end
    end

    -- Clock & timer
    self._clockElapsed = (self._clockElapsed or 0) + elapsed
    if self._clockElapsed >= CLOCK_UPDATE_INTERVAL then
        self._clockElapsed = 0

        -- Main clock: local time
        local localTime = date("%H:%M")
        self.clockText:SetText(localTime)

        -- Sub clock: server time
        local sHour, sMin = GetGameTime()
        self.serverClockText:SetText("服务器  " .. string.format("%02d:%02d", sHour, sMin))

        -- AFK duration
        if self.afkStartTime then
            local duration = GetTime() - self.afkStartTime
            self.timerText:SetText("已离开  " .. FormatDuration(duration))
        end

        -- World buff timers
        self:UpdateWorldBuffTimers()
    end

    -- Particle animation
    local now = GetTime()
    if not self.particles then return end
    local sw = self.frame:GetWidth()
    local sh = self.frame:GetHeight()
    if sw < 100 then sw = 1024 end
    if sh < 100 then sh = 768 end

    for _, p in ipairs(self.particles) do
        p._y = p._y + p._speed * elapsed
        p._x = p._x + p._drift * elapsed

        if p._y > sh + 20 then
            p._y = -20
            p._x = math.random() * sw
        end
        if p._x < -20 then p._x = sw + 10 end
        if p._x > sw + 20 then p._x = -10 end

        p:ClearAllPoints()
        p:SetPoint("CENTER", self.frame, "BOTTOMLEFT", p._x, p._y)

        local pulse = p._baseAlpha * (0.6 + 0.4 * math.sin(now * p._pulseSpeed + p._phase))
        p:SetAlpha(pulse)
    end

    -- Accent line gentle pulse
    if self.frame.accentLine then
        local glow = 0.7 + 0.3 * math.sin(now * 1.2)
        self.frame.accentLine:SetAlpha(glow * T.accentLine[4])
    end

    -- World buff panel glow pulse
    if self._wbGlow and self._wbGlow:IsShown() then
        self._wbPulse = (self._wbPulse or 0) + elapsed * 1.8
        local pa = 0.18 + 0.12 * math.sin(self._wbPulse)
        self._wbGlow:SetBackdropBorderColor(1, 0.82, 0.30, pa)
    end

    -- Hint text gentle pulse
    if self.hintText then
        local hAlpha = 0.4 + 0.25 * math.sin(now * 1.8)
        self.hintText:SetAlpha(hAlpha)
    end
end

--------------------------------------------------------------------------------
-- Show / Hide
--------------------------------------------------------------------------------

function AFK:Show()
    if self.isShowing then return end
    if self._exiting then return end

    -- Check config: is AFK screen enabled?
    if SFramesDB and SFramesDB.afkEnabled == false then return end

    -- Only trigger in resting areas unless config allows everywhere, or manual toggle
    if not self._manualTrigger then
        local allowOutside = SFramesDB and SFramesDB.afkOutsideRest == true
        if not allowOutside and not IsResting() then return end
    end

    if not self.frame then
        self:Build()
    end

    self.isShowing = true
    self._exiting = false
    self._manualTrigger = false
    self._wbDebugDone = false
    self.afkStartTime = GetTime()
    self._clockElapsed = 99

    self.model:SetUnit("player")
    self.model:SetCamera(1)
    self.model:SetPosition(0, 0, 0)
    self.model:SetFacing(0.4)

    -- WoW 1.12 AnimationData.dbc: 69 = EmoteDance; apply after model loads
    self._danceWait = 0.3

    -- Reset particles
    local sw = UIParent:GetWidth() or 1024
    local sh = UIParent:GetHeight() or 768
    for _, p in ipairs(self.particles) do
        p._x = math.random() * sw
        p._y = math.random() * sh
    end

    self:RefreshInfo()

    -- Hide the game UI (our frame is parented to WorldFrame, unaffected)
    UIParent:Hide()

    -- Fade in
    self.frame:SetAlpha(0)
    self.frame:EnableKeyboard(true)
    self.frame:Show()
    self.fadeDirection = 1
end

function AFK:Hide()
    if not self.isShowing then return end
    if self._exiting then return end
    self._exiting = true
    self.fadeDirection = -1
    self.frame:EnableKeyboard(false)
    self._danceWait = nil
end

function AFK:ForceHide()
    if self.frame then
        self.frame:SetAlpha(0)
        self.frame:Hide()
        self.frame:EnableKeyboard(false)
    end
    self.isShowing = false
    self.fadeDirection = nil
    self._exiting = false
    UIParent:Show()
end

function AFK:Cleanup()
    if self.model and self.model.ClearModel then
        pcall(function() self.model:ClearModel() end)
    end

    if self.frame then
        self.frame:SetScript("OnUpdate", nil)
        self.frame:SetScript("OnKeyDown", nil)
        self.frame:SetScript("OnMouseDown", nil)
        self.frame:EnableKeyboard(false)
        self.frame:EnableMouse(false)
        self.frame:Hide()
        self.frame:SetAlpha(0)
    end

    local watcher = getglobal("NanamiAFKWatcher")
    if watcher then
        watcher:SetScript("OnUpdate", nil)
        watcher:Hide()
    end

    if self._buffTip then
        self._buffTip:Hide()
    end

    self.isShowing = false
    self.fadeDirection = nil
    self._exiting = false
    self._danceWait = nil

    UIParent:Show()
end

function AFK:RequestExit()
    if self._exiting then return end
    self:Hide()
    self._lastActivity = GetTime()
    self._isAFK = false
end

function AFK:Toggle()
    if self.isShowing then
        self:RequestExit()
    else
        self._manualTrigger = true
        self:Show()
    end
end

--------------------------------------------------------------------------------
-- Event Handling
--------------------------------------------------------------------------------

function AFK:ResetIdleTimer()
    self._lastActivity = GetTime()
    if self.isShowing and not self._exiting then
        self:RequestExit()
    end
end

function AFK:OnSystemMessage(msg)
    if not msg then return end
    local isNowAFK = false
    if MARKED_AFK_MESSAGE then
        local pattern = string.gsub(MARKED_AFK_MESSAGE, "%%s", ".+")
        if string.find(msg, pattern) then isNowAFK = true end
    end
    if not isNowAFK and string.find(msg, "You are now AFK") then isNowAFK = true end
    if not isNowAFK and string.find(msg, "暂离") then isNowAFK = true end

    if isNowAFK then
        self._isAFK = true
    end

    -- Detect leaving AFK: reset flag
    local isNoLongerAFK = false
    if CLEARED_AFK_MESSAGE and string.find(msg, CLEARED_AFK_MESSAGE, 1, true) then
        isNoLongerAFK = true
    end
    if not isNoLongerAFK and string.find(msg, "no longer AFK") then isNoLongerAFK = true end
    if not isNoLongerAFK and string.find(msg, "取消") and string.find(msg, "暂离") then isNoLongerAFK = true end
    if isNoLongerAFK then
        self._isAFK = false
    end
end

function AFK:OnTimePlayed(totalTime, levelTime)
    if not self._waitingPlaytime then return end
    self._waitingPlaytime = false

    if not self.playtimeText then return end
    if not totalTime or totalTime == 0 then
        self.playtimeText:SetText("-")
        return
    end

    local days = math.floor(totalTime / 86400)
    local hours = math.floor((totalTime - days * 86400) / 3600)
    local mins = math.floor((totalTime - days * 86400 - hours * 3600) / 60)

    local text = ""
    if days > 0 then
        text = days .. "天 " .. hours .. "小时"
    elseif hours > 0 then
        text = hours .. "小时 " .. mins .. "分钟"
    else
        text = mins .. "分钟"
    end
    self.playtimeText:SetText(text)
    self.playtimeText:SetTextColor(T.valueColor[1], T.valueColor[2], T.valueColor[3])
end

--------------------------------------------------------------------------------
-- Initialize
--------------------------------------------------------------------------------

function AFK:Initialize()
    UIParent:Show()
    self:Cleanup()

    self:Build()
    self._isAFK = false
    self._lastActivity = GetTime()

    local function MarkActive()
        AFK._lastActivity = GetTime()
    end

    if not self._hooked then
        self._hooked = true

        local origUseAction = UseAction
        UseAction = function(a1, a2, a3)
            MarkActive()
            return origUseAction(a1, a2, a3)
        end

        if CastSpellByName then
            local origCast = CastSpellByName
            CastSpellByName = function(a1, a2)
                MarkActive()
                return origCast(a1, a2)
            end
        end

        if JumpOrAscendStart then
            local origJump = JumpOrAscendStart
            JumpOrAscendStart = function()
                MarkActive()
                return origJump()
            end
        end
    end

    local activityEvents = {
        "PLAYER_STARTED_MOVING", "PLAYER_STOPPED_MOVING",
        "SPELLCAST_START", "SPELLCAST_STOP",
        "PLAYER_REGEN_DISABLED", "PLAYER_TARGET_CHANGED",
        "LOOT_OPENED", "MERCHANT_SHOW", "BANKFRAME_OPENED",
        "MAIL_SHOW", "QUEST_DETAIL", "GOSSIP_SHOW",
        "TRADE_SHOW", "AUCTION_HOUSE_SHOW",
    }
    for _, ev in ipairs(activityEvents) do
        SFrames:RegisterEvent(ev, function() AFK:ResetIdleTimer() end)
    end

    local watcher = getglobal("NanamiAFKWatcher") or CreateFrame("Frame", "NanamiAFKWatcher", UIParent)
    watcher._checkTimer = 0
    watcher._lastCursorX = 0
    watcher._lastCursorY = 0
    watcher:Show()
    watcher:SetScript("OnUpdate", function()
        this._checkTimer = (this._checkTimer or 0) + arg1
        if this._checkTimer < 1 then return end
        this._checkTimer = 0

        local cx, cy = GetCursorPosition()
        if cx ~= this._lastCursorX or cy ~= this._lastCursorY then
            this._lastCursorX = cx
            this._lastCursorY = cy
            AFK._lastActivity = GetTime()
            if AFK.isShowing and not AFK._exiting then
                AFK:RequestExit()
            end
            return
        end

        if AFK.isShowing or AFK._exiting then return end
        if SFramesDB and SFramesDB.afkEnabled == false then return end

        local delay = (SFramesDB and SFramesDB.afkDelay) or 5
        if delay <= 0 then delay = 0.01 end

        local idle = GetTime() - (AFK._lastActivity or GetTime())
        if idle >= delay * 60 then
            local allowOutside = SFramesDB and SFramesDB.afkOutsideRest == true
            if allowOutside or IsResting() then
                AFK:Show()
            end
        end
    end)

    SFrames:RegisterEvent("CHAT_MSG_SYSTEM", function()
        AFK:OnSystemMessage(arg1)
    end)

    SFrames:RegisterEvent("TIME_PLAYED_MSG", function()
        AFK:OnTimePlayed(arg1, arg2)
    end)

    SFrames:RegisterEvent("ZONE_CHANGED_NEW_AREA", function()
        if AFK.isShowing and AFK.zoneText then
            local zone = GetZoneText() or ""
            local subZone = GetSubZoneText() or ""
            if subZone ~= "" and subZone ~= zone then
                AFK.zoneText:SetText(zone .. " - " .. subZone)
            else
                AFK.zoneText:SetText(zone)
            end
        end
    end)

    SFrames:RegisterEvent("ZONE_CHANGED", function()
        if AFK.isShowing and AFK.zoneText then
            local zone = GetZoneText() or ""
            local subZone = GetSubZoneText() or ""
            if subZone ~= "" and subZone ~= zone then
                AFK.zoneText:SetText(zone .. " - " .. subZone)
            else
                AFK.zoneText:SetText(zone)
            end
        end
    end)

    SFrames:RegisterEvent("PLAYER_LEAVING_WORLD", function()
        AFK:Cleanup()
    end)
end
