--------------------------------------------------------------------------------
-- Nanami-UI: GearScore (GearScore.lua)
-- 基于乌龟服(Turtle WoW)属性收益理论的装备评分系统
-- 评分 1-10: 衡量该装备对当前职业各天赋的适配度
-- 考虑: 属性权重 × 装备类型兼容性 × 等级适配
--------------------------------------------------------------------------------

SFrames.GearScore = {}
local GS = SFrames.GearScore

--------------------------------------------------------------------------------
-- Item budget cost per unit of each stat (for normalization)
-- Higher cost = stat is "rarer/more expensive" per item budget point
--------------------------------------------------------------------------------

local BUDGET_COST = {
    STR = 1.0, AGI = 1.0, STA = 1.0, INT = 1.0, SPI = 1.0,
    CRIT = 14.0, TOHIT = 15.0, RANGEDCRIT = 14.0,
    SPELLCRIT = 14.0, SPELLTOHIT = 15.0,
    ATTACKPOWER = 0.5, RANGEDATTACKPOWER = 0.5,
    DMG = 0.86, HEAL = 0.58,
    DEFENSE = 1.5, DODGE = 12.0, PARRY = 12.0, BLOCK = 12.0,
    BLOCKVALUE = 0.5, ARMOR = 0.05, BASEARMOR = 0.02,
    HEALTHREG = 2.0, MANAREG = 2.0,
    HEALTH = 0.07, MANA = 0.07,
    WEAPONDPS = 3.0,
    WEAPONSPEED = 1.5,
}

--------------------------------------------------------------------------------
-- EP Weight Tables (tab = talent tab index for primary spec detection)
--------------------------------------------------------------------------------

local WEIGHTS = {
    -- ================================================================
    -- Pawn-style normalization: primary stat = 1.0
    -- TOHIT/CRIT per 1%; PDF: 1%hit≈18AP, 1%crit≈25AP, 1%spellhit≈14SP
    -- ================================================================
    WARRIOR = {
        specs = {
            { name = "武器", tab = 1, color = "ffC79C6E",
              w = { STR=1.0, AGI=0.7, STA=0.5, TOHIT=9, CRIT=12,
                    ATTACKPOWER=0.5, HEALTH=0.05, WEAPONDPS=5, BASEARMOR=0.005 } },
            { name = "狂怒", tab = 2, color = "ffC79C6E",
              w = { STR=1.0, AGI=0.6, STA=0.5, TOHIT=10, CRIT=11,
                    ATTACKPOWER=0.5, HEALTH=0.05, WEAPONDPS=5, BASEARMOR=0.005 } },
            { name = "防护", tab = 3, color = "ff69CCF0",
              w = { STA=1.0, STR=0.5, AGI=0.7, TOHIT=5, CRIT=3, ATTACKPOWER=0.2,
                    DEFENSE=0.8, DODGE=8, PARRY=7, BLOCK=6, BLOCKVALUE=0.35,
                    ARMOR=0.02, HEALTH=0.1, HEALTHREG=1.0, WEAPONDPS=3, BASEARMOR=0.03 } },
        },
        -- 硬核物理(战士型): 耐 > 力 > 敏 > 攻强
        hc = { name = "硬核", color = "ffFF4444",
               w = { STA=1.5, STR=1.0, AGI=0.8, ATTACKPOWER=0.5,
                     TOHIT=3, CRIT=3, DEFENSE=0.5, DODGE=5, PARRY=3, BLOCK=3, BLOCKVALUE=0.3,
                     ARMOR=0.05, HEALTH=0.1, HEALTHREG=1.5, WEAPONDPS=3, BASEARMOR=0.02 } },
    },
    PALADIN = {
        specs = {
            { name = "神圣", tab = 1, color = "ff00FF96",
              w = { INT=1.0, SPI=0.3, STA=0.5, HEAL=0.55, DMG=0.1,
                    SPELLCRIT=5, MANAREG=1.3, MANA=0.01, BASEARMOR=0.005 } },
            { name = "防护", tab = 2, color = "ff69CCF0",
              w = { STA=1.0, STR=0.5, AGI=0.5, INT=0.3, TOHIT=5, CRIT=3,
                    ATTACKPOWER=0.2, DMG=0.4,
                    DEFENSE=0.7, DODGE=7, PARRY=6, BLOCK=6, BLOCKVALUE=0.15,
                    ARMOR=0.02, HEALTH=0.1, MANAREG=1.0, WEAPONDPS=2, BASEARMOR=0.03 } },
            { name = "惩戒", tab = 3, color = "ffF58CBA",
              w = { STR=1.0, AGI=0.6, STA=0.5, INT=0.3,
                    TOHIT=8, CRIT=10, SPELLCRIT=5,
                    ATTACKPOWER=0.5, DMG=0.3, HEAL=0.05, WEAPONDPS=5, BASEARMOR=0.005 } },
        },
        -- 硬核圣骑士: 耐 > 力 > 智 = 敏 = 精
        hc = { name = "硬核", color = "ffFF4444",
               w = { STA=1.5, STR=1.0, INT=0.8, AGI=0.8, SPI=0.8,
                     TOHIT=3, CRIT=3, ATTACKPOWER=0.3, HEAL=0.4, DMG=0.2,
                     DEFENSE=0.5, DODGE=3, ARMOR=0.04, HEALTH=0.1,
                     HEALTHREG=1.0, MANAREG=1.0, WEAPONDPS=2, BASEARMOR=0.02 } },
    },
    HUNTER = {
        specs = {
            { name = "野兽", tab = 1, color = "ffABD473",
              w = { AGI=1.0, STR=0.05, STA=0.5, INT=0.8,
                    TOHIT=10, CRIT=10, RANGEDCRIT=10,
                    ATTACKPOWER=0.4, RANGEDATTACKPOWER=0.5, MANAREG=2.0,
                    WEAPONDPS=4, BASEARMOR=0.005 } },
            { name = "射击", tab = 2, color = "ffABD473",
              w = { AGI=1.0, STR=0.05, STA=0.5, INT=0.9,
                    TOHIT=10, CRIT=10, RANGEDCRIT=10,
                    ATTACKPOWER=0.4, RANGEDATTACKPOWER=0.5, MANAREG=2.0,
                    WEAPONDPS=4, BASEARMOR=0.005 } },
            { name = "生存", tab = 3, color = "ffABD473",
              w = { AGI=1.0, STR=0.4, STA=0.5, INT=0.3, SPI=0.3,
                    TOHIT=8, CRIT=8, RANGEDCRIT=8,
                    ATTACKPOWER=0.5, RANGEDATTACKPOWER=0.5, MANAREG=1.0,
                    WEAPONDPS=3, BASEARMOR=0.005 } },
        },
        -- 硬核猎人: 耐 > 敏 > 智 > 力 (精神配合灵魂链接有价值)
        hc = { name = "硬核", color = "ffFF4444",
               w = { STA=1.5, AGI=1.0, INT=0.5, STR=0.3, SPI=0.8,
                     TOHIT=3, CRIT=3, RANGEDCRIT=3,
                     ATTACKPOWER=0.2, RANGEDATTACKPOWER=0.3,
                     ARMOR=0.04, HEALTH=0.1, HEALTHREG=1.5, WEAPONDPS=2, BASEARMOR=0.01 } },
    },
    ROGUE = {
        specs = {
            { name = "刺杀", tab = 1, color = "ffFFF569",
              w = { AGI=1.0, STR=0.5, STA=0.5, TOHIT=10, CRIT=12,
                    ATTACKPOWER=0.45, WEAPONDPS=5, BASEARMOR=0.005 } },
            { name = "战斗", tab = 2, color = "ffFFF569",
              w = { AGI=1.0, STR=0.5, STA=0.5, TOHIT=10, CRIT=11,
                    ATTACKPOWER=0.45, WEAPONDPS=6, BASEARMOR=0.005 } },
            { name = "敏锐", tab = 3, color = "ffFFF569",
              w = { AGI=1.0, STR=0.5, STA=0.5, TOHIT=8, CRIT=10,
                    ATTACKPOWER=0.45, DODGE=3, WEAPONDPS=4, BASEARMOR=0.005 } },
        },
        -- 硬核潜行者: 耐 > 敏 > 力 > 攻强
        hc = { name = "硬核", color = "ffFF4444",
               w = { STA=1.5, AGI=1.2, STR=0.8, ATTACKPOWER=0.5,
                     TOHIT=3, CRIT=3, DODGE=3, ARMOR=0.04,
                     HEALTH=0.1, HEALTHREG=1.5, WEAPONDPS=3, BASEARMOR=0.01 } },
    },
    PRIEST = {
        specs = {
            { name = "戒律", tab = 1, color = "ff00FF96",
              w = { INT=1.0, SPI=0.5, STA=0.5, HEAL=0.7, DMG=0.1,
                    SPELLCRIT=4, MANAREG=1.2, MANA=0.01,
                    WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
            { name = "神圣", tab = 2, color = "ff00FF96",
              w = { INT=1.0, SPI=0.7, STA=0.5, HEAL=0.8, DMG=0.1,
                    SPELLCRIT=3, MANAREG=1.35, MANA=0.01,
                    WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
            { name = "暗影", tab = 3, color = "ff9482C9",
              w = { DMG=1.0, INT=0.2, SPI=0.2, STA=0.5,
                    SPELLTOHIT=14, SPELLCRIT=8, MANAREG=1.0,
                    WEAPONDPS=2.5, WEAPONSPEED=0.6, BASEARMOR=0.003 } },
        },
        -- 硬核法系(牧师): 耐 > 智 = 治疗 > 精
        hc = { name = "硬核", color = "ffFF4444",
               w = { STA=1.5, INT=1.0, HEAL=1.0, DMG=0.8, SPI=0.8,
                     SPELLCRIT=2, SPELLTOHIT=3, MANAREG=1.5,
                     WEAPONDPS=1.5, WEAPONSPEED=0.3,
                     ARMOR=0.04, HEALTH=0.08, HEALTHREG=0.8, BASEARMOR=0.01 } },
    },
    SHAMAN = {
        specs = {
            { name = "元素", tab = 1, color = "ff0070DE",
              w = { DMG=1.0, INT=0.3, SPI=0.1, STA=0.5,
                    SPELLTOHIT=10, SPELLCRIT=8, ATTACKPOWER=0.1,
                    MANAREG=1.1, BASEARMOR=0.005 } },
            { name = "增强", tab = 2, color = "ff0070DE",
              w = { STR=1.0, AGI=0.9, STA=0.5, INT=0.3,
                    TOHIT=10, CRIT=11, ATTACKPOWER=0.5,
                    DMG=0.3, SPELLCRIT=3, MANAREG=1.0,
                    WEAPONDPS=5, BASEARMOR=0.005 } },
            { name = "恢复", tab = 3, color = "ff00FF96",
              w = { INT=1.0, SPI=0.3, STA=0.5, HEAL=0.9, DMG=0.1,
                    SPELLCRIT=5, MANAREG=1.7, MANA=0.01, BASEARMOR=0.005 } },
        },
        -- 硬核萨满(混合): 耐 > 力=敏 > 智=精
        hc = { name = "硬核", color = "ffFF4444",
               w = { STA=1.5, STR=0.8, AGI=0.8, INT=0.5, SPI=0.5,
                     TOHIT=3, CRIT=3, ATTACKPOWER=0.3, HEAL=0.4, DMG=0.3,
                     MANAREG=1.0, ARMOR=0.04, HEALTH=0.1, HEALTHREG=1.0,
                     WEAPONDPS=2, BASEARMOR=0.01 } },
    },
    MAGE = {
        specs = {
            { name = "奥术", tab = 1, color = "ff69CCF0",
              w = { DMG=1.0, INT=0.46, SPI=0.6, STA=0.3,
                    SPELLTOHIT=10, SPELLCRIT=7, MANAREG=1.1,
                    MANA=0.04, WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
            { name = "火焰", tab = 2, color = "ff69CCF0",
              w = { DMG=1.0, INT=0.44, SPI=0.07, STA=0.3,
                    SPELLTOHIT=12, SPELLCRIT=9, MANAREG=1.0,
                    MANA=0.04, WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
            { name = "冰霜", tab = 3, color = "ff69CCF0",
              w = { DMG=1.0, INT=0.37, SPI=0.06, STA=0.3,
                    SPELLTOHIT=12, SPELLCRIT=7, MANAREG=0.8,
                    MANA=0.03, WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
        },
        -- 硬核法系(法师): 耐 > 智 = 法伤 > 精
        hc = { name = "硬核", color = "ffFF4444",
               w = { STA=1.5, INT=1.0, DMG=1.0, SPI=0.8,
                     SPELLCRIT=2, SPELLTOHIT=3, MANAREG=1.5,
                     WEAPONDPS=1.5, WEAPONSPEED=0.3,
                     ARMOR=0.05, HEALTH=0.08, HEALTHREG=0.8, BASEARMOR=0.01 } },
    },
    WARLOCK = {
        specs = {
            { name = "痛苦", tab = 1, color = "ff9482C9",
              w = { DMG=1.0, INT=0.4, SPI=0.1, STA=0.5,
                    SPELLTOHIT=12, SPELLCRIT=4, MANAREG=1.0,
                    HEALTH=0.05, WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
            { name = "恶魔", tab = 2, color = "ff9482C9",
              w = { DMG=1.0, INT=0.4, SPI=0.5, STA=0.5,
                    SPELLTOHIT=12, SPELLCRIT=7, MANAREG=1.0,
                    WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
            { name = "毁灭", tab = 3, color = "ff9482C9",
              w = { DMG=1.0, INT=0.34, SPI=0.25, STA=0.5,
                    SPELLTOHIT=14, SPELLCRIT=9, MANAREG=0.65,
                    WEAPONDPS=2.5, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
        },
        -- 硬核法系(术士): 耐 > 智 = 法伤 > 精 (精神低于法师因有生命分流)
        hc = { name = "硬核", color = "ffFF4444",
               w = { STA=1.5, INT=1.0, DMG=1.0, SPI=0.5,
                     SPELLCRIT=2, SPELLTOHIT=3, MANAREG=1.0,
                     WEAPONDPS=1.5, WEAPONSPEED=0.3,
                     ARMOR=0.04, HEALTH=0.08, HEALTHREG=0.8, BASEARMOR=0.01 } },
    },
    DRUID = {
        specs = {
            { name = "平衡", tab = 1, color = "ffFF7D0A",
              w = { DMG=1.0, INT=0.38, SPI=0.34, STA=0.5,
                    HEAL=0.1, SPELLTOHIT=12, SPELLCRIT=7,
                    MANAREG=0.6, MANA=0.03, BASEARMOR=0.005 } },
            { name = "野猫", tab = 2, color = "ffFF7D0A",
              w = { STR=1.2, AGI=1.0, STA=0.5, TOHIT=10, CRIT=11,
                    ATTACKPOWER=0.5, DODGE=1, WEAPONDPS=0.5, BASEARMOR=0.005 } },
            { name = "野熊", tab = 2, color = "ff69CCF0",
              w = { STA=1.0, AGI=0.5, STR=0.2, TOHIT=3, CRIT=3, ATTACKPOWER=0.35,
                    DEFENSE=0.5, DODGE=6, ARMOR=0.1, HEALTH=0.08, BASEARMOR=0.04 } },
            { name = "恢复", tab = 3, color = "ff00FF96",
              w = { INT=1.0, SPI=0.87, STA=0.5, HEAL=1.2, DMG=0.1,
                    SPELLCRIT=4, MANAREG=1.7, MANA=0.01, BASEARMOR=0.005 } },
        },
        -- 硬核德鲁伊(混合偏生存): 耐 > 敏 > 力 > 智 = 精
        hc = { name = "硬核", color = "ffFF4444",
               w = { STA=1.5, AGI=1.0, STR=0.8, INT=0.5, SPI=0.5,
                     TOHIT=3, CRIT=3, ATTACKPOWER=0.3, HEAL=0.4, DMG=0.3,
                     DODGE=3, ARMOR=0.04, HEALTH=0.1, HEALTHREG=1.0,
                     WEAPONDPS=1, BASEARMOR=0.01 } },
    },
}

--------------------------------------------------------------------------------
-- Armor type compatibility (class × armor subclass × level)
-- 1.0 = ideal, 0 = should never wear this
--------------------------------------------------------------------------------

local ARMOR_COMPAT = {
    WARRIOR = { Plate=1.0, Mail=0.3, Leather=0.1, Cloth=0.05 },
    PALADIN = { Plate=1.0, Mail=0.3, Leather=0.1, Cloth=0.05 },
    HUNTER  = { Mail=1.0, Leather=0.5, Cloth=0.1 },
    SHAMAN  = { Mail=1.0, Leather=0.5, Cloth=0.1 },
    ROGUE   = { Leather=1.0, Cloth=0.3 },
    DRUID   = { Leather=1.0, Cloth=0.3 },
    PRIEST  = { Cloth=1.0 },
    MAGE    = { Cloth=1.0 },
    WARLOCK = { Cloth=1.0 },
}

local ARMOR_COMPAT_LOW = {
    WARRIOR = { Mail=1.0, Leather=0.6, Cloth=0.1 },
    PALADIN = { Mail=1.0, Leather=0.6, Cloth=0.1 },
    HUNTER  = { Leather=1.0, Cloth=0.3 },
    SHAMAN  = { Leather=1.0, Cloth=0.3 },
    ROGUE   = { Leather=1.0, Cloth=0.3 },
    DRUID   = { Leather=1.0, Cloth=0.3 },
    PRIEST  = { Cloth=1.0 },
    MAGE    = { Cloth=1.0 },
    WARLOCK = { Cloth=1.0 },
}

--------------------------------------------------------------------------------
-- Equipment slot compatibility per spec (CLASS_specIdx)
-- 2H weapons for tanks = 0.05, shields for DPS = low, etc.
-- Missing entries default to 1.0
--------------------------------------------------------------------------------

local SLOT_COMPAT = {
    WARRIOR_1 = { INVTYPE_SHIELD = 0.1, INVTYPE_HOLDABLE = 0.05 },
    WARRIOR_2 = { INVTYPE_SHIELD = 0.1, INVTYPE_HOLDABLE = 0.05 },
    WARRIOR_3 = { INVTYPE_2HWEAPON = 0.05, INVTYPE_HOLDABLE = 0.05 },

    PALADIN_1 = { INVTYPE_2HWEAPON = 0.3 },
    PALADIN_2 = { INVTYPE_2HWEAPON = 0.05 },
    PALADIN_3 = { INVTYPE_SHIELD = 0.2, INVTYPE_HOLDABLE = 0.1 },

    HUNTER_1 = { INVTYPE_SHIELD = 0 },
    HUNTER_2 = { INVTYPE_SHIELD = 0 },
    HUNTER_3 = { INVTYPE_SHIELD = 0 },

    ROGUE_1 = { INVTYPE_2HWEAPON = 0, INVTYPE_SHIELD = 0, INVTYPE_HOLDABLE = 0 },
    ROGUE_2 = { INVTYPE_2HWEAPON = 0, INVTYPE_SHIELD = 0, INVTYPE_HOLDABLE = 0 },
    ROGUE_3 = { INVTYPE_2HWEAPON = 0, INVTYPE_SHIELD = 0, INVTYPE_HOLDABLE = 0 },

    SHAMAN_1 = { INVTYPE_2HWEAPON = 0.3 },
    SHAMAN_2 = { INVTYPE_SHIELD = 0.3 },

    DRUID_1 = { INVTYPE_SHIELD = 0 },
    DRUID_2 = { INVTYPE_SHIELD = 0, INVTYPE_HOLDABLE = 0 },
    DRUID_3 = { INVTYPE_SHIELD = 0, INVTYPE_HOLDABLE = 0 },
    DRUID_4 = { INVTYPE_SHIELD = 0 },

    MAGE_1 = { INVTYPE_SHIELD = 0 },
    MAGE_2 = { INVTYPE_SHIELD = 0 },
    MAGE_3 = { INVTYPE_SHIELD = 0 },
    WARLOCK_1 = { INVTYPE_SHIELD = 0 },
    WARLOCK_2 = { INVTYPE_SHIELD = 0 },
    WARLOCK_3 = { INVTYPE_SHIELD = 0 },
    PRIEST_1 = { INVTYPE_SHIELD = 0 },
    PRIEST_2 = { INVTYPE_SHIELD = 0 },
    PRIEST_3 = { INVTYPE_SHIELD = 0 },
}

--------------------------------------------------------------------------------
-- Level-based weight adjustments
--------------------------------------------------------------------------------

local function AdjustWeightsForLevel(baseWeights, level, isHC)
    if level >= 55 then return baseWeights end
    local adj = {}
    for k, v in pairs(baseWeights) do adj[k] = v end

    if isHC then
        -- HC: STA always stays high (survival is the whole point)
        if level <= 20 then
            adj.SPI = math.max((adj.SPI or 0) * 1.3, 0.3)
            adj.TOHIT = (adj.TOHIT or 0) * 0.2
            adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.2
            adj.CRIT = (adj.CRIT or 0) * 0.3
            adj.SPELLCRIT = (adj.SPELLCRIT or 0) * 0.3
            adj.HEALTHREG = (adj.HEALTHREG or 0) + 0.8
            adj.ARMOR = (adj.ARMOR or 0) + 0.02
        elseif level <= 40 then
            adj.TOHIT = (adj.TOHIT or 0) * 0.5
            adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.5
            adj.ARMOR = (adj.ARMOR or 0) + 0.01
        else
            adj.TOHIT = (adj.TOHIT or 0) * 0.8
            adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.8
        end
    else
        if level <= 20 then
            -- PDF: 精神至上 at 1-20; SPI matters for all classes (regen)
            adj.SPI = math.max((adj.SPI or 0) * 1.5, 0.3)
            adj.STA = (adj.STA or 0) * 0.7
            -- SP/HEAL don't exist on items at this level
            adj.DMG = (adj.DMG or 0) * 0.15
            adj.HEAL = (adj.HEAL or 0) * 0.15
            adj.TOHIT = (adj.TOHIT or 0) * 0.2
            adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.2
            adj.CRIT = (adj.CRIT or 0) * 0.2
            adj.SPELLCRIT = (adj.SPELLCRIT or 0) * 0.2
            adj.RANGEDCRIT = (adj.RANGEDCRIT or 0) * 0.2
            adj.HEALTHREG = (adj.HEALTHREG or 0) + 0.8
            adj.ARMOR = (adj.ARMOR or 0) + 0.02
        elseif level <= 40 then
            -- PDF: 40级解锁板甲/锁甲, SP开始出现
            adj.SPI = math.max((adj.SPI or 0) * 1.2, 0.15)
            adj.DMG = (adj.DMG or 0) * 0.5
            adj.HEAL = (adj.HEAL or 0) * 0.5
            adj.TOHIT = (adj.TOHIT or 0) * 0.6
            adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.6
            adj.ARMOR = (adj.ARMOR or 0) + 0.01
        else
            adj.TOHIT = (adj.TOHIT or 0) * 0.85
            adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.85
        end
    end
    return adj
end

--------------------------------------------------------------------------------
-- Player info helpers
--------------------------------------------------------------------------------

local function GetPlayerClassToken()
    local _, classEN = UnitClass("player")
    if classEN then return string.upper(classEN) end
    return nil
end

local function GetPlayerLevel()
    return UnitLevel("player") or 60
end

local function GetPrimaryTabIndex()
    if not GetNumTalentTabs then return 1 end
    local maxPts, maxIdx = 0, 1
    for i = 1, GetNumTalentTabs() do
        local _, _, pts = GetTalentTabInfo(i)
        if pts and pts > maxPts then
            maxPts = pts
            maxIdx = i
        end
    end
    return maxIdx
end

local function IsPlayerHardcore()
    if IsHardcore and IsHardcore("player") then return true end
    if C_TurtleWoW and C_TurtleWoW.IsHardcore and C_TurtleWoW.IsHardcore("player") then
        return true
    end
    return false
end

--------------------------------------------------------------------------------
-- Item link/info utilities
--------------------------------------------------------------------------------

local function ExtractItemString(link)
    if not link then return nil end
    local _, _, itemStr = string.find(link, "(item:[%-?%d:]+)")
    return itemStr
end

-- Item quality detection from link color code
-- WoW link format: |cffXXXXXX|Hitem:...|h[Name]|h|r
local QUALITY_FROM_COLOR = {
    ["9d9d9d"] = 0,  -- Poor (gray)
    ["ffffff"] = 1,  -- Common (white)
    ["1eff00"] = 2,  -- Uncommon (green)
    ["0070dd"] = 3,  -- Rare (blue)
    ["a335ee"] = 4,  -- Epic (purple)
    ["ff8000"] = 5,  -- Legendary (orange)
    ["e6cc80"] = 6,  -- Artifact
}

local QUALITY_MULT = {
    [0] = 0,     -- Poor: skip entirely
    [1] = 0.50,  -- Common: penalty
    [2] = 0.90,  -- Uncommon: slight penalty
    [3] = 0.95,  -- Rare: near full
    [4] = 1.0,   -- Epic: full
    [5] = 1.0,   -- Legendary: full
    [6] = 1.0,   -- Artifact: full
}

local function GetQualityFromLink(link)
    if not link then return -1 end
    local _, _, hex = string.find(link, "|cff(%x%x%x%x%x%x)")
    if hex then
        return QUALITY_FROM_COLOR[string.lower(hex)] or 1
    end
    return 1
end

local function GetQualityFromTooltip(tooltip)
    if not tooltip then return 1 end
    local tipName = tooltip:GetName()
    if not tipName then return 1 end
    local nameObj = getglobal(tipName .. "TextLeft1")
    if not nameObj then return 1 end
    local r, g, b = nameObj:GetTextColor()
    if not r then return 1 end
    -- Gray: r≈0.62, g≈0.62, b≈0.62
    if r < 0.7 and g < 0.7 and b < 0.7 then return 0 end
    -- White: r=1, g=1, b=1
    if r > 0.95 and g > 0.95 and b > 0.95 then return 1 end
    -- Green: r≈0.12, g=1, b=0
    if g > 0.9 and r < 0.2 then return 2 end
    -- Blue: r=0, g≈0.44, b≈0.87
    if b > 0.8 and r < 0.1 then return 3 end
    -- Purple: r≈0.64, g≈0.21, b≈0.93
    if b > 0.85 and r > 0.5 then return 4 end
    -- Orange: r=1, g≈0.5, b=0
    if r > 0.9 and g > 0.4 and b < 0.1 then return 5 end
    return 1
end

-- Normalize item class/subclass strings (Chinese client support)
local ARMOR_CLASS_NAMES = { Armor=true, ["护甲"]=true, ["铠甲"]=true }
local ARMOR_MISC_NAMES = { Miscellaneous=true, ["其它"]=true, ["杂项"]=true, ["其他"]=true }
local SHIELD_NAMES = { Shield=true, Shields=true, ["盾牌"]=true, ["盾"]=true }
local SUBCLASS_NORMALIZE = {
    Plate="Plate", ["板甲"]="Plate", ["铠甲"]="Plate",
    Mail="Mail", ["锁甲"]="Mail", ["链甲"]="Mail",
    Leather="Leather", ["皮甲"]="Leather", ["皮革"]="Leather",
    Cloth="Cloth", ["布甲"]="Cloth", ["布料"]="Cloth",
}

-- Parse equipLoc and armor type directly from tooltip text
local SLOT_TEXT_TO_INVTYPE = {
    ["Head"]="INVTYPE_HEAD", ["头部"]="INVTYPE_HEAD",
    ["Neck"]="INVTYPE_NECK", ["颈部"]="INVTYPE_NECK",
    ["Shoulder"]="INVTYPE_SHOULDER", ["肩部"]="INVTYPE_SHOULDER",
    ["Shirt"]="INVTYPE_BODY", ["衬衣"]="INVTYPE_BODY",
    ["Chest"]="INVTYPE_CHEST", ["胸部"]="INVTYPE_CHEST",
    ["Robe"]="INVTYPE_ROBE", ["胸甲"]="INVTYPE_ROBE",
    ["Waist"]="INVTYPE_WAIST", ["腰部"]="INVTYPE_WAIST",
    ["Legs"]="INVTYPE_LEGS", ["腿部"]="INVTYPE_LEGS",
    ["Feet"]="INVTYPE_FEET", ["脚"]="INVTYPE_FEET", ["足"]="INVTYPE_FEET",
    ["Wrist"]="INVTYPE_WRIST", ["手腕"]="INVTYPE_WRIST",
    ["Hands"]="INVTYPE_HAND", ["手"]="INVTYPE_HAND",
    ["Finger"]="INVTYPE_FINGER", ["手指"]="INVTYPE_FINGER",
    ["Trinket"]="INVTYPE_TRINKET", ["饰品"]="INVTYPE_TRINKET",
    ["Back"]="INVTYPE_CLOAK", ["背部"]="INVTYPE_CLOAK",
    ["Main Hand"]="INVTYPE_WEAPONMAINHAND", ["主手"]="INVTYPE_WEAPONMAINHAND",
    ["One-Hand"]="INVTYPE_WEAPON", ["单手"]="INVTYPE_WEAPON",
    ["Off Hand"]="INVTYPE_WEAPONOFFHAND", ["副手"]="INVTYPE_WEAPONOFFHAND",
    ["Two-Hand"]="INVTYPE_2HWEAPON", ["双手"]="INVTYPE_2HWEAPON",
    ["Held In Off-hand"]="INVTYPE_HOLDABLE", ["副手物品"]="INVTYPE_HOLDABLE",
    ["Ranged"]="INVTYPE_RANGED", ["远程"]="INVTYPE_RANGED",
    ["Thrown"]="INVTYPE_THROWN", ["投掷"]="INVTYPE_THROWN",
    ["Relic"]="INVTYPE_RELIC", ["圣物"]="INVTYPE_RELIC",
    ["Shield"]="INVTYPE_SHIELD", ["盾牌"]="INVTYPE_SHIELD",
}

local function ParseEquipLocFromTooltip(tooltip)
    if not tooltip then return nil, nil, nil end
    local tipName = tooltip:GetName()
    if not tipName then return nil, nil, nil end
    local numLines = tooltip:NumLines()
    if not numLines or numLines < 2 then return nil, nil, nil end

    local foundEquipLoc, foundArmorType, foundItemClass
    for i = 2, math.min(numLines, 8) do
        local leftObj = getglobal(tipName .. "TextLeft" .. i)
        local rightObj = getglobal(tipName .. "TextRight" .. i)
        if leftObj then
            local leftText = leftObj:GetText()
            if leftText and leftText ~= "" then
                local invtype = SLOT_TEXT_TO_INVTYPE[leftText]
                if invtype then
                    foundEquipLoc = invtype
                    if rightObj then
                        local rightText = rightObj:GetText()
                        if rightText and rightText ~= "" then
                            foundArmorType = rightText
                            if ARMOR_CLASS_NAMES[rightText] or SUBCLASS_NORMALIZE[rightText]
                                or SHIELD_NAMES[rightText] then
                                foundItemClass = "Armor"
                            end
                        end
                    end
                    break
                end
            end
        end
    end
    return foundEquipLoc, foundArmorType, foundItemClass
end

local GS_EQUIP_LOCS = {
    INVTYPE_HEAD=true, INVTYPE_NECK=true, INVTYPE_SHOULDER=true,
    INVTYPE_BODY=true, INVTYPE_CHEST=true, INVTYPE_ROBE=true,
    INVTYPE_WAIST=true, INVTYPE_LEGS=true, INVTYPE_FEET=true,
    INVTYPE_WRIST=true, INVTYPE_HAND=true,
    INVTYPE_FINGER=true, INVTYPE_TRINKET=true,
    INVTYPE_CLOAK=true,
    INVTYPE_WEAPON=true, INVTYPE_2HWEAPON=true, INVTYPE_WEAPONMAINHAND=true,
    INVTYPE_SHIELD=true, INVTYPE_WEAPONOFFHAND=true, INVTYPE_HOLDABLE=true,
    INVTYPE_RANGED=true, INVTYPE_RANGEDRIGHT=true, INVTYPE_THROWN=true,
    INVTYPE_RELIC=true,
}

--------------------------------------------------------------------------------
-- Tooltip text parser (zero external dependency)
--------------------------------------------------------------------------------

local STAT_PATTERNS = {
    -- Weapon DPS (float, e.g. "(每秒伤害19.7)" or "(12.3 damage per second)")
    { p = "%((%d+%.?%d*) damage per second%)", s = "WEAPONDPS" },
    { p = "每秒伤害(%d+%.?%d*)", s = "WEAPONDPS" },
    { p = "每秒(%d+%.?%d*)点伤害", s = "WEAPONDPS" },

    -- Weapon attack speed ("速度 1.50" / "Speed 1.50") — stored as raw value
    { p = "^速度 (%d+%.?%d*)", s = "WEAPONSPEED" },
    { p = "^Speed (%d+%.?%d*)", s = "WEAPONSPEED" },

    -- Base armor value ("63点护甲" / "123 护甲" / "500 Armor", no + prefix)
    { p = "^(%d+)点护甲", s = "BASEARMOR" },
    { p = "^(%d+) 点护甲", s = "BASEARMOR" },
    { p = "^(%d+) 护甲$", s = "BASEARMOR" },
    { p = "^(%d+) Armor$", s = "BASEARMOR" },

    -- Bonus armor ("+30 Armor" / "+30 护甲" from enchants)
    { p = "%+(%d+) Armor", s = "ARMOR" },
    { p = "%+(%d+) 护甲", s = "ARMOR" },
    { p = "护甲值提高(%d+)", s = "ARMOR" },

    -- Primary stats
    { p = "%+(%d+) Strength",  s = "STR" },
    { p = "%+(%d+) Agility",   s = "AGI" },
    { p = "%+(%d+) Stamina",   s = "STA" },
    { p = "%+(%d+) Intellect", s = "INT" },
    { p = "%+(%d+) Spirit",    s = "SPI" },
    { p = "%+(%d+) 力量", s = "STR" },
    { p = "%+(%d+) 敏捷", s = "AGI" },
    { p = "%+(%d+) 耐力", s = "STA" },
    { p = "%+(%d+) 智力", s = "INT" },
    { p = "%+(%d+) 精神", s = "SPI" },

    -- Crit (spell > ranged > melee, more specific patterns first)
    { p = "critical strike with spells by (%d+)%%", s = "SPELLCRIT" },
    { p = "法术暴击.-(%d+)%%", s = "SPELLCRIT" },
    { p = "法术.-致命一击.-(%d+)%%", s = "SPELLCRIT" },
    { p = "法术.-爆击.-(%d+)%%", s = "SPELLCRIT" },
    { p = "critical strike with ranged weapons by (%d+)%%", s = "RANGEDCRIT" },
    { p = "远程暴击.-(%d+)%%", s = "RANGEDCRIT" },
    { p = "远程.-致命一击.-(%d+)%%", s = "RANGEDCRIT" },
    { p = "critical strike by (%d+)%%", s = "CRIT" },
    { p = "致命一击几率.-(%d+)%%", s = "CRIT" },
    { p = "致命一击.-提高(%d+)%%", s = "CRIT" },
    { p = "致命一击.-(%d+)%%", s = "CRIT" },
    { p = "暴击几率.-(%d+)%%", s = "CRIT" },
    { p = "暴击.-(%d+)%%", s = "CRIT" },

    -- Hit (green equip effects: "使你击中目标的几率提高X%", "chance to hit by X%")
    { p = "hit with spells by (%d+)%%", s = "SPELLTOHIT" },
    { p = "法术击中.-(%d+)%%", s = "SPELLTOHIT" },
    { p = "法术命中.-(%d+)%%", s = "SPELLTOHIT" },
    { p = "用法术击中.-几率.-(%d+)%%", s = "SPELLTOHIT" },
    { p = "chance to hit by (%d+)%%", s = "TOHIT" },
    { p = "击中目标.-几率.-(%d+)%%", s = "TOHIT" },
    { p = "击中.-提高(%d+)%%", s = "TOHIT" },
    { p = "命中.-(%d+)%%", s = "TOHIT" },

    -- Attack Power
    { p = "%+(%d+) ranged Attack Power", s = "RANGEDATTACKPOWER" },
    { p = "%+(%d+) 远程攻击强度", s = "RANGEDATTACKPOWER" },
    { p = "远程攻击强度提高(%d+)", s = "RANGEDATTACKPOWER" },
    { p = "%+(%d+) Attack Power", s = "ATTACKPOWER" },
    { p = "%+(%d+) 攻击强度", s = "ATTACKPOWER" },
    { p = "攻击强度提高(%d+)", s = "ATTACKPOWER" },

    -- Spell damage + healing (combined, MUST be before individual patterns)
    { p = "damage and healing done by magical spells and effects by up to (%d+)", s = "DMG", a = "HEAL" },
    { p = "伤害和治疗效果.-最多(%d+)", s = "DMG", a = "HEAL" },
    { p = "法术伤害和治疗.-(%d+)", s = "DMG", a = "HEAL" },

    -- Healing only
    { p = "healing done by spells and effects by up to (%d+)", s = "HEAL" },
    { p = "Increases healing done by up to (%d+)", s = "HEAL" },
    { p = "治疗效果.-最多(%d+)", s = "HEAL" },
    { p = "治疗法术.-最多(%d+)", s = "HEAL" },
    { p = "治疗量.-最多(%d+)", s = "HEAL" },

    -- Spell damage only
    { p = "damage done by magical spells and effects by up to (%d+)", s = "DMG" },
    { p = "法术伤害.-最多(%d+)", s = "DMG" },
    { p = "魔法伤害.-最多(%d+)", s = "DMG" },

    -- Mana/health regen
    { p = "Restores (%d+) mana per 5 sec", s = "MANAREG" },
    { p = "(%d+) mana per 5 sec", s = "MANAREG" },
    { p = "每5秒恢复(%d+)点法力", s = "MANAREG" },
    { p = "每5秒回复(%d+)点法力", s = "MANAREG" },
    { p = "5秒回复(%d+)点法力", s = "MANAREG" },
    { p = "Restores (%d+) health per 5 sec", s = "HEALTHREG" },
    { p = "每5秒恢复(%d+)点生命", s = "HEALTHREG" },
    { p = "每5秒回复(%d+)点生命", s = "HEALTHREG" },

    -- Defense (green equip: "提高你的防御技能X点", "+X Defense")
    { p = "Increased Defense %+(%d+)", s = "DEFENSE" },
    { p = "Defense %+(%d+)", s = "DEFENSE" },
    { p = "%+(%d+) Defense", s = "DEFENSE" },
    { p = "防御技能提高(%d+)", s = "DEFENSE" },
    { p = "防御等级提高(%d+)", s = "DEFENSE" },
    { p = "防御.-提高(%d+)", s = "DEFENSE" },

    -- Avoidance (green equip: "使你的躲闪几率提高X%")
    { p = "dodge.-by (%d+)%%", s = "DODGE" },
    { p = "躲闪几率.-(%d+)%%", s = "DODGE" },
    { p = "躲闪.-提高(%d+)%%", s = "DODGE" },
    { p = "躲闪.-(%d+)%%", s = "DODGE" },
    { p = "parry.-by (%d+)%%", s = "PARRY" },
    { p = "招架几率.-(%d+)%%", s = "PARRY" },
    { p = "招架.-提高(%d+)%%", s = "PARRY" },
    { p = "招架.-(%d+)%%", s = "PARRY" },
    { p = "block attacks.-by (%d+)%%", s = "BLOCK" },
    { p = "格挡几率.-(%d+)%%", s = "BLOCK" },
    { p = "格挡.-提高(%d+)%%", s = "BLOCK" },
    { p = "格挡率.-(%d+)%%", s = "BLOCK" },
    { p = "block value.-by (%d+)", s = "BLOCKVALUE" },
    { p = "格挡值.-(%d+)", s = "BLOCKVALUE" },
    { p = "盾牌格挡值.-(%d+)", s = "BLOCKVALUE" },

    -- HP/Mana
    { p = "%+(%d+) Health", s = "HEALTH" },
    { p = "%+(%d+) Hit Points", s = "HEALTH" },
    { p = "%+(%d+) 生命值", s = "HEALTH" },
    { p = "%+(%d+) Mana", s = "MANA" },
    { p = "%+(%d+) 法力值", s = "MANA" },
}

--------------------------------------------------------------------------------
-- Debug helper: /gsdebug to toggle
--------------------------------------------------------------------------------
local GS_DEBUG = false
local function GSDebug(msg)
    if GS_DEBUG and DEFAULT_CHAT_FRAME then
        DEFAULT_CHAT_FRAME:AddMessage("|cffFFFF00[GS]|r " .. tostring(msg))
    end
end

SLASH_GSDEBUG1 = "/gsdebug"
SlashCmdList["GSDEBUG"] = function()
    GS_DEBUG = not GS_DEBUG
    DEFAULT_CHAT_FRAME:AddMessage("|cffFFFF00[GearScore]|r Debug: " .. (GS_DEBUG and "ON" or "OFF"))
end

--------------------------------------------------------------------------------
-- Stat parser: scan a VISIBLE tooltip's text lines directly
-- This is the most reliable method — no hidden tooltip, no library needed
--------------------------------------------------------------------------------

local function ScanTooltipForStats(tooltip)
    if not tooltip then return nil end
    local tipName = tooltip:GetName()
    if not tipName then return nil end

    local numLines = tooltip:NumLines()
    if not numLines or numLines < 2 then return nil end

    local stats = {}
    for i = 2, numLines do
        local lineObj = getglobal(tipName .. "TextLeft" .. i)
        if lineObj then
            local text = lineObj:GetText()
            if text and text ~= "" then
                for _, pat in ipairs(STAT_PATTERNS) do
                    local _, _, val = string.find(text, pat.p)
                    if val then
                        local n = tonumber(val)
                        if n and n > 0 then
                            stats[pat.s] = (stats[pat.s] or 0) + n
                            if pat.a then
                                stats[pat.a] = (stats[pat.a] or 0) + n
                            end
                            GSDebug("  L" .. i .. ": " .. pat.s .. "=" .. n .. " (" .. text .. ")")
                        end
                        break
                    end
                end
            end
        end
    end

    local hasAny = false
    for _ in pairs(stats) do hasAny = true; break end
    return hasAny and stats or nil
end

local function ParseItemWithLib(link)
    if not link then return nil end
    if not AceLibrary or not AceLibrary.HasInstance then return nil end
    if not AceLibrary:HasInstance("ItemBonusLib-1.0") then return nil end
    local lib = AceLibrary("ItemBonusLib-1.0")
    if not lib or not lib.ScanItem then return nil end
    local ok, result = pcall(function() return lib:ScanItem(link, true) end)
    if ok and result then
        local hasAny = false
        for _ in pairs(result) do hasAny = true; break end
        if hasAny then return result end
    end
    return nil
end

--------------------------------------------------------------------------------
-- Compatibility multipliers
--------------------------------------------------------------------------------

local function GetArmorCompat(classToken, itemClass, itemSubClass, level)
    if not itemClass or not ARMOR_CLASS_NAMES[itemClass] then return 1.0 end
    if not itemSubClass or itemSubClass == "" then return 1.0 end
    if ARMOR_MISC_NAMES[itemSubClass] then return 1.0 end
    if SHIELD_NAMES[itemSubClass] then return 1.0 end

    local normalizedSub = SUBCLASS_NORMALIZE[itemSubClass]
    if not normalizedSub then return 1.0 end

    local tbl = (level < 40) and ARMOR_COMPAT_LOW or ARMOR_COMPAT
    local classTbl = tbl[classToken]
    if not classTbl then return 1.0 end
    return classTbl[normalizedSub] or 0.3
end

local function GetSlotCompat(classToken, specIdx, equipLoc)
    if not equipLoc or equipLoc == "" then return 1.0 end
    local key = classToken .. "_" .. specIdx
    local tbl = SLOT_COMPAT[key]
    if not tbl then return 1.0 end
    local val = tbl[equipLoc]
    if val ~= nil then return val end
    return 1.0
end

--------------------------------------------------------------------------------
-- Core scoring: 1-10 normalized scale
-- Score = (actual_EP / ideal_EP) * 10 * equipment_compat
-- ideal_EP = total_budget * best_efficiency_for_spec
--------------------------------------------------------------------------------

-- Slot-specific and non-standard stats excluded from reference efficiency
local EFF_EXCLUDE = { WEAPONDPS=true, WEAPONSPEED=true, BASEARMOR=true, HEALTH=true, MANA=true }
-- Stats that only appear on higher-level items
local EFF_LATE_GAME = { DMG=true, HEAL=true, SPELLTOHIT=true, SPELLCRIT=true,
                        TOHIT=true, CRIT=true, RANGEDCRIT=true }
local EFF_MID_GAME = { DMG=true, HEAL=true }

local function GetRefEfficiency(weights, level)
    local effs = {}
    for stat, w in pairs(weights) do
        if not EFF_EXCLUDE[stat] then
            local cost = BUDGET_COST[stat]
            if cost and cost > 0 and w > 0 then
                local skip = false
                if level and level < 25 and EFF_LATE_GAME[stat] then skip = true end
                if level and level >= 25 and level < 40 and EFF_MID_GAME[stat] then skip = true end
                if not skip then
                    table.insert(effs, w / cost)
                end
            end
        end
    end

    table.sort(effs, function(a,b) return a > b end)

    local n = table.getn(effs)
    local ref = 0
    if n >= 3 then
        ref = effs[1] * 0.45 + effs[2] * 0.30 + effs[3] * 0.25
    elseif n == 2 then
        ref = effs[1] * 0.55 + effs[2] * 0.45
    elseif n == 1 then
        ref = effs[1]
    end

    if ref <= 0 then ref = 1.0 end
    return ref
end

local DPS_DAMPEN_MELEE  = 0.40
local DPS_DAMPEN_RANGED = 0.70

local function CalcRawEP(bonuses, weights, dpsDampen)
    if not bonuses or not weights then return 0 end
    dpsDampen = dpsDampen or DPS_DAMPEN_MELEE
    local ep = 0
    for stat, value in pairs(bonuses) do
        local w = weights[stat]
        if w and w > 0 then
            if stat == "WEAPONDPS" then
                ep = ep + value * w * dpsDampen
            elseif stat == "WEAPONSPEED" then
                local speedBonus = 3.0 - value
                if speedBonus > 0 then
                    ep = ep + speedBonus * w
                end
            else
                ep = ep + value * w
            end
        end
    end
    return ep
end

local function CalcTotalBudget(bonuses)
    if not bonuses then return 0 end
    local total = 0
    for stat, value in pairs(bonuses) do
        local cost = BUDGET_COST[stat]
        if cost and cost > 0 then
            if stat == "WEAPONSPEED" then
                local speedBonus = 3.0 - value
                if speedBonus > 0 then
                    total = total + speedBonus * cost
                end
            else
                total = total + math.abs(value) * cost
            end
        end
    end
    return total
end

local GS_RANGED_LOCS = {
    INVTYPE_RANGED = true, INVTYPE_RANGEDRIGHT = true, INVTYPE_THROWN = true,
}

--------------------------------------------------------------------------------
-- Horizontal comparison: reference EP for the best rare item at level/slot
--------------------------------------------------------------------------------

local SLOT_BUDGET_MOD = {
    INVTYPE_HEAD = 1.0,  INVTYPE_CHEST = 1.0, INVTYPE_ROBE = 1.0, INVTYPE_LEGS = 1.0,
    INVTYPE_SHOULDER = 0.77, INVTYPE_HAND = 0.77, INVTYPE_WAIST = 0.77, INVTYPE_FEET = 0.77,
    INVTYPE_WRIST = 0.56, INVTYPE_CLOAK = 0.56,
    INVTYPE_NECK = 0.56, INVTYPE_FINGER = 0.56, INVTYPE_TRINKET = 0.56,
    INVTYPE_WEAPON = 0.42, INVTYPE_WEAPONMAINHAND = 0.42, INVTYPE_WEAPONOFFHAND = 0.36,
    INVTYPE_2HWEAPON = 1.0,
    INVTYPE_SHIELD = 0.56, INVTYPE_HOLDABLE = 0.42,
    INVTYPE_RANGED = 0.32, INVTYPE_RANGEDRIGHT = 0.32, INVTYPE_THROWN = 0.32,
    INVTYPE_RELIC = 0.32, INVTYPE_TABARD = 0, INVTYPE_BODY = 0,
}

local WEAPON_EQUIP_LOCS = {
    INVTYPE_WEAPON = true, INVTYPE_WEAPONMAINHAND = true, INVTYPE_WEAPONOFFHAND = true,
    INVTYPE_2HWEAPON = true, INVTYPE_RANGED = true, INVTYPE_RANGEDRIGHT = true,
    INVTYPE_THROWN = true,
}

local ARMOR_EQUIP_LOCS = {
    INVTYPE_HEAD = true, INVTYPE_CHEST = true, INVTYPE_ROBE = true, INVTYPE_LEGS = true,
    INVTYPE_SHOULDER = true, INVTYPE_HAND = true, INVTYPE_WAIST = true, INVTYPE_FEET = true,
    INVTYPE_WRIST = true, INVTYPE_SHIELD = true,
}

local function GetRefWeaponDPS(equipLoc, level)
    local dps
    if level >= 58 then     dps = 48
    elseif level >= 40 then dps = 28 + (level - 40) * 0.55
    elseif level >= 20 then dps = 13 + (level - 20) * 0.75
    elseif level >= 10 then dps = 6  + (level - 10) * 0.7
    else                    dps = 3  + level * 0.3
    end
    if equipLoc == "INVTYPE_2HWEAPON" then dps = dps * 1.3 end
    return dps
end

local function GetReferenceEP(equipLoc, level, weights, refEff)
    local slotMod = SLOT_BUDGET_MOD[equipLoc] or 0.56

    local refIlvl
    if level >= 58 then     refIlvl = 63
    elseif level >= 40 then refIlvl = level + 7
    elseif level >= 20 then refIlvl = level + 5
    else                    refIlvl = level + 3
    end

    local statBudget = refIlvl * 0.65 * slotMod
    local ep = statBudget * refEff

    if WEAPON_EQUIP_LOCS[equipLoc] then
        local wDPS = weights.WEAPONDPS
        if wDPS and wDPS > 0 then
            local refDPS = GetRefWeaponDPS(equipLoc, level)
            local dampen = GS_RANGED_LOCS[equipLoc] and DPS_DAMPEN_RANGED or DPS_DAMPEN_MELEE
            ep = ep + refDPS * wDPS * dampen
        end
    end

    if ARMOR_EQUIP_LOCS[equipLoc] then
        local wArmor = weights.BASEARMOR
        if wArmor and wArmor > 0 then
            local refArmor = level * 1.5 * slotMod
            ep = ep + refArmor * wArmor
        end
    end

    return ep
end

local function CalcNormalizedScore(bonuses, weights, armorCompat, slotCompat, level, equipLoc)
    local dpsDampen = DPS_DAMPEN_MELEE
    if equipLoc and GS_RANGED_LOCS[equipLoc] then dpsDampen = DPS_DAMPEN_RANGED end
    local rawEP = CalcRawEP(bonuses, weights, dpsDampen)
    local refEff = GetRefEfficiency(weights, level)
    local refEP = GetReferenceEP(equipLoc, level, weights, refEff)

    if refEP <= 0 then return 0 end

    local rawScore = (rawEP / refEP) * 10

    local compat = (armorCompat or 1.0) * (slotCompat or 1.0)
    local finalScore = rawScore * compat

    if finalScore < 0 then finalScore = 0 end
    if finalScore > 10 then finalScore = 10 end

    return math.floor(finalScore * 10 + 0.5) / 10
end

--------------------------------------------------------------------------------
-- Score color (1-10 scale)
--------------------------------------------------------------------------------

local function ScoreColorHex(score)
    if score >= 9 then return "ffFF8800" end
    if score >= 7 then return "ff00FF00" end
    if score >= 5 then return "ffFFFFFF" end
    if score >= 3 then return "ffFFFF00" end
    return "ffFF4444"
end

local function ScoreLabel(score)
    if score >= 9 then return "极品" end
    if score >= 7 then return "优秀" end
    if score >= 5 then return "适用" end
    if score >= 3 then return "一般" end
    return "不适"
end

--------------------------------------------------------------------------------
-- Score tooltip (uses GameTooltipTemplate — proven reliable in this addon)
-- Created lazily on first use (nil parent like Trade.lua pattern)
--------------------------------------------------------------------------------

local GSTip

local function GS_ShowTip(parentTip, scoreLines)
    if not scoreLines or not parentTip then return end

    if not GSTip then
        GSTip = CreateFrame("GameTooltip", "NanamiGSTooltip", nil, "GameTooltipTemplate")
        GSTip:SetFrameStrata("TOOLTIP")
        GSTip:SetClampedToScreen(true)
    end

    GSTip:SetOwner(parentTip, "ANCHOR_NONE")
    GSTip:ClearAllPoints()

    local bottom = parentTip:GetBottom()
    if bottom and bottom > 80 then
        GSTip:SetPoint("TOPLEFT", parentTip, "BOTTOMLEFT", 0, 2)
    else
        GSTip:SetPoint("BOTTOMLEFT", parentTip, "TOPLEFT", 0, -2)
    end

    for _, entry in ipairs(scoreLines) do
        if entry.left and entry.right then
            GSTip:AddDoubleLine(entry.left, entry.right,
                entry.lr or 1, entry.lg or 1, entry.lb or 1,
                entry.rr or 1, entry.rg or 1, entry.rb or 1)
        else
            GSTip:AddLine(entry.text or "", entry.r or 1, entry.g or 0.84, entry.b or 0)
        end
    end

    GSTip:Show()
end

local function GS_HideFrame()
    if GSTip then GSTip:Hide() end
end

--------------------------------------------------------------------------------
-- Main tooltip function
--------------------------------------------------------------------------------

local GS_SCORE_CACHE = {}
local GS_CACHE_SIZE = 0
local GS_CACHE_MAX = 200

function GS:AddScoreToTooltip(tooltip, link)
    if not tooltip or not link then return end
    if SFramesDB and SFramesDB.gearScore == false then return end
    if tooltip._gsScoreAdded then return end

    local classToken = GetPlayerClassToken()
    if not classToken then return end
    local classData = WEIGHTS[classToken]
    if not classData then return end

    local cacheKey = classToken .. "|" .. link
    local cached = GS_SCORE_CACHE[cacheKey]
    if cached then
        tooltip._gsScoreAdded = true
        if cached.scoreLines then
            GS_ShowTip(tooltip, cached.scoreLines)
        else
            GS_HideFrame()
        end
        return
    end

    GSDebug("Processing: " .. tostring(link))

    local quality = GetQualityFromLink(link)
    if quality < 0 then
        quality = GetQualityFromTooltip(tooltip)
    end
    local qualityMult = QUALITY_MULT[quality] or 0.75
    if quality == 0 then
        GSDebug("Skipped: poor quality (gray) item")
        return
    end
    GSDebug("Quality=" .. quality .. " mult=" .. qualityMult)

    local equipLoc, itemClass, itemSubClass
    pcall(function()
        local itemStr = ExtractItemString(link)
        local infoArg = itemStr or link
        GSDebug("GetItemInfo arg: " .. tostring(infoArg))
        local results = { GetItemInfo(infoArg) }
        if results[1] then
            itemClass = results[6]
            itemSubClass = results[7]
            for idx = 8, 10 do
                local v = results[idx]
                if type(v) == "string" and (v == "" or string.find(v, "^INVTYPE")) then
                    equipLoc = v
                    break
                end
            end
        end
        GSDebug("GetItemInfo: equip=" .. tostring(equipLoc)
            .. " class=" .. tostring(itemClass) .. " sub=" .. tostring(itemSubClass))
    end)

    if not equipLoc then
        local ttEquip, ttArmor, ttClass = ParseEquipLocFromTooltip(tooltip)
        if ttEquip then
            equipLoc = ttEquip
            if ttArmor and not itemSubClass then itemSubClass = ttArmor end
            if ttClass and not itemClass then itemClass = ttClass end
            GSDebug("Parsed from tooltip: equip=" .. tostring(equipLoc) .. " armor=" .. tostring(ttArmor))
        end
    end

    if equipLoc == "INVTYPE_BAG" or equipLoc == "INVTYPE_AMMO" or equipLoc == "INVTYPE_TABARD" then
        GSDebug("Skipped: bag/ammo/tabard")
        return
    end
    if equipLoc and equipLoc ~= "" and not GS_EQUIP_LOCS[equipLoc] then
        GSDebug("Skipped: not equippable (" .. tostring(equipLoc) .. ")")
        return
    end

    local bonuses = ParseItemWithLib(link)
    if bonuses then
        GSDebug("Stats from ItemBonusLib")
    else
        bonuses = ScanTooltipForStats(tooltip)
        if bonuses then
            GSDebug("Stats from tooltip text")
        end
    end

    if not bonuses then
        GSDebug("No stats found, skipping")
        return
    end

    if not equipLoc or equipLoc == "" then
        GSDebug("No equipLoc, not equipment, skipping")
        return
    end

    local level = GetPlayerLevel()
    local primaryTab = GetPrimaryTabIndex()
    local isHC = IsPlayerHardcore()

    local armorCompat = GetArmorCompat(classToken, itemClass, itemSubClass, level)
    GSDebug("ArmorCompat=" .. armorCompat .. " class=" .. classToken
        .. " slot=" .. tostring(equipLoc) .. " lv=" .. level)

    local specs = classData.specs
    if not specs or table.getn(specs) == 0 then return end

    local scores = {}
    local anyShow = false
    for i, spec in ipairs(specs) do
        local w = AdjustWeightsForLevel(spec.w, level, false)
        local slotCompat = GetSlotCompat(classToken, i, equipLoc)
        local refEff = GetRefEfficiency(w, level)
        local refEP = GetReferenceEP(equipLoc, level, w, refEff)
        local s = CalcNormalizedScore(bonuses, w, armorCompat, slotCompat, level, equipLoc)
        GSDebug("  " .. spec.name .. ": rawScore=" .. string.format("%.2f", s)
            .. " refEP=" .. string.format("%.1f", refEP) .. " refEff=" .. string.format("%.3f", refEff))
        s = math.floor(s * qualityMult * 10 + 0.5) / 10
        if s < 1.0 and s > 0 then s = 1.0 end
        table.insert(scores, {
            name = spec.name,
            color = spec.color,
            score = s,
            isPrimary = (spec.tab == primaryTab),
            label = ScoreLabel(s),
        })
        if s > 0 then anyShow = true end
    end

    local hcScore = 0
    if classData.hc then
        local hw = AdjustWeightsForLevel(classData.hc.w, level, true)
        hcScore = CalcNormalizedScore(bonuses, hw, armorCompat, 1.0, level, equipLoc)
        if not bonuses.STA or bonuses.STA <= 0 then
            hcScore = hcScore * 0.35
        end
        hcScore = math.floor(hcScore * qualityMult * 10 + 0.5) / 10
        if hcScore < 1.0 and hcScore > 0 then hcScore = 1.0 end
        if hcScore > 0 then anyShow = true end
    end

    if not anyShow then
        GS_SCORE_CACHE[cacheKey] = { scoreLines = nil }
        GS_CACHE_SIZE = GS_CACHE_SIZE + 1
        GS_HideFrame()
        return
    end

    tooltip._gsScoreAdded = true

    local scoreLines = {}
    table.insert(scoreLines, { text = "── 装备评分 ──", r = 1, g = 0.84, b = 0 })

    for _, sd in ipairs(scores) do
        local star = sd.isPrimary and "★ " or "  "
        local sStr = string.format("%.1f", sd.score)
        local sColor = ScoreColorHex(sd.score)
        table.insert(scoreLines, {
            left  = star .. "|c" .. sd.color .. sd.name .. "|r",
            right = "|c" .. sColor .. sStr .. " " .. sd.label .. "|r",
        })
    end

    if classData.hc and hcScore > 0 then
        local hcSColor = ScoreColorHex(hcScore)
        local hcStar = isHC and "★ " or "  "
        table.insert(scoreLines, {
            left  = hcStar .. "|c" .. classData.hc.color .. "硬核|r",
            right = "|c" .. hcSColor .. string.format("%.1f", hcScore)
                .. " " .. ScoreLabel(hcScore) .. "|r",
        })
    end

    GS_ShowTip(tooltip, scoreLines)

    if GS_CACHE_SIZE >= GS_CACHE_MAX then
        GS_SCORE_CACHE = {}
        GS_CACHE_SIZE = 0
    end
    GS_SCORE_CACHE[cacheKey] = { scoreLines = scoreLines }
    GS_CACHE_SIZE = GS_CACHE_SIZE + 1
end

--------------------------------------------------------------------------------
-- Self-contained tooltip hooks
--------------------------------------------------------------------------------

local function GS_TryEnhance(tooltip, link)
    if not link then return end
    pcall(function() GS:AddScoreToTooltip(tooltip, link) end)
end

function GS:HookTooltips()
    if self._hooked then return end
    self._hooked = true

    local origHide = GameTooltip:GetScript("OnHide")
    GameTooltip:SetScript("OnHide", function()
        this._gsScoreAdded = nil
        GS_HideFrame()
        if origHide then origHide() end
    end)

    local origBag = GameTooltip.SetBagItem
    GameTooltip.SetBagItem = function(self, bag, slot)
        self._gsScoreAdded = nil
        local r1, r2, r3 = origBag(self, bag, slot)
        GS_TryEnhance(self, GetContainerItemLink(bag, slot))
        return r1, r2, r3
    end

    local origInv = GameTooltip.SetInventoryItem
    GameTooltip.SetInventoryItem = function(self, unit, slotId)
        self._gsScoreAdded = nil
        local r1, r2, r3 = origInv(self, unit, slotId)
        if unit and slotId then
            GS_TryEnhance(self, GetInventoryItemLink(unit, slotId))
        end
        return r1, r2, r3
    end

    if GameTooltip.SetMerchantItem then
        local orig = GameTooltip.SetMerchantItem
        GameTooltip.SetMerchantItem = function(self, idx)
            self._gsScoreAdded = nil
            orig(self, idx)
            if GetMerchantItemLink then GS_TryEnhance(self, GetMerchantItemLink(idx)) end
        end
    end

    if GameTooltip.SetQuestItem then
        local orig = GameTooltip.SetQuestItem
        GameTooltip.SetQuestItem = function(self, qtype, idx)
            self._gsScoreAdded = nil
            orig(self, qtype, idx)
            if GetQuestItemLink then GS_TryEnhance(self, GetQuestItemLink(qtype, idx)) end
        end
    end

    if GameTooltip.SetQuestLogItem then
        local orig = GameTooltip.SetQuestLogItem
        GameTooltip.SetQuestLogItem = function(self, itype, idx)
            self._gsScoreAdded = nil
            orig(self, itype, idx)
            if GetQuestLogItemLink then GS_TryEnhance(self, GetQuestLogItemLink(itype, idx)) end
        end
    end

    if GameTooltip.SetLootItem then
        local orig = GameTooltip.SetLootItem
        GameTooltip.SetLootItem = function(self, idx)
            self._gsScoreAdded = nil
            orig(self, idx)
            if GetLootSlotLink then GS_TryEnhance(self, GetLootSlotLink(idx)) end
        end
    end

    if GameTooltip.SetLootRollItem then
        local orig = GameTooltip.SetLootRollItem
        GameTooltip.SetLootRollItem = function(self, rollId)
            self._gsScoreAdded = nil
            orig(self, rollId)
            if GetLootRollItemLink then GS_TryEnhance(self, GetLootRollItemLink(rollId)) end
        end
    end

    if GameTooltip.SetAuctionItem then
        local orig = GameTooltip.SetAuctionItem
        GameTooltip.SetAuctionItem = function(self, atype, idx)
            self._gsScoreAdded = nil
            orig(self, atype, idx)
            if GetAuctionItemLink then GS_TryEnhance(self, GetAuctionItemLink(atype, idx)) end
        end
    end

    if GameTooltip.SetTradeSkillItem then
        local orig = GameTooltip.SetTradeSkillItem
        GameTooltip.SetTradeSkillItem = function(self, skillIdx, reagentIdx)
            self._gsScoreAdded = nil
            orig(self, skillIdx, reagentIdx)
            local link
            if reagentIdx then
                if GetTradeSkillReagentItemLink then link = GetTradeSkillReagentItemLink(skillIdx, reagentIdx) end
            else
                if GetTradeSkillItemLink then link = GetTradeSkillItemLink(skillIdx) end
            end
            GS_TryEnhance(self, link)
        end
    end

    if GameTooltip.SetCraftItem then
        local orig = GameTooltip.SetCraftItem
        GameTooltip.SetCraftItem = function(self, skill, slot)
            self._gsScoreAdded = nil
            orig(self, skill, slot)
            if GetCraftReagentItemLink then GS_TryEnhance(self, GetCraftReagentItemLink(skill, slot)) end
        end
    end

    if GameTooltip.SetTradePlayerItem then
        local orig = GameTooltip.SetTradePlayerItem
        GameTooltip.SetTradePlayerItem = function(self, idx)
            self._gsScoreAdded = nil
            orig(self, idx)
            if GetTradePlayerItemLink then GS_TryEnhance(self, GetTradePlayerItemLink(idx)) end
        end
    end

    if GameTooltip.SetTradeTargetItem then
        local orig = GameTooltip.SetTradeTargetItem
        GameTooltip.SetTradeTargetItem = function(self, idx)
            self._gsScoreAdded = nil
            orig(self, idx)
            if GetTradeTargetItemLink then GS_TryEnhance(self, GetTradeTargetItemLink(idx)) end
        end
    end

    if GameTooltip.SetInboxItem then
        local orig = GameTooltip.SetInboxItem
        GameTooltip.SetInboxItem = function(self, mailID, attachIdx)
            self._gsScoreAdded = nil
            orig(self, mailID, attachIdx)
            local left1 = getglobal("GameTooltipTextLeft1")
            if left1 and GetItemLinkByName then
                GS_TryEnhance(self, GetItemLinkByName(left1:GetText()))
            end
        end
    end

    local origRef = SetItemRef
    if origRef then
        SetItemRef = function(link, text, button)
            origRef(link, text, button)
            if IsAltKeyDown() or IsShiftKeyDown() or IsControlKeyDown() then return end
            pcall(function()
                local _, _, itemStr = string.find(link or "", "(item:[%-?%d:]+)")
                if itemStr then
                    ItemRefTooltip._gsScoreAdded = nil
                    GS:AddScoreToTooltip(ItemRefTooltip, itemStr)
                end
            end)
        end
    end
end

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

local initFrame = CreateFrame("Frame")
initFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
initFrame:SetScript("OnEvent", function()
    initFrame:UnregisterEvent("PLAYER_ENTERING_WORLD")
    GS:HookTooltips()
end)
