-- DynamicTooltips: Blue Neumorphic Tooltip Replacement
-- Core addon file - Integrates Math and Minimap modules
-- Retail Midnight Edition - v2.0.0

local addonName, DT = ...
DT.version = "2.0.0"

-- API Version Detection for Retail vs Classic compatibility
DT.isRetail = (WOW_PROJECT_ID == WOW_PROJECT_MAINLINE) or (select(4, GetBuildInfo()) >= 110000)

-- ============================================================================
-- DEFAULT DATABASE
-- ============================================================================
local defaults = {
    enabled = true,
    showCoefficients = true,
    showTotal = true,
    showDPS = true,
    showSpellID = true,
    showMana = true,
    showOptimization = false,
    showManaEfficiency = false,
    showDownrankPenalty = false,
    showBaseDamage = true,
    showRankComparisons = true,
    showTalentModifiers = true,
    showThreat = true,
    showDuration = true,
    neumorphicStyle = true,
    colorBonusNumbers = true,
    minimapPos = 225,
    hideMinimapButton = false,
    -- Theme system
    currentTheme = "medieval",
    -- Debug
    debugMode = false,
}

-- Debug print helper
local function DebugPrint(...)
    if DT.DB and DT.DB.debugMode then
        print("|cffff9900[DT Debug]|r", ...)
    end
end

-- ============================================================================
-- STYLE CONFIGURATION (Blue Neumorphic Theme)
-- ============================================================================
DT.Style = {
    -- Background colors (clean dark backdrop like Seconds_to_full)
    bgColor = { r = 0.0, g = 0.0, b = 0.0, a = 0.85 },
    bgColorLight = { r = 0.1, g = 0.1, b = 0.1, a = 0.90 },
    
    -- Border/edge colors for neumorphic effect
    highlightColor = { r = 1, g = 1, b = 1, a = 0.08 },  -- Light edge
    shadowColor = { r = 0, g = 0, b = 0, a = 0.45 },      -- Dark shadow
    innerShadow = { r = 0, g = 0, b = 0, a = 0.35 },      -- Inner shadow
    
    -- Divider line color
    dividerColor = { r = 0.35, g = 0.55, b = 0.75, a = 0.6 },
    
    -- Text colors (hex converted to RGB 0-1)
    -- CONSISTENT COLOR CODING: Base → Green, Spell Power → Gold
    colors = {
        title = { r = 0.812, g = 0.914, b = 1.0 },        -- #cfe9ff - Spell name
        rank = { r = 0.545, g = 0.737, b = 1.0 },         -- #8bbcff - Rank text
        stats = { r = 0.663, g = 0.831, b = 1.0 },        -- #a9d4ff - Mana/range/cast
        description = { r = 0.902, g = 0.953, b = 1.0 },  -- #e6f3ff - Main description
        restriction = { r = 1.0, g = 0.604, b = 0.635 },  -- #ff9aa2 - Cannot cast warnings
        footer = { r = 0.478, g = 0.651, b = 0.8 },       -- #7aa6cc - Spell ID footer
        mana = { r = 0.4, g = 0.6, b = 1.0 },             -- Blue for mana
        range = { r = 0.663, g = 0.831, b = 1.0 },        -- Light blue
        castTime = { r = 0.8, g = 0.8, b = 0.6 },         -- Yellowish
        cooldown = { r = 0.7, g = 0.7, b = 0.9 },         -- Light purple
        -- CONSISTENT: Base damage/healing = GREEN
        base = { r = 0.0, g = 0.85, b = 0.0 },            -- #00d900 - Green for base values
        -- CONSISTENT: Spell Power contribution = GOLD
        gold = { r = 1.0, g = 0.82, b = 0.0 },            -- #ffd200 - Gold for SP contribution
        spellPower = { r = 1.0, g = 0.82, b = 0.0 },      -- #ffd200 - Gold (alias)
        -- Other colors
        damage = { r = 1.0, g = 0.82, b = 0.0 },          -- Gold for total damage
        healing = { r = 0.0, g = 1.0, b = 0.0 },          -- Bright green for healing
        dps = { r = 1.0, g = 0.65, b = 0.0 },             -- Orange for DPS/HPS
        calculation = { r = 0.6, g = 0.8, b = 1.0 },      -- Light blue for calculations
        efficiency = { r = 0.5, g = 0.8, b = 1.0 },       -- Cyan for efficiency
        warning = { r = 1.0, g = 0.5, b = 0.0 },          -- Orange for warnings
    },
    
    -- Dimensions
    padding = 14,
    cornerRadius = 14,
    borderWidth = 2,
    
    -- Fonts
    fontFace = "Fonts\\FRIZQT__.TTF",
    titleSize = 12,
    textSize = 12,
    smallSize = 12,
}

-- ============================================================================
-- AVAILABLE FONTS
-- ============================================================================

DT.Fonts = {
    list = {
        ["blizzard"] = {
            name = "Blizzard Default",
            path = "Fonts\\FRIZQT__.TTF",
        },
        ["caslon"] = {
            name = "Caslon Antique",
            path = "Interface\\AddOns\\DynamicTooltips_Retail\\Data\\fonts\\Calson Antique.ttf",
        },
        ["curwen"] = {
            name = "Curwen Sans",
            path = "Interface\\AddOns\\DynamicTooltips_Retail\\Data\\fonts\\CurwenSans.ttf",
        },
        ["baskerville"] = {
            name = "Libre Baskerville",
            path = "Interface\\AddOns\\DynamicTooltips_Retail\\Data\\fonts\\LibreBaskerville.ttf",
        },
        ["gillsans"] = {
            name = "Gill Sans Nova",
            path = "Interface\\AddOns\\DynamicTooltips_Retail\\Data\\fonts\\Gill Sans Nova.ttf",
        },
        ["myriad"] = {
            name = "Myriad Condensed",
            path = "Interface\\AddOns\\DynamicTooltips_Retail\\Data\\fonts\\Myriad Condensed Web.ttf",
        },
        ["roboto"] = {
            name = "Roboto",
            path = "Interface\\AddOns\\DynamicTooltips_Retail\\Data\\fonts\\Roboto.ttf",
        },
        ["uncial"] = {
            name = "Uncial Antiqua",
            path = "Interface\\AddOns\\DynamicTooltips_Retail\\Data\\fonts\\UncialAntiqua-Regular.ttf",
        },
        ["diablo"] = {
            name = "Cinzel Decorative",
            path = "Interface\\AddOns\\DynamicTooltips_Retail\\Data\\fonts\\CinzelDecorative-Regular.ttf",
        },
    },
    current = "blizzard",
}

function DT.Fonts:GetList()
    local result = {}
    for id, data in pairs(self.list) do
        table.insert(result, { id = id, name = data.name, path = data.path })
    end
    -- Always put Blizzard Default first, then sort alphabetically
    table.sort(result, function(a, b)
        if a.id == "blizzard" then return true end
        if b.id == "blizzard" then return false end
        return a.name < b.name
    end)
    return result
end

function DT.Fonts:Set(fontId)
    fontId = fontId:lower()
    if self.list[fontId] then
        self.current = fontId
        DT.Style.fontFace = self.list[fontId].path
        if DT.DB then
            DT.DB.font = fontId
        end
        -- Re-apply font objects with new face (TipTac-style)
        if DT.ApplyThemeFonts then
            DT.ApplyThemeFonts()
        end
        return true
    end
    return false
end

function DT.Fonts:GetCurrent()
    return self.current, self.list[self.current]
end

-- Add icons and dividers to existing DT.Style
DT.Style.icons = {
    mana = "|cff6699ff*|r",       -- Blue star for mana
    range = "->",                  -- Arrow for range  
    castTime = "~",                -- Tilde for cast time
    instant = "!",                 -- Exclaim for instant
    cooldown = "@",                -- At for cooldown
    damage = "+",                  -- Plus for damage
    healing = "+",                 -- Plus for healing
}

DT.Style.dividers = {
    thin = "------------------------------",
    thick = "==============================",
    section = "--- %s ---",
    header = "=== %s ===",
}

-- ============================================================================
-- VISUAL DIVIDER SYSTEM
-- Pool of texture-based dividers that attach to tooltips
-- ============================================================================

DT.Dividers = {
    pool = {},           -- Recycled divider frames
    active = {},         -- Currently displayed dividers
    templateMap = {
        ["default"] = "Solid",
        ["medieval"] = "Glow",
        ["minimal"] = "Fade",
        ["cyberpunk"] = "Cyber",
        ["nature"] = "Nature",
        ["fire"] = "Fire",
        ["void"] = "Arcane",
        ["frost"] = "Frost",
        ["fel"] = "Fel",
    },
}

-- Get or create a divider frame
function DT.Dividers:Get(tooltip)
    local divider = table.remove(self.pool)
    if not divider then
        -- Create new divider frame
        local themeName = DT.DB and DT.DB.currentTheme or "medieval"
        local templateName = self.templateMap[themeName] or "Glow"
        
        divider = CreateFrame("Frame", nil, tooltip)
        divider:SetSize(200, 4)
        
        -- Create the main texture
        divider.line = divider:CreateTexture(nil, "ARTWORK")
        divider.line:SetAllPoints()
        
        -- Create optional glow texture
        divider.glow = divider:CreateTexture(nil, "BACKGROUND")
        divider.glow:SetPoint("TOPLEFT", -2, 2)
        divider.glow:SetPoint("BOTTOMRIGHT", 2, -2)
        divider.glow:Hide()
        
        -- Create optional accent textures
        divider.accentL = divider:CreateTexture(nil, "OVERLAY")
        divider.accentL:SetSize(6, 6)
        divider.accentL:SetPoint("LEFT", divider, "LEFT", 0, 0)
        divider.accentL:Hide()
        
        divider.accentR = divider:CreateTexture(nil, "OVERLAY")
        divider.accentR:SetSize(6, 6)
        divider.accentR:SetPoint("RIGHT", divider, "RIGHT", 0, 0)
        divider.accentR:Hide()
        
        divider.centerStar = divider:CreateTexture(nil, "OVERLAY")
        divider.centerStar:SetSize(8, 8)
        divider.centerStar:SetPoint("CENTER")
        divider.centerStar:Hide()
    end
    
    divider:SetParent(tooltip)
    divider:Show()
    table.insert(self.active, divider)
    return divider
end

-- Style a divider based on current theme
function DT.Dividers:Style(divider, dividerType)
    local themeName = DT.DB and DT.DB.currentTheme or "medieval"
    local theme = DT.Themes and DT.Themes.GetCurrent() or nil
    
    -- Reset to defaults
    divider.line:SetColorTexture(0.4, 0.6, 0.9, 0.8)
    divider.line:Show()
    divider.glow:Hide()
    divider.accentL:Hide()
    divider.accentR:Hide()
    divider.centerStar:Hide()
    divider:SetHeight(4)
    
    -- Apply theme-specific styling
    if themeName == "default" then
        -- Standard Blizzard style - simple grey line
        divider.line:SetColorTexture(0.5, 0.5, 0.5, 0.6)
        divider:SetHeight(2)

    elseif themeName == "medieval" then
        -- Warm gold line
        divider.line:SetColorTexture(0.8, 0.65, 0.35, 0.9)
        divider.glow:SetColorTexture(0.7, 0.55, 0.25, 0.3)
        divider.glow:Show()
        divider:SetHeight(4)
        
    elseif themeName == "minimal" then
        -- Subtle fade line
        divider.line:SetColorTexture(1, 1, 1, 0.3)
        divider:SetHeight(1)
        
    elseif themeName == "cyberpunk" then
        -- Neon cyan with pink accents
        divider.line:SetColorTexture(0, 1, 0.8, 0.9)
        divider.accentL:SetColorTexture(1, 0, 0.5, 0.9)
        divider.accentL:Show()
        divider.accentR:SetColorTexture(1, 0, 0.5, 0.9)
        divider.accentR:Show()
        divider:SetHeight(3)
        
    elseif themeName == "nature" then
        -- Green vine with leaf accents
        divider.line:SetColorTexture(0.3, 0.7, 0.3, 0.7)
        divider.accentL:SetColorTexture(0.4, 0.8, 0.3, 0.9)
        divider.accentL:Show()
        divider.accentR:SetColorTexture(0.4, 0.8, 0.3, 0.9)
        divider.accentR:Show()
        divider:SetHeight(4)
        
    elseif themeName == "fire" then
        -- Orange/red gradient (single color fallback)
        divider.line:SetColorTexture(1, 0.5, 0.1, 0.9)
        divider.glow:SetColorTexture(1, 0.2, 0, 0.3)
        divider.glow:Show()
        divider:SetHeight(4)

    elseif themeName == "void" then
        -- Deep purple with eldritch glow
        divider.line:SetColorTexture(0.55, 0.25, 0.85, 0.9)
        divider.glow:SetColorTexture(0.45, 0.15, 0.75, 0.3)
        divider.glow:Show()
        divider.centerStar:SetColorTexture(0.8, 0.6, 1.0, 0.9)
        divider.centerStar:Show()
        divider:SetHeight(5)

    elseif themeName == "frost" then
        -- Icy blue crystalline
        divider.line:SetColorTexture(0.5, 0.78, 1.0, 0.85)
        divider.glow:SetColorTexture(0.35, 0.6, 0.9, 0.25)
        divider.glow:Show()
        divider:SetHeight(3)

    elseif themeName == "fel" then
        -- Sickly green demonic
        divider.line:SetColorTexture(0.4, 0.95, 0.2, 0.9)
        divider.glow:SetColorTexture(0.3, 0.8, 0.1, 0.3)
        divider.glow:Show()
        divider.accentL:SetColorTexture(0.6, 1.0, 0.1, 0.8)
        divider.accentL:Show()
        divider.accentR:SetColorTexture(0.6, 1.0, 0.1, 0.8)
        divider.accentR:Show()
        divider:SetHeight(4)
    end
    
    -- Type-specific adjustments (header = bold, footer = subtle, section = decorated)
    if dividerType == "header" then
        -- Header dividers are thicker and more prominent
        local h = divider:GetHeight()
        divider:SetHeight(h + 1)
        local r, g, b = divider.line:GetVertexColor()
        divider.line:SetColorTexture(r, g, b, 1.0)  -- Full opacity
        
    elseif dividerType == "section" then
        -- Section dividers show decorative elements
        divider.accentL:Show()
        divider.accentR:Show()
        if themeName == "void" then
            divider.centerStar:Show()
        end
        divider:SetHeight(divider:GetHeight() + 2)
        
    elseif dividerType == "footer" then
        -- Footer dividers are more subdued
        local r, g, b = divider.line:GetVertexColor()
        divider.line:SetColorTexture(r * 0.7, g * 0.7, b * 0.7, 0.6)
        divider:SetHeight(2)
        divider.glow:Hide()
        divider.accentL:Hide()
        divider.accentR:Hide()
    end
end

-- Position a divider over a specific tooltip line (replaces the spacer visually)
function DT.Dividers:AddAfterLine(tooltip, lineNum, dividerType)
    local divider = self:Get(tooltip)
    
    -- Get the tooltip text line to anchor to (the spacer line)
    local textLine = _G[tooltip:GetName() .. "TextLeft" .. lineNum]
    if not textLine then return end
    
    -- Style based on theme
    self:Style(divider, dividerType)
    
    -- Anchor to the tooltip frame itself for full width, vertically aligned to the spacer line
    local padding = 12
    divider:ClearAllPoints()
    divider:SetPoint("LEFT", tooltip, "LEFT", padding, 0)
    divider:SetPoint("RIGHT", tooltip, "RIGHT", -padding, 0)
    divider:SetPoint("TOP", textLine, "TOP", 0, 0)
    divider:SetPoint("BOTTOM", textLine, "BOTTOM", 0, 0)
    
    return divider
end

-- Hide all active dividers and return to pool
function DT.Dividers:HideAll()
    for _, divider in ipairs(self.active) do
        divider:Hide()
        divider:ClearAllPoints()
        table.insert(self.pool, divider)
    end
    wipe(self.active)
    wipe(DT.DividerPositions)
end

-- Hook tooltip hide to clean up dividers
local function OnTooltipCleared(tooltip)
    DT.Dividers:HideAll()
end

-- Theme-specific divider styles (simple colored bars)
DT.DividerStyles = {
    -- Default: Standard Blizzard-style grey bars
    ["default"] = {
        header = { text = "------------------------------", r = 0.5, g = 0.5, b = 0.5 },
        section = { text = "--- %s ---", r = 0.6, g = 0.6, b = 0.6 },
        footer = { text = "------------------------------", r = 0.4, g = 0.4, b = 0.4 },
    },
    -- Medieval: Warm gold bars
    ["medieval"] = {
        header = { text = "------------------------------", r = 0.8, g = 0.65, b = 0.35 },
        section = { text = "--- %s ---", r = 0.85, g = 0.72, b = 0.45 },
        footer = { text = "------------------------------", r = 0.55, g = 0.5, b = 0.4 },
    },
    -- Minimal: Subtle grey bars
    ["minimal"] = {
        header = { text = "------------------------------", r = 0.8, g = 0.8, b = 0.82 },
        section = { text = "--- %s ---", r = 0.85, g = 0.85, b = 0.87 },
        footer = { text = "------------------------------", r = 0.55, g = 0.55, b = 0.58 },
    },
    -- Cyberpunk: Neon cyan and pink bars
    ["cyberpunk"] = {
        header = { text = "------------------------------", r = 0, g = 1, b = 1 },
        section = { text = "--- %s ---", r = 1, g = 0, b = 0.8 },
        footer = { text = "------------------------------", r = 0, g = 1, b = 0.5 },
    },
    -- Nature: Green bars
    ["nature"] = {
        header = { text = "------------------------------", r = 0.3, g = 0.8, b = 0.3 },
        section = { text = "--- %s ---", r = 0.4, g = 0.9, b = 0.4 },
        footer = { text = "------------------------------", r = 0.2, g = 0.65, b = 0.2 },
    },
    -- Fire: Orange/red bars
    ["fire"] = {
        header = { text = "------------------------------", r = 1, g = 0.5, b = 0.1 },
        section = { text = "--- %s ---", r = 1, g = 0.6, b = 0.2 },
        footer = { text = "------------------------------", r = 0.8, g = 0.3, b = 0.1 },
    },
    -- Void: Deep purple bars
    ["void"] = {
        header = { text = "------------------------------", r = 0.6, g = 0.35, b = 0.85 },
        section = { text = "--- %s ---", r = 0.7, g = 0.45, b = 0.95 },
        footer = { text = "------------------------------", r = 0.4, g = 0.25, b = 0.6 },
    },
    -- Frost: Icy blue bars
    ["frost"] = {
        header = { text = "------------------------------", r = 0.5, g = 0.78, b = 1.0 },
        section = { text = "--- %s ---", r = 0.6, g = 0.85, b = 1.0 },
        footer = { text = "------------------------------", r = 0.35, g = 0.55, b = 0.75 },
    },
    -- Fel: Sickly green bars
    ["fel"] = {
        header = { text = "------------------------------", r = 0.4, g = 0.95, b = 0.2 },
        section = { text = "--- %s ---", r = 0.5, g = 1.0, b = 0.3 },
        footer = { text = "------------------------------", r = 0.3, g = 0.65, b = 0.15 },
    },
}

-- Add a themed divider line directly to tooltip
function DT.Dividers:AddLine(tooltip, dividerType, label)
    local themeName = DT.DB and DT.DB.currentTheme or "medieval"
    local themeStyles = self.parent and self.parent[themeName] or DT.DividerStyles[themeName] or DT.DividerStyles["medieval"]
    local style = themeStyles[dividerType] or themeStyles.header
    
    local text = style.text
    if label and text:match("%%s") then
        text = string.format(text, label)
    end
    
    tooltip:AddLine(text, style.r, style.g, style.b)
end

-- Tracking for divider positions during tooltip build
DT.DividerPositions = {}

-- Add a themed divider to tooltip (texture-based colored bars)
function DT.Dividers:AddPlaceholder(tooltip, dividerType, label)
    -- Add a small spacer line for the divider to anchor to
    tooltip:AddLine(" ")
    local lineNum = tooltip:NumLines()
    
    -- Track for positioning after tooltip shows
    table.insert(DT.DividerPositions, {
        tooltip = tooltip,
        line = lineNum,
        type = dividerType,
        label = label
    })
    
    return lineNum
end

-- Position all tracked dividers after tooltip is shown
function DT.Dividers:PositionAll(tooltip)
    for _, pos in ipairs(DT.DividerPositions) do
        if pos.tooltip == tooltip then
            self:AddAfterLine(tooltip, pos.line, pos.type)
        end
    end
end

-- Clear divider tracking
function DT.Dividers:ClearTracking()
    wipe(DT.DividerPositions)
end

-- ============================================================================
-- UTILITY FUNCTIONS
-- ============================================================================

local function FormatNumber(num)
    if not num then return "0" end
    num = math.floor(num + 0.5)
    if num >= 1000000 then
        return string.format("%.1fM", num / 1000000)
    elseif num >= 1000 then
        return string.format("%.1fk", num / 1000)
    end
    return tostring(num)
end

local function ColorText(text, color)
    if type(color) == "table" then
        return string.format("|cff%02x%02x%02x%s|r", 
            color.r * 255, color.g * 255, color.b * 255, text)
    end
    return text
end

local function GetSpellSchool(school)
    local schools = {
        [1] = "Physical",
        [2] = "Holy", 
        [4] = "Fire",
        [8] = "Nature",
        [16] = "Frost",
        [32] = "Shadow",
        [64] = "Arcane",
    }
    return schools[school] or "Physical"
end

-- ============================================================================
-- TOOLTIP STYLING (TipTac-inspired: NineSlice strip, backdrop, gradient, color lock)
-- ============================================================================

-- Color locking: prevents external code (Blizzard, other addons) from
-- overriding our backdrop colors after we've applied a theme.
local isSettingBackdrop = false

local function LockTooltipColors(tooltip)
    if tooltip.DT_colorsLocked then return end
    
    -- Hook SetBackdrop to re-enforce the entire theme backdrop (edge texture + colors).
    -- This is the KEY fix: Blizzard calls SetBackdrop() between tooltips to reset
    -- the border texture, not just the colors. Without this hook, our themed
    -- edgeFile gets replaced by Blizzard's default on subsequent tooltips.
    if tooltip.SetBackdrop then
        hooksecurefunc(tooltip, "SetBackdrop", function(self, backdropInfo)
            if isSettingBackdrop then return end
            if not DT.DB or not DT.DB.neumorphicStyle then return end
            local bd = DT.Themes and DT.Themes.GetBackdrop()
            if bd then
                isSettingBackdrop = true
                self:SetBackdrop({
                    bgFile = bd.bgFile,
                    edgeFile = bd.edgeFile,
                    tile = bd.tile,
                    tileSize = bd.tileSize,
                    edgeSize = bd.edgeSize,
                    insets = bd.insets,
                })
                self:SetBackdropColor(bd.bgColor.r, bd.bgColor.g, bd.bgColor.b, bd.bgColor.a)
                self:SetBackdropBorderColor(bd.borderColor.r, bd.borderColor.g, bd.borderColor.b, bd.borderColor.a)
                isSettingBackdrop = false
            end
        end)
    end
    
    -- Hook SetBackdropColor to re-enforce theme colors
    hooksecurefunc(tooltip, "SetBackdropColor", function(self, r, g, b, a)
        if isSettingBackdrop then return end
        if not DT.DB or not DT.DB.neumorphicStyle then return end
        local bd = DT.Themes and DT.Themes.GetBackdrop()
        if bd then
            isSettingBackdrop = true
            self:SetBackdropColor(bd.bgColor.r, bd.bgColor.g, bd.bgColor.b, bd.bgColor.a)
            isSettingBackdrop = false
        end
    end)
    
    -- Hook SetBackdropBorderColor to re-enforce theme border
    hooksecurefunc(tooltip, "SetBackdropBorderColor", function(self, r, g, b, a)
        if isSettingBackdrop then return end
        if not DT.DB or not DT.DB.neumorphicStyle then return end
        local bd = DT.Themes and DT.Themes.GetBackdrop()
        if bd then
            isSettingBackdrop = true
            self:SetBackdropBorderColor(bd.borderColor.r, bd.borderColor.g, bd.borderColor.b, bd.borderColor.a)
            isSettingBackdrop = false
        end
    end)
    
    tooltip.DT_colorsLocked = true
end

-- Gradient overlay: subtle vertical gradient from top of tooltip.
-- Each theme defines its own gradient color and height (like TipTac's gradientTip).
local function ApplyGradientToTip(tooltip)
    local grad = DT.Themes and DT.Themes.GetGradient()
    
    -- Hide gradient if disabled or theme has no gradient
    if not grad or not grad.enabled then
        if tooltip.DT_gradient then tooltip.DT_gradient:Hide() end
        return
    end
    
    -- Create gradient texture if it doesn't exist yet
    if not tooltip.DT_gradient then
        local tex = tooltip:CreateTexture(nil, "ARTWORK")
        tex:SetColorTexture(1, 1, 1, 1)
        tooltip.DT_gradient = tex
    end
    
    local tex = tooltip.DT_gradient
    local gc = grad.color
    
    -- TipTac-style: vertical gradient — transparent at bottom, themed color at top
    if tex.SetGradientAlpha then  -- TBC / pre-DF 10.0.0
        tex:SetGradientAlpha("VERTICAL", 0, 0, 0, 0, gc.r, gc.g, gc.b, gc.a)
    elseif tex.SetGradient then   -- DF 10.0.0+
        tex:SetGradient("VERTICAL", CreateColor(0, 0, 0, 0), CreateColor(gc.r, gc.g, gc.b, gc.a))
    end
    
    -- Position within the backdrop insets
    local bd = DT.Themes and DT.Themes.GetBackdrop()
    local insets = (bd and bd.insets) or { left = 3, right = 3, top = 3, bottom = 3 }
    
    tex:ClearAllPoints()
    tex:SetPoint("TOPLEFT", insets.left, -insets.top)
    tex:SetPoint("BOTTOMRIGHT", tooltip, "TOPRIGHT", -insets.right, -(grad.height or 32))
    tex:Show()
end

-- Aggressive NineSlice suppression: belt-and-suspenders approach so Blizzard's
-- border textures can NEVER reappear.  A simple :Hide() is insufficient —
-- the TBC Anniversary client re-shows NineSlice via SharedTooltip_SetBackdropStyle,
-- GameTooltip_CalculatePadding, and internal layout passes.
local function SuppressNineSlice(tooltip)
    if not tooltip.NineSlice then return end
    if tooltip.DT_nineSliceSuppressed then
        -- Already fully suppressed — just make sure it's still hidden
        tooltip.NineSlice:Hide()
        return
    end

    local ns = tooltip.NineSlice

    -- 1. Hide + zero alpha (even if re-shown, invisible)
    ns:Hide()
    ns:SetAlpha(0)

    -- 2. Hook Show so Blizzard can never re-show NineSlice
    hooksecurefunc(ns, "Show", function(self)
        self:Hide()
    end)

    -- 3. Nuclear: clear every individual NineSlice piece texture
    local pieces = {
        "TopLeftCorner", "TopRightCorner", "BottomLeftCorner", "BottomRightCorner",
        "TopEdge", "BottomEdge", "LeftEdge", "RightEdge", "Center",
    }
    for _, piece in ipairs(pieces) do
        local region = ns[piece]
        if region then
            region:SetTexture(nil)
            region:SetAlpha(0)
            region:Hide()
        end
    end

    -- 4. Also try the generic child-region iteration in case pieces use
    --    non-standard keys (some client builds name them differently)
    for _, child in ipairs({ ns:GetRegions() }) do
        if child.SetTexture then
            child:SetTexture(nil)
            child:SetAlpha(0)
            child:Hide()
        end
    end

    tooltip.DT_nineSliceSuppressed = true
end

local function ApplyNeumorphicStyle(tooltip)
    if not DT.DB.neumorphicStyle then return end
    if not tooltip.SetBackdrop then return end
    
    -- === NineSlice stripping (TipTac-inspired) ===
    -- Aggressively suppress Blizzard's NineSlice frame so its textures
    -- can never flash or persist over our themed backdrop.
    SuppressNineSlice(tooltip)
    tooltip.layoutType = nil
    tooltip.layoutTextureKit = nil
    tooltip.backdropInfo = nil
    
    -- === Apply theme backdrop ===
    local bd
    if DT.Themes then
        bd = DT.Themes.GetBackdrop()
    end
    if not bd then
        bd = {
            bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            tile = true, tileSize = 16, edgeSize = 16,
            insets = { left = 3, right = 3, top = 3, bottom = 3 },
            bgColor = { r = 0, g = 0, b = 0, a = 0.9 },
            borderColor = { r = 0.5, g = 0.5, b = 0.5, a = 0.7 },
        }
    end
    
    -- Use isSettingBackdrop flag so our color-lock hooks don't fight us
    isSettingBackdrop = true
    
    tooltip:SetBackdrop({
        bgFile = bd.bgFile,
        edgeFile = bd.edgeFile,
        tile = bd.tile,
        tileSize = bd.tileSize,
        edgeSize = bd.edgeSize,
        insets = bd.insets,
    })
    tooltip:SetBackdropColor(bd.bgColor.r, bd.bgColor.g, bd.bgColor.b, bd.bgColor.a)
    tooltip:SetBackdropBorderColor(bd.borderColor.r, bd.borderColor.g, bd.borderColor.b, bd.borderColor.a)
    
    isSettingBackdrop = false
    
    -- === Install color locks (once per tooltip frame) ===
    LockTooltipColors(tooltip)
    
    -- === Apply gradient overlay ===
    ApplyGradientToTip(tooltip)
end

local function StyleTooltipText(tooltip)
    if not DT.DB.neumorphicStyle then return end
    
    -- Get theme-specific colors
    local theme = DT.Themes and DT.Themes.GetCurrent()
    local c = (theme and theme.text) or DT.Style.colors
    local titleColor = (theme and theme.titleColor) or { r = 1, g = 0.82, b = 0 }
    local bodyColor  = (theme and theme.bodyColor) or { r = 0.9, g = 0.9, b = 0.9 }
    local footerColor = (theme and theme.footerColor) or { r = 0.5, g = 0.5, b = 0.5 }
    
    local fonts = DT.Themes and DT.Themes.GetFonts() or {
        titleSize = DT.Style.titleSize,
        textSize = DT.Style.textSize,
        footerSize = DT.Style.smallSize,
        outline = "OUTLINE"
    }
    
    -- Style title (first line) - theme's title color
    local titleLine = _G[tooltip:GetName() .. "TextLeft1"]
    if titleLine then
        titleLine:SetFont(DT.Style.fontFace, fonts.titleSize, fonts.outline)
        titleLine:SetTextColor(titleColor.r, titleColor.g, titleColor.b)
    end
    
    -- Style right side of title (rank)
    local titleRight = _G[tooltip:GetName() .. "TextRight1"]
    if titleRight and titleRight:GetText() then
        titleRight:SetFont(DT.Style.fontFace, fonts.textSize, fonts.outline)
        titleRight:SetTextColor(c.rank.r, c.rank.g, c.rank.b)
    end
    
    -- Style body lines - theme's body/footer colors
    for i = 2, tooltip:NumLines() do
        local leftText = _G[tooltip:GetName() .. "TextLeft" .. i]
        local rightText = _G[tooltip:GetName() .. "TextRight" .. i]
        
        if leftText then
            local text = leftText:GetText() or ""
            
            if text == "" and not leftText:IsShown() then
                -- Skip hidden/blanked lines
            else
                leftText:SetFont(DT.Style.fontFace, fonts.textSize, fonts.outline)
                -- Restore word wrap after SetFont() — prevents long description
                -- lines from stretching the tooltip to extreme widths
                leftText:SetWordWrap(true)
                
                if string.find(text, "Cannot") or string.find(text, "Requires") then
                    leftText:SetTextColor(c.restriction.r, c.restriction.g, c.restriction.b)
                elseif string.find(text, "Spell ID:") or string.find(text, "Icon ID:") then
                    leftText:SetTextColor(footerColor.r, footerColor.g, footerColor.b)
                    leftText:SetFont(DT.Style.fontFace, fonts.footerSize, fonts.outline)
                end
            end
        end
        
        if rightText and rightText:IsShown() and rightText:GetText() then
            rightText:SetFont(DT.Style.fontFace, fonts.textSize, fonts.outline)
        end
    end
end

-- Styling for rebuilt tooltips (our custom layout) - same per-theme approach
local function StyleRebuiltTooltip(tooltip)
    if not DT.DB.neumorphicStyle then return end
    
    local theme = DT.Themes and DT.Themes.GetCurrent()
    local titleColor = (theme and theme.titleColor) or { r = 1, g = 0.82, b = 0 }
    local footerColor = (theme and theme.footerColor) or { r = 0.5, g = 0.5, b = 0.5 }
    
    local fonts = DT.Themes and DT.Themes.GetFonts() or {
        titleSize = 12, textSize = 12, footerSize = 12, outline = "OUTLINE"
    }
    
    -- Theme's title color
    local titleLine = _G[tooltip:GetName() .. "TextLeft1"]
    if titleLine then
        titleLine:SetFont(DT.Style.fontFace, fonts.titleSize, fonts.outline)
        titleLine:SetTextColor(titleColor.r, titleColor.g, titleColor.b)
    end
    
    local titleRight = _G[tooltip:GetName() .. "TextRight1"]
    if titleRight and titleRight:GetText() then
        titleRight:SetFont(DT.Style.fontFace, fonts.textSize, fonts.outline)
    end
    
    -- Style all other lines
    for i = 2, tooltip:NumLines() do
        local leftText = _G[tooltip:GetName() .. "TextLeft" .. i]
        local rightText = _G[tooltip:GetName() .. "TextRight" .. i]
        
        if leftText then
            local text = leftText:GetText() or ""
            
            if text:match("Spell ID:") then
                leftText:SetFont(DT.Style.fontFace, fonts.footerSize, fonts.outline)
                leftText:SetTextColor(footerColor.r, footerColor.g, footerColor.b)
            else
                leftText:SetFont(DT.Style.fontFace, fonts.textSize, fonts.outline)
            end
        end
        
        if rightText and rightText:GetText() then
            rightText:SetFont(DT.Style.fontFace, fonts.textSize, fonts.outline)
        end
    end
end

-- Lightweight font pass for non-spell tooltips (items, NPCs, buffs, etc.)
-- Applies the theme font face/size to every line without touching colors.
local function ApplyFontsToLines(tooltip)
    if not DT.DB.neumorphicStyle then return end
    local name = tooltip:GetName()
    if not name then return end
    
    local fonts = DT.Themes and DT.Themes.GetFonts() or {
        titleSize = 12, textSize = 12, footerSize = 12, outline = "OUTLINE"
    }
    local flags = (fonts.outline ~= "NONE") and fonts.outline or ""
    
    for i = 1, tooltip:NumLines() do
        local left  = _G[name .. "TextLeft" .. i]
        local right = _G[name .. "TextRight" .. i]
        
        if left then
            left:SetFont(DT.Style.fontFace, (i == 1) and fonts.titleSize or fonts.textSize, flags)
        end
        if right and right:GetText() then
            right:SetFont(DT.Style.fontFace, fonts.textSize, flags)
        end
    end
end

-- ============================================================================
-- SPELL TOOLTIP ENHANCEMENT
-- ============================================================================

local function ParseBaseDamageFromTooltip(tooltip, data)
    -- Try to extract base damage/healing/absorb values from the tooltip text
    -- This is a heuristic approach since we can't always know base values
    local minDmg, maxDmg = nil, nil
    
    -- For weapon-speed-scaling spells (seals, enchants), prioritize "additional X damage"
    -- to avoid picking up Judgement damage ranges from secondary tooltip paragraphs
    if data and data.weaponSpeedScaling then
        for i = 1, tooltip:NumLines() do
            local line = _G[tooltip:GetName() .. "TextLeft" .. i]
            if line then
                local text = line:GetText() or ""
                local cleanText = text:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
                -- Match "additional 59 Holy damage" or "additional 59 damage"
                local sealDmg = cleanText:match("additional%s+(%d+)%s+%a*%s*[Dd]amage")
                if sealDmg then
                    DebugPrint("Matched seal/enchant additional damage:", sealDmg)
                    return tonumber(sealDmg), tonumber(sealDmg)
                end
                -- Match Flametongue-style "causes X to Y additional Fire damage"
                local ftMin, ftMax = cleanText:match("[Cc]auses%s+(%d+)%s+to%s+(%d+)%s+additional")
                if ftMin and ftMax then
                    DebugPrint("Matched enchant range damage:", ftMin, "-", ftMax)
                    return tonumber(ftMin), tonumber(ftMax)
                end
            end
        end
        -- Fall through to generic parser if no seal-specific pattern found
        DebugPrint("weaponSpeedScaling: NO seal-specific pattern matched, falling through to generic parser")
        for di = 1, tooltip:NumLines() do
            local dl = _G[tooltip:GetName() .. "TextLeft" .. di]
            if dl then DebugPrint("  Line", di, ":", dl:GetText() or "<nil>") end
        end
    end
    
    for i = 1, tooltip:NumLines() do
        local line = _G[tooltip:GetName() .. "TextLeft" .. i]
        if line then
            local text = line:GetText() or ""
            
            -- Debug: log each line if debug mode is on
            DebugPrint("Parse line", i, ":", text)
            
            -- Strip color codes for better pattern matching
            local cleanText = text:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
            
            -- Look for patterns like "150 to 200" or "deals 150" or "150-200"
            local min, max = cleanText:match("(%d+)%s*to%s*(%d+)")
            if min and max then
                DebugPrint("Matched range:", min, "-", max)
                minDmg = tonumber(min)
                maxDmg = tonumber(max)
                break
            end
            
            -- Totem tick pattern: "for X every Y seconds" (Healing Stream, Mana Spring)
            local totemTickVal = cleanText:match("for%s+(%d+)%s+every")
            if totemTickVal then
                DebugPrint("Matched totem tick:", totemTickVal)
                minDmg = tonumber(totemTickVal)
                maxDmg = minDmg
                break
            end
            
            -- HoT/DoT pattern: "for 175 over 15 sec" or "for 500 Shadow damage over"
            local hotValue = cleanText:match("for%s+(%d+)%s+over") or cleanText:match("for%s+(%d+).-over%s+%d+")
            if hotValue then
                DebugPrint("Matched HoT value:", hotValue)
                minDmg = tonumber(hotValue)
                maxDmg = minDmg
                break
            end
            
            -- Channel/DoT: "causing 363 Shadow damage over 3 sec"
            local causingValue = cleanText:match("[Cc]ausing%s+(%d+)")
            if causingValue then
                DebugPrint("Matched causing value:", causingValue)
                minDmg = tonumber(causingValue)
                maxDmg = minDmg
                break
            end
            
            -- Single value pattern: "deals 150" or "causes 500"
            -- Skip "deals 110%" (percentage = coefficient, not base damage)
            local single = nil
            local dealMatch = cleanText:match("[Dd]eals%s+(%d+)")
            if dealMatch and not cleanText:match("[Dd]eals%s+%d+%%") then
                single = dealMatch
            end
            single = single or cleanText:match("[Cc]auses%s+(%d+)")
            if single then
                DebugPrint("Matched single value:", single)
                minDmg = tonumber(single)
                maxDmg = minDmg
                break
            end
            
            -- Weapon damage flat bonus patterns:
            -- "increases melee damage by 21" (Heroic Strike, Raptor Strike, Maul)
            -- "additional 50 damage" or "bonus 50 damage" (Cleave)
            -- "damage plus 68" or "in addition to 68" (Sinister Strike, Backstab)
            local wdBonus = cleanText:match("increases.-damage%s+by%s+(%d+)")
                or cleanText:match("additional%s+(%d+)%s+damage")
                or cleanText:match("bonus%s+(%d+)")
                or cleanText:match("damage%s+plus%s+(%d+)")
                or cleanText:match("in%s+addition%s+to%s+(%d+)")
                or cleanText:match("an%s+extra%s+(%d+)")
            if wdBonus then
                DebugPrint("Matched WD bonus:", wdBonus)
                minDmg = tonumber(wdBonus)
                maxDmg = minDmg
                break
            end
            
            -- Generic: "X damage over" or "X healing over" (catches remaining DoT/HoT/channel phrasings)
            local genericDot = cleanText:match("(%d+)%s+%a+%s+damage%s+over") or cleanText:match("(%d+)%s+%a+%s+healing%s+over")
            if genericDot then
                DebugPrint("Matched generic DoT:", genericDot)
                minDmg = tonumber(genericDot)
                maxDmg = minDmg
                break
            end
            
            -- Mana restore patterns: "restore 46 ... Mana" (Seal of Wisdom, Mana Spring Totem)
            local restoreVal = cleanText:match("[Rr]estores?%s+(%d+).-[Mm]ana")
            if restoreVal then
                DebugPrint("Matched mana restore:", restoreVal)
                minDmg = tonumber(restoreVal)
                maxDmg = minDmg
                break
            end
            
            -- Healing patterns: "Heals ... for X" or "heal ... for X" or "heals X"
            -- Require space after heals? to avoid matching "health" or "Healing" as word prefix
            local healFor = cleanText:match("[Hh]eals?%s.-for%s+(%d+)")
            if healFor then
                DebugPrint("Matched heal for:", healFor)
                minDmg = tonumber(healFor)
                maxDmg = minDmg
                break
            end
            
            -- "Heals X" or "Heal X" without "for"
            local healDirect = cleanText:match("[Hh]eals?%s+(%d+)")
            if healDirect then
                DebugPrint("Matched heal direct:", healDirect)
                minDmg = tonumber(healDirect)
                maxDmg = minDmg
                break
            end
            
            -- Absorb patterns for shields: "Absorbs 1265 damage" or "absorbing 1000"
            local absorb = cleanText:match("[Aa]bsorbs?%s+(%d+)") or cleanText:match("[Aa]bsorbing%s+(%d+)")
            if absorb then
                DebugPrint("Matched absorb:", absorb)
                minDmg = tonumber(absorb)
                maxDmg = minDmg
                break
            end
            
            -- Last resort: bare number followed by damage/healing keyword (allow 1+ digits)
            local bareNum = cleanText:match("(%d+)%s+%a*%s*[Dd]amage") or cleanText:match("(%d+)%s+%a*%s*[Hh]ealing")
            if bareNum then
                DebugPrint("Matched bare number:", bareNum)
                minDmg = tonumber(bareNum)
                maxDmg = minDmg
                break
            end
        end
    end
    
    DebugPrint("ParseBaseDamage result:", minDmg or "nil", maxDmg or "nil")
    return minDmg, maxDmg
end

-- Parse DoT BASE damage from Blizzard tooltip for hybrid spells
-- Looks for: "additional 600 Arcane damage over 12 sec" or "and 420 Fire damage over 12 sec"
-- Returns single number (total DoT base) or nil
local function ParseDotBaseDamageFromTooltip(tooltip)
    for i = 1, tooltip:NumLines() do
        local line = _G[tooltip:GetName() .. "TextLeft" .. i]
        if line then
            local text = line:GetText() or ""
            local cleanText = text:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
            -- "additional 600 Arcane damage over" (Moonfire, Immolate, Holy Fire, Pyroblast)
            local additional = cleanText:match("additional%s+(%d+)%s+%a+%s+damage%s+over")
            if additional then return tonumber(additional) end
            -- "additional 600 ... healing over" (hybrid heals if any)
            additional = cleanText:match("additional%s+(%d+)%s+%a+%s+healing%s+over")
            if additional then return tonumber(additional) end
            -- "and 420 Fire damage over 12 sec" (Flame Shock)
            local andDot = cleanText:match("and%s+(%d+)%s+%a+%s+damage%s+over")
                        or cleanText:match("and%s+(%d+)%s+%a+%s+healing%s+over")
            if andDot then return tonumber(andDot) end
        end
    end
    return nil
end

-- Parse healing values from tooltip text for dual damage/heal spells
-- Looks for healing-specific patterns that ParseBaseDamageFromTooltip skips
-- Returns healMin, healMax or nil, nil
local function ParseHealingFromTooltip(tooltip)
    local healMin, healMax = nil, nil
    for i = 1, tooltip:NumLines() do
        local line = _G[tooltip:GetName() .. "TextLeft" .. i]
        if line then
            local text = line:GetText() or ""
            local cleanText = text:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
            
            -- "351 to 379 healing" (Holy Shock pattern)
            local hMin, hMax = cleanText:match("(%d+)%s+to%s+(%d+)%s+healing")
            if hMin and hMax then
                healMin = tonumber(hMin)
                healMax = tonumber(hMax)
                break
            end
            
            -- "heals ... for X to Y" or "healing ... X to Y"
            hMin, hMax = cleanText:match("[Hh]eal[si]?n?g?.-(%d+)%s+to%s+(%d+)")
            if hMin and hMax then
                healMin = tonumber(hMin)
                healMax = tonumber(hMax)
                break
            end
            
            -- "heals ... for X" single value (Holy Nova-style)
            local hSingle = cleanText:match("[Hh]eals.-for%s+(%d+)")
            if hSingle then
                healMin = tonumber(hSingle)
                healMax = healMin
                break
            end
            
            -- "heals X" direct 
            hSingle = cleanText:match("[Hh]eals%s+(%d+)")
            if hSingle then
                healMin = tonumber(hSingle)
                healMax = healMin
                break
            end
        end
    end
    return healMin, healMax
end

-- Parse healing values from description lines (for rebuilt tooltips)
local function ParseHealingFromLines(lines)
    local healMin, healMax = nil, nil
    for _, line in ipairs(lines) do
        local cleanText = line:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
        
        local hMin, hMax = cleanText:match("(%d+)%s+to%s+(%d+)%s+healing")
        if hMin and hMax then
            healMin = tonumber(hMin)
            healMax = tonumber(hMax)
            break
        end
        
        hMin, hMax = cleanText:match("[Hh]eal[si]?n?g?.-(%d+)%s+to%s+(%d+)")
        if hMin and hMax then
            healMin = tonumber(hMin)
            healMax = tonumber(hMax)
            break
        end
        
        local hSingle = cleanText:match("[Hh]eals.-for%s+(%d+)")
        if hSingle then
            healMin = tonumber(hSingle)
            healMax = healMin
            break
        end
        
        hSingle = cleanText:match("[Hh]eals%s+(%d+)")
        if hSingle then
            healMin = tonumber(hSingle)
            healMax = healMin
            break
        end
    end
    return healMin, healMax
end

-- Parse DoT BASE damage from description lines (for rebuilt tooltips)
-- Same logic as ParseDotBaseDamageFromTooltip but works on lines table
local function ParseDotBaseDamageFromLines(lines)
    for _, line in ipairs(lines) do
        local cleanText = line:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
        local additional = cleanText:match("additional%s+(%d+)%s+%a+%s+damage%s+over")
        if additional then return tonumber(additional) end
        additional = cleanText:match("additional%s+(%d+)%s+%a+%s+healing%s+over")
        if additional then return tonumber(additional) end
        local andDot = cleanText:match("and%s+(%d+)%s+%a+%s+damage%s+over")
                    or cleanText:match("and%s+(%d+)%s+%a+%s+healing%s+over")
        if andDot then return tonumber(andDot) end
    end
    return nil
end

-- Parse HoT BASE healing from Blizzard tooltip for hybrid direct+HoT heal spells (Regrowth, etc.)
-- Looks for: "additional 98 health over 21 sec" or "regenerates 458 over 21 sec"
-- Returns single number (total HoT base healing) or nil
local function ParseHotBaseHealingFromTooltip(tooltip)
    for i = 1, tooltip:NumLines() do
        local line = _G[tooltip:GetName() .. "TextLeft" .. i]
        if line then
            local text = line:GetText() or ""
            local cleanText = text:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
            -- "additional 98 health over 21 sec" (Regrowth)
            local additional = cleanText:match("additional%s+(%d+)%s+health%s+over")
            if additional then return tonumber(additional) end
            -- "regenerates 458 over" (alternate phrasing)
            local regen = cleanText:match("[Rr]egenerates%s+(%d+)%s+over")
            if regen then return tonumber(regen) end
            -- "and X health over Y sec"
            local andHot = cleanText:match("and%s+(%d+)%s+health%s+over")
            if andHot then return tonumber(andHot) end
        end
    end
    return nil
end

-- Same logic as ParseHotBaseHealingFromTooltip but works on description lines table
local function ParseHotBaseHealingFromLines(lines)
    for _, line in ipairs(lines) do
        local cleanText = line:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
        local additional = cleanText:match("additional%s+(%d+)%s+health%s+over")
        if additional then return tonumber(additional) end
        local regen = cleanText:match("[Rr]egenerates%s+(%d+)%s+over")
        if regen then return tonumber(regen) end
        local andHot = cleanText:match("and%s+(%d+)%s+health%s+over")
        if andHot then return tonumber(andHot) end
    end
    return nil
end

-- Parse Lifebloom bloom instant heal value from tooltip
-- Looks for: "heals themself for 600" or "instantly heals ... for 600"
local function ParseBloomHealFromTooltip(tooltip)
    for i = 1, tooltip:NumLines() do
        local line = _G[tooltip:GetName() .. "TextLeft" .. i]
        if line then
            local text = line:GetText() or ""
            local cleanText = text:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
            -- "heals themself for 600" or "heals the target for 600"
            local bloom = cleanText:match("heals%s+%w+%s+for%s+(%d+)")
            if bloom then return tonumber(bloom) end
        end
    end
    return nil
end

-- Parse Lifebloom bloom instant heal value from description lines table
local function ParseBloomHealFromLines(lines)
    for _, line in ipairs(lines) do
        local cleanText = line:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
        local bloom = cleanText:match("heals%s+%w+%s+for%s+(%d+)")
        if bloom then return tonumber(bloom) end
    end
    return nil
end

-- Parse combo point damage ranges from tooltip text
-- Returns table: {[1]={min=X, max=Y}, [2]={min=X, max=Y}, ...} or nil
local function ParseComboPointDamage(tooltip)
    local cpData = {}
    local found = false
    for i = 1, tooltip:NumLines() do
        local line = _G[tooltip:GetName() .. "TextLeft" .. i]
        if line then
            local text = line:GetText()
            if text then
                -- Strip color codes for more reliable matching
                local clean = text:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
                -- Use gmatch to find ALL combo point entries in each line
                -- (Blizzard may pack all CP lines into a single tooltip FontString)
                -- Pattern 1: "X points: MIN-MAX" or "X points: MIN–MAX" (damage range)
                for cp, dmgMin, dmgMax in clean:gmatch("(%d+)%s+points?%s*:%s*(%d+)%s*[-–]%s*(%d+)") do
                    cpData[tonumber(cp)] = { min = tonumber(dmgMin), max = tonumber(dmgMax) }
                    found = true
                end
                -- Pattern 1b: "X points: MIN to MAX"
                for cp, dmgMin, dmgMax in clean:gmatch("(%d+)%s+points?%s*:%s*(%d+)%s+to%s+(%d+)") do
                    if not cpData[tonumber(cp)] then
                        cpData[tonumber(cp)] = { min = tonumber(dmgMin), max = tonumber(dmgMax) }
                        found = true
                    end
                end
                -- Pattern 2: "X point(s) : Y damage" (single value, e.g. Rip/Rupture DoT finishers)
                for cp, dmg in clean:gmatch("(%d+)%s+points?%s*:%s*(%d+)%s+damage") do
                    if not cpData[tonumber(cp)] then
                        cpData[tonumber(cp)] = { min = tonumber(dmg), max = tonumber(dmg) }
                        found = true
                    end
                end
            end
        end
    end
    return found and cpData or nil
end

-- Get the theme icons for mana/range (TGA textures)
local function GetIcons()
    if DT.Themes then
        local theme = DT.Themes.GetCurrent()
        if theme and theme.icons then
            return theme.icons
        end
    end
    return DT.Style.icons
end

-- Append mana/range/cast/cooldown lines with icons.
-- Used when a spell has no coefficient data but we still want to show the meta.
local function AppendMetaLines(tooltip, meta, manaCost)
    if not meta then return end
    
    local c = DT.Style.colors
    if DT.Themes then
        local theme = DT.Themes.GetCurrent()
        if theme and theme.text then
            c = setmetatable({}, {__index = function(t, k)
                return theme.text[k] or DT.Style.colors[k]
            end})
        end
    end
    local icons = GetIcons()
    
    local hasMeta = meta.mana or meta.range or meta.castTime or meta.cooldown
    if not hasMeta then return end
    
    tooltip:AddLine(" ")
    
    -- Mana and Range on separate lines
    local manaStr = manaCost and (icons.mana .. " " .. manaCost .. " Mana") or nil
    local rangeStr = meta.range and (icons.range .. " " .. meta.range) or nil
    
    if manaStr then
        tooltip:AddLine(manaStr, c.mana.r, c.mana.g, c.mana.b)
    end
    if rangeStr then
        tooltip:AddLine(rangeStr, c.range.r, c.range.g, c.range.b)
    end
    
    -- Cast time + Cooldown on one double-line
    local castStr = meta.castTime
    local cdStr = meta.cooldown
    
    if castStr and cdStr then
        tooltip:AddDoubleLine(castStr, cdStr, c.castTime.r, c.castTime.g, c.castTime.b, (c.cooldown or c.castTime).r, (c.cooldown or c.castTime).g, (c.cooldown or c.castTime).b)
    elseif castStr then
        tooltip:AddLine(castStr, c.castTime.r, c.castTime.g, c.castTime.b)
    elseif cdStr then
        tooltip:AddLine(cdStr, (c.cooldown or c.castTime).r, (c.cooldown or c.castTime).g, (c.cooldown or c.castTime).b)
    end
end

local function AddScalingInfo(tooltip, spellID, manaCost, spellName, meta)
    if not DT.DB.enabled then 
        DebugPrint("AddScalingInfo: Addon disabled")
        return 
    end
    if not DT.Math then 
        DebugPrint("AddScalingInfo: DT.Math not loaded")
        return 
    end
    if not DT.SpellData then 
        DebugPrint("AddScalingInfo: DT.SpellData not loaded")
        return 
    end
    
    -- Try ID lookup first, then name-based fallback
    local data = DT.SpellData[spellID]
    local lookupMethod = "ID"
    
    if not data and spellName and DT.SpellDataByName then
        data = DT.SpellDataByName[spellName:lower()]
        lookupMethod = "Name"
    end
    
    if not data then 
        DebugPrint("AddScalingInfo: No data for spellID", spellID, "name:", spellName or "nil")
        return 
    end
    
    DebugPrint("AddScalingInfo: Found data via", lookupMethod, "- SP:", data.sp or 0, "WD:", data.wd or 0, "AP:", data.ap or 0, "baseDamage:", data.baseDamage or "nil")
    
    -- Get theme colors, fallback to static style
    local c = DT.Style.colors
    if DT.Themes then
        local theme = DT.Themes.GetCurrent()
        if theme and theme.text then
            -- Merge theme colors with static colors for compatibility
            c = setmetatable({}, {__index = function(t, k)
                return theme.text[k] or DT.Style.colors[k]
            end})
        end
    end
    local icons = GetIcons()
    
    -- Parse base damage from tooltip if possible
    local baseDmgMin, baseDmgMax = ParseBaseDamageFromTooltip(tooltip, data)
    local baseDmg = 0
    local baseDmgText = "???"
    
    if baseDmgMin and baseDmgMax then
        if baseDmgMin == baseDmgMax then
            baseDmg = baseDmgMin
            baseDmgText = tostring(baseDmgMin)
        else
            baseDmg = (baseDmgMin + baseDmgMax) / 2
            baseDmgText = baseDmgMin .. "-" .. baseDmgMax
        end
        DebugPrint("Using parsed base:", baseDmgText)
    elseif data.baseDamage then
        -- Use spell data if available
        baseDmg = data.baseDamage
        baseDmgText = tostring(data.baseDamage)
        DebugPrint("Using fallback baseDamage:", baseDmgText)
    else
        -- For weapon damage spells with no flat bonus in tooltip, default to 0
        if data.wd and data.wd > 0 then
            baseDmg = 0
            baseDmgText = "0"
            DebugPrint("No flat bonus for WD spell - defaulting to 0")
        else
            DebugPrint("No base damage found - showing ???")
        end
    end
    
    -- Get player/pet stats
    local statValue = 0
    local statName = ""
    local coefficient = 0
    local isNormalized = nil  -- nil = not weapon, true = normalized, false = unnormalized
    local normSpeed = nil
    local petDmgMin, petDmgMax, petDmgAvg = 0, 0, 0  -- pet melee damage range
    local wdMin, wdMax = nil, nil  -- weapon damage min/max for range display
    
    if data.isPet then
        -- ════════════════════════════════════════════════════════
        -- PET SPELLS: Use pet stats instead of player stats
        -- ════════════════════════════════════════════════════════
        if data.sp and data.sp > 0 then
            statValue = DT.Math:GetPetSpellPower()
            statName = "Pet Spell Power"
            coefficient = data.sp
        elseif data.wd and data.wd > 0 then
            petDmgMin, petDmgMax, petDmgAvg = DT.Math:GetPetDamage()
            statValue = petDmgAvg
            statName = "Pet Melee"
            coefficient = data.wd
        end
    elseif data.isHeal then
        statValue = DT.Math:GetHealingPower()
        statName = "Healing Power"
        coefficient = data.sp or 0
    elseif data.sp and data.sp > 0 then
        statValue = DT.Math:GetSpellPower(data.school or "Shadow")
        statName = "Spell Power"
        coefficient = data.sp
    elseif data.wd and data.wd > 0 then
        isNormalized = DT.Math:IsNormalized(spellName or (data.name or ""))
        if isNormalized == false then
            -- Unnormalized: use actual weapon min/max
            if data.type == "Ranged" then
                local _, minR, maxR = DT.Math:GetNormalizedWeaponDamage("ranged")
                -- For ranged unnormalized, read actual damage
                if UnitRangedDamage then
                    local speed, minD, maxD = UnitRangedDamage("player")
                    wdMin = minD or 0
                    wdMax = maxD or 0
                else
                    wdMin, wdMax = 0, 0
                end
                statValue = (wdMin + wdMax) / 2
                statName = "Weapon Damage"
            else
                wdMin, wdMax = DT.Math:GetWeaponDamageRange("main")
                -- Dual-wield: add off-hand weapon damage if OH equipped
                if data.isDualWield then
                    local ohMin, ohMax = DT.Math:GetWeaponDamageRange("off")
                    if ohMin and ohMax and ohMin > 0 then
                        wdMin = (wdMin or 0) + ohMin
                        wdMax = (wdMax or 0) + ohMax
                        statName = "Weapon Damage (MH+OH)"
                    else
                        statName = "Weapon Damage"
                    end
                else
                    statName = "Weapon Damage"
                end
                statValue = (wdMin + wdMax) / 2
            end
        else
            -- Normalized (true) or unknown (nil) — default to normalized
            if data.type == "Ranged" then
                local normAvg, normMin, normMax, ns = DT.Math:GetNormalizedWeaponDamage("ranged")
                statValue = normAvg
                wdMin = normMin
                wdMax = normMax
                normSpeed = ns
                statName = "Normalized WD"
            else
                local normAvg, normMin, normMax, ns = DT.Math:GetNormalizedWeaponDamage("main")
                statValue = normAvg
                wdMin = normMin
                wdMax = normMax
                normSpeed = ns
                -- Dual-wield: add off-hand normalized weapon damage if OH equipped
                if data.isDualWield then
                    local ohAvg, ohMin, ohMax = DT.Math:GetNormalizedWeaponDamage("off")
                    if ohAvg and ohAvg > 0 then
                        statValue = statValue + ohAvg
                        wdMin = (wdMin or 0) + (ohMin or 0)
                        wdMax = (wdMax or 0) + (ohMax or 0)
                        statName = "Normalized WD (MH+OH)"
                    else
                        statName = "Normalized WD"
                    end
                else
                    statName = "Normalized WD"
                end
            end
            isNormalized = true
        end
        coefficient = data.wd
    elseif data.ap and data.ap > 0 then
        statValue = DT.Math:GetAttackPower()
        statName = "Attack Power"
        coefficient = data.ap
    end
    
    -- Combo point finisher handling: scale coefficient and override base damage per CP
    local cpData = nil
    local currentCP = 0
    if data.isFinisher then
        cpData = ParseComboPointDamage(tooltip)
        currentCP = GetComboPoints("player", "target") or 0
        if currentCP < 1 then currentCP = 5 end  -- Default to 5 CP when no target
        
        if cpData then
            -- Override base damage with CP-appropriate values
            if cpData[currentCP] then
                baseDmgMin = cpData[currentCP].min
                baseDmgMax = cpData[currentCP].max
                baseDmg = (baseDmgMin + baseDmgMax) / 2
                baseDmgText = baseDmgMin .. "-" .. baseDmgMax
            end
        end
        
        -- Scale coefficient by combo points (supports both WD and AP finishers)
        local finisherCoeff = data.wd or data.ap or 0
        coefficient = finisherCoeff * currentCP
    end
    
    -- Weapon speed scaling for Proc/Enchant spells (SoR, Flametongue, etc.)
    -- SP coefficient is flat per hit for both SoR and Flametongue.
    -- Base damage per hit scales with weapon speed (already reflected in tooltip).
    -- We read base weapon speed (unaffected by haste) to calculate per-hit and DPS.
    -- Also needed for Windfury AP-to-damage conversion.
    local baseWeaponSpeed = nil
    local effectiveCoefficient = coefficient
    if (data.weaponSpeedScaling and data.sp and data.sp > 0) or data.enchantProcChance or data.sealPPM or data.sealWeaponDmgCoeff then
        baseWeaponSpeed = DT.Math:GetBaseWeaponSpeed("main")
        DebugPrint("Weapon scaling check - baseWeaponSpeed:", baseWeaponSpeed or "nil",
            "weaponSpeedScaling:", data.weaponSpeedScaling and "yes" or "no",
            "sp:", data.sp or "nil")
    end
    
    -- Calculate bonus (Spell Power contribution)
    local spBonus = math.floor(statValue * coefficient + 0.5)
    local hasScalingData = spBonus > 0 or coefficient > 0
    DebugPrint("Scaling check - statValue:", statValue, "coefficient:", coefficient,
        "spBonus:", spBonus, "hasScalingData:", hasScalingData and "yes" or "no",
        "baseDmg:", baseDmg or "nil")
    local hasSealDPS = data.sealPPM or data.sealWeaponDmgCoeff or data.enchantProcChance
    local hasBaseOnly = not hasScalingData and not hasSealDPS and baseDmg > 0
    local hasThreat = data.threat and data.threat > 0
    
    -- If no scaling data AND no seal DPS AND no base data AND no threat AND no meta to show, bail out
    if not hasScalingData and not hasSealDPS and not hasBaseOnly and not hasThreat and not (meta and (meta.mana or meta.range or meta.castTime or meta.cooldown)) then
        DebugPrint("BAIL OUT: no data to show at all")
        return
    end
    
    -- If no scaling data and no seal DPS and no base data and no threat, skip straight to meta lines (no empty spacer)
    if not hasScalingData and not hasSealDPS and not hasBaseOnly and not hasThreat then
        DebugPrint("SKIP to meta only: no scaling/seal/base data")
        AppendMetaLines(tooltip, meta, manaCost)
        return
    end
    
    -- Add separator line (when we have scaling data or base data to show)
    tooltip:AddLine(" ")
    
    -- ========================================================================
    -- FORMAT: Matching reference image with consistent colors
    -- Base → GREEN, Spell Power/Healing Power → GOLD
    -- ========================================================================
    
    local actionWord = data.isHeal and "Healing" or "Damage"
    if data.type == "Shield" then actionWord = "Absorb" end
    
    -- Effect word for sentences (lowercase)
    local effectWord = data.isHeal and "healing" or "damage"
    if data.type == "Shield" then effectWord = "absorb" end
    
    -- Line 1: "Base Damage: 150 + 22% of Spell Power"
    -- GREEN base + GOLD coefficient
    -- When showBaseDamage is unchecked, skip this entire line (not just the base value)
    -- For apInBase spells (Bloodthirst etc.), tooltip already shows computed AP damage, skip base
    if (hasScalingData or hasBaseOnly) and DT.DB.showBaseDamage and not data.apInBase then
        if data.isManaRestore then
            -- Mana restore: totem tick vs seal proc
            local manaLabel = data.isTotemTick and "Mana per Tick" or "Mana per Proc"
            local line1 = string.format("%s: %s", manaLabel, ColorText(baseDmgText, c.base))
            tooltip:AddLine(line1, c.description.r, c.description.g, c.description.b)
        elseif data.isTotemTick then
            -- Totem tick heals (Healing Stream Totem)
            local tickLabel = data.isHeal and "Heal per Tick" or "Base per Tick"
            local parts = {}
            table.insert(parts, ColorText(baseDmgText, c.base))
            if DT.DB.showCoefficients and coefficient > 0 then
                table.insert(parts, ColorText(string.format("%.0f%%", coefficient * 100), c.gold) .. " of " .. statName)
            end
            local line1 = string.format("%s: %s", tickLabel, table.concat(parts, " + "))
            tooltip:AddLine(line1, c.description.r, c.description.g, c.description.b)
        else
            local parts = {}
            -- Skip "0" for weapon damage spells with no flat bonus (e.g. Stormstrike)
            if not (data.wd and data.wd > 0 and baseDmg == 0) then
                table.insert(parts, ColorText(baseDmgText, c.base))
            end
            if DT.DB.showCoefficients and coefficient > 0 then
                table.insert(parts, ColorText(string.format("%.0f%%", coefficient * 100), c.gold) .. " of " .. statName)
            end
            if #parts > 0 then
                local line1 = string.format("Base %s: %s", actionWord, table.concat(parts, " + "))
                tooltip:AddLine(line1, c.description.r, c.description.g, c.description.b)
            end
        end
    elseif hasScalingData and DT.DB.showCoefficients then
        -- Show only the coefficient line without base damage
        local line1 = string.format("Coefficient: %s of %s", 
            ColorText(string.format("%.0f%%", coefficient * 100), c.gold), statName)
        tooltip:AddLine(line1, c.description.r, c.description.g, c.description.b)
    end
    
    -- Show normalization info for weapon damage spells
    if isNormalized ~= nil and DT.DB.showCoefficients then
        if isNormalized then
            local normLabel = string.format("(Normalized: %.1f speed)", normSpeed or 0)
            tooltip:AddLine(normLabel, c.footer.r, c.footer.g, c.footer.b)
        else
            tooltip:AddLine("(Uses actual weapon speed)", c.footer.r, c.footer.g, c.footer.b)
        end
    end
    
    -- Threat display for abilities with flat threat (Sunder Armor, etc.)
    if hasThreat then
        local baseThreat = data.threat
        local threatMod, appliedThreatMods = 0, {}
        if DT.DB.showTalentModifiers and DT.Modifiers and DT.Modifiers.GetThreatModifier then
            threatMod, appliedThreatMods = DT.Modifiers:GetThreatModifier(data)
        end
        local finalThreat = baseThreat * (1 + threatMod)
        local threatText = ColorText(string.format("%.0f", finalThreat), c.gold)
        local threatLine = string.format("Threat: %s", threatText)
        tooltip:AddLine(threatLine, c.description.r, c.description.g, c.description.b)
        -- Show threat modifier breakdown
        if DT.DB.showTalentModifiers and #appliedThreatMods > 0 then
            local modParts = {}
            for _, mod in ipairs(appliedThreatMods) do
                local sign = mod.modifier >= 0 and "+" or ""
                table.insert(modParts, string.format("%s%s: %s%.0f%%", 
                    mod.source == "talent" and "  " or "", mod.name, sign, mod.modifier * 100))
            end
            for _, part in ipairs(modParts) do
                tooltip:AddLine(part, c.footer.r, c.footer.g, c.footer.b)
            end
        end
    end
    
    -- Show pet melee damage range for pet melee abilities
    if data.isPet and data.wd and data.wd > 0 and petDmgMin > 0 then
        local petLabel = string.format("Pet Melee: %s", 
            ColorText(string.format("%d-%d", petDmgMin, petDmgMax), c.base))
        tooltip:AddLine(petLabel, c.footer.r, c.footer.g, c.footer.b)
    elseif data.isPet and data.sp and data.sp > 0 then
        local petSP = DT.Math:GetPetSpellPower()
        if petSP > 0 then
            tooltip:AddLine(string.format("Pet Spell Power: %s", 
                ColorText(tostring(petSP), c.base)), c.footer.r, c.footer.g, c.footer.b)
        end
    end
    
    -- Total line: "Total Healing: 1.2k-1.5k holy" or "Total Healing: 1.2k holy"
    if hasScalingData and DT.DB.showTotal then
        local schoolText = (data.school or "Shadow"):lower()
        local totalText
        
        -- Get talent/buff modifiers
        local talentMod, appliedMods, baseTalentMod = 0, {}, 0
        if DT.DB.showTalentModifiers and DT.Modifiers then
            talentMod, appliedMods, baseTalentMod = DT.Modifiers:GetCombinedModifier(data)
        end
        
        -- For weapon damage spells: total = flatBonus + (weaponDmg * coeff)
        -- Use weapon min/max for range display
        if wdMin and wdMax and data.wd and data.wd > 0 then
            local flatBonus = baseDmg or 0  -- flat bonus from tooltip (e.g. +21 for Heroic Strike)
            local totalMin = flatBonus + wdMin * coefficient
            local totalMax = flatBonus + wdMax * coefficient
            if talentMod > 0 then
                totalMin = totalMin * (1 + talentMod)
                totalMax = totalMax * (1 + talentMod)
            end
            totalText = ColorText(string.format("%.1f-%.1f", totalMin, totalMax), c.gold)
        -- For AP spells where tooltip already shows computed damage (Bloodthirst, Victory Rush)
        elseif data.apInBase then
            local totalDmg = baseDmg > 0 and baseDmg or spBonus
            if talentMod > 0 then
                totalDmg = math.floor(totalDmg * (1 + talentMod) + 0.5)
            end
            totalText = ColorText(FormatNumber(totalDmg), c.gold)
        -- For spells with parsed base damage range (e.g. "150 to 200")
        elseif baseDmgMin and baseDmgMax and baseDmgMin ~= baseDmgMax then
            local adjMin = baseDmgMin
            local adjMax = baseDmgMax
            if baseTalentMod > 0 then
                adjMin = adjMin * (1 + baseTalentMod)
                adjMax = adjMax * (1 + baseTalentMod)
            end
            local totalMin = adjMin + spBonus
            local totalMax = adjMax + spBonus
            if talentMod > 0 then
                totalMin = math.floor(totalMin * (1 + talentMod) + 0.5)
                totalMax = math.floor(totalMax * (1 + talentMod) + 0.5)
            end
            totalText = ColorText(FormatNumber(totalMin) .. "-" .. FormatNumber(totalMax), c.gold)
        else
            local adjBase = baseDmg
            if baseTalentMod > 0 then adjBase = adjBase * (1 + baseTalentMod) end
            local totalDmg = adjBase + spBonus
            if talentMod > 0 then
                totalDmg = math.floor(totalDmg * (1 + talentMod) + 0.5)
            end
            totalText = ColorText(FormatNumber(totalDmg), c.gold)
        end
        
        local totalLabel = data.isTotemTick and "Total per Tick" or string.format("Total %s", actionWord)
        local line = string.format("%s: %s %s %s", 
            totalLabel, totalText, schoolText, effectWord)
        tooltip:AddLine(line, c.stats.r, c.stats.g, c.stats.b)
        
        -- Show modifier breakdown if talent modifiers are active
        if DT.DB.showTalentModifiers and #appliedMods > 0 then
            local modParts = {}
            for _, mod in ipairs(appliedMods) do
                local pct = string.format("+%.0f%%", mod.modifier * 100)
                local label = mod.name
                if mod.baseDmgOnly then label = label .. " (base)" end
                if mod.source == "talent" then
                    table.insert(modParts, ColorText(pct, c.gold) .. " " .. label)
                else
                    table.insert(modParts, ColorText(pct, c.spellPower) .. " " .. label)
                end
            end
            tooltip:AddLine("  Modified by: " .. table.concat(modParts, ", "), c.footer.r, c.footer.g, c.footer.b)
        end
        
        -- Dual damage/heal spells (Holy Shock, Holy Nova): show healing total too
        if data.healSp and data.healSp > 0 then
            local healPower = DT.Math:GetHealingPower()
            local healCoeff = data.healSp
            local healBonus = math.floor(healPower * healCoeff + 0.5)
            local healMin, healMax = ParseHealingFromTooltip(tooltip)
            local healTotalText
            if healMin and healMax and healMin ~= healMax then
                local hTotalMin = healMin + healBonus
                local hTotalMax = healMax + healBonus
                if talentMod > 0 then
                    hTotalMin = math.floor(hTotalMin * (1 + talentMod) + 0.5)
                    hTotalMax = math.floor(hTotalMax * (1 + talentMod) + 0.5)
                end
                healTotalText = ColorText(FormatNumber(hTotalMin) .. "-" .. FormatNumber(hTotalMax), c.gold)
            elseif healMin then
                local hTotal = healMin + healBonus
                if talentMod > 0 then
                    hTotal = math.floor(hTotal * (1 + talentMod) + 0.5)
                end
                healTotalText = ColorText(FormatNumber(hTotal), c.gold)
            end
            if healTotalText then
                local schoolText = (data.school or "Holy"):lower()
                local healLine = string.format("Total Healing: %s %s healing", healTotalText, schoolText)
                tooltip:AddLine(healLine, c.stats.r, c.stats.g, c.stats.b)
                if DT.DB.showCoefficients then
                    tooltip:AddLine(string.format("  (%s of Healing Power)",
                        ColorText(string.format("%.0f%%", healCoeff * 100), c.gold)),
                        c.footer.r, c.footer.g, c.footer.b)
                end
            end
        end
    end
    
    -- Ranged weapon DPS display (Shoot/Wand)
    if data.type == "Ranged" and data.wd and data.wd > 0 and wdMin and wdMax and DT.DB.showDPS then
        local rangedSpeed = nil
        if UnitRangedDamage then
            rangedSpeed = select(1, UnitRangedDamage("player"))
        end
        if rangedSpeed and rangedSpeed > 0 then
            local avgDmg = (wdMin + wdMax) / 2
            local rangedDPS = avgDmg / rangedSpeed
            tooltip:AddLine(string.format("Weapon DPS: %s (%.1f avg / %.2fs)",
                ColorText(string.format("%.1f", rangedDPS), c.dps), avgDmg, rangedSpeed),
                c.dps.r, c.dps.g, c.dps.b)
        end
    end
    
    -- DoT coefficient display for hybrid direct+DoT spells (Moonfire, Immolate, Rake, etc.)
    if hasScalingData and DT.DB.showCoefficients then
        if data.dotSp and data.dotSp > 0 then
            local dotStatValue = data.isHeal and DT.Math:GetHealingPower() or DT.Math:GetSpellPower(data.school or "Shadow")
            local dotBonus = math.floor(dotStatValue * data.dotSp + 0.5)
            local dotDuration = data.duration or 0
            local dotLine = string.format("DoT: %s of %s",
                ColorText(string.format("%.0f%%", data.dotSp * 100), c.gold),
                data.isHeal and "Healing Power" or "Spell Power")
            if dotDuration > 0 then
                dotLine = dotLine .. string.format(" (+%s over %ds)",
                    ColorText(tostring(dotBonus), c.gold), dotDuration)
            else
                dotLine = dotLine .. string.format(" (+%s)", ColorText(tostring(dotBonus), c.gold))
            end
            tooltip:AddLine(dotLine, c.footer.r, c.footer.g, c.footer.b)
        end
        if data.dotAp and data.dotAp > 0 then
            local dotStatValue = DT.Math:GetAttackPower()
            local dotBonus = math.floor(dotStatValue * data.dotAp + 0.5)
            local dotDuration = data.duration or 0
            local dotLine = string.format("DoT: %s of Attack Power",
                ColorText(string.format("%.0f%%", data.dotAp * 100), c.gold))
            if dotDuration > 0 then
                dotLine = dotLine .. string.format(" (+%s over %ds)",
                    ColorText(tostring(dotBonus), c.gold), dotDuration)
            else
                dotLine = dotLine .. string.format(" (+%s)", ColorText(tostring(dotBonus), c.gold))
            end
            tooltip:AddLine(dotLine, c.footer.r, c.footer.g, c.footer.b)
        end
    end
    
    -- Bloom heal display for Lifebloom (instant heal when HoT expires)
    if data.bloomSp and data.bloomSp > 0 and data.isHeal then
        local bloomBase = ParseBloomHealFromTooltip(tooltip) or 0
        local bloomStatValue = DT.Math:GetHealingPower()
        local bloomBonus = math.floor(bloomStatValue * data.bloomSp + 0.5)
        if DT.DB.showCoefficients then
            local bloomCoeffLine = string.format("Bloom: %s of Healing Power (+%s)",
                ColorText(string.format("%.1f%%", data.bloomSp * 100), c.gold),
                ColorText(tostring(bloomBonus), c.gold))
            tooltip:AddLine(bloomCoeffLine, c.footer.r, c.footer.g, c.footer.b)
        end
        if DT.DB.showTotal and bloomBase > 0 then
            local bloomTotal = bloomBase + bloomBonus
            local talentMod = 0
            if DT.DB.showTalentModifiers and DT.Modifiers then
                talentMod = DT.Modifiers:GetCombinedModifier(data)
            end
            if talentMod > 0 then
                bloomTotal = math.floor(bloomTotal * (1 + talentMod) + 0.5)
            end
            local schoolText = (data.school or "Nature"):lower()
            local bloomTotalLine = string.format("Total Bloom: %s %s healing",
                ColorText(FormatNumber(bloomTotal), c.gold), schoolText)
            tooltip:AddLine(bloomTotalLine, c.stats.r, c.stats.g, c.stats.b)
        end
    end
    
    -- Per-CP breakdown for combo point finishers
    if data.isFinisher and cpData and DT.DB.showTotal then
        -- Get talent modifier for CP breakdown
        local cpTalentMod = 0
        if DT.DB.showTalentModifiers and DT.Modifiers then
            cpTalentMod = DT.Modifiers:GetCombinedModifier(data)
        end
        
        for cp = 1, 5 do
            if cpData[cp] then
                local cpFinisherCoeff = data.wd or data.ap or 0
                local cpCoeff = cpFinisherCoeff * cp
                local cpTotalMin, cpTotalMax
                if wdMin and wdMax then
                    cpTotalMin = cpData[cp].min + wdMin * cpCoeff
                    cpTotalMax = cpData[cp].max + wdMax * cpCoeff
                else
                    local cpSpBonus = math.floor(statValue * cpCoeff + 0.5)
                    cpTotalMin = cpData[cp].min + cpSpBonus
                    cpTotalMax = cpData[cp].max + cpSpBonus
                end
                if cpTalentMod > 0 then
                    cpTotalMin = cpTotalMin * (1 + cpTalentMod)
                    cpTotalMax = cpTotalMax * (1 + cpTalentMod)
                end
                local marker = (cp == currentCP) and " <<" or ""
                local color = (cp == currentCP) and c.gold or c.base
                local cpLine
                if cpTotalMin == cpTotalMax then
                    cpLine = string.format("  CP %d - %s%s",
                        cp,
                        ColorText(FormatNumber(math.floor(cpTotalMin + 0.5)), color),
                        marker)
                else
                    cpLine = string.format("  CP %d - %s%s",
                        cp,
                        ColorText(string.format("%.0f-%.0f", cpTotalMin, cpTotalMax), color),
                        marker)
                end
                tooltip:AddLine(cpLine, c.footer.r, c.footer.g, c.footer.b)
            end
        end
    end
    
    -- DPS/HPS section (for DoTs/HoTs and base-only DoTs like Rend)
    if (hasScalingData or (baseDmg > 0 and data.duration and data.duration > 0)) and DT.DB.showDPS then
        local adjBase = baseDmg
        local duration = data.duration or 0
        
        -- Apply talent/buff modifiers to DPS calc
        if DT.DB.showTalentModifiers and DT.Modifiers then
            local talentMod, _, baseMod = DT.Modifiers:GetCombinedModifier(data)
            if baseMod > 0 then adjBase = adjBase * (1 + baseMod) end
            local totalDmgCalc = adjBase + spBonus
            if talentMod > 0 then
                totalDmgCalc = math.floor(totalDmgCalc * (1 + talentMod) + 0.5)
            end
            adjBase = totalDmgCalc  -- reuse adjBase as final total for display
        else
            adjBase = baseDmg + spBonus
        end
        local totalDmg = adjBase
        
        if duration > 0 then
            -- "Deals/Heals 198 holy healing over 18 sec."
            local totalGold = ColorText(FormatNumber(totalDmg), c.gold)
            local schoolText = (data.school or "Shadow"):lower()
            local actionText = data.isHeal and "healing" or "damage"
            local dealWord = data.isHeal and "Heals" or "Deals"
            local dealLine = string.format("%s %s %s %s over %d sec.", 
                dealWord, totalGold, schoolText, actionText, duration)
            tooltip:AddLine(dealLine, c.description.r, c.description.g, c.description.b)
            
            -- "(11 Healing/Damage per Second)"
            local dps = totalDmg / duration
            local dpsOrange = ColorText(string.format("%.1f", dps), c.dps)
            local rateWord = data.isHeal and "Healing" or "Damage"
            local dpsLine = string.format("(%s %s per Second)", dpsOrange, rateWord)
            tooltip:AddLine(dpsLine, c.footer.r, c.footer.g, c.footer.b)
        end
        
        -- Heals for damage dealt note (Siphon Life, etc.)
        if data.healsForDamage and duration > 0 then
            tooltip:AddLine("Also heals caster for damage dealt", c.footer.r, c.footer.g, c.footer.b)
        end
    end
    
    -- ========================================================================
    -- SEAL / ENCHANT DPS SECTION
    -- Per-hit (SoR, Flametongue, Frostbrand), PPM (SoC), Proc (Windfury, SoM)
    -- ========================================================================
    if DT.DB.showDPS then
        local hasteSpeed = select(1, UnitAttackSpeed("player")) or 2.0
        local schoolText = (data.school or "Holy"):lower()
        
        -- Per-hit seals/enchants (SoR, Flametongue, Frostbrand)
        -- Base damage already includes weapon speed scaling (baked into tooltip).
        -- We just show the SP DPS contribution at the player's current attack speed.
        DebugPrint("SoR DPS check - hasScalingData:", hasScalingData and "yes" or "no",
            "weaponSpeedScaling:", data.weaponSpeedScaling and "yes" or "no",
            "baseWeaponSpeed:", baseWeaponSpeed or "nil",
            "hasteSpeed:", hasteSpeed or "nil",
            "spBonus:", spBonus)
        if hasScalingData and data.weaponSpeedScaling and hasteSpeed and hasteSpeed > 0 then
            local totalPerHit = baseDmg + spBonus
            local totalDps = totalPerHit / hasteSpeed
            
            tooltip:AddLine(" ")
            tooltip:AddLine(string.format("Total DPS: %s %s (%s base + %s SP / %.2fs attack speed)",
                ColorText(string.format("%.1f", totalDps), c.dps), schoolText,
                ColorText(FormatNumber(baseDmg), c.base), ColorText(FormatNumber(spBonus), c.gold), hasteSpeed),
                c.dps.r, c.dps.g, c.dps.b)
        
        -- PPM-based seals (Seal of Command)
        elseif data.sealPPM and data.weaponDmgCoeff then
            local wdMinVal, wdMaxVal = DT.Math:GetWeaponDamageRange("main")
            local avgWD = ((wdMinVal or 0) + (wdMaxVal or 0)) / 2
            local procDmg = math.floor(avgWD * data.weaponDmgCoeff + spBonus + 0.5)
            
            -- Apply talent/buff modifiers
            if DT.DB.showTalentModifiers and DT.Modifiers then
                local talentMod = DT.Modifiers:GetCombinedModifier(data)
                if talentMod > 0 then
                    procDmg = math.floor(procDmg * (1 + talentMod) + 0.5)
                end
            end
            
            local dps = procDmg * data.sealPPM / 60
            
            tooltip:AddLine(" ")
            tooltip:AddLine(string.format("Per Proc: %s %s (%.0f%% of %d avg WD + %s SP)",
                ColorText(FormatNumber(procDmg), c.gold), schoolText,
                data.weaponDmgCoeff * 100, math.floor(avgWD),
                ColorText(FormatNumber(spBonus), c.gold)),
                c.description.r, c.description.g, c.description.b)
            tooltip:AddLine(string.format("DPS: %s %s (%d procs/min)",
                ColorText(string.format("%.1f", dps), c.dps), schoolText,
                data.sealPPM),
                c.dps.r, c.dps.g, c.dps.b)
        
        -- Per-hit weapon damage seals (Seal of Martyrdom)
        elseif data.sealWeaponDmgCoeff then
            local wdMinVal, wdMaxVal = DT.Math:GetWeaponDamageRange("main")
            local avgWD = ((wdMinVal or 0) + (wdMaxVal or 0)) / 2
            local perHit = math.floor(avgWD * data.sealWeaponDmgCoeff + spBonus + 0.5)
            
            -- Apply talent/buff modifiers
            if DT.DB.showTalentModifiers and DT.Modifiers then
                local talentMod = DT.Modifiers:GetCombinedModifier(data)
                if talentMod > 0 then
                    perHit = math.floor(perHit * (1 + talentMod) + 0.5)
                end
            end
            
            local dps = perHit / hasteSpeed
            
            tooltip:AddLine(" ")
            tooltip:AddLine(string.format("Per Hit: %s %s (%.0f%% of %d avg WD + %s SP)",
                ColorText(FormatNumber(perHit), c.gold), schoolText,
                data.sealWeaponDmgCoeff * 100, math.floor(avgWD),
                ColorText(FormatNumber(spBonus), c.gold)),
                c.description.r, c.description.g, c.description.b)
            tooltip:AddLine(string.format("DPS: %s %s (at %.2fs attack speed)",
                ColorText(string.format("%.1f", dps), c.dps), schoolText, hasteSpeed),
                c.dps.r, c.dps.g, c.dps.b)
        
        -- Proc-based extra attacks (Windfury Weapon)
        elseif data.enchantProcChance and data.enchantExtraSwings and data.bonusAP then
            local wdMinVal, wdMaxVal = DT.Math:GetWeaponDamageRange("main")
            local avgWD = ((wdMinVal or 0) + (wdMaxVal or 0)) / 2
            local weapSpeed = baseWeaponSpeed or hasteSpeed
            local apBonus = data.bonusAP / 14 * weapSpeed
            local swingDmg = math.floor(avgWD + apBonus + 0.5)
            local procDps = data.enchantExtraSwings * swingDmg * data.enchantProcChance / hasteSpeed
            
            tooltip:AddLine(" ")
            tooltip:AddLine(string.format("Per Proc: %d x %s (%d avg WD + %d AP bonus)",
                data.enchantExtraSwings,
                ColorText(FormatNumber(swingDmg), c.gold),
                math.floor(avgWD), math.floor(apBonus)),
                c.description.r, c.description.g, c.description.b)
            tooltip:AddLine(string.format("  (+%d AP \195\151 %.1fs / 14 = +%d per swing)",
                data.bonusAP, weapSpeed, math.floor(apBonus)),
                c.footer.r, c.footer.g, c.footer.b)
            tooltip:AddLine(string.format("DPS: %s (%.0f%% chance, %d swings, %.2fs speed)",
                ColorText(string.format("%.1f", procDps), c.dps),
                data.enchantProcChance * 100, data.enchantExtraSwings, hasteSpeed),
                c.dps.r, c.dps.g, c.dps.b)
        end
    end
    
    -- ========================================================================
    -- THREAT VALUES
    -- ========================================================================
    if DT.DB.showThreat and DT.ThreatData and spellName then
        local threatInfo = DT.ThreatData[spellName]
        if threatInfo then
            local threatColor = c.warning or { r = 1.0, g = 0.5, b = 0.0 }
            if threatInfo.note then
                tooltip:AddLine("Threat: " .. ColorText(threatInfo.note, threatColor), c.footer.r, c.footer.g, c.footer.b)
            else
                -- Compute total damage with talent modifiers applied
                local totalDmg = baseDmg + spBonus
                if DT.DB.showTalentModifiers and DT.Modifiers then
                    local talentMod, _, baseMod = DT.Modifiers:GetCombinedModifier(data)
                    if baseMod > 0 then
                        totalDmg = (baseDmg * (1 + baseMod)) + spBonus
                    end
                    if talentMod > 0 then
                        totalDmg = totalDmg * (1 + talentMod)
                    end
                end
                
                -- Add flat threat bonus (e.g. Heroic Strike +145)
                local totalThreat = totalDmg
                if threatInfo.flat and threatInfo.flat ~= 0 then
                    totalThreat = totalThreat + threatInfo.flat
                end
                
                -- Apply base threat multiplier from ThreatData (e.g. 0.5x for heals)
                if threatInfo.multi and threatInfo.multi ~= 1.0 and threatInfo.multi ~= 0 then
                    totalThreat = totalThreat * threatInfo.multi
                end
                
                -- Apply dynamic threat modifiers (Righteous Fury, Warrior stances, Defiance)
                local threatModParts = {}
                if DT.Modifiers then
                    local threatMod, appliedThreatMods = DT.Modifiers:GetThreatModifier(data)
                    if threatMod ~= 0 then
                        totalThreat = totalThreat * (1 + threatMod)
                        for _, tm in ipairs(appliedThreatMods) do
                            local sign = tm.modifier >= 0 and "+" or ""
                            table.insert(threatModParts, string.format("%s%.0f%% %s", sign, tm.modifier * 100, tm.name))
                        end
                    end
                end
                
                totalThreat = math.floor(totalThreat + 0.5)
                
                -- Build display
                local threatLabel
                if threatInfo.flat and threatInfo.flat ~= 0 then
                    local sign = threatInfo.flat > 0 and "+" or ""
                    threatLabel = string.format("Threat: %s%d bonus (%s total)",
                        sign, threatInfo.flat, ColorText(FormatNumber(totalThreat), threatColor))
                else
                    threatLabel = string.format("Threat: %s",
                        ColorText(FormatNumber(totalThreat), threatColor))
                end
                
                -- Append modifier notes
                if #threatModParts > 0 then
                    threatLabel = threatLabel .. "  (" .. table.concat(threatModParts, ", ") .. ")"
                end
                
                tooltip:AddLine(threatLabel, c.footer.r, c.footer.g, c.footer.b)
            end
        end
    end
    
    -- ========================================================================
    -- MANA / RANGE / CAST / COOLDOWN (with TGA icons)
    -- ========================================================================
    do
        local hasMeta = (manaCost and manaCost > 0) or (meta and (meta.range or meta.castTime or meta.cooldown))
        if hasMeta then
            tooltip:AddLine(" ")
            
            -- Mana and Range on separate lines
            local manaStr = (manaCost and manaCost > 0) and (icons.mana .. " " .. manaCost .. " Mana") or nil
            local rangeStr = meta and meta.range and (icons.range .. " " .. meta.range) or nil
            
            if manaStr then
                tooltip:AddLine(manaStr, c.mana.r, c.mana.g, c.mana.b)
            end
            if rangeStr then
                tooltip:AddLine(rangeStr, c.range.r, c.range.g, c.range.b)
            end
            
            -- Row 2: Cast time (left) + Cooldown (right)
            local castStr  = meta and meta.castTime or nil
            local cdStr    = meta and meta.cooldown or nil
            
            if castStr and cdStr then
                local cdColor = c.cooldown or c.castTime
                tooltip:AddDoubleLine(castStr, cdStr, c.castTime.r, c.castTime.g, c.castTime.b, cdColor.r, cdColor.g, cdColor.b)
            elseif castStr then
                tooltip:AddLine(castStr, c.castTime.r, c.castTime.g, c.castTime.b)
            elseif cdStr then
                local cdColor = c.cooldown or c.castTime
                tooltip:AddLine(cdStr, cdColor.r, cdColor.g, cdColor.b)
            end
        end
    end
    
    -- Mana Efficiency section (includes downrank penalty in calculation)
    if hasScalingData and DT.DB.showManaEfficiency and manaCost and manaCost > 0 then
        local totalDmg = baseDmg + spBonus
        
        -- Add DoT damage for hybrid direct+DoT spells (Moonfire, Flame Shock, Immolate, etc.)
        local dotTotal = 0
        local hasDotComponent = false
        if data.dotSp and data.dotSp > 0 and (data.sp and data.sp > 0) then
            local dotBaseDmg = ParseDotBaseDamageFromTooltip(tooltip) or 0
            local dotStatValue = data.isHeal and DT.Math:GetHealingPower() or DT.Math:GetSpellPower(data.school or "Shadow")
            local dotBonus = math.floor(dotStatValue * data.dotSp + 0.5)
            dotTotal = dotBaseDmg + dotBonus
            hasDotComponent = true
        end
        if data.dotAp and data.dotAp > 0 and (data.ap and data.ap > 0) then
            local dotBaseDmg = ParseDotBaseDamageFromTooltip(tooltip) or 0
            local dotStatValue = DT.Math:GetAttackPower()
            local dotBonus = math.floor(dotStatValue * data.dotAp + 0.5)
            dotTotal = dotTotal + dotBaseDmg + dotBonus
            hasDotComponent = true
        end
        
        -- Add HoT healing for hybrid direct+HoT heal spells (Regrowth, etc.)
        local hotTotal = 0
        local hasHotComponent = false
        if data.hotSp and data.hotSp > 0 and data.isHeal then
            local hotBaseHeal = ParseHotBaseHealingFromTooltip(tooltip) or 0
            local hotStatValue = DT.Math:GetHealingPower()
            local hotBonus = math.floor(hotStatValue * data.hotSp + 0.5)
            hotTotal = hotBaseHeal + hotBonus
            hasHotComponent = true
        end
        
        -- Add bloom heal for Lifebloom
        local bloomTotal = 0
        local hasBloomComponent = false
        if data.bloomSp and data.bloomSp > 0 and data.isHeal then
            local bloomBase = ParseBloomHealFromTooltip(tooltip) or 0
            local bloomStatValue = DT.Math:GetHealingPower()
            local bloomBonus = math.floor(bloomStatValue * data.bloomSp + 0.5)
            bloomTotal = bloomBase + bloomBonus
            hasBloomComponent = true
        end
        
        local combinedDmg = totalDmg + dotTotal + hotTotal + bloomTotal
        
        -- Apply downrank penalty to the total used for efficiency calc
        local rankLearnLevel = data.level
        if rankLearnLevel then
            local playerLevel = UnitLevel("player") or 70
            if rankLearnLevel + 6 < playerLevel then
                local penaltyCoeff = math.min(1.0, (rankLearnLevel + 6) / playerLevel)
                combinedDmg = combinedDmg * penaltyCoeff
            end
        end
        
        tooltip:AddLine(" ")
        
        -- Damage per Mana
        local dpm = combinedDmg / manaCost
        local dpmText = ColorText(string.format("%.2f", dpm), c.efficiency)
        local effWord = data.isHeal and "healing" or "damage"
        local effLine
        if hasDotComponent then
            effLine = string.format("Efficiency: %s %s per Mana (direct + DoT)", dpmText, effWord)
        elseif hasHotComponent and hasBloomComponent then
            effLine = string.format("Efficiency: %s %s per Mana (HoT + Bloom)", dpmText, effWord)
        elseif hasHotComponent then
            effLine = string.format("Efficiency: %s %s per Mana (direct + HoT)", dpmText, effWord)
        elseif hasBloomComponent then
            effLine = string.format("Efficiency: %s %s per Mana (direct + Bloom)", dpmText, effWord)
        else
            effLine = string.format("Efficiency: %s %s per Mana", dpmText, effWord)
        end
        tooltip:AddLine(effLine, c.efficiency.r, c.efficiency.g, c.efficiency.b)
        
        -- Healing per Mana for dual damage/heal spells (Holy Shock, Holy Nova)
        if data.healSp and data.healSp > 0 then
            local healPower = DT.Math:GetHealingPower()
            local healCoeff = data.healSp
            local healBonus = math.floor(healPower * healCoeff + 0.5)
            local healMin, healMax = ParseHealingFromTooltip(tooltip)
            if healMin then
                local healBase = (healMax and healMax ~= healMin) and ((healMin + healMax) / 2) or healMin
                local totalHeal = healBase + healBonus
                -- Apply same downrank penalty
                local rankLearnLevel = data.level
                if rankLearnLevel then
                    local playerLevel = UnitLevel("player") or 70
                    if rankLearnLevel + 6 < playerLevel then
                        local penaltyCoeff = math.min(1.0, (rankLearnLevel + 6) / playerLevel)
                        totalHeal = totalHeal * penaltyCoeff
                    end
                end
                local hpm = totalHeal / manaCost
                local hpmText = ColorText(string.format("%.2f", hpm), c.efficiency)
                tooltip:AddLine(string.format("Efficiency: %s healing per Mana", hpmText), c.efficiency.r, c.efficiency.g, c.efficiency.b)
            end
        end
    end
    
    -- AoE section: show per-target and total for multiple mobs
    if data.isAoE then
        local totalDmg = baseDmg + spBonus
        tooltip:AddLine(" ")
        
        local perTargetText = ColorText(FormatNumber(totalDmg), c.gold)
        tooltip:AddLine("Per Target: " .. perTargetText .. " " .. effectWord, 
            c.description.r, c.description.g, c.description.b)
        
        -- Show totals for targets up to maxTargets
        local maxT = data.maxTargets or 10
        local targets
        if maxT >= 999 then
            -- Unlimited targets
            targets = {3, 5, 10, 20}
        elseif maxT <= 5 then
            targets = {}
            for i = 2, maxT do
                targets[#targets + 1] = i
            end
        else
            targets = {3, 5, math.min(10, maxT)}
        end
        
        local totals = {}
        for _, count in ipairs(targets) do
            totals[#totals + 1] = ColorText(FormatNumber(totalDmg * count), c.gold) .. " (" .. count .. ")"
        end
        
        if #totals > 0 then
            tooltip:AddLine("Multi-target: " .. table.concat(totals, " / "), 
                c.stats.r, c.stats.g, c.stats.b)
        end
        
        -- Show max targets if limited
        if data.maxTargets and data.maxTargets < 999 then
            tooltip:AddLine("Max Targets: " .. data.maxTargets, 
                c.footer.r, c.footer.g, c.footer.b)
        end
    end
    
    -- Chain spell section: show per-bounce breakdown with diminishing returns
    if data.chainTargets and data.chainReduction and DT.DB.showTotal then
        local singleTotal = baseDmg + spBonus
        -- Apply talent modifiers to single target
        if DT.DB.showTalentModifiers and DT.Modifiers then
            local talentMod = DT.Modifiers:GetCombinedModifier(data)
            if talentMod > 0 then
                singleTotal = math.floor(singleTotal * (1 + talentMod) + 0.5)
            end
        end
        
        tooltip:AddLine(" ")
        local chainLabel = data.isHeal and "Chain Healing" or "Chain Damage"
        local reduction = data.chainReduction
        local allTargetsTotal = 0
        local bounceTexts = {}
        for t = 1, data.chainTargets do
            local mult = (1 - reduction) ^ (t - 1)
            local targetDmg = math.floor(singleTotal * mult + 0.5)
            allTargetsTotal = allTargetsTotal + targetDmg
            bounceTexts[#bounceTexts + 1] = ColorText(FormatNumber(targetDmg), c.gold)
        end
        
        tooltip:AddLine(string.format("%s (%d targets, -%d%% per jump):",
            chainLabel, data.chainTargets, data.chainReduction * 100),
            c.description.r, c.description.g, c.description.b)
        tooltip:AddLine("  " .. table.concat(bounceTexts, " \226\134\146 "),
            c.footer.r, c.footer.g, c.footer.b)
        tooltip:AddLine(string.format("  All targets: %s %s",
            ColorText(FormatNumber(allTargetsTotal), c.gold), effectWord),
            c.stats.r, c.stats.g, c.stats.b)
    end
    
    -- Totem tick rate section: show per-tick and per-second values
    if data.isTotemTick and data.tickInterval and data.tickInterval > 0 and DT.DB.showDPS then
        local tickTotal = baseDmg + spBonus
        -- Apply talent modifiers
        if DT.DB.showTalentModifiers and DT.Modifiers then
            local talentMod = DT.Modifiers:GetCombinedModifier(data)
            if talentMod > 0 then
                tickTotal = math.floor(tickTotal * (1 + talentMod) + 0.5)
            end
        end
        
        local perSecond = tickTotal / data.tickInterval
        if data.isManaRestore then
            tooltip:AddLine(string.format("Mana per Second: %s",
                ColorText(string.format("%.1f", perSecond), c.dps)),
                c.dps.r, c.dps.g, c.dps.b)
        elseif data.isHeal then
            tooltip:AddLine(string.format("Healing per Second: %s",
                ColorText(string.format("%.1f", perSecond), c.dps)),
                c.dps.r, c.dps.g, c.dps.b)
        end
    end
    
    -- Downrank penalty warning
    if DT.DB.showDownrankPenalty then
        -- Use the rank and level data directly from spell data (now each spell ID has its own entry)
        local spellRank = data.rank
        local rankLearnLevel = data.level
        
        -- TBC downrank penalty formula:
        -- Effective coefficient = BaseCoeff * ((SpellLearnLevel + 6) / PlayerLevel)
        -- SpellLearnLevel is when that specific rank was learned
        if spellRank and rankLearnLevel then
            local playerLevel = UnitLevel("player") or 70
            
            -- Get max rank available at player's current level (like HintMeRank)
            local maxAtLevel = spellRank
            if DT.GetHighestTrainableRank and data.name then
                local highest = DT.GetHighestTrainableRank(data.name, 0, playerLevel)
                if highest then maxAtLevel = highest end
            end
            
            -- Only show penalty if the rank's learn level is significantly below player level
            if rankLearnLevel + 6 < playerLevel then
                -- TBC formula: (RankLearnLevel + 6) / PlayerLevel
                local effectiveCoeff = math.min(1.0, (rankLearnLevel + 6) / playerLevel)
                local penaltyPct = math.floor((1 - effectiveCoeff) * 100)
                
                if penaltyPct > 0 then
                    tooltip:AddLine(" ")
                    local penaltyText = ColorText(string.format("-%d%%", penaltyPct), c.warning)
                    local levelText = ColorText("trained L" .. rankLearnLevel, c.warning)
                    local rankText = ColorText("Rank " .. spellRank .. "/" .. maxAtLevel, c.warning)
                    tooltip:AddLine("Downrank: " .. rankText .. " " .. levelText .. " (" .. penaltyText .. " coeff)", 
                        c.warning.r, c.warning.g, c.warning.b)
                end
            elseif spellRank < maxAtLevel then
                -- Only show lower rank note if there's actually a higher rank available at this level
                tooltip:AddLine(" ")
                local levelText = ColorText("trained L" .. rankLearnLevel, c.stats)
                local rankText = ColorText("Rank " .. spellRank .. "/" .. maxAtLevel, c.stats)
                tooltip:AddLine("Using lower rank: " .. rankText .. " " .. levelText, 
                    c.footer.r, c.footer.g, c.footer.b)
            end
        end
    end
    
    -- Optimization advice
    if DT.DB.showOptimization then
        -- Check if there's a higher rank available that's trainable at player's current level
        local playerLevel = UnitLevel("player") or 70
        local highestTrainableRank, trainLevel = DT.GetHighestTrainableRank(data.name, data.rank, playerLevel)
        
        if highestTrainableRank and highestTrainableRank > (data.rank or 0) then
            tooltip:AddLine(" ")
            local moreWhat = data.isHeal and "healing" or "damage"
            if data.type == "Shield" then moreWhat = "absorb" end
            local tipText = string.format("Tip: Rank %d available (L%d trainer) for more %s", 
                highestTrainableRank, trainLevel, moreWhat)
            tooltip:AddLine(tipText, c.efficiency.r, c.efficiency.g, c.efficiency.b)
        end
    end
    
    -- Note: Spell ID is NOT shown here to avoid duplicates
    -- The caller (RebuildTooltip flow) handles Spell ID display

    -- ========================================================================
    -- EXPORT: Store last computed spell info for other addons (DamageTaken)
    -- ========================================================================
    if hasScalingData then
        local talentMod, _, baseMod = 0, {}, 0
        if DT.DB.showTalentModifiers and DT.Modifiers then
            talentMod, _, baseMod = DT.Modifiers:GetCombinedModifier(data)
        end
        local exportMin, exportMax
        if wdMin and wdMax and data.wd and data.wd > 0 then
            exportMin = (baseDmg or 0) + wdMin * coefficient
            exportMax = (baseDmg or 0) + wdMax * coefficient
        elseif baseDmgMin and baseDmgMax and baseDmgMin ~= baseDmgMax then
            local adjMin = baseDmgMin
            local adjMax = baseDmgMax
            if baseMod > 0 then
                adjMin = adjMin * (1 + baseMod)
                adjMax = adjMax * (1 + baseMod)
            end
            exportMin = adjMin + spBonus
            exportMax = adjMax + spBonus
        else
            local adjBase = baseDmg
            if baseMod > 0 then adjBase = adjBase * (1 + baseMod) end
            exportMin = adjBase + spBonus
            exportMax = exportMin
        end
        if talentMod > 0 then
            exportMin = exportMin * (1 + talentMod)
            exportMax = exportMax * (1 + talentMod)
        end
        DT.LastSpellInfo = {
            spellID    = spellID,
            spellName  = data.name or spellName,
            isHeal     = data.isHeal or false,
            school     = data.school or "Shadow",
            totalMin   = exportMin,
            totalMax   = exportMax,
            totalAvg   = (exportMin + exportMax) / 2,
            duration   = data.duration or 0,
            coefficient = coefficient,
            time       = GetTime(),
        }
    end
end

-- ============================================================================
-- SCALING INFO FOR REBUILT TOOLTIPS
-- Adds scaling data to already-rebuilt tooltip (doesn't add spell ID - caller does)
-- ============================================================================

local function AddScalingInfoToRebuilt(tooltip, spellID, manaCost, spellName, data, info)
    if not DT.DB.enabled then return end
    if not DT.Math then return end
    if not data then return end
    
    -- Get theme colors
    local c = DT.Style.colors
    if DT.Themes then
        local theme = DT.Themes.GetCurrent()
        if theme and theme.text then
            c = setmetatable({}, {__index = function(t, k)
                return theme.text[k] or DT.Style.colors[k]
            end})
        end
    end
    
    -- Parse base damage from description
    local baseDmgMin, baseDmgMax = nil, nil
    for _, line in ipairs(info.description) do
        local cleanText = line:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
        
        local min, max = cleanText:match("(%d+)%s*to%s*(%d+)")
        if min and max then
            baseDmgMin = tonumber(min)
            baseDmgMax = tonumber(max)
            break
        end
        
        -- Totem tick pattern: "for X every Y seconds" (Healing Stream, Mana Spring)
        local totemTickVal = cleanText:match("for%s+(%d+)%s+every")
        if totemTickVal then
            baseDmgMin = tonumber(totemTickVal)
            baseDmgMax = baseDmgMin
            break
        end
        
        local hotValue = cleanText:match("for%s+(%d+)%s+over") or cleanText:match("for%s+(%d+).-over%s+%d+")
        if hotValue then
            baseDmgMin = tonumber(hotValue)
            baseDmgMax = baseDmgMin
            break
        end
        
        local causingValue = cleanText:match("[Cc]ausing%s+(%d+)")
        if causingValue then
            baseDmgMin = tonumber(causingValue)
            baseDmgMax = baseDmgMin
            break
        end
        
        -- Skip "deals 110%" (percentage = coefficient, not base damage)
        local single = nil
        local dealMatch = cleanText:match("[Dd]eals%s+(%d+)")
        if dealMatch and not cleanText:match("[Dd]eals%s+%d+%%") then
            single = dealMatch
        end
        single = single or cleanText:match("[Cc]auses%s+(%d+)")
        if single then
            baseDmgMin = tonumber(single)
            baseDmgMax = baseDmgMin
            break
        end
        
        -- Weapon damage flat bonus patterns:
        local wdBonus = cleanText:match("increases.-damage%s+by%s+(%d+)")
            or cleanText:match("additional%s+(%d+)%s+damage")
            or cleanText:match("bonus%s+(%d+)")
            or cleanText:match("damage%s+plus%s+(%d+)")
            or cleanText:match("in%s+addition%s+to%s+(%d+)")
            or cleanText:match("an%s+extra%s+(%d+)")
        if wdBonus then
            baseDmgMin = tonumber(wdBonus)
            baseDmgMax = baseDmgMin
            break
        end
        
        local genericDot = cleanText:match("(%d+)%s+%a+%s+damage%s+over") or cleanText:match("(%d+)%s+%a+%s+healing%s+over")
        if genericDot then
            baseDmgMin = tonumber(genericDot)
            baseDmgMax = baseDmgMin
            break
        end
        
        -- Mana restore patterns: "restore 46 ... Mana" (Seal of Wisdom, Mana Spring Totem)
        local restoreVal = cleanText:match("[Rr]estores?%s+(%d+).-[Mm]ana")
        if restoreVal then
            baseDmgMin = tonumber(restoreVal)
            baseDmgMax = baseDmgMin
            break
        end
        
        -- Healing patterns: require space after heals? to avoid matching "health" / "Healing"
        local healFor = cleanText:match("[Hh]eals?%s.-for%s+(%d+)")
        if healFor then
            baseDmgMin = tonumber(healFor)
            baseDmgMax = baseDmgMin
            break
        end
        
        -- "Heals X" or "Heal X" without "for"
        local healDirect = cleanText:match("[Hh]eals?%s+(%d+)")
        if healDirect then
            baseDmgMin = tonumber(healDirect)
            baseDmgMax = baseDmgMin
            break
        end
        
        -- Absorb patterns for shields
        local absorb = cleanText:match("[Aa]bsorbs?%s+(%d+)") or cleanText:match("[Aa]bsorbing%s+(%d+)")
        if absorb then
            baseDmgMin = tonumber(absorb)
            baseDmgMax = baseDmgMin
            break
        end
        
        -- Last resort: bare number followed by damage/healing keyword
        local bareNum = cleanText:match("(%d+)%s+%a*%s*[Dd]amage") or cleanText:match("(%d+)%s+%a*%s*[Hh]ealing")
        if bareNum then
            baseDmgMin = tonumber(bareNum)
            baseDmgMax = baseDmgMin
            break
        end
    end
    
    local baseDmg = 0
    local baseDmgText = "???"
    
    if baseDmgMin and baseDmgMax then
        if baseDmgMin == baseDmgMax then
            baseDmg = baseDmgMin
            baseDmgText = tostring(baseDmgMin)
        else
            baseDmg = (baseDmgMin + baseDmgMax) / 2
            baseDmgText = baseDmgMin .. "-" .. baseDmgMax
        end
    elseif data.baseDamage then
        baseDmg = data.baseDamage
        baseDmgText = tostring(data.baseDamage)
    elseif data.wd and data.wd > 0 then
        -- For weapon damage spells with no flat bonus in tooltip, default to 0
        baseDmg = 0
        baseDmgText = "0"
    end
    
    -- Get player/pet stats
    local statValue = 0
    local statName = ""
    local coefficient = 0
    local isNormalized = nil
    local normSpeed = nil
    local petDmgMin, petDmgMax, petDmgAvg = 0, 0, 0
    local wdMin, wdMax = nil, nil  -- weapon damage min/max for range display
    
    if data.isPet then
        -- PET SPELLS: Use pet stats instead of player stats
        if data.sp and data.sp > 0 then
            statValue = DT.Math:GetPetSpellPower()
            statName = "Pet Spell Power"
            coefficient = data.sp
        elseif data.wd and data.wd > 0 then
            petDmgMin, petDmgMax, petDmgAvg = DT.Math:GetPetDamage()
            statValue = petDmgAvg
            statName = "Pet Melee"
            coefficient = data.wd
        end
    elseif data.isHeal then
        statValue = DT.Math:GetHealingPower()
        statName = "Healing Power"
        coefficient = data.sp or 0
    elseif data.sp and data.sp > 0 then
        statValue = DT.Math:GetSpellPower(data.school or "Shadow")
        statName = "Spell Power"
        coefficient = data.sp
    elseif data.wd and data.wd > 0 then
        isNormalized = DT.Math:IsNormalized(spellName or (data.name or ""))
        if isNormalized == false then
            -- Unnormalized: use actual weapon min/max
            if data.type == "Ranged" then
                if UnitRangedDamage then
                    local speed, minD, maxD = UnitRangedDamage("player")
                    wdMin = minD or 0
                    wdMax = maxD or 0
                else
                    wdMin, wdMax = 0, 0
                end
                statValue = (wdMin + wdMax) / 2
                statName = "Weapon Damage"
            else
                wdMin, wdMax = DT.Math:GetWeaponDamageRange("main")
                -- Dual-wield: add off-hand weapon damage if OH equipped
                if data.isDualWield then
                    local ohMin, ohMax = DT.Math:GetWeaponDamageRange("off")
                    if ohMin and ohMax and ohMin > 0 then
                        wdMin = (wdMin or 0) + ohMin
                        wdMax = (wdMax or 0) + ohMax
                        statName = "Weapon Damage (MH+OH)"
                    else
                        statName = "Weapon Damage"
                    end
                else
                    statName = "Weapon Damage"
                end
                statValue = (wdMin + wdMax) / 2
            end
        else
            -- Normalized (true) or unknown (nil) — default to normalized
            if data.type == "Ranged" then
                local normAvg, normMin, normMax, ns = DT.Math:GetNormalizedWeaponDamage("ranged")
                statValue = normAvg
                wdMin = normMin
                wdMax = normMax
                normSpeed = ns
                statName = "Normalized WD"
            else
                local normAvg, normMin, normMax, ns = DT.Math:GetNormalizedWeaponDamage("main")
                statValue = normAvg
                wdMin = normMin
                wdMax = normMax
                normSpeed = ns
                -- Dual-wield: add off-hand normalized weapon damage if OH equipped
                if data.isDualWield then
                    local ohAvg, ohMin, ohMax = DT.Math:GetNormalizedWeaponDamage("off")
                    if ohAvg and ohAvg > 0 then
                        statValue = statValue + ohAvg
                        wdMin = (wdMin or 0) + (ohMin or 0)
                        wdMax = (wdMax or 0) + (ohMax or 0)
                        statName = "Normalized WD (MH+OH)"
                    else
                        statName = "Normalized WD"
                    end
                else
                    statName = "Normalized WD"
                end
            end
            isNormalized = true
        end
        coefficient = data.wd
    elseif data.ap and data.ap > 0 then
        statValue = DT.Math:GetAttackPower()
        statName = "Attack Power"
        coefficient = data.ap
    end
    
    -- Combo point finisher handling: scale coefficient and override base damage per CP
    local cpData = nil
    local currentCP = 0
    if data.isFinisher then
        cpData = ParseComboPointDamage(tooltip)
        currentCP = GetComboPoints("player", "target") or 0
        if currentCP < 1 then currentCP = 5 end  -- Default to 5 CP when no target
        
        if cpData then
            -- Override base damage with CP-appropriate values
            if cpData[currentCP] then
                baseDmgMin = cpData[currentCP].min
                baseDmgMax = cpData[currentCP].max
                baseDmg = (baseDmgMin + baseDmgMax) / 2
                baseDmgText = baseDmgMin .. "-" .. baseDmgMax
            end
        end
        
        -- Scale coefficient by combo points (supports both WD and AP finishers)
        local finisherCoeff = data.wd or data.ap or 0
        coefficient = finisherCoeff * currentCP
    end
    
    -- Weapon speed scaling for Proc/Enchant spells (SoR, Flametongue, etc.)
    local baseWeaponSpeed = nil
    local effectiveCoefficient = coefficient
    if data.weaponSpeedScaling and data.sp and data.sp > 0 then
        baseWeaponSpeed = DT.Math:GetBaseWeaponSpeed("main")
    end
    
    local spBonus = math.floor(statValue * coefficient + 0.5)
    local hasBaseOnly = baseDmg > 0 and spBonus <= 0 and coefficient <= 0
    local hasThreat = data.threat and data.threat > 0
    if spBonus <= 0 and coefficient <= 0 and not hasBaseOnly and not hasThreat then return end
    
    -- Add spacing before scaling section
    tooltip:AddLine(" ")
    
    local actionWord = data.isHeal and "Healing" or "Damage"
    if data.type == "Shield" then actionWord = "Absorb" end
    
    -- Effect word for sentences (lowercase)
    local effectWord = data.isHeal and "healing" or "damage"
    if data.type == "Shield" then effectWord = "absorb" end
    
    -- Line 1: Base + Coefficient
    -- For apInBase spells (Bloodthirst etc.), tooltip already shows computed AP damage, skip base
    if DT.DB.showBaseDamage and not data.apInBase then
        if data.isManaRestore then
            -- Mana restore: totem tick vs seal proc
            local manaLabel = data.isTotemTick and "Mana per Tick" or "Mana per Proc"
            local line1 = string.format("%s: %s", manaLabel, ColorText(baseDmgText, c.base))
            tooltip:AddLine(line1, c.description.r, c.description.g, c.description.b)
        elseif data.isTotemTick then
            -- Totem tick heals (Healing Stream Totem)
            local tickLabel = data.isHeal and "Heal per Tick" or "Base per Tick"
            local parts = {}
            table.insert(parts, ColorText(baseDmgText, c.base))
            if DT.DB.showCoefficients and coefficient > 0 then
                table.insert(parts, ColorText(string.format("%.0f%%", coefficient * 100), c.gold) .. " of " .. statName)
            end
            local line1 = string.format("%s: %s", tickLabel, table.concat(parts, " + "))
            tooltip:AddLine(line1, c.description.r, c.description.g, c.description.b)
        else
            local parts = {}
            -- Skip "0" for weapon damage spells with no flat bonus (e.g. Stormstrike)
            if not (data.wd and data.wd > 0 and baseDmg == 0) then
                table.insert(parts, ColorText(baseDmgText, c.base))
            end
            if DT.DB.showCoefficients then
                table.insert(parts, ColorText(string.format("%.0f%%", coefficient * 100), c.gold) .. " of " .. statName)
            end
            if #parts > 0 then
                local line1 = string.format("Base: %s", table.concat(parts, " + "))
                tooltip:AddLine(line1, c.description.r, c.description.g, c.description.b)
            end
        end
    elseif DT.DB.showCoefficients then
        -- Show only the coefficient line without base damage
        local line1 = string.format("Coefficient: %s of %s", 
            ColorText(string.format("%.0f%%", coefficient * 100), c.gold), statName)
        tooltip:AddLine(line1, c.description.r, c.description.g, c.description.b)
    end
    
    -- Show normalization info for weapon damage spells
    if isNormalized ~= nil and DT.DB.showCoefficients then
        if isNormalized then
            local normLabel = string.format("(Normalized: %.1f speed)", normSpeed or 0)
            tooltip:AddLine(normLabel, c.footer.r, c.footer.g, c.footer.b)
        else
            tooltip:AddLine("(Uses actual weapon speed)", c.footer.r, c.footer.g, c.footer.b)
        end
    end
    
    -- Threat display for abilities with flat threat (Sunder Armor, etc.)
    if hasThreat then
        local baseThreat = data.threat
        local threatMod, appliedThreatMods = 0, {}
        if DT.DB.showTalentModifiers and DT.Modifiers and DT.Modifiers.GetThreatModifier then
            threatMod, appliedThreatMods = DT.Modifiers:GetThreatModifier(data)
        end
        local finalThreat = baseThreat * (1 + threatMod)
        local threatText = ColorText(string.format("%.0f", finalThreat), c.gold)
        local threatLine = string.format("Threat: %s", threatText)
        tooltip:AddLine(threatLine, c.description.r, c.description.g, c.description.b)
        -- Show threat modifier breakdown
        if DT.DB.showTalentModifiers and #appliedThreatMods > 0 then
            local modParts = {}
            for _, mod in ipairs(appliedThreatMods) do
                local sign = mod.modifier >= 0 and "+" or ""
                table.insert(modParts, string.format("%s%s: %s%.0f%%", 
                    mod.source == "talent" and "  " or "", mod.name, sign, mod.modifier * 100))
            end
            for _, part in ipairs(modParts) do
                tooltip:AddLine(part, c.footer.r, c.footer.g, c.footer.b)
            end
        end
    end
    
    -- Show pet melee damage range for pet melee abilities
    if data.isPet and data.wd and data.wd > 0 and petDmgMin > 0 then
        local petLabel = string.format("Pet Melee: %s",
            ColorText(string.format("%d-%d", petDmgMin, petDmgMax), c.base))
        tooltip:AddLine(petLabel, c.footer.r, c.footer.g, c.footer.b)
    elseif data.isPet and data.sp and data.sp > 0 then
        local petSP = DT.Math:GetPetSpellPower()
        if petSP > 0 then
            tooltip:AddLine(string.format("Pet Spell Power: %s",
                ColorText(tostring(petSP), c.base)), c.footer.r, c.footer.g, c.footer.b)
        end
    end
    
    -- Total line with range
    if DT.DB.showTotal then
        local schoolText = (data.school or "Shadow"):lower()
        local totalText
        
        -- Get talent/buff modifiers
        local talentMod, appliedMods, baseTalentMod = 0, {}, 0
        if DT.DB.showTalentModifiers and DT.Modifiers then
            talentMod, appliedMods, baseTalentMod = DT.Modifiers:GetCombinedModifier(data)
        end
        
        -- For weapon damage spells: total = flatBonus + (weaponDmg * coeff)
        if wdMin and wdMax and data.wd and data.wd > 0 then
            local flatBonus = baseDmg or 0
            local totalMin = flatBonus + wdMin * coefficient
            local totalMax = flatBonus + wdMax * coefficient
            if talentMod > 0 then
                totalMin = totalMin * (1 + talentMod)
                totalMax = totalMax * (1 + talentMod)
            end
            totalText = ColorText(string.format("%.1f-%.1f", totalMin, totalMax), c.gold)
        -- For AP spells where tooltip already shows computed damage (Bloodthirst, Victory Rush)
        elseif data.apInBase then
            local totalDmg = baseDmg > 0 and baseDmg or spBonus
            if talentMod > 0 then
                totalDmg = math.floor(totalDmg * (1 + talentMod) + 0.5)
            end
            totalText = ColorText(FormatNumber(totalDmg), c.gold)
        elseif baseDmgMin and baseDmgMax and baseDmgMin ~= baseDmgMax then
            local adjMin = baseDmgMin
            local adjMax = baseDmgMax
            if baseTalentMod > 0 then
                adjMin = adjMin * (1 + baseTalentMod)
                adjMax = adjMax * (1 + baseTalentMod)
            end
            local totalMin = adjMin + spBonus
            local totalMax = adjMax + spBonus
            if talentMod > 0 then
                totalMin = math.floor(totalMin * (1 + talentMod) + 0.5)
                totalMax = math.floor(totalMax * (1 + talentMod) + 0.5)
            end
            totalText = ColorText(FormatNumber(totalMin) .. "-" .. FormatNumber(totalMax), c.gold)
        else
            local adjBase = baseDmg
            if baseTalentMod > 0 then adjBase = adjBase * (1 + baseTalentMod) end
            local totalDmg = adjBase + spBonus
            if talentMod > 0 then
                totalDmg = math.floor(totalDmg * (1 + talentMod) + 0.5)
            end
            totalText = ColorText(FormatNumber(totalDmg), c.gold)
        end
        
        local totalLabel = data.isTotemTick and "Total per Tick" or "Total"
        local line = string.format("%s: %s %s %s", totalLabel, totalText, schoolText, effectWord)
        tooltip:AddLine(line, c.stats.r, c.stats.g, c.stats.b)
        
        -- Dual damage/heal spells (Holy Shock, Holy Nova): show healing total too
        if data.healSp and data.healSp > 0 then
            local healPower = DT.Math:GetHealingPower()
            local healCoeff = data.healSp
            local healBonus = math.floor(healPower * healCoeff + 0.5)
            local healMin, healMax = ParseHealingFromLines(info.description or {})
            local healTotalText
            if healMin and healMax and healMin ~= healMax then
                local hTotalMin = healMin + healBonus
                local hTotalMax = healMax + healBonus
                if talentMod > 0 then
                    hTotalMin = math.floor(hTotalMin * (1 + talentMod) + 0.5)
                    hTotalMax = math.floor(hTotalMax * (1 + talentMod) + 0.5)
                end
                healTotalText = ColorText(FormatNumber(hTotalMin) .. "-" .. FormatNumber(hTotalMax), c.gold)
            elseif healMin then
                local hTotal = healMin + healBonus
                if talentMod > 0 then
                    hTotal = math.floor(hTotal * (1 + talentMod) + 0.5)
                end
                healTotalText = ColorText(FormatNumber(hTotal), c.gold)
            end
            if healTotalText then
                local hSchoolText = (data.school or "Holy"):lower()
                local healLine = string.format("Total Healing: %s %s healing", healTotalText, hSchoolText)
                tooltip:AddLine(healLine, c.stats.r, c.stats.g, c.stats.b)
                if DT.DB.showCoefficients then
                    tooltip:AddLine(string.format("  (%s of Healing Power)",
                        ColorText(string.format("%.0f%%", healCoeff * 100), c.gold)),
                        c.footer.r, c.footer.g, c.footer.b)
                end
            end
        end
    end
    
    -- Ranged weapon DPS display (Shoot/Wand)
    if data.type == "Ranged" and data.wd and data.wd > 0 and wdMin and wdMax and DT.DB.showDPS then
        local rangedSpeed = nil
        if UnitRangedDamage then
            rangedSpeed = select(1, UnitRangedDamage("player"))
        end
        if rangedSpeed and rangedSpeed > 0 then
            local avgDmg = (wdMin + wdMax) / 2
            local rangedDPS = avgDmg / rangedSpeed
            tooltip:AddLine(string.format("Weapon DPS: %s (%.1f avg / %.2fs)",
                ColorText(string.format("%.1f", rangedDPS), c.dps), avgDmg, rangedSpeed),
                c.dps.r, c.dps.g, c.dps.b)
        end
    end
    
    -- DoT coefficient display for hybrid direct+DoT spells (Moonfire, Immolate, Rake, etc.)
    if DT.DB.showCoefficients then
        if data.dotSp and data.dotSp > 0 then
            local dotStatValue = data.isHeal and DT.Math:GetHealingPower() or DT.Math:GetSpellPower(data.school or "Shadow")
            local dotBonus = math.floor(dotStatValue * data.dotSp + 0.5)
            local dotDuration = data.duration or 0
            local dotLine = string.format("DoT: %s of %s",
                ColorText(string.format("%.0f%%", data.dotSp * 100), c.gold),
                data.isHeal and "Healing Power" or "Spell Power")
            if dotDuration > 0 then
                dotLine = dotLine .. string.format(" (+%s over %ds)",
                    ColorText(tostring(dotBonus), c.gold), dotDuration)
            else
                dotLine = dotLine .. string.format(" (+%s)", ColorText(tostring(dotBonus), c.gold))
            end
            tooltip:AddLine(dotLine, c.footer.r, c.footer.g, c.footer.b)
        end
        if data.dotAp and data.dotAp > 0 then
            local dotStatValue = DT.Math:GetAttackPower()
            local dotBonus = math.floor(dotStatValue * data.dotAp + 0.5)
            local dotDuration = data.duration or 0
            local dotLine = string.format("DoT: %s of Attack Power",
                ColorText(string.format("%.0f%%", data.dotAp * 100), c.gold))
            if dotDuration > 0 then
                dotLine = dotLine .. string.format(" (+%s over %ds)",
                    ColorText(tostring(dotBonus), c.gold), dotDuration)
            else
                dotLine = dotLine .. string.format(" (+%s)", ColorText(tostring(dotBonus), c.gold))
            end
            tooltip:AddLine(dotLine, c.footer.r, c.footer.g, c.footer.b)
        end
    end
    
    -- Bloom heal display for Lifebloom (instant heal when HoT expires)
    if data.bloomSp and data.bloomSp > 0 and data.isHeal then
        local bloomBase = ParseBloomHealFromLines(info.description or {}) or 0
        local bloomStatValue = DT.Math:GetHealingPower()
        local bloomBonus = math.floor(bloomStatValue * data.bloomSp + 0.5)
        if DT.DB.showCoefficients then
            local bloomCoeffLine = string.format("Bloom: %s of Healing Power (+%s)",
                ColorText(string.format("%.1f%%", data.bloomSp * 100), c.gold),
                ColorText(tostring(bloomBonus), c.gold))
            tooltip:AddLine(bloomCoeffLine, c.footer.r, c.footer.g, c.footer.b)
        end
        if DT.DB.showTotal and bloomBase > 0 then
            local bloomTotal = bloomBase + bloomBonus
            local talentMod = 0
            if DT.DB.showTalentModifiers and DT.Modifiers then
                talentMod = DT.Modifiers:GetCombinedModifier(data)
            end
            if talentMod > 0 then
                bloomTotal = math.floor(bloomTotal * (1 + talentMod) + 0.5)
            end
            local schoolText = (data.school or "Nature"):lower()
            local bloomTotalLine = string.format("Total Bloom: %s %s healing",
                ColorText(FormatNumber(bloomTotal), c.gold), schoolText)
            tooltip:AddLine(bloomTotalLine, c.stats.r, c.stats.g, c.stats.b)
        end
    end
    
    -- Per-CP breakdown for combo point finishers
    if data.isFinisher and cpData and DT.DB.showTotal then
        -- Get talent modifier for CP breakdown
        local cpTalentMod = 0
        if DT.DB.showTalentModifiers and DT.Modifiers then
            cpTalentMod = DT.Modifiers:GetCombinedModifier(data)
        end
        
        for cp = 1, 5 do
            if cpData[cp] then
                local cpFinisherCoeff = data.wd or data.ap or 0
                local cpCoeff = cpFinisherCoeff * cp
                local cpTotalMin, cpTotalMax
                if wdMin and wdMax then
                    cpTotalMin = cpData[cp].min + wdMin * cpCoeff
                    cpTotalMax = cpData[cp].max + wdMax * cpCoeff
                else
                    local cpSpBonus = math.floor(statValue * cpCoeff + 0.5)
                    cpTotalMin = cpData[cp].min + cpSpBonus
                    cpTotalMax = cpData[cp].max + cpSpBonus
                end
                if cpTalentMod > 0 then
                    cpTotalMin = cpTotalMin * (1 + cpTalentMod)
                    cpTotalMax = cpTotalMax * (1 + cpTalentMod)
                end
                local marker = (cp == currentCP) and " <<" or ""
                local color = (cp == currentCP) and c.gold or c.base
                local cpLine
                if cpTotalMin == cpTotalMax then
                    cpLine = string.format("  CP %d - %s%s",
                        cp,
                        ColorText(FormatNumber(math.floor(cpTotalMin + 0.5)), color),
                        marker)
                else
                    cpLine = string.format("  CP %d - %s%s",
                        cp,
                        ColorText(string.format("%.0f-%.0f", cpTotalMin, cpTotalMax), color),
                        marker)
                end
                tooltip:AddLine(cpLine, c.footer.r, c.footer.g, c.footer.b)
            end
        end
    end
    
    -- DPS/HPS section
    if DT.DB.showDPS then
        local totalDmg = baseDmg + spBonus
        local duration = data.duration or 0
        
        if duration > 0 then
            local dps = totalDmg / duration
            local dpsOrange = ColorText(string.format("%.1f", dps), c.dps)
            local rateWord = data.isHeal and "HPS" or "DPS"
            tooltip:AddLine("(" .. dpsOrange .. " " .. rateWord .. " over " .. duration .. "s)", 
                c.footer.r, c.footer.g, c.footer.b)
            
            -- Heals for damage dealt note (Siphon Life, etc.)
            if data.healsForDamage then
                tooltip:AddLine("Also heals caster for damage dealt", c.footer.r, c.footer.g, c.footer.b)
            end
        end
    end
    
    -- Chain spell section: show per-bounce breakdown with diminishing returns
    if data.chainTargets and data.chainReduction and DT.DB.showTotal then
        local singleTotal = baseDmg + spBonus
        -- Apply talent modifiers to single target
        if DT.DB.showTalentModifiers and DT.Modifiers then
            local talentMod = DT.Modifiers:GetCombinedModifier(data)
            if talentMod > 0 then
                singleTotal = math.floor(singleTotal * (1 + talentMod) + 0.5)
            end
        end
        
        tooltip:AddLine(" ")
        local chainLabel = data.isHeal and "Chain Healing" or "Chain Damage"
        local effectWord = data.isHeal and "healing" or "damage"
        local reduction = data.chainReduction
        local allTargetsTotal = 0
        local bounceTexts = {}
        for t = 1, data.chainTargets do
            local mult = (1 - reduction) ^ (t - 1)
            local targetDmg = math.floor(singleTotal * mult + 0.5)
            allTargetsTotal = allTargetsTotal + targetDmg
            bounceTexts[#bounceTexts + 1] = ColorText(FormatNumber(targetDmg), c.gold)
        end
        
        tooltip:AddLine(string.format("%s (%d targets, -%d%% per jump):",
            chainLabel, data.chainTargets, data.chainReduction * 100),
            c.description.r, c.description.g, c.description.b)
        tooltip:AddLine("  " .. table.concat(bounceTexts, " \226\134\146 "),
            c.footer.r, c.footer.g, c.footer.b)
        tooltip:AddLine(string.format("  All targets: %s %s",
            ColorText(FormatNumber(allTargetsTotal), c.gold), effectWord),
            c.stats.r, c.stats.g, c.stats.b)
    end
    
    -- Totem tick rate section: show per-tick and per-second values
    if data.isTotemTick and data.tickInterval and data.tickInterval > 0 and DT.DB.showDPS then
        local tickTotal = baseDmg + spBonus
        -- Apply talent modifiers
        if DT.DB.showTalentModifiers and DT.Modifiers then
            local talentMod = DT.Modifiers:GetCombinedModifier(data)
            if talentMod > 0 then
                tickTotal = math.floor(tickTotal * (1 + talentMod) + 0.5)
            end
        end
        
        local perSecond = tickTotal / data.tickInterval
        if data.isManaRestore then
            tooltip:AddLine(string.format("Mana per Second: %s",
                ColorText(string.format("%.1f", perSecond), c.dps)),
                c.dps.r, c.dps.g, c.dps.b)
        elseif data.isHeal then
            tooltip:AddLine(string.format("Healing per Second: %s",
                ColorText(string.format("%.1f", perSecond), c.dps)),
                c.dps.r, c.dps.g, c.dps.b)
        end
    end
    
    -- Weapon speed DPS section for Proc/Enchant spells (SoR, Flametongue, etc.)
    -- Base damage already includes weapon speed scaling; just show SP DPS contribution
    if DT.DB.showDPS and data.weaponSpeedScaling then
        local hasteSpeed = select(1, UnitAttackSpeed("player")) or 2.0
        if hasteSpeed and hasteSpeed > 0 then
            local totalPerHit = baseDmg + spBonus
            local totalDps = totalPerHit / hasteSpeed
            local schoolText = (data.school or "Holy"):lower()
            
            tooltip:AddLine(" ")
            tooltip:AddLine(string.format("Total DPS: %s %s (%s base + %s SP / %.2fs attack speed)",
                ColorText(string.format("%.1f", totalDps), c.dps), schoolText,
                ColorText(FormatNumber(baseDmg), c.base), ColorText(FormatNumber(spBonus), c.gold), hasteSpeed),
                c.dps.r, c.dps.g, c.dps.b)
        end
    end
    
    -- AoE section
    if data.isAoE then
        local totalDmg = baseDmg + spBonus
        tooltip:AddLine(" ")
        
        local perTargetText = ColorText(FormatNumber(totalDmg), c.gold)
        tooltip:AddLine("Per Target: " .. perTargetText .. " " .. effectWord, 
            c.description.r, c.description.g, c.description.b)
        
        local maxT = data.maxTargets or 10
        local targets
        if maxT >= 999 then
            targets = {3, 5, 10}
        elseif maxT <= 5 then
            targets = {}
            for i = 2, maxT do targets[#targets + 1] = i end
        else
            targets = {3, 5, math.min(10, maxT)}
        end
        
        local totals = {}
        for _, count in ipairs(targets) do
            totals[#totals + 1] = ColorText(FormatNumber(totalDmg * count), c.gold) .. " (" .. count .. ")"
        end
        
        if #totals > 0 then
            tooltip:AddLine("Multi: " .. table.concat(totals, " / "), 
                c.stats.r, c.stats.g, c.stats.b)
        end
    end
    
    -- Mana efficiency (includes downrank penalty in calculation)
    if DT.DB.showManaEfficiency and manaCost and manaCost > 0 then
        local totalDmg = baseDmg + spBonus
        
        -- Add DoT damage for hybrid direct+DoT spells (Moonfire, Flame Shock, Immolate, etc.)
        local dotTotal = 0
        local hasDotComponent = false
        if data.dotSp and data.dotSp > 0 and (data.sp and data.sp > 0) then
            local dotBaseDmg = ParseDotBaseDamageFromLines(info.description or {}) or 0
            local dotStatValue = data.isHeal and DT.Math:GetHealingPower() or DT.Math:GetSpellPower(data.school or "Shadow")
            local dotBonus = math.floor(dotStatValue * data.dotSp + 0.5)
            dotTotal = dotBaseDmg + dotBonus
            hasDotComponent = true
        end
        if data.dotAp and data.dotAp > 0 and (data.ap and data.ap > 0) then
            local dotBaseDmg = ParseDotBaseDamageFromLines(info.description or {}) or 0
            local dotStatValue = DT.Math:GetAttackPower()
            local dotBonus = math.floor(dotStatValue * data.dotAp + 0.5)
            dotTotal = dotTotal + dotBaseDmg + dotBonus
            hasDotComponent = true
        end
        
        -- Add HoT healing for hybrid direct+HoT heal spells (Regrowth, etc.)
        local hotTotal = 0
        local hasHotComponent = false
        if data.hotSp and data.hotSp > 0 and data.isHeal then
            local hotBaseHeal = ParseHotBaseHealingFromLines(info.description or {}) or 0
            local hotStatValue = DT.Math:GetHealingPower()
            local hotBonus = math.floor(hotStatValue * data.hotSp + 0.5)
            hotTotal = hotBaseHeal + hotBonus
            hasHotComponent = true
        end
        
        -- Add bloom heal for Lifebloom
        local bloomTotal = 0
        local hasBloomComponent = false
        if data.bloomSp and data.bloomSp > 0 and data.isHeal then
            local bloomBase = ParseBloomHealFromLines(info.description or {}) or 0
            local bloomStatValue = DT.Math:GetHealingPower()
            local bloomBonus = math.floor(bloomStatValue * data.bloomSp + 0.5)
            bloomTotal = bloomBase + bloomBonus
            hasBloomComponent = true
        end
        
        local combinedDmg = totalDmg + dotTotal + hotTotal + bloomTotal
        
        -- Apply downrank penalty to the total used for efficiency calc
        local rankLearnLevel = data.level
        if rankLearnLevel then
            local playerLevel = UnitLevel("player") or 70
            if rankLearnLevel + 6 < playerLevel then
                local penaltyCoeff = math.min(1.0, (rankLearnLevel + 6) / playerLevel)
                combinedDmg = combinedDmg * penaltyCoeff
            end
        end
        
        local dpm = combinedDmg / manaCost
        local dpmText = ColorText(string.format("%.2f", dpm), c.efficiency)
        local effWord = data.isHeal and "healing" or "damage"
        local effLine
        if hasDotComponent then
            effLine = string.format("Efficiency: %s %s per Mana (direct + DoT)", dpmText, effWord)
        elseif hasHotComponent and hasBloomComponent then
            effLine = string.format("Efficiency: %s %s per Mana (HoT + Bloom)", dpmText, effWord)
        elseif hasHotComponent then
            effLine = string.format("Efficiency: %s %s per Mana (direct + HoT)", dpmText, effWord)
        elseif hasBloomComponent then
            effLine = string.format("Efficiency: %s %s per Mana (direct + Bloom)", dpmText, effWord)
        else
            effLine = string.format("Efficiency: %s %s per Mana", dpmText, effWord)
        end
        tooltip:AddLine(effLine, c.efficiency.r, c.efficiency.g, c.efficiency.b)
        
        -- Healing per Mana for dual damage/heal spells (Holy Shock, Holy Nova)
        if data.healSp and data.healSp > 0 then
            local healPower = DT.Math:GetHealingPower()
            local healCoeff = data.healSp
            local healBonus = math.floor(healPower * healCoeff + 0.5)
            local healMin, healMax = ParseHealingFromLines(info.description or {})
            if healMin then
                local healBase = (healMax and healMax ~= healMin) and ((healMin + healMax) / 2) or healMin
                local totalHeal = healBase + healBonus
                -- Apply same downrank penalty
                local rankLearnLevel = data.level
                if rankLearnLevel then
                    local playerLevel = UnitLevel("player") or 70
                    if rankLearnLevel + 6 < playerLevel then
                        local penaltyCoeff = math.min(1.0, (rankLearnLevel + 6) / playerLevel)
                        totalHeal = totalHeal * penaltyCoeff
                    end
                end
                local hpm = totalHeal / manaCost
                local hpmText = ColorText(string.format("%.2f", hpm), c.efficiency)
                tooltip:AddLine(string.format("Efficiency: %s healing per Mana", hpmText), c.efficiency.r, c.efficiency.g, c.efficiency.b)
            end
        end
    end
    
    -- Downrank penalty
    if DT.DB.showDownrankPenalty then
        local spellRank = data.rank
        local rankLearnLevel = data.level
        
        if spellRank and rankLearnLevel then
            local playerLevel = UnitLevel("player") or 70
            
            -- Get max rank available at player's current level (like HintMeRank)
            local maxAtLevel = spellRank
            if DT.GetHighestTrainableRank and data.name then
                local highest = DT.GetHighestTrainableRank(data.name, 0, playerLevel)
                if highest then maxAtLevel = highest end
            end
            
            if rankLearnLevel + 6 < playerLevel then
                local effectiveCoeff = math.min(1.0, (rankLearnLevel + 6) / playerLevel)
                local penaltyPct = math.floor((1 - effectiveCoeff) * 100)
                
                if penaltyPct > 0 then
                    local penaltyText = ColorText(string.format("-%d%%", penaltyPct), c.warning)
                    local levelText = ColorText("L" .. rankLearnLevel, c.warning)
                    tooltip:AddLine("Downrank: R" .. spellRank .. "/" .. maxAtLevel .. " " .. levelText .. " (" .. penaltyText .. " coeff)", 
                        c.warning.r, c.warning.g, c.warning.b)
                end
            elseif spellRank < maxAtLevel then
                -- Only show lower rank note if there's actually a higher rank available at this level
                tooltip:AddLine("Lower rank: R" .. spellRank .. "/" .. maxAtLevel .. " (no penalty)", 
                    c.footer.r, c.footer.g, c.footer.b)
            end
        end
    end
end

local function GetSpellManaCost(spellID)
    -- Retail Midnight: Try C_Spell API first
    if C_Spell and C_Spell.GetSpellPowerCost then
        local costInfo = C_Spell.GetSpellPowerCost(spellID)
        if costInfo then
            for _, info in ipairs(costInfo) do
                local isMana = (info.name == "MANA") or 
                               (info.type == 0) or
                               (Enum and Enum.PowerType and info.type == Enum.PowerType.Mana)
                if isMana then
                    return info.cost
                end
            end
        end
    end
    
    -- Legacy: Try GetSpellPowerCost (Classic/older versions)
    if GetSpellPowerCost then
        local costInfo = GetSpellPowerCost(spellID)
        if costInfo then
            for _, info in ipairs(costInfo) do
                local isMana = (info.name == "MANA") or 
                               (info.type == 0) or
                               (Enum and Enum.PowerType and info.type == Enum.PowerType.Mana)
                if isMana then
                    return info.cost
                end
            end
        end
    end
    return nil
end

-- ============================================================================
-- SPELL INFO HELPERS (Retail/Classic Compatible)
-- ============================================================================

local function GetSpellName(spellID)
    -- Retail: Use C_Spell API
    if C_Spell and C_Spell.GetSpellInfo then
        local spellInfo = C_Spell.GetSpellInfo(spellID)
        return spellInfo and spellInfo.name
    end
    -- Legacy fallback (Classic/TBC)  
    if GetSpellInfo then
        return (GetSpellInfo(spellID))
    end
    return nil
end

local function GetSpellDescription(spellID)
    -- Retail: Use C_Spell API
    if C_Spell and C_Spell.GetSpellDescription then
        return C_Spell.GetSpellDescription(spellID)
    end
    -- Legacy fallback
    if GetSpellDescription then
        return GetSpellDescription(spellID)
    end
    return nil
end

local function GetSpellCooldown(spellID)
    -- Retail: Use C_Spell API
    if C_Spell and C_Spell.GetSpellCooldown then
        local cooldownInfo = C_Spell.GetSpellCooldown(spellID)
        return cooldownInfo and cooldownInfo.duration or 0
    end
    -- Legacy fallback
    if GetSpellCooldown then
        local start, duration = GetSpellCooldown(spellID)
        return duration or 0
    end
    return 0
end

-- ============================================================================
-- COMPLETE TOOLTIP REPLACEMENT SYSTEM
-- Captures original content and rebuilds with custom layout
-- ============================================================================

local function CaptureTooltipInfo(tooltip)
    -- Capture all useful information from the original tooltip
    local info = {
        lines = {},
        title = "",
        rank = "",
        mana = "",
        range = "",
        castTime = "",
        cooldown = "",
        description = {},
        restrictions = {},
    }
    
    for i = 1, tooltip:NumLines() do
        local leftText = _G[tooltip:GetName() .. "TextLeft" .. i]
        local rightText = _G[tooltip:GetName() .. "TextRight" .. i]
        
        local left = leftText and leftText:GetText() or ""
        local right = rightText and rightText:GetText() or ""
        
        -- Clean color codes for matching
        local cleanLeft = left:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
        local cleanRight = right:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
        
        if i == 1 then
            info.title = cleanLeft
            info.rank = cleanRight
        elseif cleanLeft:match("^%d+%s+[Mm]ana") then
            info.mana = cleanLeft:match("%d+") or ""
            -- Also check right side for cast time on same line
            if cleanRight:match("[Ii]nstant") then
                info.castTime = "Instant"
            elseif cleanRight:match("sec cast") then
                info.castTime = cleanRight
            end
        elseif cleanLeft:match("yd range") or cleanLeft:match("yard") then
            info.range = cleanLeft
        elseif cleanLeft:match("sec cast") then
            info.castTime = cleanLeft
        elseif cleanLeft:match("^[Ii]nstant$") or cleanLeft:match("^[Ii]nstant%s+[Cc]ast") then
            info.castTime = "Instant"
        elseif cleanLeft:match("cooldown") or cleanLeft:match("Cooldown") then
            info.cooldown = cleanLeft
        elseif cleanLeft:match("[Cc]annot") or cleanLeft:match("[Rr]equires") then
            table.insert(info.restrictions, cleanLeft)
        elseif cleanLeft:match("[Ss]pell%s*[Ii][Dd]") or cleanLeft:match("[Ii]con%s*[Ii][Dd]") or cleanLeft:match("[Mm]acro%s*[Ii][Dd]") or cleanLeft:match("^Spell ID:") then
            -- Skip SpellID/IconID/MacroID lines (we add our own)
        elseif cleanLeft ~= "" and not cleanLeft:match("^%s*$") then
            -- Description lines - split long sentences for readability
            -- Check if this is a long run-on description with multiple sentences
            if cleanLeft:len() > 80 and cleanLeft:match("%.[^%.]+%.") then
                -- Split on sentence boundaries and combine short sentences
                local sentences = {}
                for sentence in cleanLeft:gmatch("([^%.]+%.)") do
                    sentence = sentence:match("^%s*(.-)%s*$") -- trim
                    if sentence and sentence ~= "" then
                        table.insert(sentences, sentence)
                    end
                end
                -- Combine short sentences with previous line for readability
                local combined = {}
                for _, s in ipairs(sentences) do
                    if #combined > 0 and (#combined[#combined] + #s + 2) < 90 and #s < 30 then
                        -- Short sentence - append to previous
                        combined[#combined] = combined[#combined] .. "  " .. s
                    else
                        table.insert(combined, s)
                    end
                end
                for _, line in ipairs(combined) do
                    table.insert(info.description, line)
                end
            else
                table.insert(info.description, cleanLeft)
            end
        end
        
        -- Also check right side for various info
        if cleanRight:match("yd range") or cleanRight:match("yard") then
            info.range = cleanRight
        end
        if cleanRight:match("[Ii]nstant") and info.castTime == "" then
            info.castTime = "Instant"
        elseif cleanRight:match("sec cast") and info.castTime == "" then
            info.castTime = cleanRight
        end
        
        table.insert(info.lines, { left = cleanLeft, right = cleanRight })
    end
    
    -- Store the spell ID for later use (passed from tooltip:GetSpell())
    -- This will be set by the caller after CaptureTooltipInfo returns
    info.spellID = nil
    
    return info
end

-- Helper to get spell description from API (for spells with no captured description)
local function GetSpellDescriptionFallback(spellID)
    if not spellID then return nil end
    
    -- Try GetSpellDescription (available in some versions)
    if GetSpellDescription then
        local desc = GetSpellDescription(spellID)
        if desc and desc ~= "" then
            return desc:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
        end
    end
    
    -- Fallback: create a hidden tooltip and read from it
    -- This is a last resort for TBC
    local scanTip = _G["DynamicTooltipsScanTip"]
    if not scanTip then
        scanTip = CreateFrame("GameTooltip", "DynamicTooltipsScanTip", nil, "GameTooltipTemplate")
        scanTip:SetOwner(WorldFrame, "ANCHOR_NONE")
    end
    
    scanTip:ClearLines()
    
    -- Try SetSpellByID if available, otherwise try SetHyperlink
    if scanTip.SetSpellByID then
        scanTip:SetSpellByID(spellID)
    elseif scanTip.SetHyperlink then
        scanTip:SetHyperlink("spell:" .. spellID)
    else
        return nil
    end
    
    local desc = nil
    for i = 2, scanTip:NumLines() do
        local leftText = _G["DynamicTooltipsScanTipTextLeft" .. i]
        if leftText then
            local text = leftText:GetText() or ""
            local clean = text:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
            -- Skip mana, range, cast time, cooldown, restriction lines
            if clean ~= "" 
               and not clean:match("[Mm]ana")
               and not clean:match("yd range")
               and not clean:match("sec cast")
               and not clean:match("[Ii]nstant")
               and not clean:match("cooldown")
               and not clean:match("[Cc]annot")
               and not clean:match("[Rr]equires")
               and not clean:match("[Ss]pell%s*[Ii][Dd]")
               and not clean:match("[Ii]con%s*[Ii][Dd]")
               and not clean:match("[Mm]acro%s*[Ii][Dd]")
               and not clean:match("^%s*$") then
                desc = clean
                break
            end
        end
    end
    
    return desc
end

local function RebuildTooltip(tooltip, info, spellID, data, manaCost)
    -- Get theme colors
    local c = DT.Style.colors
    if DT.Themes then
        local theme = DT.Themes.GetCurrent()
        if theme and theme.text then
            c = setmetatable({}, {__index = function(t, k)
                return theme.text[k] or DT.Style.colors[k]
            end}) 
        end
    end
    
    local fonts = DT.Themes and DT.Themes.GetFonts() or {
        titleSize = 12, textSize = 12, footerSize = 12, outline = "OUTLINE"
    }
    local icons = DT.Themes and DT.Themes.GetIcons() or DT.Style.icons
    
    -- Clear lines for rebuild
    tooltip:ClearLines()
    
    -- ════════════════════════════════════════════════════════════════════════
    -- HEADER SECTION: Spell Name + Rank
    -- ════════════════════════════════════════════════════════════════════════
    if info.rank and info.rank ~= "" then
        tooltip:AddDoubleLine(info.title, info.rank,
            c.title.r, c.title.g, c.title.b,
            c.rank.r, c.rank.g, c.rank.b)
    else
        tooltip:AddLine(info.title, c.title.r, c.title.g, c.title.b)
    end
    
    -- ========================================================================
    
    -- ════════════════════════════════════════════════════════════════════════
    -- CAST INFO SECTION: Mana | Cast Time | Range | Cooldown
    -- ════════════════════════════════════════════════════════════════════════
    local castInfoParts = {}
    
    local manaTexture = "|TInterface\\AddOns\\DynamicTooltips_Retail\\Data\\mana:12:12|t"
    local rangeTexture = "|TInterface\\AddOns\\DynamicTooltips_Retail\\Data\\range:12:12|t"
    
    if info.mana and info.mana ~= "" then
        local manaIcon = icons.mana or manaTexture
        table.insert(castInfoParts, {
            text = manaIcon .. " " .. info.mana .. " Mana",
            color = c.mana
        })
    end
    
    if info.castTime and info.castTime ~= "" then
        local castIcon = info.castTime == "Instant" and (icons.instant or "!") or (icons.castTime or "~")
        table.insert(castInfoParts, {
            text = castIcon .. " " .. info.castTime,
            color = c.castTime
        })
    end
    
    if info.range and info.range ~= "" then
        local rangeIcon = icons.range or rangeTexture
        table.insert(castInfoParts, {
            text = rangeIcon .. " " .. info.range,
            color = c.range
        })
    end
    
    if info.cooldown and info.cooldown ~= "" then
        local cdIcon = icons.cooldown or "@"
        table.insert(castInfoParts, {
            text = cdIcon .. " " .. info.cooldown,
            color = c.cooldown or c.castTime
        })
    end
    
    -- Display cast info (2 per line for compact layout)
    for i = 1, #castInfoParts, 2 do
        local left = castInfoParts[i]
        local right = castInfoParts[i + 1]
        if right then
            tooltip:AddDoubleLine(left.text, right.text,
                left.color.r, left.color.g, left.color.b,
                right.color.r, right.color.g, right.color.b)
        else
            tooltip:AddLine(left.text, left.color.r, left.color.g, left.color.b)
        end
    end
    
    -- ════════════════════════════════════════════════════════════════════════
    -- DESCRIPTION SECTION
    -- ════════════════════════════════════════════════════════════════════════
    local descriptionLines = info.description
    
    -- Fallback: if no description was captured, try to get it from API
    if #descriptionLines == 0 and spellID then
        local fallbackDesc = GetSpellDescriptionFallback(spellID)
        if fallbackDesc and fallbackDesc ~= "" then
            -- Split long descriptions with same logic as capture
            if fallbackDesc:len() > 80 and fallbackDesc:match("%.[^%.]+%.") then
                local sentences = {}
                for sentence in fallbackDesc:gmatch("([^%.]+%.)") do
                    sentence = sentence:match("^%s*(.-)%s*$")
                    if sentence and sentence ~= "" then
                        table.insert(sentences, sentence)
                    end
                end
                local combined = {}
                for _, s in ipairs(sentences) do
                    if #combined > 0 and (#combined[#combined] + #s + 2) < 90 and #s < 30 then
                        combined[#combined] = combined[#combined] .. "  " .. s
                    else
                        table.insert(combined, s)
                    end
                end
                for _, line in ipairs(combined) do
                    table.insert(descriptionLines, line)
                end
            else
                table.insert(descriptionLines, fallbackDesc)
            end
        end
    end
    
    if #descriptionLines > 0 then
        tooltip:AddLine(" ")
        for _, line in ipairs(descriptionLines) do
            tooltip:AddLine(line, c.description.r, c.description.g, c.description.b, true)
        end
    end
    
    -- ════════════════════════════════════════════════════════════════════════
    -- RESTRICTIONS
    -- ════════════════════════════════════════════════════════════════════════
    for _, restriction in ipairs(info.restrictions) do
        tooltip:AddLine(restriction, c.restriction.r, c.restriction.g, c.restriction.b)
    end
    
    return c
end

-- ============================================================================
-- TOOLTIP PROCESSING (Pawn-style: hook individual Set* methods)
-- ============================================================================

-- Single entry point for all spell tooltip processing.
-- Called by hooksecurefunc on every Set* method that can produce a spell tooltip.
-- Uses tooltip.DT_processedSpell to prevent duplicate appends (cleared on
-- OnTooltipCleared and OnHide).
-- Scan Blizzard tooltip lines and extract mana/range/cast/cooldown,
-- then hide those lines so we can re-present them with icons in our section.
local function ExtractAndHideBlizzardMeta(tooltip)
    local meta = { mana = nil, range = nil, castTime = nil, cooldown = nil }
    local tooltipName = tooltip:GetName()
    
    for i = 2, tooltip:NumLines() do
        local leftFS  = _G[tooltipName .. "TextLeft" .. i]
        local rightFS = _G[tooltipName .. "TextRight" .. i]
        local leftRaw  = leftFS  and leftFS:GetText()  or ""
        local rightRaw = rightFS and rightFS:GetText() or ""
        -- Strip color codes for matching
        local left  = leftRaw:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
        local right = rightRaw:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
        
        local hideLine = false
        local isMetaLine = false
        
        -- Mana line: "298 Mana" (left) — sometimes right side has range or cast
        -- Anchored to start of line so description text containing "mana" isn't caught
        if left:match("^%d+%s+[Mm]ana") then
            meta.mana = left:match("(%d+)")
            hideLine = true
            isMetaLine = true
        end
        
        -- Range: "36 yd range" — can appear on left or right
        if left:match("%d+%s+yd%s+range") then
            meta.range = left
            hideLine = true
            isMetaLine = true
        end
        if right:match("%d+%s+yd%s+range") then
            meta.range = right
            isMetaLine = true
            -- Hide the right text; if left was also hidden this line disappears entirely
            if rightFS then rightFS:SetText("") rightFS:Hide() end
        end
        
        -- Cast time: "1.5 sec cast" or "Instant" or "Instant cast" — can be left or right
        -- NOTE: Must NOT match "Instantly" (start of description lines like "Instantly shocks...")
        if left:match("sec%s+cast") or left:match("^[Ii]nstant$") or left:match("^[Ii]nstant%s+[Cc]ast") then
            meta.castTime = left
            hideLine = true
            isMetaLine = true
        end
        if right:match("sec%s+cast") or right:match("^[Ii]nstant$") or right:match("^[Ii]nstant%s+[Cc]ast") then
            meta.castTime = right
            isMetaLine = true
            if rightFS then rightFS:SetText("") rightFS:Hide() end
        end
        
        -- Cooldown: "7 sec cooldown"
        if left:match("sec%s+cooldown") or left:match("min%s+cooldown") then
            meta.cooldown = left
            hideLine = true
            isMetaLine = true
        end
        if right:match("sec%s+cooldown") or right:match("min%s+cooldown") then
            meta.cooldown = right
            isMetaLine = true
            if rightFS then rightFS:SetText("") rightFS:Hide() end
        end
        
        -- If this line's left side matched, blank it
        if hideLine and leftFS then
            leftFS:SetText("")
            leftFS:Hide()
        end
        
    end
    
    return meta
end

-- Hide Blizzard's description lines — DISABLED (always show explanatory text)
local function HideBlizzardDescriptionLines(tooltip)
end

local function ProcessSpellTooltip(tooltip, providedSpellID)
    if not DT.DB or not DT.DB.enabled then return end
    
    local name, spellID
    
    -- In Retail (10.0+), spellID is passed from TooltipDataProcessor
    if providedSpellID then
        spellID = providedSpellID
        name = GetSpellName(spellID)
    -- In Classic/Legacy, use tooltip:GetSpell()
    elseif tooltip.GetSpell then
        name, spellID = tooltip:GetSpell()
    end
    
    -- Fallback for pet action bar: GetSpell() may return nil for pet actions
    if not spellID and tooltip.DT_petActionSlot then
        local petSlot = tooltip.DT_petActionSlot
        if GetPetActionInfo then
            local petName = GetPetActionInfo(petSlot)
            if petName and petName ~= "" then
                name = petName
                -- Use name-based lookup since we don't have a spellID
                -- Generate a synthetic ID from name hash to avoid re-processing
                spellID = -1 * (petSlot or 0)
            end
        end
    end
    
    -- Fallback for buff/debuff tooltips: GetSpell() often returns nil for
    -- SetUnitBuff/SetUnitDebuff/SetUnitAura, so use UnitBuff/UnitAura API instead
    if not spellID and tooltip.DT_buffUnit and tooltip.DT_buffIndex then
        local buffUnit = tooltip.DT_buffUnit
        local buffIndex = tooltip.DT_buffIndex
        local buffFilter = tooltip.DT_buffFilter
        if UnitAura then
            local bName, _, _, _, _, _, _, _, _, bSpellID
            if buffFilter then
                bName, _, _, _, _, _, _, _, _, bSpellID = UnitAura(buffUnit, buffIndex, buffFilter)
            else
                bName, _, _, _, _, _, _, _, _, bSpellID = UnitAura(buffUnit, buffIndex)
            end
            if bSpellID and bSpellID > 0 then
                spellID = bSpellID
                name = bName or name
                DebugPrint("Buff fallback found spellID:", spellID, "name:", name)
            end
        end
    end
    
    if not spellID then
        DebugPrint("ProcessSpellTooltip - NO spellID, bailing")
        -- Clear stored method so OnShow doesn't re-process with a non-spell action (e.g. empty Dominos slot)
        if not tooltip.DT_reprocessing then
            tooltip.DT_lastSetMethod = nil
            tooltip.DT_lastSetArgs = nil
        end
        return
    end
    
    -- Already processed this exact spell on this tooltip instance?
    -- Use a generation counter so equipment changes force re-processing
    local currentGen = tooltip.DT_generation or 0
    if tooltip.DT_processedSpell == spellID and tooltip.DT_processedGen == currentGen then
        DebugPrint("ProcessSpellTooltip - ALREADY PROCESSED spellID:", spellID, "gen:", currentGen)
        return
    end
    tooltip.DT_processedSpell = spellID
    tooltip.DT_processedGen = currentGen
    
    DebugPrint("ProcessSpellTooltip - Name:", name or "nil", "ID:", spellID)
    
    -- Mark this tooltip as a spell tooltip so OnTooltipShow knows to style it
    tooltip.DT_isSpellTooltip = true
    
    -- Capture and hide Blizzard's mana/range/cast/cooldown lines
    local meta = ExtractAndHideBlizzardMeta(tooltip)
    
    -- Get mana cost (prefer parsed from tooltip, fall back to API)
    local manaCost = meta.mana and tonumber(meta.mana) or GetSpellManaCost(spellID)
    DebugPrint("Mana cost:", manaCost or "nil")
    
    -- Try ID lookup first, then name-based fallback
    local data = DT.SpellData and DT.SpellData[spellID]
    if not data and name and DT.SpellDataByName then
        data = DT.SpellDataByName[name:lower()]
    end
    
    -- Append scaling info under Blizzard's native tooltip (no rebuild)
    if data then
        AddScalingInfo(tooltip, spellID, manaCost, name, meta)
    else
        -- Even without scaling data, append our mana/range/cast/cooldown section
        AppendMetaLines(tooltip, meta, manaCost)
    end
    
    -- Hide Blizzard description AFTER parsing (so base damage extraction still works)
    HideBlizzardDescriptionLines(tooltip)
    
    -- Add spell ID footer
    if DT.DB.showSpellID then
        tooltip:AddLine(" ")
        local footerColor = {r = 0.5, g = 0.5, b = 0.5}
        if DT.Themes then
            local theme = DT.Themes.GetCurrent()
            if theme and theme.text and theme.text.footer then
                footerColor = theme.text.footer
            end
        end
        tooltip:AddLine("Spell ID: " .. spellID, footerColor.r, footerColor.g, footerColor.b)
    end
    
    
    -- Apply visual styling
    ApplyNeumorphicStyle(tooltip)
    StyleTooltipText(tooltip)
    tooltip:Show()
end

local function OnTooltipShow(tooltip)
    if not DT.DB or not DT.DB.enabled then return end
    -- Skip if we just rebuilt this tooltip (prevents double styling)
    if tooltip.DT_justRebuilt then return end

    -- Apply themed backdrop + gradient to ALL tooltips
    ApplyNeumorphicStyle(tooltip)

    -- Spell tooltips get full color styling; everything else gets font-only pass
    if tooltip.DT_isSpellTooltip then
        StyleTooltipText(tooltip)
    else
        ApplyFontsToLines(tooltip)
    end
end

-- ============================================================================
-- FONT SYSTEM OVERHAUL (TipTac-inspired)
-- Modifies GameTooltipText/Header/Small font OBJECTS directly so all tooltip
-- text (even from other addons) gets themed font sizes. Remembers originals
-- so we can restore if theming is disabled.
-- ============================================================================

local originalFonts = {}  -- Remembers Blizzard's defaults

local function RememberOriginalFonts()
    if originalFonts.saved then return end
    
    local function SaveFont(fontObj, key)
        if fontObj then
            local face, size, flags = fontObj:GetFont()
            originalFonts[key] = { face = face, size = size, flags = flags }
        end
    end
    
    SaveFont(GameTooltipText, "body")
    SaveFont(GameTooltipHeaderText, "header")
    SaveFont(GameTooltipTextSmall, "small")
    originalFonts.saved = true
end

local function ApplyThemeFonts()
    if not DT.DB or not DT.DB.neumorphicStyle then return end
    
    RememberOriginalFonts()
    
    local fonts = DT.Themes and DT.Themes.GetFonts() or {
        titleSize = 12, textSize = 12, footerSize = 12, outline = "OUTLINE"
    }
    local fontFace = DT.Style.fontFace
    local flags = (fonts.outline ~= "NONE") and fonts.outline or ""
    
    -- Modify the font template objects — affects ALL tooltips using these fonts
    if GameTooltipHeaderText then
        GameTooltipHeaderText:SetFont(fontFace, fonts.titleSize, flags)
    end
    if GameTooltipText then
        GameTooltipText:SetFont(fontFace, fonts.textSize, flags)
    end
    if GameTooltipTextSmall then
        GameTooltipTextSmall:SetFont(fontFace, fonts.footerSize, flags)
    end
end

local function RestoreOriginalFonts()
    if not originalFonts.saved then return end
    
    local function RestoreFont(fontObj, key)
        local orig = originalFonts[key]
        if fontObj and orig then
            fontObj:SetFont(orig.face, orig.size, orig.flags or "")
        end
    end
    
    RestoreFont(GameTooltipText, "body")
    RestoreFont(GameTooltipHeaderText, "header")
    RestoreFont(GameTooltipTextSmall, "small")
end

-- Expose for theme change callback
DT.ApplyThemeFonts = ApplyThemeFonts
DT.RestoreOriginalFonts = RestoreOriginalFonts

-- ============================================================================
-- INITIALIZATION
-- ============================================================================

local function InitializeDatabase()
    if not DynamicTooltipsDB then
        DynamicTooltipsDB = {}
    end
    
    -- Apply defaults
    for key, value in pairs(defaults) do
        if DynamicTooltipsDB[key] == nil then
            DynamicTooltipsDB[key] = value
        end
    end
    
    DT.DB = DynamicTooltipsDB

    -- neumorphicStyle and colorBonusNumbers are always on (no longer user-toggled)
    DT.DB.neumorphicStyle = true
    DT.DB.colorBonusNumbers = true
    
    -- Load saved font preference
    if DT.DB.font and DT.Fonts.list[DT.DB.font] then
        DT.Fonts:Set(DT.DB.font)
    end
end

local function HookTooltips()
    -- ========================================================================
    -- RETAIL TOOLTIP SYSTEM (10.0+)
    -- Uses TooltipDataProcessor for spell tooltip handling
    -- ========================================================================
    if TooltipDataProcessor and TooltipDataProcessor.AddTooltipPostCall then
        -- Hook spell tooltips via TooltipDataProcessor (Retail Dragonflight+)
        TooltipDataProcessor.AddTooltipPostCall(Enum.TooltipDataType.Spell, function(tooltip, data)
            if not DT.DB or not DT.DB.enabled then return end
            if tooltip ~= GameTooltip then return end
            
            local spellID = data.id
            if spellID then
                DebugPrint("TooltipDataProcessor Spell hook - ID:", spellID)
                tooltip.DT_lastSpellID = spellID
                ProcessSpellTooltip(tooltip, spellID)
            end
        end)
        
        -- Also hook item tooltips for theming
        TooltipDataProcessor.AddTooltipPostCall(Enum.TooltipDataType.Item, function(tooltip, data)
            if not DT.DB or not DT.DB.neumorphicStyle then return end
            ApplyNeumorphicStyle(tooltip)
            ApplyFontsToLines(tooltip)
        end)
        
        -- Hook unit tooltips
        TooltipDataProcessor.AddTooltipPostCall(Enum.TooltipDataType.Unit, function(tooltip, data)
            if not DT.DB or not DT.DB.neumorphicStyle then return end
            ApplyNeumorphicStyle(tooltip)
            ApplyFontsToLines(tooltip)
        end)
        
        DebugPrint("Using Retail TooltipDataProcessor hooks")
    end
    
    -- ========================================================================
    -- CLASSIC/LEGACY TOOLTIP SYSTEM
    -- Hook individual Set* methods with hooksecurefunc (fallback/classic)
    -- ========================================================================
    local spellMethods = {
        "SetAction",          -- Action bar buttons
        "SetSpellBookItem",   -- Spellbook
        "SetSpellByID",       -- Generic spell-by-ID references
        "SetTalent",          -- Talent tree
        "SetTrainerService",  -- Trainer window
        "SetShapeshift",      -- Stance bar
        "SetPetAction",       -- Pet action bar
        "SetUnitBuff",        -- Unit buff tooltips
        "SetUnitDebuff",      -- Unit debuff tooltips
        "SetUnitAura",        -- Unit aura tooltips
    }
    
    for _, method in ipairs(spellMethods) do
        if GameTooltip[method] then
            hooksecurefunc(GameTooltip, method, function(self, arg1, arg2, arg3)
                DebugPrint("Set* hook fired:", method, "arg1:", tostring(arg1), "arg2:", tostring(arg2),
                    "reprocessing:", self.DT_reprocessing and "yes" or "no")
                -- Don't overwrite stored method during re-processing; OnShow already has the correct values
                if not self.DT_reprocessing then
                    self.DT_lastSetMethod = method
                    self.DT_lastSetArgs = { arg1, arg2, arg3 }
                end
                -- For pet action bar, store slot so we can fallback to GetPetActionInfo
                if method == "SetPetAction" then
                    self.DT_petActionSlot = arg1
                end
                -- For buff/debuff/aura tooltips, store unit + index so we can
                -- fallback to UnitAura() when GetSpell() returns nil
                if method == "SetUnitBuff" or method == "SetUnitDebuff" then
                    self.DT_buffUnit = arg1
                    self.DT_buffIndex = arg2
                    self.DT_buffFilter = nil
                elseif method == "SetUnitAura" then
                    self.DT_buffUnit = arg1
                    self.DT_buffIndex = arg2
                    self.DT_buffFilter = arg3  -- "HELPFUL" or "HARMFUL"
                end
                ProcessSpellTooltip(self)
            end)
        end
    end
    
    -- ========================================================================
    -- EQUIPMENT CHANGE HANDLER
    -- When the player swaps weapons, bump the generation counter so the
    -- tooltip re-processes with the new weapon speed / damage on next hover.
    -- If a spell tooltip is currently visible, force an immediate re-process.
    -- ========================================================================
    local equipFrame = CreateFrame("Frame")
    equipFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
    equipFrame:SetScript("OnEvent", function(_, _, slot)
        -- Slot 16 = main hand, 17 = off hand, 18 = ranged
        if slot == 16 or slot == 17 or slot == 18 then
            GameTooltip.DT_generation = (GameTooltip.DT_generation or 0) + 1
            -- If a spell tooltip is currently showing, re-trigger processing
            if GameTooltip:IsVisible() and GameTooltip.DT_lastSetMethod and GameTooltip.DT_processedSpell then
                GameTooltip.DT_processedSpell = nil
                GameTooltip.DT_processedGen = nil
                local m = GameTooltip.DT_lastSetMethod
                local a = GameTooltip.DT_lastSetArgs
                if m and a and GameTooltip[m] then
                    GameTooltip[m](GameTooltip, a[1], a[2], a[3])
                end
            end
        end
    end)
    
    -- Clear tracking when tooltip content is replaced (e.g. mousing between
    -- two action buttons without the tooltip hiding first).
    GameTooltip:HookScript("OnTooltipCleared", function(self)
        DebugPrint("OnTooltipCleared - processedSpell:", self.DT_processedSpell or "nil",
            "lastSetMethod:", self.DT_lastSetMethod or "nil",
            "isVisible:", self:IsVisible() and "yes" or "no")
        self.DT_processedSpell = nil
        self.DT_processedGen = nil
        self.DT_isSpellTooltip = nil
        self.DT_petActionSlot = nil
        self.DT_buffUnit = nil
        self.DT_buffIndex = nil
        self.DT_buffFilter = nil
        -- NOTE: DT_lastSetMethod / DT_lastSetArgs are intentionally kept
        -- so OnShow can re-process if Blizzard clears+re-shows without a Set* call
        -- Immediately reapply our backdrop so Blizzard's default border never
        -- flashes between tooltips (e.g. moving between action bar spells).
        if DT.DB and DT.DB.neumorphicStyle then
            ApplyNeumorphicStyle(self)
        end
    end)
    
    -- Hook OnShow for styling ALL tooltips AND re-processing spell tooltips
    -- that Blizzard cleared+re-showed without calling a Set* method again
    GameTooltip:HookScript("OnShow", function(self)
        DebugPrint("OnShow - processedSpell:", self.DT_processedSpell or "nil",
            "lastSetMethod:", self.DT_lastSetMethod or "nil",
            "reprocessing:", self.DT_reprocessing and "yes" or "no")
        -- If we had a spell processed but it was cleared (e.g. Blizzard refreshed
        -- the tooltip internally), re-trigger processing via the stored Set* call
        if not self.DT_processedSpell and self.DT_lastSetMethod and not self.DT_reprocessing then
            DebugPrint("OnShow RE-PROCESSING via", self.DT_lastSetMethod)
            self.DT_reprocessing = true
            local m = self.DT_lastSetMethod
            local a = self.DT_lastSetArgs
            if m and a and self[m] then
                self[m](self, a[1], a[2], a[3])
            end
            self.DT_reprocessing = nil
        end
        OnTooltipShow(self)
    end)
    
    -- Hook additional tooltip frames so items, chat links, comparisons all get themed
    local extraTooltips = {
        "ItemRefTooltip",       -- Chat link tooltips
        "ShoppingTooltip1",     -- Comparison tooltip 1
        "ShoppingTooltip2",     -- Comparison tooltip 2
        "EmbeddedItemTooltip",  -- Embedded item tooltips
        "GameTooltipTooltip",   -- Sub-tooltip
    }
    for _, tipName in ipairs(extraTooltips) do
        local tip = _G[tipName]
        if tip and tip.HookScript then
            tip:HookScript("OnShow", function(self)
                if not DT.DB or not DT.DB.enabled then return end
                ApplyNeumorphicStyle(self)
                ApplyFontsToLines(self)
            end)
            tip:HookScript("OnTooltipCleared", function(self)
                if DT.DB and DT.DB.neumorphicStyle then
                    ApplyNeumorphicStyle(self)
                end
            end)
            tip:HookScript("OnHide", function(self)
                if self.DT_gradient then self.DT_gradient:Hide() end
                -- Re-enforce theme border on hide for extra tooltips too
                if DT.DB and DT.DB.neumorphicStyle and self.SetBackdropBorderColor then
                    local bd = DT.Themes and DT.Themes.GetBackdrop()
                    if bd then
                        isSettingBackdrop = true
                        self:SetBackdropBorderColor(bd.borderColor.r, bd.borderColor.g, bd.borderColor.b, bd.borderColor.a)
                        self:SetBackdropColor(bd.bgColor.r, bd.bgColor.g, bd.bgColor.b, bd.bgColor.a)
                        isSettingBackdrop = false
                    end
                end
            end)
        end
    end
    
    -- Hook OnHide to re-enforce theme colors and clean up all flags.
    -- (Blizzard's GameTooltip_OnHide resets border/bg to TOOLTIP_DEFAULT_COLOR;
    --  we override those globals in Themes.Set(), but also re-apply here as a safety net)
    GameTooltip:HookScript("OnHide", function(self)
        DebugPrint("OnHide - clearing ALL flags including lastSetMethod/Args, processedSpell:", self.DT_processedSpell or "nil")
        self.DT_justRebuilt = nil
        self.DT_isSpellTooltip = nil
        self.DT_processedSpell = nil
        self.DT_processedGen = nil
        self.DT_petActionSlot = nil
        self.DT_buffUnit = nil
        self.DT_buffIndex = nil
        self.DT_buffFilter = nil
        self.DT_lastSetMethod = nil
        self.DT_lastSetArgs = nil
        if DT.DB and DT.DB.neumorphicStyle and self.SetBackdropBorderColor then
            local bd = DT.Themes and DT.Themes.GetBackdrop()
            if bd then
                isSettingBackdrop = true
                self:SetBackdropBorderColor(bd.borderColor.r, bd.borderColor.g, bd.borderColor.b, bd.borderColor.a)
                self:SetBackdropColor(bd.bgColor.r, bd.bgColor.g, bd.bgColor.b, bd.bgColor.a)
                isSettingBackdrop = false
            end
        end
        -- Hide gradient on hide
        if self.DT_gradient then
            self.DT_gradient:Hide()
        end
    end)
    
    -- Hook item/unit tooltip sets so themed border persists on non-spell tooltips
    -- Note: In Retail (10.0+), OnTooltipSetItem doesn't exist - use TooltipDataProcessor instead (hooked earlier)
    if not DT.isRetail and GameTooltip.HookScript then
        -- Classic/TBC: Use the old script hook method
        GameTooltip:HookScript("OnTooltipSetItem", function(self)
            if DT.DB and DT.DB.neumorphicStyle then
                ApplyNeumorphicStyle(self)
            end
        end)
    end
    if GameTooltip.SetUnit then
        hooksecurefunc(GameTooltip, "SetUnit", function(self)
            if DT.DB and DT.DB.neumorphicStyle then
                ApplyNeumorphicStyle(self)
            end
        end)
    end
    -- Hook SetHyperlink for tracking tooltips and other hyperlink-based tooltips
    if GameTooltip.SetHyperlink then
        hooksecurefunc(GameTooltip, "SetHyperlink", function(self)
            if DT.DB and DT.DB.neumorphicStyle then
                ApplyNeumorphicStyle(self)
            end
        end)
    end
    -- Hook SetTrackingSpell for Find Herbs/Minerals/etc tracking tooltips
    if GameTooltip.SetTrackingSpell then
        hooksecurefunc(GameTooltip, "SetTrackingSpell", function(self)
            if DT.DB and DT.DB.neumorphicStyle then
                ApplyNeumorphicStyle(self)
            end
        end)
    end
    
    -- TipTac-inspired: hook SharedTooltip_SetBackdropStyle to reapply our
    -- backdrop after Blizzard resets it (eg. item tooltips, map POIs, talent tree)
    if SharedTooltip_SetBackdropStyle then
        hooksecurefunc("SharedTooltip_SetBackdropStyle", function(tip, style, embedded)
            if not DT.DB or not DT.DB.neumorphicStyle then return end
            ApplyNeumorphicStyle(tip)
        end)
    end
    
    -- Hook GameTooltip_CalculatePadding if it exists (reapply after padding recalc)
    if GameTooltip_CalculatePadding then
        hooksecurefunc("GameTooltip_CalculatePadding", function(tip)
            if not DT.DB or not DT.DB.neumorphicStyle then return end
            ApplyNeumorphicStyle(tip)
        end)
    end
end

local function Initialize()
    InitializeDatabase()
    HookTooltips()
    
    -- Make Blizzard's default tooltip border completely transparent.
    -- Our themes provide their own distinct borders; this prevents the
    -- default grey border from ever flashing between tooltip transitions.
    TOOLTIP_DEFAULT_COLOR = CreateColor(0, 0, 0, 0)
    TOOLTIP_DEFAULT_BACKGROUND_COLOR = CreateColor(0, 0, 0, 0)
    
    -- Initialize theme from saved settings (this overrides the globals above
    -- with the theme's own border/bg colors for GameTooltip_OnHide compat)
    if DT.Themes and DT.DB.currentTheme then
        DT.Themes.Set(DT.DB.currentTheme)
    end
    
    -- TipTac-inspired: apply theme fonts to GameTooltip font objects
    ApplyThemeFonts()
    
    -- Wire up theme change callback: re-apply fonts + reapply backdrop globals
    -- whenever the user switches themes via the options panel
    DT.Themes.OnThemeChanged = function(themeName)
        ApplyThemeFonts()
    end
    
    -- Initialize minimap button
    if DT.Minimap and DT.Minimap.Initialize then
        DT.Minimap:Initialize()
    end
    
    local themeName = DT.Themes and DT.Themes.GetCurrent().name or "Medieval"
    print("|cff66ccffDynamicTooltips|r v" .. DT.version .. " loaded - " .. themeName .. " Theme")
    print("  Type |cff66ccff/dt|r for commands, or click the minimap button for options.")
end

-- ============================================================================
-- EVENT HANDLING
-- ============================================================================

local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("PLAYER_LOGIN")
eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:SetScript("OnEvent", function(self, event, arg1)
    if event == "ADDON_LOADED" and arg1 == addonName then
        InitializeDatabase()
    elseif event == "PLAYER_LOGIN" then
        Initialize()
    end
end)

-- ============================================================================
-- SLASH COMMANDS
-- ============================================================================

SLASH_DYNAMICTOOLTIPS1 = "/dt"
SLASH_DYNAMICTOOLTIPS2 = "/dynamictooltips"
SlashCmdList["DYNAMICTOOLTIPS"] = function(msg)
    msg = (msg or ""):lower():trim()
    
    local c = DT.Style.colors
    
    if msg == "test" then
        -- Show a test tooltip with full custom styling
        GameTooltip:SetOwner(UIParent, "ANCHOR_CURSOR")
        GameTooltip:ClearLines()
        
        -- HEADER: Spell Name + Rank
        GameTooltip:AddDoubleLine("Corruption", "Rank 8", 
            c.title.r, c.title.g, c.title.b,
            c.rank.r, c.rank.g, c.rank.b)
        
        -- CAST INFO (compact 2-column layout)
        GameTooltip:AddDoubleLine("* 370 Mana", "! Instant",
            c.mana.r, c.mana.g, c.mana.b,
            c.castTime.r, c.castTime.g, c.castTime.b)
        GameTooltip:AddDoubleLine("-> 30 yd range", "@ 8 sec cooldown",
            c.range.r, c.range.g, c.range.b,
            c.cooldown.r, c.cooldown.g, c.cooldown.b)
        
        -- DESCRIPTION
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("Corrupts the target, causing 900 Shadow damage over 18 sec.", 
            c.description.r, c.description.g, c.description.b, true)
        
        -- SCALING SECTION
        GameTooltip:AddLine(" ")
        
        local baseGreen = ColorText("900", c.base)
        local coeffGold = ColorText("110%", c.gold)
        GameTooltip:AddLine("Base: " .. baseGreen .. " + " .. coeffGold .. " of Spell Power",
            c.description.r, c.description.g, c.description.b)
        
        local totalGold = ColorText("1.3k", c.gold)
        GameTooltip:AddLine("Total: " .. totalGold .. " shadow damage",
            c.stats.r, c.stats.g, c.stats.b)
        
        local dpsOrange = ColorText("72.2", c.dps)
        GameTooltip:AddLine("(" .. dpsOrange .. " DPS over 18s)",
            c.footer.r, c.footer.g, c.footer.b)
        
        -- FOOTER
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("Spell ID: 27216", c.footer.r, c.footer.g, c.footer.b)
        
        ApplyNeumorphicStyle(GameTooltip)
        StyleRebuiltTooltip(GameTooltip)
        GameTooltip:Show()
        
    elseif msg == "stats" then
        if DT.Math then
            local spellDmg = DT.Math:GetSpellPower("Shadow")
            local healPower = DT.Math:GetHealingPower()
            local weaponDmg = DT.Math:GetWeaponDamage("main")
            local ap = DT.Math:GetAttackPower()
            local rap = DT.Math:GetRangedAttackPower()
            
            print("|cff66ccffDynamicTooltips|r - Your Stats:")
            print("  Spell Power (Shadow): " .. FormatNumber(spellDmg))
            print("  Healing Power: " .. FormatNumber(healPower))
            print("  Weapon Damage: " .. FormatNumber(weaponDmg))
            print("  Attack Power: " .. FormatNumber(ap))
            print("  Ranged Attack Power: " .. FormatNumber(rap))
        else
            print("|cff66ccffDynamicTooltips|r: Math module not loaded.")
        end
        
    elseif msg == "toggle" then
        DT.DB.enabled = not DT.DB.enabled
        local status = DT.DB.enabled and "|cff00ff00Enabled|r" or "|cffff0000Disabled|r"
        print("|cff66ccffDynamicTooltips|r: " .. status)
        
    elseif msg == "minimap" then
        if DT.Minimap then
            DT.Minimap:Toggle()
            print("|cff66ccffDynamicTooltips|r: Minimap button toggled.")
        end
        
    elseif msg == "config" or msg == "options" then
        if DT.Minimap then
            DT.Minimap:ShowOptions()
        elseif DynamicTooltipsOptionsPanel then
            DynamicTooltipsOptionsPanel:Show()
        else
            print("|cff66ccffDynamicTooltips|r: Options panel not available. Click the minimap button.")
        end
    
    elseif msg == "fonts" then
        -- List available fonts
        print("|cff66ccffDynamicTooltips|r - Available Fonts:")
        local list = DT.Fonts:GetList()
        local currentId = DT.Fonts.current
        for _, font in ipairs(list) do
            local marker = font.id == currentId and " |cff00ff00(active)|r" or ""
            print("  |cffffffff" .. font.id .. "|r - " .. font.name .. marker)
        end
        print("  Use |cffffffff/dt font <name>|r to switch fonts.")
    
    elseif msg:find("^font%s+") then
        local fontName = msg:match("^font%s+(.+)$")
        if fontName then
            if DT.Fonts:Set(fontName) then
                local _, fontData = DT.Fonts:GetCurrent()
                print("|cff66ccffDynamicTooltips|r: Font changed to |cffffffff" .. fontData.name .. "|r")
                print("  Note: Some tooltips may need to be refreshed.")
            else
                print("|cff66ccffDynamicTooltips|r: Unknown font '" .. fontName .. "'. Use |cffffffff/dt fonts|r to list available fonts.")
            end
        end
    
    elseif msg == "themes" then
        -- List available themes
        if DT.Themes then
            print("|cff66ccffDynamicTooltips|r - Available Themes:")
            local list = DT.Themes.GetList()
            local currentName = DT.Themes.GetCurrentName()
            for _, theme in ipairs(list) do
                local marker = theme.id == currentName and " |cff00ff00(active)|r" or ""
                print("  |cffffffff" .. theme.id .. "|r - " .. theme.name .. marker)
            end
            print("  Use |cffffffff/dt theme <name>|r to switch themes.")
        else
            print("|cff66ccffDynamicTooltips|r: Theme system not loaded.")
        end
    
    elseif msg:find("^theme%s+") then
        local themeName = msg:match("^theme%s+(.+)$")
        if themeName and DT.Themes then
            if DT.Themes.Set(themeName) then
                local theme = DT.Themes.GetCurrent()
                print("|cff66ccffDynamicTooltips|r: Theme changed to |cffffffff" .. theme.name .. "|r")
            else
                print("|cff66ccffDynamicTooltips|r: Unknown theme '" .. themeName .. "'. Use |cffffffff/dt themes|r to list available themes.")
            end
        else
            print("|cff66ccffDynamicTooltips|r: Theme system not loaded.")
        end
    
    elseif msg == "debug" then
        DT.DB.debugMode = not DT.DB.debugMode
        local status = DT.DB.debugMode and "|cff00ff00ON|r" or "|cffff0000OFF|r"
        print("|cff66ccffDynamicTooltips|r: Debug mode " .. status)
        if DT.DB.debugMode then
            -- Print some diagnostic info
            print("  DT.SpellData loaded:", DT.SpellData and "YES" or "NO")
            print("  DT.SpellDataByName loaded:", DT.SpellDataByName and "YES" or "NO")
            print("  DT.Math loaded:", DT.Math and "YES" or "NO")
            print("  DT.Themes loaded:", DT.Themes and "YES" or "NO")
            if DT.SpellData then
                local count = 0
                for _ in pairs(DT.SpellData) do count = count + 1 end
                print("  Spells by ID:", count)
            end
            if DT.SpellDataByName then
                local count = 0
                for _ in pairs(DT.SpellDataByName) do count = count + 1 end
                print("  Spells by Name:", count)
            end
        end
    
    elseif msg == "lookup" or msg:find("^lookup%s+") then
        -- Lookup a spell by name or ID
        local searchTerm = msg:match("^lookup%s+(.+)$")
        if searchTerm then
            local asNum = tonumber(searchTerm)
            local found = false
            
            -- Try ID lookup
            if asNum and DT.SpellData and DT.SpellData[asNum] then
                local d = DT.SpellData[asNum]
                print("|cff66ccffDynamicTooltips|r: Found spell ID " .. asNum)
                print("  Name:", d.name or "unknown")
                print("  SP Coeff:", d.sp or 0)
                print("  WD Coeff:", d.wd or 0)
                print("  School:", d.school or "unknown")
                print("  Type:", d.type or "unknown")
                found = true
            end
            
            -- Try name lookup
            if DT.SpellDataByName and DT.SpellDataByName[searchTerm:lower()] then
                local d = DT.SpellDataByName[searchTerm:lower()]
                print("|cff66ccffDynamicTooltips|r: Found spell by name '" .. searchTerm .. "'")
                print("  Name:", d.name or "unknown")
                print("  SP Coeff:", d.sp or 0)
                print("  WD Coeff:", d.wd or 0)
                print("  School:", d.school or "unknown")
                print("  Type:", d.type or "unknown")
                found = true
            end
            
            if not found then
                print("|cff66ccffDynamicTooltips|r: No spell found for '" .. searchTerm .. "'")
            end
        else
            print("|cff66ccffDynamicTooltips|r: Usage: /dt lookup <spellID or spellName>")
        end
        
    else
        print("|cff66ccffDynamicTooltips|r Commands:")
        print("  |cffffffff/dt test|r - Show a test tooltip")
        print("  |cffffffff/dt stats|r - Show your current stats")
        print("  |cffffffff/dt toggle|r - Enable/disable the addon")
        print("  |cffffffff/dt minimap|r - Toggle minimap button")
        print("  |cffffffff/dt config|r - Open options panel")
        print("  |cffffffff/dt fonts|r - List available fonts")
        print("  |cffffffff/dt font <name>|r - Switch to a font")
        print("  |cffffffff/dt themes|r - List available themes")
        print("  |cffffffff/dt theme <name>|r - Switch to a theme")
        print("  |cffffffff/dt debug|r - Toggle debug mode")
        print("  |cffffffff/dt lookup <name/id>|r - Lookup spell data")
    end
end

-- ============================================================================
-- EXPORTS
-- ============================================================================

_G.DynamicTooltips = DT
