SFrames.Target = {}
local _A = SFrames.ActiveTheme

local function Clamp(value, minValue, maxValue)
    if value < minValue then
        return minValue
    end
    if value > maxValue then
        return maxValue
    end
    return value
end

local DIST_BASE_WIDTH    = 80
local DIST_BASE_HEIGHT   = 24
local DIST_BASE_FONTSIZE = 14

function SFrames.Target:GetDistance(unit)
    if not UnitExists(unit) then return nil end
    if UnitIsUnit(unit, "player") then return "0 码" end
    -- Using multiple "scale rulers" (rungs) for better precision in 1.12
    if CheckInteractDistance(unit, 2) then return "< 8 码" -- Trade
    elseif CheckInteractDistance(unit, 3) then return "8-10 码" -- Duel
    elseif CheckInteractDistance(unit, 4) then return "10-28 码" -- Follow
    elseif UnitIsVisible(unit) then return "28-100 码"
    else return "> 100 码" end
end

function SFrames.Target:GetConfig()
    local db = SFramesDB or {}

    local width = tonumber(db.targetFrameWidth) or SFrames.Config.width or 220
    width = Clamp(math.floor(width + 0.5), 170, 420)

    local portraitWidth = tonumber(db.targetPortraitWidth) or SFrames.Config.portraitWidth or 50
    portraitWidth = Clamp(math.floor(portraitWidth + 0.5), 32, 95)
    if portraitWidth > width - 90 then
        portraitWidth = width - 90
    end

    local healthHeight = tonumber(db.targetHealthHeight) or 38
    healthHeight = Clamp(math.floor(healthHeight + 0.5), 14, 80)

    local powerHeight = tonumber(db.targetPowerHeight) or 9
    powerHeight = Clamp(math.floor(powerHeight + 0.5), 6, 40)

    local height = healthHeight + powerHeight + 4
    height = Clamp(height, 30, 140)

    local nameFont = tonumber(db.targetNameFontSize) or 10
    nameFont = Clamp(math.floor(nameFont + 0.5), 8, 18)

    local valueFont = tonumber(db.targetValueFontSize) or 10
    valueFont = Clamp(math.floor(valueFont + 0.5), 8, 18)

    local frameScale = tonumber(db.targetFrameScale) or 1
    frameScale = Clamp(frameScale, 0.7, 1.8)

    return {
        width = width,
        height = height,
        portraitWidth = portraitWidth,
        healthHeight = healthHeight,
        powerHeight = powerHeight,
        nameFont = nameFont,
        valueFont = valueFont,
        scale = frameScale,
    }
end

function SFrames.Target:ApplyConfig()
    if not self.frame then return end

    local cfg = self:GetConfig()
    local f = self.frame
    local db = SFramesDB or {}

    local showPortrait = db.targetShowPortrait ~= false
    local frameAlpha = tonumber(db.targetFrameAlpha) or 1
    frameAlpha = Clamp(frameAlpha, 0.1, 1.0)

    f:SetScale(cfg.scale)
    f:SetWidth(cfg.width)
    f:SetHeight(cfg.height)
    f:SetAlpha(frameAlpha)

    if showPortrait then
        if f.portrait then
            f.portrait:SetWidth(cfg.portraitWidth)
            f.portrait:SetHeight(cfg.height - 2)
            f.portrait:Show()
        end
        if f.portraitBG then
            f.portraitBG:ClearAllPoints()
            f.portraitBG:SetPoint("TOPLEFT", f.portrait, "TOPLEFT", -1, 0)
            f.portraitBG:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
            f.portraitBG:Show()
        end
        if f.health then
            f.health:ClearAllPoints()
            f.health:SetPoint("TOPLEFT", f, "TOPLEFT", 1, -1)
            f.health:SetPoint("TOPRIGHT", f.portrait, "TOPLEFT", -1, 0)
            f.health:SetHeight(cfg.healthHeight)
        end
        if f.classIcon and f.classIcon.overlay then
            f.classIcon.overlay:ClearAllPoints()
            f.classIcon.overlay:SetPoint("CENTER", f.portrait, "TOPRIGHT", 0, 0)
        end
        if f.comboText then
            f.comboText:ClearAllPoints()
            f.comboText:SetPoint("CENTER", f.portrait, "CENTER", 0, 0)
        end
    else
        if f.portrait then f.portrait:Hide() end
        if f.portraitBG then f.portraitBG:Hide() end
        if f.health then
            f.health:ClearAllPoints()
            f.health:SetPoint("TOPLEFT", f, "TOPLEFT", 1, -1)
            f.health:SetPoint("TOPRIGHT", f, "TOPRIGHT", -1, -1)
            f.health:SetHeight(cfg.healthHeight)
        end
        if f.classIcon and f.classIcon.overlay then
            f.classIcon.overlay:ClearAllPoints()
            f.classIcon.overlay:SetPoint("CENTER", f, "TOPRIGHT", -8, 0)
        end
        if f.comboText then
            f.comboText:ClearAllPoints()
            f.comboText:SetPoint("RIGHT", f.health, "RIGHT", -4, 0)
        end
    end

    if f.healthBGFrame then
        f.healthBGFrame:ClearAllPoints()
        f.healthBGFrame:SetPoint("TOPLEFT", f.health, "TOPLEFT", -1, 1)
        f.healthBGFrame:SetPoint("BOTTOMRIGHT", f.health, "BOTTOMRIGHT", 1, -1)
    end

    if f.power then
        f.power:ClearAllPoints()
        f.power:SetPoint("TOPLEFT", f.health, "BOTTOMLEFT", 0, -1)
        f.power:SetPoint("TOPRIGHT", f.health, "BOTTOMRIGHT", 0, 0)
        f.power:SetHeight(cfg.powerHeight)
    end

    if f.powerBGFrame then
        f.powerBGFrame:ClearAllPoints()
        f.powerBGFrame:SetPoint("TOPLEFT", f.power, "TOPLEFT", -1, 1)
        f.powerBGFrame:SetPoint("BOTTOMRIGHT", f.power, "BOTTOMRIGHT", 1, -1)
    end

    local outline = (SFrames and SFrames.Media and SFrames.Media.fontOutline) or "OUTLINE"
    local fontPath = SFrames:GetFont()

    if f.nameText then
        f.nameText:SetFont(fontPath, cfg.nameFont, outline)
    end
    if f.healthText then
        f.healthText:SetFont(fontPath, cfg.valueFont, outline)
    end
    if f.powerText then
        f.powerText:SetFont(fontPath, cfg.valueFont, outline)
    end

    if f.castbar then
        f.castbar:ClearAllPoints()
        if showPortrait then
            f.castbar:SetPoint("BOTTOMLEFT", f, "TOPLEFT", 0, 6)
            f.castbar:SetPoint("BOTTOMRIGHT", f.portrait, "TOPRIGHT", -(SFrames.Config.castbarHeight + 6), 6)
        else
            f.castbar:SetPoint("BOTTOMLEFT", f, "TOPLEFT", 0, 6)
            f.castbar:SetPoint("BOTTOMRIGHT", f, "TOPRIGHT", -(SFrames.Config.castbarHeight + 6), 6)
        end
    end

    if self.distanceFrame then
        local dScale = tonumber(SFramesDB and SFramesDB.targetDistanceScale) or 1
        self:ApplyDistanceScale(dScale)
    end

    if UnitExists("target") then
        self:UpdateAll()
    end
end

function SFrames.Target:ApplyDistanceScale(scale)
    local f = self.distanceFrame
    if not f then return end
    scale = Clamp(tonumber(scale) or 1, 0.7, 1.8)
    f:SetWidth(DIST_BASE_WIDTH * scale)
    f:SetHeight(DIST_BASE_HEIGHT * scale)
    if f.text then
        local fontPath = SFrames:GetFont()
        local outline = (SFrames.Media and SFrames.Media.fontOutline) or "OUTLINE"
        local fontSize = math.max(8, math.floor(DIST_BASE_FONTSIZE * scale + 0.5))
        f.text:SetFont(fontPath, fontSize, outline)
    end
end

function SFrames.Target:InitializeDistanceFrame()
    local f = CreateFrame("Button", "SFramesTargetDistanceFrame", UIParent)
    f:SetFrameStrata("HIGH")

    local dScale = (SFramesDB and type(SFramesDB.targetDistanceScale) == "number") and SFramesDB.targetDistanceScale or 1
    dScale = Clamp(dScale, 0.7, 1.8)
    f:SetWidth(DIST_BASE_WIDTH * dScale)
    f:SetHeight(DIST_BASE_HEIGHT * dScale)

    if SFramesDB and SFramesDB.Positions and SFramesDB.Positions["TargetDistanceFrame"] then
        local pos = SFramesDB.Positions["TargetDistanceFrame"]
        f:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs, pos.yOfs)
    else
        f:SetPoint("CENTER", UIParent, "CENTER", 0, 100)
    end

    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", function() if IsAltKeyDown() or SFrames.isUnlocked then this:StartMoving() end end)
    f:SetScript("OnDragStop", function()
        this:StopMovingOrSizing()
        if not SFramesDB then SFramesDB = {} end
        if not SFramesDB.Positions then SFramesDB.Positions = {} end
        local point, relativeTo, relativePoint, xOfs, yOfs = this:GetPoint()
        SFramesDB.Positions["TargetDistanceFrame"] = { point = point, relativePoint = relativePoint, xOfs = xOfs, yOfs = yOfs }
    end)

    SFrames:CreateUnitBackdrop(f)
    f:SetBackdrop(nil)

    local fontSize = math.max(8, math.floor(DIST_BASE_FONTSIZE * dScale + 0.5))
    f.text = SFrames:CreateFontString(f, fontSize, "CENTER")
    f.text:SetPoint("CENTER", f, "CENTER", 0, 0)
    f.text:SetTextColor(1, 0.8, 0.2)
    f.text:SetShadowColor(0, 0, 0, 1)
    f.text:SetShadowOffset(1, -1)

    SFrames.Target.distanceFrame = f
    f:Hide()

    f.timer = 0
    f:SetScript("OnUpdate", function()
        if SFramesDB and SFramesDB.targetDistanceEnabled == false then
            if this:IsShown() then this:Hide() end
            return
        end
        if not UnitExists("target") then
            if this:IsShown() then this:Hide() end
            return
        end
        this.timer = this.timer + (arg1 or 0)
        if this.timer >= 0.4 then
            this.timer = 0
            local dist = SFrames.Target:GetDistance("target")
            this.text:SetText(dist or "---")
            if not this:IsShown() then this:Show() end
        end
    end)
end

local AURA_SIZE = 24
local AURA_SPACING = 2
local AURA_ROW_SPACING = 1

local function GetIncomingHeals(unit)
    return SFrames:GetIncomingHeals(unit)
end

local function TryDropCursorOnUnit(unit)
    if not unit or not UnitExists(unit) then return false end
    if not CursorHasItem or not CursorHasItem() then return false end
    if not DropItemOnUnit then return false end

    local ok = pcall(DropItemOnUnit, unit)
    if not ok then
        return false
    end

    return not CursorHasItem()
end

function SFrames.Target:Initialize()
    local f = CreateFrame("Button", "SFramesTargetFrame", UIParent)
    f:SetWidth(SFrames.Config.width)
    f:SetHeight(SFrames.Config.height)
    if SFramesDB and SFramesDB.Positions and SFramesDB.Positions["TargetFrame"] then
        local pos = SFramesDB.Positions["TargetFrame"]
        f:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs, pos.yOfs)
    else
        f:SetPoint("CENTER", UIParent, "CENTER", 200, -100) -- Mirrored from player
    end
    local frameScale = (SFramesDB and type(SFramesDB.targetFrameScale) == "number") and SFramesDB.targetFrameScale or 1
    f:SetScale(frameScale)
    
    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", function() if IsAltKeyDown() or SFrames.isUnlocked then f:StartMoving() end end)
    f:SetScript("OnDragStop", function()
        f:StopMovingOrSizing()
        if not SFramesDB then SFramesDB = {} end
        if not SFramesDB.Positions then SFramesDB.Positions = {} end
        local point, relativeTo, relativePoint, xOfs, yOfs = f:GetPoint()
        SFramesDB.Positions["TargetFrame"] = { point = point, relativePoint = relativePoint, xOfs = xOfs, yOfs = yOfs }
    end)
    
    f:RegisterForClicks("LeftButtonUp", "RightButtonUp")
    f:SetScript("OnClick", function()
        if arg1 == "LeftButton" then
            if TryDropCursorOnUnit(this.unit) then
                return
            end
            if SpellIsTargeting and SpellIsTargeting() then
                SpellTargetUnit(this.unit)
            end
        elseif arg1 == "RightButton" then
            if SpellIsTargeting() then
                SpellStopTargeting()
                return
            end
            HideDropDownMenu(1)
            TargetFrameDropDown.unit = "target"
            TargetFrameDropDown.name = UnitName("target")
            TargetFrameDropDown.initialize = TargetFrameDropDown_Initialize
            ToggleDropDownMenu(1, nil, TargetFrameDropDown, "SFramesTargetFrame", 120, 10)
        end
    end)
    f:SetScript("OnReceiveDrag", function()
        if TryDropCursorOnUnit(this.unit) then
            return
        end
        if SpellIsTargeting and SpellIsTargeting() then
            SpellTargetUnit(this.unit)
        end
    end)
    
    SFrames:CreateUnitBackdrop(f)
    
    -- 3D Portrait (Right side for target)
    local pWidth = SFrames.Config.portraitWidth
    f.portrait = CreateFrame("PlayerModel", nil, f)
    f.portrait:SetWidth(pWidth)
    f.portrait:SetHeight(SFrames.Config.height - 2)
    f.portrait:SetPoint("RIGHT", f, "RIGHT", -1, 0)
    
    local pbg = CreateFrame("Frame", nil, f)
    pbg:SetPoint("TOPLEFT", f.portrait, "TOPLEFT", -1, 0)
    pbg:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
    pbg:SetFrameLevel(f:GetFrameLevel())
    SFrames:CreateUnitBackdrop(pbg)
    f.portraitBG = pbg
    
    -- Health Bar (Left side)
    f.health = SFrames:CreateStatusBar(f, "SFramesTargetHealth")
    f.health:SetPoint("TOPLEFT", f, "TOPLEFT", 1, -1)
    f.health:SetPoint("TOPRIGHT", f.portrait, "TOPLEFT", -1, 0)
    f.health:SetHeight((SFrames.Config.height - 2) * 0.82 - 1)
    
    local hbg = CreateFrame("Frame", nil, f)
    hbg:SetPoint("TOPLEFT", f.health, "TOPLEFT", -1, 1)
    hbg:SetPoint("BOTTOMRIGHT", f.health, "BOTTOMRIGHT", 1, -1)
    hbg:SetFrameLevel(f:GetFrameLevel() - 1)
    SFrames:CreateUnitBackdrop(hbg)
    f.healthBGFrame = hbg
    
    -- Add a dark backdrop behind the health texture
    f.health.bg = f.health:CreateTexture(nil, "BACKGROUND")
    f.health.bg:SetAllPoints()
    f.health.bg:SetTexture(SFrames:GetTexture())
    f.health.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)

    -- Heal prediction overlay (incoming heals)
    f.health.healPredMine = f.health:CreateTexture(nil, "OVERLAY")
    f.health.healPredMine:SetTexture(SFrames:GetTexture())
    f.health.healPredMine:SetVertexColor(0.4, 1.0, 0.55, 0.78)
    f.health.healPredMine:Hide()

    f.health.healPredOther = f.health:CreateTexture(nil, "OVERLAY")
    f.health.healPredOther:SetTexture(SFrames:GetTexture())
    f.health.healPredOther:SetVertexColor(0.2, 0.9, 0.35, 0.5)
    f.health.healPredOther:Hide()
    
    -- Power Bar
    f.power = SFrames:CreateStatusBar(f, "SFramesTargetPower")
    f.power:SetPoint("TOPLEFT", f.health, "BOTTOMLEFT", 0, -1)
    f.power:SetPoint("BOTTOMRIGHT", f.portrait, "BOTTOMLEFT", -1, 0)
    
    local powerbg = CreateFrame("Frame", nil, f)
    powerbg:SetPoint("TOPLEFT", f.power, "TOPLEFT", -1, 1)
    powerbg:SetPoint("BOTTOMRIGHT", f.power, "BOTTOMRIGHT", 1, -1)
    powerbg:SetFrameLevel(f:GetFrameLevel() - 1)
    SFrames:CreateUnitBackdrop(powerbg)
    f.powerBGFrame = powerbg
    
    -- Add a dark backdrop behind the power texture
    f.power.bg = f.power:CreateTexture(nil, "BACKGROUND")
    f.power.bg:SetAllPoints()
    f.power.bg:SetTexture(SFrames:GetTexture())
    f.power.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)

    -- Class Icon Badge (overlaid on portrait, top-right corner with 1/3 outside)
    f.classIcon = SFrames:CreateClassIcon(f, 16)
    f.classIcon.overlay:SetPoint("CENTER", f.portrait, "TOPRIGHT", 0, 0)

    -- Texts
    f.nameText = SFrames:CreateFontString(f.health, 10, "LEFT")
    f.nameText:SetPoint("LEFT", f.health, "LEFT", 4, 0)
    
    f.healthText = SFrames:CreateFontString(f.health, 10, "RIGHT")
    f.healthText:SetPoint("RIGHT", f.health, "RIGHT", -4, 0)

    f.powerText = SFrames:CreateFontString(f.power, 10, "RIGHT")
    f.powerText:SetPoint("RIGHT", f.power, "RIGHT", -4, 0)

    -- Outline/shadow setup for text to make it pop
    f.nameText:SetShadowColor(0, 0, 0, 1)
    f.nameText:SetShadowOffset(1, -1)
    f.healthText:SetShadowColor(0, 0, 0, 1)
    f.healthText:SetShadowOffset(1, -1)
    f.powerText:SetShadowColor(0, 0, 0, 1)
    f.powerText:SetShadowOffset(1, -1)
    
    -- Combo Points
    f.comboText = SFrames:CreateFontString(f, 20, "CENTER")
    f.comboText:SetPoint("CENTER", f.portrait, "CENTER", 0, 0)
    f.comboText:SetTextColor(1, 0.8, 0)
    f.comboText:SetText("")

    -- Raid Target Icon (top center of health bar, half outside frame)
    local raidIconSize = 22
    local raidIconOvr = CreateFrame("Frame", nil, f)
    raidIconOvr:SetFrameLevel((f:GetFrameLevel() or 0) + 5)
    raidIconOvr:SetWidth(raidIconSize)
    raidIconOvr:SetHeight(raidIconSize)
    raidIconOvr:SetPoint("CENTER", f.health, "TOP", 0, 0)
    f.raidIcon = raidIconOvr:CreateTexture(nil, "OVERLAY")
    f.raidIcon:SetTexture("Interface\\TargetingFrame\\UI-RaidTargetingIcons")
    f.raidIcon:SetAllPoints(raidIconOvr)
    f.raidIcon:Hide()
    f.raidIconOverlay = raidIconOvr

    self.frame = f
    self:ApplyConfig()
    f:Hide()
    
    SFrames:RegisterEvent("PLAYER_TARGET_CHANGED", function() self:OnTargetChanged() end)
    SFrames:RegisterEvent("UNIT_HEALTH", function() if arg1 == "target" then self:UpdateHealth() end end)
    SFrames:RegisterEvent("UNIT_MAXHEALTH", function() if arg1 == "target" then self:UpdateHealth() end end)
    SFrames:RegisterEvent("UNIT_MANA", function() if arg1 == "target" then self:UpdatePower() end end)
    SFrames:RegisterEvent("UNIT_MAXMANA", function() if arg1 == "target" then self:UpdatePower() end end)
    SFrames:RegisterEvent("UNIT_ENERGY", function() if arg1 == "target" then self:UpdatePower() end end)
    SFrames:RegisterEvent("UNIT_MAXENERGY", function() if arg1 == "target" then self:UpdatePower() end end)
    SFrames:RegisterEvent("UNIT_RAGE", function() if arg1 == "target" then self:UpdatePower() end end)
    SFrames:RegisterEvent("UNIT_MAXRAGE", function() if arg1 == "target" then self:UpdatePower() end end)
    SFrames:RegisterEvent("PLAYER_COMBO_POINTS", function() self:UpdateComboPoints() end)
    SFrames:RegisterEvent("UNIT_DISPLAYPOWER", function() if arg1 == "target" then self:UpdatePowerType() end end)
    SFrames:RegisterEvent("UNIT_PORTRAIT_UPDATE", function()
        if arg1 == "target" and self.frame.portrait and not (SFramesDB and SFramesDB.targetShowPortrait == false) then
            self.frame.portrait:SetUnit("target")
            self.frame.portrait:SetCamera(0)
            self.frame.portrait:SetPosition(-1.0, 0, 0)
        end
    end)
    SFrames:RegisterEvent("UNIT_DYNAMIC_FLAGS", function() if arg1 == "target" then self:UpdateAll() end end)
    SFrames:RegisterEvent("UNIT_FACTION", function() if arg1 == "target" then self:UpdateAll() end end)
    SFrames:RegisterEvent("RAID_TARGET_UPDATE", function() self:UpdateRaidIcon() end)
    
    self:CreateAuras()
    self:CreateCastbar()
    self:InitializeDistanceFrame()
    
    f.unit = "target"
    f:SetScript("OnEnter", function()
        GameTooltip_SetDefaultAnchor(GameTooltip, this)
        GameTooltip:SetUnit(this.unit)
        GameTooltip:Show()
    end)
    f:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)
    
    -- If target already exists on load (e.g. after /reload), show and update it immediately
    self:OnTargetChanged()

    -- Register movers
    if SFrames.Movers and SFrames.Movers.RegisterMover then
        SFrames.Movers:RegisterMover("TargetFrame", f, "目标",
            "CENTER", "UIParent", "CENTER", 200, -100)
        if SFrames.Target.distanceFrame then
            SFrames.Movers:RegisterMover("TargetDistanceFrame", SFrames.Target.distanceFrame, "目标距离",
                "CENTER", "UIParent", "CENTER", 0, 100)
        end
    end
end

function SFrames.Target:OnTargetChanged()
    if UnitExists("target") then
        self.frame:Show()
        self:UpdateAll()
        -- Force distance update immediately
        if SFrames.Target.distanceFrame then
            local dist = self:GetDistance("target")
            SFrames.Target.distanceFrame.text:SetText(dist or "---")
            if not (SFramesDB and SFramesDB.targetDistanceEnabled == false) then
                SFrames.Target.distanceFrame:Show()
            else
                SFrames.Target.distanceFrame:Hide()
            end
        end
    else
        self.frame:Hide()
        if SFrames.Target.distanceFrame then SFrames.Target.distanceFrame:Hide() end
    end
end

function SFrames.Target:UpdateAll()
    self:UpdateHealth()
    self:UpdatePowerType()
    self:UpdatePower()
    self:UpdateComboPoints()
    self:UpdateRaidIcon()
    self:UpdateAuras()

    local showPortrait = not (SFramesDB and SFramesDB.targetShowPortrait == false)
    if showPortrait and self.frame.portrait then
        self.frame.portrait:SetUnit("target")
        self.frame.portrait:SetCamera(0)
        self.frame.portrait:Hide()
        self.frame.portrait:Show()
        self.frame.portrait:SetPosition(-1.0, 0, 0)
    end
    
    local name = UnitName("target") or ""
    local level = UnitLevel("target")
    local levelText = level
    
    -- Difficulty Color logic
    local function RGBToHex(r, g, b)
        return string.format("|cff%02x%02x%02x", r*255, g*255, b*255)
    end
    
    local function GetLevelDiffColor(targetLevel)
        local playerLevel = UnitLevel("player")
        if targetLevel == -1 then return 1, 0, 0 end -- Skull
        
        local diff = targetLevel - playerLevel
        if diff >= 5 then
            return 1, 0.1, 0.1 -- Red
        elseif diff >= 3 then
            return 1, 0.5, 0.25 -- Orange
        elseif diff >= -2 then
            return 1, 1, 0 -- Yellow
        elseif -diff <= GetQuestGreenRange() then
            return 0.25, 0.75, 0.25 -- Green
        else
            return 0.5, 0.5, 0.5 -- Grey
        end
    end
    
    local levelColor = RGBToHex(1, 1, 1) -- default white
    
    if level == -1 then 
        levelText = "??" 
        levelColor = RGBToHex(1, 0, 0) -- skull is always red
    else
        local r, g, b = GetLevelDiffColor(level)
        levelColor = RGBToHex(r, g, b)
    end
    
    local classif = UnitClassification("target")
    if classif == "elite" or classif == "rareelite" then 
        levelText = levelText .. "+" 
    elseif classif == "rare" then 
        levelText = levelText .. "R" 
    elseif classif == "worldboss" then 
        levelText = "??" 
        levelColor = RGBToHex(1, 0, 0)
    end
    
    local formattedLevel = levelColor .. levelText .. "|r"
    
    -- Toggle level display from config DB
    if SFramesDB and SFramesDB.showLevel == false then
        formattedLevel = ""
    else
        formattedLevel = formattedLevel .. " "
    end
    local showClassText = not (SFramesDB and SFramesDB.targetShowClass == false)
    if showClassText and UnitIsPlayer("target") then
        local localizedClass = UnitClass("target")
        if localizedClass and localizedClass ~= "" then
            name = name .. " " .. localizedClass
        end
    end

    if UnitIsPlayer("target") and not (SFramesDB and SFramesDB.targetShowClassIcon == false) then
        local _, tClass = UnitClass("target")
        SFrames:SetClassIcon(self.frame.classIcon, tClass)
    else
        if self.frame.classIcon then
            self.frame.classIcon:Hide()
            if self.frame.classIcon.overlay then self.frame.classIcon.overlay:Hide() end
        end
    end

    local useClassColor = not (SFramesDB and SFramesDB.classColorHealth == false)

    if UnitIsPlayer("target") and useClassColor then
        local _, class = UnitClass("target")
        if class and SFrames.Config.colors.class[class] then
            local color = SFrames.Config.colors.class[class]
            -- Set Health Color
            self.frame.health:SetStatusBarColor(color.r, color.g, color.b)
            -- Apply Class Color to Name
            self.frame.nameText:SetText(formattedLevel .. name)
            self.frame.nameText:SetTextColor(color.r, color.g, color.b)
        else
            self.frame.health:SetStatusBarColor(0, 1, 0)
            self.frame.nameText:SetText(formattedLevel .. name)
            self.frame.nameText:SetTextColor(1, 1, 1)
        end
    else
        local r, g, b = 0.85, 0.77, 0.36 -- Neutral (Softer Yellow)
        local isTapped = UnitIsTapped("target") and not UnitIsTappedByPlayer("target")
        if isTapped then
            r, g, b = 0.53, 0.53, 0.53 -- Tapped by others (Grey)
            name = name .. " (无拾取)" -- For colorblind
        elseif UnitIsEnemy("player", "target") then
            r, g, b = 0.78, 0.25, 0.25 -- Enemy (Softer Red)
        elseif UnitIsFriend("player", "target") then
            r, g, b = 0.33, 0.59, 0.33 -- Friend (Softer Green)
        end
        self.frame.health:SetStatusBarColor(r, g, b)
        -- Color Name same as reaction
        self.frame.nameText:SetText(formattedLevel .. name)
        self.frame.nameText:SetTextColor(r, g, b)
    end
end

function SFrames.Target:UpdateHealth()
    local hp = UnitHealth("target")
    local maxHp = UnitHealthMax("target")
    self.frame.health:SetMinMaxValues(0, maxHp)
    self.frame.health:SetValue(hp)

    local displayHp, displayMax = hp, maxHp
    if (not SFramesDB or SFramesDB.mobRealHealth ~= false) and maxHp == 100
        and not UnitIsPlayer("target") and not UnitPlayerControlled("target") then
        local name = UnitName("target")
        local level = UnitLevel("target")
        if name and level and LibMobHealth_Cache then
            local key = name .. ":" .. level
            local realMax = LibMobHealth_Cache[key]
            if realMax and realMax > 0 then
                displayMax = realMax
                displayHp = math.floor(realMax * hp / 100)
            end
        end
    end

    if displayMax > 0 then
        self.frame.healthText:SetText(displayHp .. " / " .. displayMax)
    else
        self.frame.healthText:SetText(displayHp)
    end

    self:UpdateHealPrediction()
end

function SFrames.Target:UpdateHealPrediction()
    if not (self.frame and self.frame.health and self.frame.health.healPredMine and self.frame.health.healPredOther) then return end
    local predMine = self.frame.health.healPredMine
    local predOther = self.frame.health.healPredOther

    local function HidePredictions()
        predMine:Hide()
        predOther:Hide()
    end

    if not UnitExists("target") then
        HidePredictions()
        return
    end

    local hp = UnitHealth("target") or 0
    local maxHp = UnitHealthMax("target") or 0
    if maxHp <= 0 or hp >= maxHp then
        HidePredictions()
        return
    end

    local _, mineIncoming, othersIncoming = GetIncomingHeals("target")
    local missing = maxHp - hp
    if missing <= 0 then
        HidePredictions()
        return
    end

    local mineShown = math.min(math.max(0, mineIncoming), missing)
    local remaining = missing - mineShown
    local otherShown = math.min(math.max(0, othersIncoming), remaining)
    if mineShown <= 0 and otherShown <= 0 then
        HidePredictions()
        return
    end

    local barWidth = self.frame.health:GetWidth() or 0
    if barWidth <= 0 then
        HidePredictions()
        return
    end

    local currentWidth = math.floor((hp / maxHp) * barWidth + 0.5)
    if currentWidth < 0 then currentWidth = 0 end
    if currentWidth > barWidth then currentWidth = barWidth end

    local availableWidth = barWidth - currentWidth
    if availableWidth <= 0 then
        HidePredictions()
        return
    end

    local mineWidth = math.floor((mineShown / maxHp) * barWidth + 0.5)
    local otherWidth = math.floor((otherShown / maxHp) * barWidth + 0.5)
    if mineWidth < 0 then mineWidth = 0 end
    if otherWidth < 0 then otherWidth = 0 end
    if mineWidth > availableWidth then mineWidth = availableWidth end
    if otherWidth > (availableWidth - mineWidth) then
        otherWidth = availableWidth - mineWidth
    end

    if mineWidth > 0 then
        predMine:ClearAllPoints()
        predMine:SetPoint("TOPLEFT", self.frame.health, "TOPLEFT", currentWidth, 0)
        predMine:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMLEFT", currentWidth, 0)
        predMine:SetWidth(mineWidth)
        predMine:Show()
    else
        predMine:Hide()
    end

    if otherWidth > 0 then
        predOther:ClearAllPoints()
        predOther:SetPoint("TOPLEFT", self.frame.health, "TOPLEFT", currentWidth + mineWidth, 0)
        predOther:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMLEFT", currentWidth + mineWidth, 0)
        predOther:SetWidth(otherWidth)
        predOther:Show()
    else
        predOther:Hide()
    end
end

function SFrames.Target:UpdatePowerType()
    local powerType = UnitPowerType("target")
    local color = SFrames.Config.colors.power[powerType]
    if color then
        self.frame.power:SetStatusBarColor(color.r, color.g, color.b)
    else
        self.frame.power:SetStatusBarColor(0, 0, 1)
    end
end

function SFrames.Target:UpdatePower()
    local power = UnitMana("target")
    local maxPower = UnitManaMax("target")
    self.frame.power:SetMinMaxValues(0, maxPower)
    self.frame.power:SetValue(power)
    if maxPower > 0 then
        self.frame.powerText:SetText(power .. " / " .. maxPower)
    else
        self.frame.powerText:SetText("")
    end
end

function SFrames.Target:UpdateComboPoints()
    local points = GetComboPoints()
    if points > 0 then
        self.frame.comboText:SetText(points)
    else
        self.frame.comboText:SetText("")
    end
end

function SFrames.Target:UpdateRaidIcon()
    if not (self.frame and self.frame.raidIcon) then return end
    if not GetRaidTargetIndex then
        self.frame.raidIcon:Hide()
        return
    end
    if not UnitExists("target") then
        self.frame.raidIcon:Hide()
        return
    end
    local index = GetRaidTargetIndex("target")
    if index and index > 0 and index <= 8 then
        local col = math.mod(index - 1, 4)
        local row = math.floor((index - 1) / 4)
        self.frame.raidIcon:SetTexCoord(col * 0.25, (col + 1) * 0.25, row * 0.25, (row + 1) * 0.25)
        self.frame.raidIcon:Show()
    else
        self.frame.raidIcon:Hide()
    end
end

--------------------------------------------------------------------------------
-- Target Auras (Buffs / Debuffs)
--------------------------------------------------------------------------------

function SFrames.Target:CreateAuras()
    self.frame.buffs = {}
    self.frame.debuffs = {}
    
    -- Target Buffs (Top left to right)
    for i = 1, 16 do
        local b = CreateFrame("Button", "SFramesTargetBuff"..i, self.frame)
        b:SetWidth(AURA_SIZE)
        b:SetHeight(AURA_SIZE)
        SFrames:CreateUnitBackdrop(b)
        
        b.icon = b:CreateTexture(nil, "ARTWORK")
        b.icon:SetAllPoints()
        b.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
        
        b.cdText = SFrames:CreateFontString(b, 9, "CENTER")
        b.cdText:SetPoint("BOTTOM", b, "BOTTOM", 0, 1)
        b.cdText:SetTextColor(1, 0.82, 0)
        b.cdText:SetShadowColor(0, 0, 0, 1)
        b.cdText:SetShadowOffset(1, -1)
        
        -- Tooltip support
        b:SetScript("OnEnter", function()
            GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT")
            GameTooltip:SetUnitBuff("target", this:GetID())
        end)
        b:SetScript("OnLeave", function() GameTooltip:Hide() end)
        
        -- Default row anchor (Starting bottom left for buffs)
        if i == 1 then
            b:SetPoint("TOPLEFT", self.frame, "BOTTOMLEFT", 0, -1)
        elseif math.mod(i - 1, 8) == 0 then
            b:SetPoint("TOP", self.frame.buffs[i-8], "BOTTOM", 0, -AURA_ROW_SPACING)
        else
            b:SetPoint("LEFT", self.frame.buffs[i-1], "RIGHT", AURA_SPACING, 0)
        end
        b:Hide()
        self.frame.buffs[i] = b
    end
    
    -- Target Debuffs (Bottom left to right)
    for i = 1, 16 do
        local b = CreateFrame("Button", "SFramesTargetDebuff"..i, self.frame)
        b:SetWidth(AURA_SIZE)
        b:SetHeight(AURA_SIZE)
        SFrames:CreateUnitBackdrop(b)
        
        b.icon = b:CreateTexture(nil, "ARTWORK")
        b.icon:SetAllPoints()
        b.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
        
        b.cdText = SFrames:CreateFontString(b, 9, "CENTER")
        b.cdText:SetPoint("BOTTOM", b, "BOTTOM", 0, 1)
        b.cdText:SetTextColor(1, 0.82, 0)
        b.cdText:SetShadowColor(0, 0, 0, 1)
        b.cdText:SetShadowOffset(1, -1)
        
        -- Tooltip support
        b:SetScript("OnEnter", function()
            GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT")
            GameTooltip:SetUnitDebuff("target", this:GetID())
        end)
        b:SetScript("OnLeave", function() GameTooltip:Hide() end)
        b:SetScript("OnLeave", function() GameTooltip:Hide() end)
        
        -- Debuff anchors are recalulated dynamically in UpdateAuras
        if i == 1 then
            b:SetPoint("TOPLEFT", self.frame, "BOTTOMLEFT", 0, -1)
        elseif math.mod(i - 1, 8) == 0 then
            b:SetPoint("TOP", self.frame.debuffs[i-8], "BOTTOM", 0, -AURA_ROW_SPACING)
        else
            b:SetPoint("LEFT", self.frame.debuffs[i-1], "RIGHT", AURA_SPACING, 0)
        end
        b:Hide()
        self.frame.debuffs[i] = b
    end
    
    SFrames:RegisterEvent("UNIT_AURA", function() if arg1 == "target" then self:UpdateAuras() end end)
    
    self.auraUpdater = CreateFrame("Frame", nil, self.frame)
    self.auraUpdater.timer = 0
    self.auraUpdater:SetScript("OnUpdate", function()
        this.timer = this.timer + arg1
        if this.timer >= 0.25 then
            SFrames.Target:TickAuras()
            SFrames.Target:UpdateHealPrediction()
            this.timer = 0
        end
    end)
end

function SFrames.Target:TickAuras()
    if not UnitExists("target") then return end

    local timeNow = GetTime()
    local npFormat = NanamiPlates_Auras and NanamiPlates_Auras.FormatTime
    local hasNP = NanamiPlates_SpellDB and NanamiPlates_SpellDB.FindEffectData

    local targetName, targetLevel, targetGUID
    if hasNP then
        targetName = UnitName("target")
        targetLevel = UnitLevel("target") or 0
        targetGUID = UnitGUID and UnitGUID("target")
    end

    -- Buffs
    for i = 1, 16 do
        local b = self.frame.buffs[i]
        if b:IsShown() and b.expirationTime then
            local timeLeft = b.expirationTime - timeNow
            if timeLeft > 0 and timeLeft < 3600 then
                if npFormat then
                    local text, r, g, bc, a = npFormat(timeLeft)
                    b.cdText:SetText(text)
                    if r then b.cdText:SetTextColor(r, g, bc, a or 1) end
                else
                    b.cdText:SetText(SFrames:FormatTime(timeLeft))
                end
            else
                b.cdText:SetText("")
            end
        end
    end

    -- Debuffs: re-query SpellDB for live-accurate timers
    for i = 1, 16 do
        local b = self.frame.debuffs[i]
        if b:IsShown() then
            local timeLeft = nil

            if hasNP and b.effectName then
                local data = targetGUID and NanamiPlates_SpellDB:FindEffectData(targetGUID, targetLevel, b.effectName)
                if not data and targetName then
                    data = NanamiPlates_SpellDB:FindEffectData(targetName, targetLevel, b.effectName)
                end
                if data and data.start and data.duration then
                    local remaining = data.duration + data.start - timeNow
                    if remaining > 0 then
                        timeLeft = remaining
                        b.expirationTime = timeNow + remaining
                    end
                end
            end

            if not timeLeft and b.expirationTime then
                timeLeft = b.expirationTime - timeNow
            end

            if timeLeft and timeLeft > 0 and timeLeft < 3600 then
                if npFormat then
                    local text, r, g, bc, a = npFormat(timeLeft)
                    b.cdText:SetText(text)
                    if r then b.cdText:SetTextColor(r, g, bc, a or 1) end
                else
                    b.cdText:SetText(SFrames:FormatTime(timeLeft))
                end
            else
                b.cdText:SetText("")
            end
        end
    end
end

function SFrames.Target:UpdateAuras()
    if not UnitExists("target") then return end
    
    local numBuffs = 0
    -- Buffs
    for i = 1, 16 do
        local texture = UnitBuff("target", i)
        local b = self.frame.buffs[i]
        b:SetID(i) -- Ensure ID is set for tooltips
        if texture then
            b.icon:SetTexture(texture)
            
            SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
            SFrames.Tooltip:ClearLines()
            SFrames.Tooltip:SetUnitBuff("target", i)
            local timeLeft = SFrames:GetAuraTimeLeft("target", i, true)
            SFrames.Tooltip:Hide()
            if timeLeft and timeLeft > 0 then
                b.expirationTime = GetTime() + timeLeft
                b.cdText:SetText(SFrames:FormatTime(timeLeft))
            else
                b.expirationTime = nil
                b.cdText:SetText("")
            end
            
            b:Show()
            numBuffs = numBuffs + 1
        else
            b.expirationTime = nil
            b.cdText:SetText("")
            b:Hide()
        end
    end
    
    -- Dynamically re-anchor the first Debuff based on visible Buffs
    local firstDebuff = self.frame.debuffs[1]
    if firstDebuff then
        firstDebuff:ClearAllPoints()
        if numBuffs > 0 then
            -- Find the start of the LAST row of buffs
            local lastRowStart = math.floor((numBuffs - 1) / 8) * 8 + 1
            firstDebuff:SetPoint("TOP", self.frame.buffs[lastRowStart], "BOTTOM", 0, -AURA_ROW_SPACING)
        else
            firstDebuff:SetPoint("TOPLEFT", self.frame, "BOTTOMLEFT", 0, -1)
        end
    end
    
    -- Debuffs
    local hasNP = NanamiPlates_SpellDB and NanamiPlates_SpellDB.UnitDebuff
    local npFormat = NanamiPlates_Auras and NanamiPlates_Auras.FormatTime

    for i = 1, 16 do
        local texture = UnitDebuff("target", i)
        local b = self.frame.debuffs[i]
        b:SetID(i)
        if texture then
            b.icon:SetTexture(texture)

            local timeLeft = 0
            local effectName = nil

            if hasNP then
                local effect, rank, _, stacks, dtype, duration, npTimeLeft, isOwn = NanamiPlates_SpellDB:UnitDebuff("target", i)
                effectName = effect
                if npTimeLeft and npTimeLeft > 0 then
                    timeLeft = npTimeLeft
                elseif effect and effect ~= "" and duration and duration > 0
                       and NanamiPlates_Auras and NanamiPlates_Auras.timers then
                    local unitKey = (UnitGUID and UnitGUID("target")) or UnitName("target") or ""
                    local cached = NanamiPlates_Auras.timers[unitKey .. "_" .. effect]
                    if not cached and UnitName("target") then
                        cached = NanamiPlates_Auras.timers[UnitName("target") .. "_" .. effect]
                    end
                    if cached and cached.startTime and cached.duration then
                        local remaining = cached.duration - (GetTime() - cached.startTime)
                        if remaining > 0 then timeLeft = remaining end
                    end
                end
            end

            if timeLeft <= 0 then
                SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
                SFrames.Tooltip:ClearLines()
                SFrames.Tooltip:SetUnitDebuff("target", i)
                timeLeft = SFrames:GetAuraTimeLeft("target", i, false)
                SFrames.Tooltip:Hide()
            end

            if timeLeft and timeLeft > 0 then
                b.expirationTime = GetTime() + timeLeft
                b.effectName = effectName
                if npFormat then
                    local text, r, g, bc, a = npFormat(timeLeft)
                    b.cdText:SetText(text)
                    if r then b.cdText:SetTextColor(r, g, bc, a or 1) end
                else
                    b.cdText:SetText(SFrames:FormatTime(timeLeft))
                end
            else
                b.expirationTime = nil
                b.effectName = nil
                b.cdText:SetText("")
            end

            b:Show()
        else
            b.expirationTime = nil
            b.effectName = nil
            b.cdText:SetText("")
            b:Hide()
        end
    end
end

--------------------------------------------------------------------------------
-- Target Castbar
--------------------------------------------------------------------------------

function SFrames.Target:CreateCastbar()
    local cb = SFrames:CreateStatusBar(self.frame, "SFramesTargetCastbar")
    cb:SetHeight(SFrames.Config.castbarHeight)
    cb:SetPoint("BOTTOMLEFT", self.frame, "TOPLEFT", 0, 6)
    cb:SetPoint("BOTTOMRIGHT", self.frame.portrait, "TOPRIGHT", -(SFrames.Config.castbarHeight + 6), 6)
    
    local cbbg = CreateFrame("Frame", nil, self.frame)
    cbbg:SetPoint("TOPLEFT", cb, "TOPLEFT", -1, 1)
    cbbg:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", 1, -1)
    cbbg:SetFrameLevel(cb:GetFrameLevel() - 1)
    SFrames:CreateUnitBackdrop(cbbg)
    
    cb.bg = cb:CreateTexture(nil, "BACKGROUND")
    cb.bg:SetAllPoints()
    cb.bg:SetTexture(SFrames:GetTexture())
    cb.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)
    cb:SetStatusBarColor(1, 0.7, 0)
    
    cb.text = SFrames:CreateFontString(cb, 10, "LEFT")
    cb.text:SetPoint("LEFT", cb, "LEFT", 4, 0)
    
    cb.icon = cb:CreateTexture(nil, "ARTWORK")
    cb.icon:SetWidth(SFrames.Config.castbarHeight + 2)
    cb.icon:SetHeight(SFrames.Config.castbarHeight + 2)
    cb.icon:SetPoint("LEFT", cb, "RIGHT", 4, 0)
    cb.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
    
    local ibg = CreateFrame("Frame", nil, self.frame)
    ibg:SetPoint("TOPLEFT", cb.icon, "TOPLEFT", -1, 1)
    ibg:SetPoint("BOTTOMRIGHT", cb.icon, "BOTTOMRIGHT", 1, -1)
    ibg:SetFrameLevel(cb:GetFrameLevel() - 1)
    SFrames:CreateUnitBackdrop(ibg)
    
    cb:Hide()
    cbbg:Hide()
    cb.icon:Hide()
    ibg:Hide()
    
    self.frame.castbar = cb
    self.frame.castbar.cbbg = cbbg
    self.frame.castbar.ibg = ibg
    
    self.frame.castbarUpdater = CreateFrame("Frame", nil, self.frame)
    self.frame.castbarUpdater:SetScript("OnUpdate", function() SFrames.Target:CastbarOnUpdate() end)
end

function SFrames.Target:CastbarOnUpdate()
    local cb = self.frame.castbar
    if not UnitExists("target") then
        cb:Hide()
        cb.cbbg:Hide()
        cb.icon:Hide()
        cb.ibg:Hide()
        return
    end
    
    -- Try to read cast from Vanilla extensions (SuperWoW or TurtleWoW modern API, or ShaguTweaks)
    local cast, nameSubtext, text, texture, startTime, endTime
    local _UnitCastingInfo = UnitCastingInfo or (ShaguTweaks and ShaguTweaks.UnitCastingInfo)
    if _UnitCastingInfo then
        cast, nameSubtext, text, texture, startTime, endTime = _UnitCastingInfo("target")
    end
    
    local channel
    local _UnitChannelInfo = UnitChannelInfo or (ShaguTweaks and ShaguTweaks.UnitChannelInfo)
    if not cast and _UnitChannelInfo then
        channel, nameSubtext, text, texture, startTime, endTime = _UnitChannelInfo("target")
        cast = channel
    end

    if cast and startTime and endTime then
        local duration = (endTime - startTime) / 1000
        local cur = GetTime() - (startTime / 1000)
        
        if channel then
            cur = duration + (startTime / 1000) - GetTime()
        end
        
        if cur > duration then cur = duration end
        if cur < 0 then cur = 0 end
        
        cb:SetMinMaxValues(0, duration)
        cb:SetValue(cur)
        cb.text:SetText(cast)
        
        if texture then
            cb.icon:SetTexture(texture)
        end
        
        cb:SetAlpha(1)
        cb.cbbg:SetAlpha(1)
        cb.icon:SetAlpha(1)
        cb.ibg:SetAlpha(1)
        
        cb:Show()
        cb.cbbg:Show()
        cb.icon:Show()
        cb.ibg:Show()
    else
        cb:Hide()
        cb.cbbg:Hide()
        cb.icon:Hide()
        cb.ibg:Hide()
    end
end

-- Diagnostic Slash Command (Position Recovery)
SLASH_NANAMIDIST1 = "/nanamidist"
SlashCmdList["NANAMIDIST"] = function()
    if SFrames.Target.distanceFrame then
        SFrames.Target.distanceFrame:ClearAllPoints()
        SFrames.Target.distanceFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 100)
        SFrames.Target.distanceFrame:Show()
        DEFAULT_CHAT_FRAME:AddMessage("|cffffd100Nanami-UI:|r 距离显示已重置到屏幕中央。")
    end
end
