SFrames.FloatingTooltip = {}

--------------------------------------------------------------------------------
-- Class colors for tooltip unit-name coloring
--------------------------------------------------------------------------------
local TT_CLASS_COLORS = {
    ["WARRIOR"]  = { 0.78, 0.61, 0.43 },
    ["MAGE"]     = { 0.41, 0.80, 0.94 },
    ["ROGUE"]    = { 1.00, 0.96, 0.41 },
    ["DRUID"]    = { 1.00, 0.49, 0.04 },
    ["HUNTER"]   = { 0.67, 0.83, 0.45 },
    ["SHAMAN"]   = { 0.14, 0.35, 1.00 },
    ["PRIEST"]   = { 1.00, 1.00, 1.00 },
    ["WARLOCK"]  = { 0.58, 0.51, 0.79 },
    ["PALADIN"]  = { 0.96, 0.55, 0.73 },
}

local TT_CLASS_REVERSE = {}
local ttClassReverseDone = false

local function TT_BuildClassReverse()
    if ttClassReverseDone then return end
    ttClassReverseDone = true
    if LOCALIZED_CLASS_NAMES_MALE then
        for en, loc in pairs(LOCALIZED_CLASS_NAMES_MALE) do
            TT_CLASS_REVERSE[loc] = en
        end
    end
    if LOCALIZED_CLASS_NAMES_FEMALE then
        for en, loc in pairs(LOCALIZED_CLASS_NAMES_FEMALE) do
            TT_CLASS_REVERSE[loc] = en
        end
    end
    local zh = {
        ["战士"]="WARRIOR", ["法师"]="MAGE", ["盗贼"]="ROGUE",
        ["德鲁伊"]="DRUID", ["猎人"]="HUNTER", ["萨满祭司"]="SHAMAN",
        ["牧师"]="PRIEST", ["术士"]="WARLOCK", ["圣骑士"]="PALADIN",
    }
    for loc, en in pairs(zh) do
        if not TT_CLASS_REVERSE[loc] then TT_CLASS_REVERSE[loc] = en end
    end
    for en, _ in pairs(TT_CLASS_COLORS) do
        TT_CLASS_REVERSE[en] = en
        TT_CLASS_REVERSE[string.lower(en)] = en
    end
end

local function TT_GetClassToken(unit)
    if not UnitExists(unit) then return nil end
    local className, classEN = UnitClass(unit)
    if classEN and classEN ~= "" then return string.upper(classEN) end
    if className then
        TT_BuildClassReverse()
        return TT_CLASS_REVERSE[className]
    end
    return nil
end

local function TT_GetClassColor(classToken)
    if not classToken then return nil, nil, nil end
    if CUSTOM_CLASS_COLORS and CUSTOM_CLASS_COLORS[classToken] then
        local c = CUSTOM_CLASS_COLORS[classToken]
        return c.r, c.g, c.b
    end
    if RAID_CLASS_COLORS and RAID_CLASS_COLORS[classToken] then
        local c = RAID_CLASS_COLORS[classToken]
        return c.r, c.g, c.b
    end
    local c = TT_CLASS_COLORS[classToken]
    if c then return c[1], c[2], c[3] end
    return nil, nil, nil
end

local function TT_ClassHex(classToken)
    local r, g, b = TT_GetClassColor(classToken)
    if not r then return "|cffffffff" end
    return string.format("|cff%02x%02x%02x", r * 255, g * 255, b * 255)
end

--------------------------------------------------------------------------------
-- Backdrop helper (applied once, not every frame)
--------------------------------------------------------------------------------
local TT_BACKDROP = {
    bgFile = "Interface\\Buttons\\WHITE8X8",
    insets = { left = 0, right = 0, top = 0, bottom = 0 },
}

local function TT_ApplyBackdrop(frame)
    frame:SetBackdrop(TT_BACKDROP)
    local _A = SFrames.ActiveTheme
    if _A and _A.panelBg then
        frame:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], 0.95)
    else
        frame:SetBackdropColor(0.08, 0.08, 0.08, 0.95)
    end
end

--------------------------------------------------------------------------------
-- Level difficulty color (fallback if GetDifficultyColor is unavailable)
--------------------------------------------------------------------------------
local function TT_DifficultyColor(unitLevel)
    local playerLevel = UnitLevel("player") or 1
    if unitLevel < 0 then return 1, 0, 0 end
    local diff = unitLevel - playerLevel
    if diff >= 5 then
        return 1, 0.1, 0.1
    elseif diff >= 3 then
        return 1, 0.5, 0.25
    elseif diff >= -2 then
        return 1, 1, 0
    elseif diff >= -8 then
        return 0.25, 0.75, 0.25
    else
        return 0.5, 0.5, 0.5
    end
end

--------------------------------------------------------------------------------
-- Initialize
--------------------------------------------------------------------------------
function SFrames.FloatingTooltip:Initialize()
    TT_ApplyBackdrop(GameTooltip)

    --------------------------------------------------------------------------
    -- Standalone backdrop frame: a SIBLING of GameTooltip (parented to
    -- UIParent, not GameTooltip) at the same TOOLTIP strata but one frame-
    -- level below.  This guarantees it renders directly behind the tooltip
    -- text and is completely immune to C++ SetBackdrop resets.
    --------------------------------------------------------------------------
    if not GameTooltip._nanamiBG then
        GameTooltip._nanamiBG = true

        local bgFrame = CreateFrame("Frame", "NanamiTooltipBG", UIParent)
        bgFrame:SetFrameStrata("TOOLTIP")
        bgFrame:SetFrameLevel(math.max(1, GameTooltip:GetFrameLevel()) - 1)
        bgFrame:Hide()
        GameTooltip._nanamiBGFrame = bgFrame

        local bg = bgFrame:CreateTexture(nil, "ARTWORK")
        bg:SetTexture("Interface\\Buttons\\WHITE8X8")
        local _Abg = SFrames.ActiveTheme
        if _Abg and _Abg.panelBg then
            bg:SetVertexColor(_Abg.panelBg[1], _Abg.panelBg[2], _Abg.panelBg[3], 0.95)
        else
            bg:SetVertexColor(0.08, 0.08, 0.08, 0.95)
        end
        bg:SetAllPoints(bgFrame)
        GameTooltip._nanamiBGTex = bg

        bgFrame:SetScript("OnUpdate", function()
            if not GameTooltip:IsVisible() then
                this:Hide()
            end
        end)
    end

    --------------------------------------------------------------------------
    -- Health bar: position, backdrop, texture, health text
    --------------------------------------------------------------------------
    local barFont = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"

    GameTooltipStatusBar:SetHeight(10)
    GameTooltipStatusBar:ClearAllPoints()
    GameTooltipStatusBar:SetPoint("BOTTOMLEFT", GameTooltip, "TOPLEFT", 4, 2)
    GameTooltipStatusBar:SetPoint("BOTTOMRIGHT", GameTooltip, "TOPRIGHT", -4, 2)
    GameTooltipStatusBar:SetStatusBarTexture(SFrames:GetTexture())

    GameTooltipStatusBar.bg = GameTooltipStatusBar.bg or GameTooltipStatusBar:CreateTexture(nil, "BACKGROUND")
    GameTooltipStatusBar.bg:SetTexture("Interface\\TARGETINGFRAME\\UI-StatusBar")
    GameTooltipStatusBar.bg:SetVertexColor(.1, .1, 0, .8)
    GameTooltipStatusBar.bg:SetAllPoints(true)

    if not GameTooltipStatusBar.backdrop then
        local bd = CreateFrame("Frame", "SFramesTooltipStatusBarBD", GameTooltipStatusBar)
        bd:SetPoint("TOPLEFT", GameTooltipStatusBar, "TOPLEFT", -3, 3)
        bd:SetPoint("BOTTOMRIGHT", GameTooltipStatusBar, "BOTTOMRIGHT", 3, -3)
        bd:SetBackdrop({
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            tile = true, tileSize = 8, edgeSize = 12,
            insets = { left = 3, right = 3, top = 3, bottom = 3 },
        })
        bd:SetBackdropBorderColor(.8, .8, .8, 1)
        GameTooltipStatusBar.backdrop = bd
    end

    if not GameTooltipStatusBar.healthText then
        local ht = GameTooltipStatusBar.backdrop:CreateFontString(nil, "DIALOG", "GameFontWhite")
        ht:SetFont(barFont, 12, "OUTLINE")
        ht:SetPoint("TOP", 0, 4)
        ht:SetNonSpaceWrap(false)
        GameTooltipStatusBar.healthText = ht
    end

    if not GameTooltipStatusBar._origSetColor then
        GameTooltipStatusBar._origSetColor = GameTooltipStatusBar.SetStatusBarColor
        GameTooltipStatusBar.SetStatusBarColor = function() return end
    end

    --------------------------------------------------------------------------
    -- Flag: true when tooltip was positioned via GameTooltip_SetDefaultAnchor
    -- (world mouseover units). False for bag/bank/inventory item tooltips
    -- that have their own anchoring — those should NOT be cursor-followed.
    --------------------------------------------------------------------------
    local ttUsesDefaultAnchor = false

    --------------------------------------------------------------------------
    -- Track mouseover name/level for health estimation
    --------------------------------------------------------------------------
    local ttMouseName, ttMouseLevel
    local ttHadUnit = false

    local barEvents = CreateFrame("Frame", nil, GameTooltipStatusBar)
    barEvents:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
    barEvents:SetScript("OnEvent", function()
        ttMouseName = UnitName("mouseover")
        ttMouseLevel = UnitLevel("mouseover")
        if UnitExists("mouseover") then
            ttHadUnit = true
        end
    end)

    local function TT_Abbreviate(val)
        if val >= 1000000 then
            return string.format("%.1fM", val / 1000000)
        elseif val >= 10000 then
            return string.format("%.1fK", val / 1000)
        end
        return tostring(math.floor(val))
    end

    local barThrottle = 0
    barEvents:SetScript("OnUpdate", function()
        barThrottle = barThrottle + arg1
        if barThrottle < 0.1 then return end
        barThrottle = 0

        local hp = GameTooltipStatusBar:GetValue()
        local _, hpmax = GameTooltipStatusBar:GetMinMaxValues()
        if hpmax and hpmax > 0 then
            if hpmax > 100 then
                GameTooltipStatusBar.healthText:SetText(
                    TT_Abbreviate(hp) .. " / " .. TT_Abbreviate(hpmax))
            else
                GameTooltipStatusBar.healthText:SetText(
                    string.format("%d%%", math.ceil(hp / hpmax * 100)))
            end
        else
            GameTooltipStatusBar.healthText:SetText("")
        end
    end)

    local linesFormatted = false

    --------------------------------------------------------------------------
    -- Track tooltip owner to skip styling for world-map / pfQuest markers
    --------------------------------------------------------------------------
    local ttOwner = nil
    local orig_SetOwner = GameTooltip.SetOwner
    GameTooltip.SetOwner = function(self, owner, anchor, xOff, yOff)
        ttOwner = owner
        ttUsesDefaultAnchor = false
        return orig_SetOwner(self, owner, anchor, xOff, yOff)
    end

    local function TT_IsMapMarkerTooltip()
        if not ttOwner then return false end
        if WorldMapFrame and WorldMapFrame:IsShown() then
            local name = ttOwner.GetName and ttOwner:GetName()
            if name and (string.find(name, "^pf") or string.find(name, "^WorldMap")) then
                return true
            end
            local parent = ttOwner.GetParent and ttOwner:GetParent()
            while parent do
                if parent == WorldMapFrame or parent == WorldMapButton then
                    return true
                end
                parent = parent.GetParent and parent:GetParent()
            end
        end
        return false
    end

    local function TT_ShowBar(show)
        if not GameTooltipStatusBar then return end
        GameTooltipStatusBar:SetStatusBarTexture(SFrames:GetTexture())
        if show then
            GameTooltipStatusBar:SetAlpha(1)
            GameTooltipStatusBar:Show()
            if GameTooltipStatusBar.backdrop then
                GameTooltipStatusBar.backdrop:SetAlpha(1)
                GameTooltipStatusBar.backdrop:Show()
            end
        else
            GameTooltipStatusBar:SetAlpha(0)
            if GameTooltipStatusBar.backdrop then
                GameTooltipStatusBar.backdrop:SetAlpha(0)
            end
            if GameTooltipStatusBar.healthText then GameTooltipStatusBar.healthText:SetText("") end
        end
    end

    -- Sync the standalone bg frame to match GameTooltip's position/size
    local function TT_SyncBGFrame()
        local bf = GameTooltip._nanamiBGFrame
        if not bf then return end
        local w = GameTooltip:GetWidth()
        local h = GameTooltip:GetHeight()
        if w and h and w > 0 and h > 0 then
            bf:SetWidth(w)
            bf:SetHeight(h)
            bf:ClearAllPoints()
            bf:SetPoint("TOPLEFT", GameTooltip, "TOPLEFT", 0, 0)
            bf:SetFrameLevel(math.max(1, GameTooltip:GetFrameLevel()) - 1)
            bf:Show()
        end
    end

    -- OnShow: apply backdrop, sync bg frame, reset formatting flag
    local orig_OnShow = GameTooltip:GetScript("OnShow")
    local ttIsMapMarker = false
    GameTooltip:SetScript("OnShow", function()
        if orig_OnShow then orig_OnShow() end
        linesFormatted = false
        ttIsMapMarker = TT_IsMapMarkerTooltip()
        TT_ApplyBackdrop(this)
        TT_SyncBGFrame()
        if not ttIsMapMarker then
            TT_ShowBar(UnitExists("mouseover"))
        end
    end)

    -- OnUpdate: line formatting (once) + cursor tracking (every frame)
    local orig_OnUpdate = GameTooltip:GetScript("OnUpdate")
    local ttFormatThrottle = 0
    GameTooltip:SetScript("OnUpdate", function()
        if orig_OnUpdate then orig_OnUpdate() end

        local isCursorMode = ttUsesDefaultAnchor and SFramesDB
            and SFramesDB.tooltipMode == "CURSOR" and not ttIsMapMarker

        if isCursorMode then
            local hasUnit = UnitExists("mouseover")

            if ttHadUnit and not hasUnit then
                TT_ShowBar(false)
                if GameTooltip._nanamiBGFrame then
                    GameTooltip._nanamiBGFrame:Hide()
                end
                this:Hide()
                return
            end

            if not hasUnit then
                TT_ShowBar(false)
            end

            local x, y = GetCursorPosition()
            local uiScale = UIParent:GetEffectiveScale()
            local ttScale = this:GetScale() or 1
            if uiScale and uiScale > 0 and ttScale > 0 then
                local effScale = uiScale * ttScale
                local tx = (x / effScale) + 16
                local ty = (y / effScale) - 16
                this:ClearAllPoints()
                this:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", tx, ty)
                TT_SyncBGFrame()
            end
        end

        ttFormatThrottle = ttFormatThrottle + arg1
        if ttFormatThrottle < 0.05 then return end
        ttFormatThrottle = 0

        if not linesFormatted then
            linesFormatted = true
            if not ttIsMapMarker and UnitExists("mouseover") then
                SFrames.FloatingTooltip:FormatLines(this)
            end
            TT_SyncBGFrame()
        end
    end)

    -- OnHide: hide bg frame, health bar, reset flags and owner tracking
    local orig_OnHide = GameTooltip:GetScript("OnHide")
    GameTooltip:SetScript("OnHide", function()
        linesFormatted = false
        ttOwner = nil
        ttHadUnit = false
        TT_ShowBar(false)
        if GameTooltip._nanamiBGFrame then
            GameTooltip._nanamiBGFrame:Hide()
        end
        if orig_OnHide then orig_OnHide() end
    end)

    -- Tooltip Positioning logic
    if not SFrames.FloatingTooltip.anchor then
        local anchor = CreateFrame("Button", "SFramesTooltipAnchor", UIParent)
        anchor:SetWidth(180)
        anchor:SetHeight(60)
        anchor:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", -20, 100)
        anchor:SetFrameStrata("HIGH")
        anchor:EnableMouse(true)
        anchor:SetMovable(true)
        anchor:RegisterForDrag("LeftButton")
        anchor:SetScript("OnDragStart", function() this:StartMoving() end)
        anchor:SetScript("OnDragStop", function()
            this:StopMovingOrSizing()
            if SFramesDB then
                local _, _, _, x, y = this:GetPoint()
                SFramesDB.tooltipX = x
                SFramesDB.tooltipY = y
            end
        end)
        
        anchor:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8x8",
            edgeFile = "Interface\\Buttons\\WHITE8x8",
            edgeSize = 1,
            insets = {left=1, right=1, top=1, bottom=1}
        })
        anchor:SetBackdropColor(0.08, 0.08, 0.08, 0.95)
        anchor:SetBackdropBorderColor(0.4, 0.8, 0.4, 1)

        local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"

        anchor.title = anchor:CreateFontString(nil, "OVERLAY")
        anchor.title:SetFont(font, 13, "OUTLINE")
        anchor.title:SetPoint("TOPLEFT", anchor, "TOPLEFT", 8, -8)
        anchor.title:SetText("Tooltip 模拟位置")
        anchor.title:SetTextColor(1, 1, 1)

        anchor.desc = anchor:CreateFontString(nil, "OVERLAY")
        anchor.desc:SetFont(font, 11, "OUTLINE")
        anchor.desc:SetPoint("TOPLEFT", anchor.title, "BOTTOMLEFT", 0, -4)
        anchor.desc:SetText("拖拽我以调整[自定义]锚点。")
        anchor.desc:SetTextColor(1, 0.82, 0)
        anchor.desc:SetJustifyH("LEFT")
        
        anchor:Hide()
        SFrames.FloatingTooltip.anchor = anchor
    end

    if not SFrames.FloatingTooltip.hookedAnchor then
        SFrames.FloatingTooltip.hookedAnchor = true
        local orig_GameTooltip_SetDefaultAnchor = GameTooltip_SetDefaultAnchor
        function GameTooltip_SetDefaultAnchor(tooltip, parent)
            if SFramesDB and SFramesDB.tooltipMode == "CURSOR" then
                tooltip:SetOwner(parent, "ANCHOR_NONE")
            elseif SFramesDB and SFramesDB.tooltipMode == "CUSTOM" then
                tooltip:SetOwner(parent, "ANCHOR_NONE")
                tooltip:ClearAllPoints()
                tooltip:SetPoint("BOTTOMRIGHT", "SFramesTooltipAnchor", "BOTTOMRIGHT", 0, 0)
            else
                orig_GameTooltip_SetDefaultAnchor(tooltip, parent)
            end
            ttUsesDefaultAnchor = true
        end
    end
    SFrames.FloatingTooltip:ApplyConfig()
    SFrames.FloatingTooltip:ApplyScale()

    -- WorldMapTooltip: raw textures on a child frame (SetBackdrop is unreliable)
    if WorldMapTooltip and not WorldMapTooltip._nanamiBG then
        WorldMapTooltip._nanamiBG = true

        local wmtBgFrame = CreateFrame("Frame", nil, WorldMapTooltip)
        wmtBgFrame:SetAllPoints(WorldMapTooltip)
        wmtBgFrame:SetFrameLevel(math.max(0, WorldMapTooltip:GetFrameLevel()))

        local bg = wmtBgFrame:CreateTexture(nil, "BACKGROUND")
        bg:SetTexture("Interface\\Buttons\\WHITE8X8")
        bg:SetVertexColor(0.05, 0.05, 0.05, 1)
        bg:SetAllPoints(wmtBgFrame)

        local function WMTEdge(p1, r1, p2, r2, w, h)
            local t = wmtBgFrame:CreateTexture(nil, "BORDER")
            t:SetTexture("Interface\\Buttons\\WHITE8X8")
            t:SetVertexColor(0.20, 0.20, 0.20, 1)
            t:SetPoint(p1, WorldMapTooltip, r1)
            t:SetPoint(p2, WorldMapTooltip, r2)
            if w then t:SetWidth(w) end
            if h then t:SetHeight(h) end
        end
        WMTEdge("TOPLEFT","TOPLEFT","TOPRIGHT","TOPRIGHT", nil, 1)
        WMTEdge("BOTTOMLEFT","BOTTOMLEFT","BOTTOMRIGHT","BOTTOMRIGHT", nil, 1)
        WMTEdge("TOPLEFT","TOPLEFT","BOTTOMLEFT","BOTTOMLEFT", 1, nil)
        WMTEdge("TOPRIGHT","TOPRIGHT","BOTTOMRIGHT","BOTTOMRIGHT", 1, nil)
    end

    if SFrames.ItemCompare and SFrames.ItemCompare.HookTooltips then
        SFrames.ItemCompare:HookTooltips()
    end

    if SFrames.Movers and SFrames.Movers.RegisterMover and self.anchor then
        SFrames.Movers:RegisterMover("Tooltip", self.anchor, "提示框",
            "BOTTOMRIGHT", "UIParent", "BOTTOMRIGHT", -20, 100)
    end
end

function SFrames.FloatingTooltip:ApplyConfig()
    if not self.anchor then return end
    local pos = SFramesDB and SFramesDB.Positions and SFramesDB.Positions["Tooltip"]
    if pos and pos.point and pos.relativePoint then
        self.anchor:ClearAllPoints()
        self.anchor:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs or 0, pos.yOfs or 0)
    elseif SFramesDB and SFramesDB.tooltipX and SFramesDB.tooltipY then
        self.anchor:ClearAllPoints()
        self.anchor:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", SFramesDB.tooltipX, SFramesDB.tooltipY)
    end
end

function SFrames.FloatingTooltip:ApplyScale()
    local scale = SFramesDB and SFramesDB.tooltipScale or 1.0
    if scale < 0.5 then scale = 0.5 end
    if scale > 2.0 then scale = 2.0 end
    GameTooltip:SetScale(scale)
    if GameTooltip._nanamiBGFrame then
        GameTooltip._nanamiBGFrame:SetScale(scale)
    end
end

function SFrames.FloatingTooltip:ToggleAnchor(show)
    if not self.anchor then return end
    if show then
        self.anchor:Show()
    else
        self.anchor:Hide()
    end
end

function SFrames.FloatingTooltip:FormatLines(tooltip)
    local unit = "mouseover"
    if not UnitExists(unit) then return end

    local nameLine = GameTooltipTextLeft1
    if not nameLine then return end

    local nameText = nameLine:GetText()
    if nameText and string.find(nameText, "\n") then
        nameText = string.gsub(nameText, "\n", " ")
        nameLine:SetText(nameText)
    end

    --------------------------------------------------------------------------
    -- Player units
    --------------------------------------------------------------------------
    if UnitIsPlayer(unit) then
        local classToken = TT_GetClassToken(unit)

        -- Class-color the name
        if classToken then
            local r, g, b = TT_GetClassColor(classToken)
            if r then
                nameLine:SetTextColor(r, g, b)
                if GameTooltipStatusBar and GameTooltipStatusBar._origSetColor then
                    GameTooltipStatusBar._origSetColor(GameTooltipStatusBar, r, g, b)
                end
            end
        end

        -- Fetch guild info for this player
        local ttGuild, ttRankStr, ttRankIdx
        if GetGuildInfo then
            ttGuild, ttRankStr, ttRankIdx = GetGuildInfo(unit)
        end

        -- Iterate existing lines: enhance guild line + color level
        local numLines = tooltip:NumLines()
        for i = 2, numLines do
            local left = getglobal("GameTooltipTextLeft" .. i)
            if left then
                local txt = left:GetText()
                if txt then
                    -- Guild line <GuildName> — append rank
                    if string.find(txt, "^<.*>$") then
                        if ttRankStr and ttRankStr ~= "" then
                            left:SetText(txt .. " - " .. ttRankStr)
                        end
                        left:SetTextColor(0.30, 0.90, 0.30)
                    end

                    -- Level line
                    local _, _, lvlStr = string.find(txt, "^Level (%d+)")
                    if not lvlStr then
                        _, _, lvlStr = string.find(txt, "^等级 (%d+)")
                    end
                    local lvlNum = tonumber(lvlStr)
                    if lvlNum then
                        local lr, lg, lb = TT_DifficultyColor(lvlNum)
                        left:SetTextColor(lr, lg, lb)
                    end
                end
            end
        end

        -----------------------------------------------------------------------
        -- Extra info lines (appended below existing content)
        -----------------------------------------------------------------------

        -- Guild info (if player is in a guild but Blizzard didn't show it)
        if ttGuild and ttRankStr then
            local foundGuild = false
            for i = 2, numLines do
                local left = getglobal("GameTooltipTextLeft" .. i)
                if left then
                    local txt = left:GetText() or ""
                    if string.find(txt, "^<.*>") then
                        foundGuild = true
                        break
                    end
                end
            end
            if not foundGuild then
                tooltip:AddLine("<" .. ttGuild .. "> - " .. ttRankStr, 0.30, 0.90, 0.30)
            end
        end

        -- PvP Rank (text only; |T...|t inline textures not supported in 1.12)
        if UnitPVPRank and GetPVPRankInfo then
            local rank = UnitPVPRank(unit)
            if rank and rank > 0 then
                local rankName = GetPVPRankInfo(rank)
                if rankName and rankName ~= "" then
                    tooltip:AddLine("军衔: " .. rankName, 1, 0.85, 0.35)
                end
            end
        end

        -- PvP flag + faction
        if UnitIsPVP and UnitIsPVP(unit) then
            local fTag = UnitFactionGroup and UnitFactionGroup(unit)
            if fTag then
                local pvpHex = (fTag == "Horde") and "|cffff4444" or "|cff44bbff"
                tooltip:AddLine(pvpHex .. "PvP " .. fTag .. "|r")
            end
        end

        -- Turtle WoW challenge modes (defensive checks)
        if IsHardcore and IsHardcore(unit) then
            tooltip:AddLine("|cffff3333[Hardcore]|r")
        end
        if C_TurtleWoW then
            if C_TurtleWoW.IsHardcore and C_TurtleWoW.IsHardcore(unit) then
                tooltip:AddLine("|cffff3333[Hardcore]|r")
            end
            if C_TurtleWoW.IsSurvivalist and C_TurtleWoW.IsSurvivalist(unit) then
                tooltip:AddLine("|cff33ff33[Survivalist]|r")
            end
            if C_TurtleWoW.IsIronMan and C_TurtleWoW.IsIronMan(unit) then
                tooltip:AddLine("|cffaaaaaa[Iron Man]|r")
            end
        end
        if UnitIsTrivial and UnitIsTrivial(unit) then
            tooltip:AddLine("|cff888888[休闲模式]|r")
        end

    --------------------------------------------------------------------------
    -- NPC / Creature units: color health bar by reaction
    --------------------------------------------------------------------------
    else
        local reaction = UnitReaction(unit, "player")
        if reaction then
            local color = UnitReactionColor and UnitReactionColor[reaction]
            if color and GameTooltipStatusBar and GameTooltipStatusBar._origSetColor then
                GameTooltipStatusBar._origSetColor(GameTooltipStatusBar, color.r, color.g, color.b)
            end
        end
    end

    --------------------------------------------------------------------------
    -- Target of mouseover (all units)
    --------------------------------------------------------------------------
    local tgtUnit = unit .. "target"
    if UnitExists(tgtUnit) then
        local tgtName = UnitName(tgtUnit)
        if tgtName then
            local line
            if UnitIsUnit(tgtUnit, "player") then
                local _At = SFrames.ActiveTheme
                local _accentHex = "ff6699"
                if _At and _At.accentHex then _accentHex = _At.accentHex end
                line = "|cffffd700目标: |r|cff" .. _accentHex .. ">> 你 <<|r"
            elseif UnitIsPlayer(tgtUnit) then
                local hex = TT_ClassHex(TT_GetClassToken(tgtUnit))
                line = "|cffffd700目标: |r" .. hex .. tgtName .. "|r"
            else
                line = "|cffffd700目标: |r|cffffffff" .. tgtName .. "|r"
            end
            tooltip:AddLine(line)
        end
    end

    tooltip:Show()
end

--------------------------------------------------------------------------------
-- Item Compare: show stat differences vs currently equipped item
--------------------------------------------------------------------------------
SFrames.ItemCompare = {}

local IC = SFrames.ItemCompare
local IC_STAT_ORDER = {
    "STR","AGI","STA","INT","SPI",
    "CRIT","TOHIT","RANGEDCRIT",
    "SPELLCRIT","SPELLTOHIT",
    "ATTACKPOWER","RANGEDATTACKPOWER",
    "DMG","HEAL",
    "DEFENSE","DODGE","PARRY","BLOCK","BLOCKVALUE",
    "ARMOR",
    "HEALTHREG","MANAREG",
    "HEALTH","MANA",
}
local IC_STAT_NAMES = {
    STR="力量", AGI="敏捷", STA="耐力", INT="智力", SPI="精神",
    CRIT="暴击%", TOHIT="命中%", RANGEDCRIT="远程暴击%",
    SPELLCRIT="法术暴击%", SPELLTOHIT="法术命中%",
    ATTACKPOWER="攻强", RANGEDATTACKPOWER="远程攻强",
    DMG="法伤", HEAL="治疗",
    DEFENSE="防御", DODGE="闪避%", PARRY="招架%", BLOCK="格挡%", BLOCKVALUE="格挡值",
    ARMOR="护甲",
    HEALTHREG="生命/5秒", MANAREG="法力/5秒",
    HEALTH="生命值", MANA="法力值",
}

local IC_EQUIP_LOC_TO_SLOT = {
    INVTYPE_HEAD = 1, INVTYPE_NECK = 2, INVTYPE_SHOULDER = 3,
    INVTYPE_BODY = 4, INVTYPE_CHEST = 5, INVTYPE_ROBE = 5,
    INVTYPE_WAIST = 6, INVTYPE_LEGS = 7, INVTYPE_FEET = 8,
    INVTYPE_WRIST = 9, INVTYPE_HAND = 10,
    INVTYPE_FINGER = 11, INVTYPE_TRINKET = 13,
    INVTYPE_CLOAK = 15,
    INVTYPE_WEAPON = 16, INVTYPE_2HWEAPON = 16, INVTYPE_WEAPONMAINHAND = 16,
    INVTYPE_SHIELD = 17, INVTYPE_WEAPONOFFHAND = 17, INVTYPE_HOLDABLE = 17,
    INVTYPE_RANGED = 18, INVTYPE_RANGEDRIGHT = 18, INVTYPE_THROWN = 18,
    INVTYPE_RELIC = 18, INVTYPE_TABARD = 19,
}

local function IC_GetLib()
    if AceLibrary and AceLibrary.HasInstance and AceLibrary:HasInstance("ItemBonusLib-1.0") then
        return AceLibrary("ItemBonusLib-1.0")
    end
    return nil
end

local function IC_GetEquipSlot(link)
    if not link then return nil end
    local _, _, _, _, _, _, _, equipLoc = GetItemInfo(link)
    if not equipLoc or equipLoc == "" then return nil end
    return IC_EQUIP_LOC_TO_SLOT[equipLoc]
end

local function IC_ScanBonuses(lib, link)
    if not lib or not link then return nil end
    local ok, result = pcall(function() return lib:ScanItem(link, true) end)
    if ok and result then return result end
    return nil
end

local function IC_AppendCompare(tooltip, newBonuses, oldBonuses)
    if not newBonuses then return end
    local hasAny = false
    local lines = {}
    for _, key in ipairs(IC_STAT_ORDER) do
        local nv = newBonuses[key] or 0
        local ov = (oldBonuses and oldBonuses[key]) or 0
        local diff = nv - ov
        if diff ~= 0 then
            local name = IC_STAT_NAMES[key] or key
            local text
            if diff > 0 then
                text = "|cff00ff00+" .. diff .. " " .. name .. "|r"
            else
                text = "|cffff4444" .. diff .. " " .. name .. "|r"
            end
            table.insert(lines, text)
            hasAny = true
        end
    end
    if hasAny then
        tooltip:AddLine(" ")
        tooltip:AddLine("与当前装备对比：", 0.6, 0.8, 1)
        for _, l in ipairs(lines) do
            tooltip:AddLine(l)
        end
        tooltip:Show()
    end
end

local function IC_GetItemLevel(link)
    if not link or not LibItem_Level then return nil end
    local _, _, itemId = string.find(link, "item:(%d+)")
    if itemId then return LibItem_Level[tonumber(itemId)] end
    return nil
end

local function IC_AppendItemLevel(tooltip, link)
    if SFramesDB and SFramesDB.showItemLevel == false then return end
    local ilvl = IC_GetItemLevel(link)
    if ilvl and ilvl > 0 then
        tooltip:AddLine("物品等级: " .. ilvl, 1, 0.82, 0)
    end
end

--------------------------------------------------------------------------------
-- Sell Price Infrastructure
--------------------------------------------------------------------------------
local function IC_GetItemIdFromLink(link)
    if not link then return nil end
    local _, _, id = string.find(link, "item:(%d+)")
    if id then return tonumber(id) end
    return nil
end

local function IC_CacheSellPrice(itemId, copper)
    if not itemId or not copper or copper <= 0 then return end
    if not SFramesGlobalDB then SFramesGlobalDB = {} end
    if not SFramesGlobalDB.sellPriceCache then SFramesGlobalDB.sellPriceCache = {} end
    SFramesGlobalDB.sellPriceCache[itemId] = copper
end

local function IC_GetSellPrice(itemId)
    if not itemId then return nil end
    if NanamiSellPriceDB then
        local price = NanamiSellPriceDB[itemId]
        if price and price > 0 then return price end
    end
    if ShaguTweaks and ShaguTweaks.SellValueDB then
        local price = ShaguTweaks.SellValueDB[itemId]
        if price and price > 0 then return price end
    end
    if SFramesGlobalDB and SFramesGlobalDB.sellPriceCache then
        local price = SFramesGlobalDB.sellPriceCache[itemId]
        if price and price > 0 then return price end
    end
    return nil
end

local function IC_TryGetItemInfoSellPrice(link)
    if not link then return nil end
    local r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11 = GetItemInfo(link)
    if r11 and type(r11) == "number" and r11 > 0 then
        return r11
    end
    return nil
end

local function IC_ExtractLinkFromTooltipName(tooltip)
    if not tooltip then return nil end
    local left1 = _G[tooltip:GetName() .. "TextLeft1"]
    if not left1 then return nil end
    local name = left1:GetText()
    if not name or name == "" then return nil end
    if GetItemLinkByName then
        local link = GetItemLinkByName(name)
        if link then return link end
    end
    if ShaguTweaks and ShaguTweaks.GetItemLinkByName then
        local link = ShaguTweaks.GetItemLinkByName(name)
        if link then return link end
    end
    return nil
end

local function IC_AddSellPrice(tooltip, link, count)
    if not link then return end
    if tooltip._nanamiSellPriceAdded then return end
    if MerchantFrame and MerchantFrame:IsShown() then return end
    local itemId = IC_GetItemIdFromLink(link)
    local price = IC_GetSellPrice(itemId)
    if not price then
        price = IC_TryGetItemInfoSellPrice(link)
        if price and itemId then IC_CacheSellPrice(itemId, price) end
    end
    if price and price > 0 then
        count = count or 1
        if count < 1 then count = 1 end
        tooltip._nanamiSellPriceAdded = true
        SetTooltipMoney(tooltip, price * count)
        tooltip:Show()
    end
end

--------------------------------------------------------------------------------
local function IC_AppendItemId(tooltip, link)
    if SFramesDB and SFramesDB.showTooltipIDs == false then return end
    local itemId = IC_GetItemIdFromLink(link)
    if itemId then
        tooltip:AddLine("物品ID: " .. itemId, 0.55, 0.55, 0.70)
    end
end

local function IC_EnhanceTooltip(tooltip, link, count, skipSellPrice)
    if not link then return end
    IC_AppendItemLevel(tooltip, link)
    IC_AppendItemId(tooltip, link)
    if not SFramesDB or SFramesDB.itemCompare ~= false then
        local lib = IC_GetLib()
        if lib then
            local eslot = IC_GetEquipSlot(link)
            if eslot then
                local newB = IC_ScanBonuses(lib, link)
                local eqLink = GetInventoryItemLink("player", eslot)
                local oldB = IC_ScanBonuses(lib, eqLink)
                IC_AppendCompare(tooltip, newB, oldB)
            end
        end
    end
    if SFrames.GearScore and SFrames.GearScore.AddScoreToTooltip then
        pcall(function() SFrames.GearScore:AddScoreToTooltip(tooltip, link) end)
    end
    if not skipSellPrice then
        IC_AddSellPrice(tooltip, link, count)
    end
    tooltip:Show()
end

function IC:HookTooltips()
    if self.hooked then return end
    self.hooked = true

    ---------------------------------------------------------------------------
    -- OnHide cleanup: reset sell-price tracking flag
    ---------------------------------------------------------------------------
    local orig_OnHide_IC = GameTooltip:GetScript("OnHide")
    GameTooltip:SetScript("OnHide", function()
        this._nanamiSellPriceAdded = nil
        if orig_OnHide_IC then orig_OnHide_IC() end
    end)

    ---------------------------------------------------------------------------
    -- Passive sell-price caching: intercept SetTooltipMoney calls while
    -- processing a bag item at a merchant so we learn the unit price.
    ---------------------------------------------------------------------------
    local IC_PendingBagLink = nil
    local IC_PendingBagCount = nil
    local orig_SetTooltipMoney = SetTooltipMoney
    SetTooltipMoney = function(frame, money, a1, a2, a3)
        if orig_SetTooltipMoney then
            orig_SetTooltipMoney(frame, money, a1, a2, a3)
        end
        if IC_PendingBagLink and money and money > 0 then
            if frame == GameTooltip or frame == SFramesScanTooltip then
                local itemId = IC_GetItemIdFromLink(IC_PendingBagLink)
                local count = IC_PendingBagCount or 1
                if count < 1 then count = 1 end
                if itemId then
                    IC_CacheSellPrice(itemId, math.floor(money / count))
                end
            end
        end
    end

    ---------------------------------------------------------------------------
    -- MERCHANT_SHOW: proactively scan all bag items to cache sell prices.
    ---------------------------------------------------------------------------
    local scanFrame = CreateFrame("Frame")
    scanFrame:RegisterEvent("MERCHANT_SHOW")
    scanFrame:SetScript("OnEvent", function()
        for bag = 0, 4 do
            local numSlots = GetContainerNumSlots(bag) or 0
            for slot = 1, numSlots do
                local link = GetContainerItemLink(bag, slot)
                if link then
                    local itemId = IC_GetItemIdFromLink(link)
                    if itemId and not IC_GetSellPrice(itemId) then
                        local infoPrice = IC_TryGetItemInfoSellPrice(link)
                        if infoPrice then
                            IC_CacheSellPrice(itemId, infoPrice)
                        elseif SFramesScanTooltip then
                            local _, cnt = GetContainerItemInfo(bag, slot)
                            cnt = cnt or 1
                            IC_PendingBagLink = link
                            IC_PendingBagCount = cnt
                            pcall(function()
                                SFramesScanTooltip:SetOwner(UIParent, "ANCHOR_NONE")
                                SFramesScanTooltip:ClearLines()
                                SFramesScanTooltip:SetBagItem(bag, slot)
                                SFramesScanTooltip:Hide()
                            end)
                            IC_PendingBagLink = nil
                            IC_PendingBagCount = nil
                        end
                    end
                end
            end
        end
    end)

    ---------------------------------------------------------------------------
    -- GameTooltip.SetBagItem
    ---------------------------------------------------------------------------
    local orig_SetBagItem = GameTooltip.SetBagItem
    GameTooltip.SetBagItem = function(self, bag, slot)
        self._nanamiSellPriceAdded = nil
        local link = GetContainerItemLink(bag, slot)
        local _, cnt = GetContainerItemInfo(bag, slot)
        IC_PendingBagLink = link
        IC_PendingBagCount = cnt or 1

        local hasItem, hasCooldown, repairCost = orig_SetBagItem(self, bag, slot)

        IC_PendingBagLink = nil
        IC_PendingBagCount = nil

        local moneyAlreadyShown = self.hasMoney

        pcall(function()
            if link then
                IC_EnhanceTooltip(GameTooltip, link, cnt, moneyAlreadyShown)
            end
        end)
        return hasItem, hasCooldown, repairCost
    end

    ---------------------------------------------------------------------------
    -- GameTooltip.SetInventoryItem
    ---------------------------------------------------------------------------
    local orig_SetInvItem = GameTooltip.SetInventoryItem
    GameTooltip.SetInventoryItem = function(self, unit, slotId)
        self._nanamiSellPriceAdded = nil
        local hasItem, hasCooldown, repairCost = orig_SetInvItem(self, unit, slotId)
        local moneyAlreadyShown = self.hasMoney
        pcall(function()
            if unit == "player" and slotId then
                local link = GetInventoryItemLink("player", slotId)
                if link then
                    IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown)
                end
            end
        end)
        return hasItem, hasCooldown, repairCost
    end

    ---------------------------------------------------------------------------
    -- GameTooltip.SetMerchantItem
    ---------------------------------------------------------------------------
    local orig_SetMerchantItem = GameTooltip.SetMerchantItem
    if orig_SetMerchantItem then
        GameTooltip.SetMerchantItem = function(self, idx)
            self._nanamiSellPriceAdded = nil
            orig_SetMerchantItem(self, idx)
            pcall(function()
                local link = GetMerchantItemLink and GetMerchantItemLink(idx)
                if link then IC_EnhanceTooltip(GameTooltip, link) end
            end)
        end
    end

    ---------------------------------------------------------------------------
    -- GameTooltip.SetQuestItem — with fallback link extraction
    ---------------------------------------------------------------------------
    local orig_SetQuestItem = GameTooltip.SetQuestItem
    if orig_SetQuestItem then
        GameTooltip.SetQuestItem = function(self, qtype, idx)
            self._nanamiSellPriceAdded = nil
            orig_SetQuestItem(self, qtype, idx)
            local moneyAlreadyShown = self.hasMoney
            pcall(function()
                local link
                if GetQuestItemLink then link = GetQuestItemLink(qtype, idx) end
                if not link then link = IC_ExtractLinkFromTooltipName(GameTooltip) end
                if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end
            end)
        end
    end

    ---------------------------------------------------------------------------
    -- GameTooltip.SetQuestLogItem — with fallback link extraction
    ---------------------------------------------------------------------------
    local orig_SetQuestLogItem = GameTooltip.SetQuestLogItem
    if orig_SetQuestLogItem then
        GameTooltip.SetQuestLogItem = function(self, itype, idx)
            self._nanamiSellPriceAdded = nil
            orig_SetQuestLogItem(self, itype, idx)
            local moneyAlreadyShown = self.hasMoney
            pcall(function()
                local link
                if GetQuestLogItemLink then link = GetQuestLogItemLink(itype, idx) end
                if not link then link = IC_ExtractLinkFromTooltipName(GameTooltip) end
                if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end
            end)
        end
    end

    ---------------------------------------------------------------------------
    -- GameTooltip.SetLootItem
    ---------------------------------------------------------------------------
    local orig_SetLootItem = GameTooltip.SetLootItem
    if orig_SetLootItem then
        GameTooltip.SetLootItem = function(self, idx)
            self._nanamiSellPriceAdded = nil
            orig_SetLootItem(self, idx)
            local moneyAlreadyShown = self.hasMoney
            pcall(function()
                local link = GetLootSlotLink and GetLootSlotLink(idx)
                if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end
            end)
        end
    end

    ---------------------------------------------------------------------------
    -- GameTooltip.SetLootRollItem
    ---------------------------------------------------------------------------
    local orig_SetLootRollItem = GameTooltip.SetLootRollItem
    if orig_SetLootRollItem then
        GameTooltip.SetLootRollItem = function(self, rollId)
            self._nanamiSellPriceAdded = nil
            orig_SetLootRollItem(self, rollId)
            local moneyAlreadyShown = self.hasMoney
            pcall(function()
                local link = GetLootRollItemLink and GetLootRollItemLink(rollId)
                if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end
            end)
        end
    end

    ---------------------------------------------------------------------------
    -- GameTooltip.SetCraftItem
    ---------------------------------------------------------------------------
    local orig_SetCraftItem = GameTooltip.SetCraftItem
    if orig_SetCraftItem then
        GameTooltip.SetCraftItem = function(self, skill, slot)
            self._nanamiSellPriceAdded = nil
            orig_SetCraftItem(self, skill, slot)
            local moneyAlreadyShown = self.hasMoney
            pcall(function()
                local link = GetCraftReagentItemLink and GetCraftReagentItemLink(skill, slot)
                if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end
            end)
        end
    end

    ---------------------------------------------------------------------------
    -- GameTooltip.SetTradeSkillItem
    ---------------------------------------------------------------------------
    local orig_SetTradeSkillItem = GameTooltip.SetTradeSkillItem
    if orig_SetTradeSkillItem then
        GameTooltip.SetTradeSkillItem = function(self, skillIndex, reagentIndex)
            self._nanamiSellPriceAdded = nil
            orig_SetTradeSkillItem(self, skillIndex, reagentIndex)
            local moneyAlreadyShown = self.hasMoney
            pcall(function()
                local link
                if reagentIndex then
                    if GetTradeSkillReagentItemLink then
                        link = GetTradeSkillReagentItemLink(skillIndex, reagentIndex)
                    end
                else
                    if GetTradeSkillItemLink then
                        link = GetTradeSkillItemLink(skillIndex)
                    end
                end
                if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end
            end)
        end
    end

    ---------------------------------------------------------------------------
    -- GameTooltip.SetAuctionItem
    ---------------------------------------------------------------------------
    local orig_SetAuctionItem = GameTooltip.SetAuctionItem
    if orig_SetAuctionItem then
        GameTooltip.SetAuctionItem = function(self, atype, idx)
            self._nanamiSellPriceAdded = nil
            orig_SetAuctionItem(self, atype, idx)
            local moneyAlreadyShown = self.hasMoney
            pcall(function()
                local _, _, cnt = GetAuctionItemInfo and GetAuctionItemInfo(atype, idx)
                local link = GetAuctionItemLink and GetAuctionItemLink(atype, idx)
                if link then IC_EnhanceTooltip(GameTooltip, link, cnt, moneyAlreadyShown) end
            end)
        end
    end

    ---------------------------------------------------------------------------
    -- GameTooltip.SetTradePlayerItem
    ---------------------------------------------------------------------------
    local orig_SetTradePlayerItem = GameTooltip.SetTradePlayerItem
    if orig_SetTradePlayerItem then
        GameTooltip.SetTradePlayerItem = function(self, idx)
            self._nanamiSellPriceAdded = nil
            orig_SetTradePlayerItem(self, idx)
            local moneyAlreadyShown = self.hasMoney
            pcall(function()
                local link = GetTradePlayerItemLink and GetTradePlayerItemLink(idx)
                if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end
            end)
        end
    end

    ---------------------------------------------------------------------------
    -- GameTooltip.SetTradeTargetItem
    ---------------------------------------------------------------------------
    local orig_SetTradeTargetItem = GameTooltip.SetTradeTargetItem
    if orig_SetTradeTargetItem then
        GameTooltip.SetTradeTargetItem = function(self, idx)
            self._nanamiSellPriceAdded = nil
            orig_SetTradeTargetItem(self, idx)
            local moneyAlreadyShown = self.hasMoney
            pcall(function()
                local link = GetTradeTargetItemLink and GetTradeTargetItemLink(idx)
                if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end
            end)
        end
    end

    ---------------------------------------------------------------------------
    -- GameTooltip.SetInboxItem
    ---------------------------------------------------------------------------
    local orig_SetInboxItem = GameTooltip.SetInboxItem
    if orig_SetInboxItem then
        GameTooltip.SetInboxItem = function(self, mailID, attachIdx)
            self._nanamiSellPriceAdded = nil
            orig_SetInboxItem(self, mailID, attachIdx)
            local moneyAlreadyShown = self.hasMoney
            pcall(function()
                local link = IC_ExtractLinkFromTooltipName(GameTooltip)
                if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end
            end)
        end
    end

    ---------------------------------------------------------------------------
    -- SetItemRef (chat item links)
    ---------------------------------------------------------------------------
    local orig_SetItemRef = SetItemRef
    if orig_SetItemRef then
        SetItemRef = function(link, text, button)
            orig_SetItemRef(link, text, button)
            if IsAltKeyDown() or IsShiftKeyDown() or IsControlKeyDown() then return end
            pcall(function()
                local _, _, itemStr = string.find(link or "", "(item:[%-?%d:]+)")
                if itemStr then
                    ItemRefTooltip._nanamiSellPriceAdded = nil
                    local itemId = IC_GetItemIdFromLink(itemStr)
                    local price = IC_GetSellPrice(itemId)
                    if not price then
                        price = IC_TryGetItemInfoSellPrice(itemStr)
                        if price and itemId then IC_CacheSellPrice(itemId, price) end
                    end
                    if price and price > 0 and not ItemRefTooltip.hasMoney then
                        SetTooltipMoney(ItemRefTooltip, price)
                        ItemRefTooltip:Show()
                    end
                    if itemId then
                        ItemRefTooltip:AddLine("物品ID: " .. itemId, 0.55, 0.55, 0.70)
                        ItemRefTooltip:Show()
                    end
                end
            end)
        end
    end

    ---------------------------------------------------------------------------
    -- Spell ID display
    ---------------------------------------------------------------------------
    local spellIdCache = {}

    local function TT_AddSpellIdLine(tooltip, sid)
        if not tooltip or not sid then return end
        if SFramesDB and SFramesDB.showTooltipIDs == false then return end
        tooltip:AddLine("法术ID: " .. sid, 0.55, 0.55, 0.70)
        tooltip:Show()
    end

    local function TT_ExtractSpellId(link)
        if not link then return nil end
        local _, _, id = string.find(link, "spell:(%d+)")
        return id
    end

    local function TT_CleanName(text)
        if not text then return nil end
        text = string.gsub(text, "|c%x%x%x%x%x%x%x%x", "")
        text = string.gsub(text, "|r", "")
        text = string.gsub(text, "|H.-|h", "")
        text = string.gsub(text, "|h", "")
        text = string.gsub(text, "^%s+", "")
        text = string.gsub(text, "%s+$", "")
        return text
    end

    local function TT_BuildSpellCache()
        spellIdCache = {}
        local function ScanBook(bookType, count)
            if not GetSpellLink or count == 0 then return end
            for i = 1, count do
                local name, rank = GetSpellName(i, bookType)
                if name then
                    local link = GetSpellLink(i, bookType)
                    local sid = TT_ExtractSpellId(link)
                    if sid then
                        spellIdCache[name] = sid
                        local clean = TT_CleanName(name)
                        if clean and clean ~= name then
                            spellIdCache[clean] = sid
                        end
                    end
                end
            end
        end

        local bookType = BOOKTYPE_SPELL or "spell"
        local numTabs = GetNumSpellTabs and GetNumSpellTabs() or 0
        local total = 0
        for t = 1, numTabs do
            local _, _, _, count = GetSpellTabInfo(t)
            total = total + (count or 0)
        end
        ScanBook(bookType, total)

        if HasPetSpells then
            local numPet = HasPetSpells() or 0
            if numPet > 0 then
                ScanBook(BOOKTYPE_PET or "pet", numPet)
            end
        end
    end

    local spellCacheFrame = CreateFrame("Frame")
    spellCacheFrame:RegisterEvent("SPELLS_CHANGED")
    spellCacheFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
    spellCacheFrame:RegisterEvent("LEARNED_SPELL_IN_TAB")
    spellCacheFrame:RegisterEvent("PET_BAR_UPDATE")
    spellCacheFrame:SetScript("OnEvent", function()
        TT_BuildSpellCache()
    end)

    local function TT_LookupSpellId(spellBookIdx, bookType)
        if GetSpellLink then
            local link = GetSpellLink(spellBookIdx, bookType)
            local sid = TT_ExtractSpellId(link)
            if sid then return sid end
        end
        local name = GetSpellName(spellBookIdx, bookType)
        if name then
            if spellIdCache[name] then return spellIdCache[name] end
            local clean = TT_CleanName(name)
            if clean and spellIdCache[clean] then return spellIdCache[clean] end
        end
        return nil
    end

    local function TT_LookupByTooltipName(tooltip)
        local ttName = tooltip and tooltip:GetName()
        if not ttName then return nil end
        local left1 = _G[ttName .. "TextLeft1"]
        if not left1 then return nil end
        local raw = left1:GetText()
        if not raw or raw == "" then return nil end

        if spellIdCache[raw] then return spellIdCache[raw] end

        local clean = TT_CleanName(raw)
        if clean and spellIdCache[clean] then return spellIdCache[clean] end

        local noRank = string.gsub(clean or raw, "%s*%(.-%)%s*$", "")
        if noRank ~= (clean or raw) and spellIdCache[noRank] then
            return spellIdCache[noRank]
        end

        return nil
    end

    local orig_SetSpell = GameTooltip.SetSpell
    if orig_SetSpell then
        GameTooltip.SetSpell = function(self, id, bookType)
            orig_SetSpell(self, id, bookType)
            pcall(function()
                local sid = TT_LookupSpellId(id, bookType)
                if sid then
                    TT_AddSpellIdLine(self, sid)
                end
            end)
        end
    end

    local orig_SetUnitBuff = GameTooltip.SetUnitBuff
    if orig_SetUnitBuff then
        GameTooltip.SetUnitBuff = function(self, unit, idx, filter)
            orig_SetUnitBuff(self, unit, idx, filter)
            pcall(function()
                local sid = TT_LookupByTooltipName(self)
                if sid then TT_AddSpellIdLine(self, sid) end
            end)
        end
    end

    local orig_SetUnitDebuff = GameTooltip.SetUnitDebuff
    if orig_SetUnitDebuff then
        GameTooltip.SetUnitDebuff = function(self, unit, idx, filter)
            orig_SetUnitDebuff(self, unit, idx, filter)
            pcall(function()
                local sid = TT_LookupByTooltipName(self)
                if sid then TT_AddSpellIdLine(self, sid) end
            end)
        end
    end

    local orig_SetCraftSpell = GameTooltip.SetCraftSpell
    if orig_SetCraftSpell then
        GameTooltip.SetCraftSpell = function(self, idx)
            orig_SetCraftSpell(self, idx)
            pcall(function()
                local sid = TT_LookupByTooltipName(self)
                if sid then TT_AddSpellIdLine(self, sid) end
            end)
        end
    end

    local orig_SetTrackingSpell = GameTooltip.SetTrackingSpell
    if orig_SetTrackingSpell then
        GameTooltip.SetTrackingSpell = function(self)
            orig_SetTrackingSpell(self)
            pcall(function()
                local sid = TT_LookupByTooltipName(self)
                if sid then TT_AddSpellIdLine(self, sid) end
            end)
        end
    end
end
