local _, ns = ...

------------------------------------------------------------
-- Power Infusion target scoring.
--
-- finalScore = baseScore × ilvlFactor × qualityFactor
--   baseScore     = bloodmallet DPS for this spec, this mode
--   ilvlFactor    = 1 + clamp(ilvl - REF_ILVL, ±ILVL_CAP) × ILVL_STEP
--   qualityFactor = 1 - clamp(enchant*0.02 + sockets*0.02, 0, 0.12)
--
-- Formula is adapted from the BestPI addon. Values chosen so that a
-- full-enchanted mid-tier player at the reference ilvl reads as 1.0×
-- their base sim score, capping the ilvl swing at roughly ±18%.
------------------------------------------------------------

ns.PI = ns.PI or {}
local PI = ns.PI
PI.Scoring = {}
local S = PI.Scoring

local REF_ILVL   = 250
local ILVL_CAP   = 30
local ILVL_STEP  = 0.006

local DPS_ENCHANT_PENALTY_PER_MISSING = 0.02
local SOCKET_PENALTY_PER_MISSING      = 0.02
local MAX_QUALITY_PENALTY             = 0.12

-- DPS-relevant enchant slots (back, legs, chest, two-hand, off-hand).
-- Rings are enchant slots too, but their enchant is an itemBonus rather
-- than a classic enchantID — we skip them to avoid false positives.
local DPS_ENCHANT_SLOTS = {
    [5]  = true,  -- chest
    [11] = true,  -- legs
    [12] = true,  -- boots (feet)
    [16] = true,  -- main-hand
    [17] = true,  -- off-hand
}

local INSPECT_GEAR_SLOTS = {
    1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
}

local function Clamp(v, lo, hi)
    if v < lo then return lo end
    if v > hi then return hi end
    return v
end

local function EnchantIDFromItemLink(link)
    if type(link) ~= "string" then return 0 end
    local itemString = link:match("item:([%-?%d:]+)")
    if not itemString then return 0 end
    local i = 0
    for part in string.gmatch(itemString, "([^:]+)") do
        i = i + 1
        if i == 2 then return tonumber(part) or 0 end
    end
    return 0
end

local function CountEmptySockets(link)
    if type(link) ~= "string" then return 0 end
    local stats
    if C_Item and C_Item.GetItemStats then
        stats = C_Item.GetItemStats(link)
    elseif GetItemStats then
        stats = GetItemStats(link)
    end
    if type(stats) ~= "table" then return 0 end
    local n = 0
    for k, v in pairs(stats) do
        if type(k) == "string" and k:find("EMPTY_SOCKET_", 1, true) then
            n = n + (tonumber(v) or 0)
        end
    end
    return n
end

-- Scan a unit's gear and return {missingEnchants, missingSockets,
-- totalPenalty, qualityMultiplier}. Caller is responsible for having
-- already received INSPECT_READY for the unit (or passing "player").
function S:GearQuality(unit)
    local out = {
        missingEnchants   = 0,
        missingSockets    = 0,
        totalPenalty      = 0,
        qualityMultiplier = 1,
    }
    if not unit or not UnitExists(unit) then return out end

    for _, slotID in ipairs(INSPECT_GEAR_SLOTS) do
        local link = GetInventoryItemLink(unit, slotID)
        if link then
            if DPS_ENCHANT_SLOTS[slotID] and EnchantIDFromItemLink(link) == 0 then
                out.missingEnchants = out.missingEnchants + 1
            end
            out.missingSockets = out.missingSockets + CountEmptySockets(link)
        end
    end

    local penalty = (out.missingEnchants * DPS_ENCHANT_PENALTY_PER_MISSING)
                  + (out.missingSockets  * SOCKET_PENALTY_PER_MISSING)
    out.totalPenalty      = Clamp(penalty, 0, MAX_QUALITY_PENALTY)
    out.qualityMultiplier = 1 - out.totalPenalty
    return out
end

-- Compute the three mode scores for a scored entry.
--   entry: { specID, ilvl, qualityMultiplier }
-- Returns { st = number|nil, cleave = number|nil, aoe = number|nil }
-- Any mode without a bloodmallet entry for this spec reads nil.
function S:ScoreAll(entry)
    local out = { st = nil, cleave = nil, aoe = nil }
    if not entry or not entry.specID or entry.specID == 0 then return out end

    local ilvl    = entry.ilvl or 0
    local quality = entry.qualityMultiplier or 1
    local delta   = Clamp(ilvl - REF_ILVL, -ILVL_CAP, ILVL_CAP)
    local ilvlF   = 1 + delta * ILVL_STEP

    for key, mode in pairs({ st = "ST", cleave = "CLEAVE", aoe = "AOE" }) do
        local base = PI:GetBaseScore(entry.specID, mode)
        if base then out[key] = base * ilvlF * quality end
    end
    return out
end

-- Convenience: single-mode score.
function S:Score(entry, mode)
    local key = (mode or "st"):lower()
    local all = self:ScoreAll(entry)
    return all[key]
end

-- Rank a list of candidates (each with specID + ilvl + quality) by
-- `mode`. Candidates without a score for that mode are dropped. Ties
-- break on iLvl desc, then name asc. Returns the sorted list and
-- leaves a `.score` field on each entry for the caller.
function S:RankByMode(candidates, mode)
    local scored = {}
    for _, c in ipairs(candidates) do
        local s = self:Score(c, mode)
        if s and s > 0 then
            c.score = s
            table.insert(scored, c)
        end
    end
    table.sort(scored, function(a, b)
        if a.score ~= b.score then return a.score > b.score end
        local ai, bi = a.ilvl or 0, b.ilvl or 0
        if ai ~= bi then return ai > bi end
        return (a.name or "") < (b.name or "")
    end)
    return scored
end
