-- ============================================================
-- Asylum Compagnon — Loot.lua
-- Aide au butin : « ce drop est-il un up pour un membre du groupe ? »
--
-- On exploite une donnée que l'addon partage déjà : l'équipement de
-- chacun. Chaque client calcule l'ilvl de SES 16 emplacements en direct
-- et le diffuse (message « E ») ; les autres le mettent en cache dans
-- peers[key].slots. Quand tu survoles un objet équipable, on compare son
-- ilvl à l'ilvl actuel de chaque membre sur cet emplacement, en filtrant
-- par équipabilité (type d'armure selon la classe + stat primaire selon
-- la spé). Résultat ajouté à l'infobulle : « up pour : Nom (+8) ».
--
-- Heuristique volontairement basée sur l'ILVL (comme un conseil de loot),
-- pas sur des poids de stats : simple, honnête, sans dépendance.
-- ============================================================

local ADDON_NAME, NS = ...

local Loot = {}
NS.Loot = Loot

-- Emplacements suivis (numéros d'inventaire), mêmes que l'export SimC.
local INV_SLOTS = { 1, 2, 3, 15, 5, 9, 10, 6, 7, 8, 11, 12, 13, 14, 16, 17 }

-- Libellés FR (page Butin)
NS.LOOT_SLOT_LABEL = {
    [1] = "Tête", [2] = "Cou", [3] = "Épaules", [15] = "Dos", [5] = "Torse",
    [9] = "Poignets", [10] = "Mains", [6] = "Ceinture", [7] = "Jambes",
    [8] = "Pieds", [11] = "Anneau 1", [12] = "Anneau 2", [13] = "Bijou 1",
    [14] = "Bijou 2", [16] = "Arme (MD)", [17] = "Main gauche",
}

-- INVTYPE → emplacements d'inventaire concernés (anneaux/bijoux = 2 slots)
local INVTYPE_SLOTS = {
    INVTYPE_HEAD = { 1 }, INVTYPE_NECK = { 2 }, INVTYPE_SHOULDER = { 3 },
    INVTYPE_CLOAK = { 15 }, INVTYPE_CHEST = { 5 }, INVTYPE_ROBE = { 5 },
    INVTYPE_WRIST = { 9 }, INVTYPE_HAND = { 10 }, INVTYPE_WAIST = { 6 },
    INVTYPE_LEGS = { 7 }, INVTYPE_FEET = { 8 },
    INVTYPE_FINGER = { 11, 12 }, INVTYPE_TRINKET = { 13, 14 },
    INVTYPE_WEAPON = { 16 }, INVTYPE_2HWEAPON = { 16 },
    INVTYPE_WEAPONMAINHAND = { 16 }, INVTYPE_RANGED = { 16 },
    INVTYPE_RANGEDRIGHT = { 16 },
    INVTYPE_WEAPONOFFHAND = { 17 }, INVTYPE_HOLDABLE = { 17 },
    INVTYPE_SHIELD = { 17 },
}

-- Emplacements « armure de corps » (soumis au type d'armure de la classe)
local ARMOR_BODY = {
    INVTYPE_HEAD = true, INVTYPE_SHOULDER = true, INVTYPE_CHEST = true,
    INVTYPE_ROBE = true, INVTYPE_WRIST = true, INVTYPE_HAND = true,
    INVTYPE_WAIST = true, INVTYPE_LEGS = true, INVTYPE_FEET = true,
}

-- Type d'armure porté par classe (sous-classe d'armure : 1 tissu / 2 cuir
-- / 3 mailles / 4 plaques)
local CLASS_ARMOR = {
    WARRIOR = 4, PALADIN = 4, DEATHKNIGHT = 4,
    HUNTER = 3, SHAMAN = 3, EVOKER = 3,
    ROGUE = 2, DRUID = 2, MONK = 2, DEMONHUNTER = 2,
    MAGE = 1, PRIEST = 1, WARLOCK = 1,
}

-- Stat primaire par spé (specID) : INT / AGI / STR
local SPEC_PRIMARY = {
    [62] = "INT", [63] = "INT", [64] = "INT",           -- Mage
    [65] = "INT", [66] = "STR", [70] = "STR",           -- Paladin
    [71] = "STR", [72] = "STR", [73] = "STR",           -- Guerrier
    [102] = "INT", [103] = "AGI", [104] = "AGI", [105] = "INT", -- Druide
    [250] = "STR", [251] = "STR", [252] = "STR",        -- DK
    [253] = "AGI", [254] = "AGI", [255] = "AGI",        -- Chasseur
    [256] = "INT", [257] = "INT", [258] = "INT",        -- Prêtre
    [259] = "AGI", [260] = "AGI", [261] = "AGI",        -- Voleur
    [262] = "INT", [263] = "AGI", [264] = "INT",        -- Chaman
    [265] = "INT", [266] = "INT", [267] = "INT",        -- Démoniste
    [268] = "AGI", [269] = "AGI", [270] = "INT",        -- Moine
    [577] = "AGI", [581] = "AGI",                       -- DH
    [1467] = "INT", [1468] = "INT", [1473] = "INT",     -- Évoker
}

-- Constantes de classe d'objet (Enum en jeu, repli numérique)
local IC = Enum and Enum.ItemClass or {}
local CLASS_ARMOR_ID  = IC.Armor  or 4
local CLASS_WEAPON_ID = IC.Weapon or 2
local SUBCLASS_SHIELD = 6

-- ---------- Lecture de l'ilvl ----------
local function ItemLevelOf(link)
    local fn = (C_Item and C_Item.GetDetailedItemLevelInfo) or GetDetailedItemLevelInfo
    if not fn or not link then return nil end
    local ok, ilvl = pcall(fn, link)
    if ok and ilvl and ilvl > 0 then return math.floor(ilvl + 0.5) end
    return nil
end

local function InfoInstant(link)
    local fn = (C_Item and C_Item.GetItemInfoInstant) or GetItemInfoInstant
    if not fn or not link then return nil end
    local ok, id, _, _, equipLoc, _, classID, subclassID = pcall(fn, link)
    if not ok then return nil end
    return id, equipLoc, classID, subclassID
end

local GetStats = (C_Item and C_Item.GetItemStats) or GetItemStats
local function PrimaryStatOf(link)
    if not GetStats or not link then return nil end
    local ok, s = pcall(GetStats, link)
    if not ok or type(s) ~= "table" then return nil end
    local function g(a, b) return math.max(s[a] or 0, s[b] or 0) end
    local int = g("ITEM_MOD_INTELLECT_SHORT", "ITEM_MOD_INTELLECT")
    local agi = g("ITEM_MOD_AGILITY_SHORT",   "ITEM_MOD_AGILITY")
    local str = g("ITEM_MOD_STRENGTH_SHORT",  "ITEM_MOD_STRENGTH")
    local best, val = nil, 0
    if int > val then best, val = "INT", int end
    if agi > val then best, val = "AGI", agi end
    if str > val then best, val = "STR", str end
    return best
end

-- Mes ilvl par emplacement (en direct)
function Loot.GetMySlotIlvls()
    local t = {}
    for _, inv in ipairs(INV_SLOTS) do
        local link = GetInventoryItemLink and GetInventoryItemLink("player", inv)
        local il = link and ItemLevelOf(link)
        if il then t[inv] = il end
    end
    return t
end

-- ---------- Sérialisation / diffusion ----------
function Loot.SerializeMySlots()
    local t = Loot.GetMySlotIlvls()
    local parts = {}
    for _, inv in ipairs(INV_SLOTS) do
        if t[inv] then parts[#parts + 1] = inv .. ":" .. t[inv] end
    end
    return table.concat(parts, "/")
end

local entered = false
function Loot.BroadcastMySlots()
    if not (NS.Sync and NS.Sync.available and NS.Sync.BroadcastSlots) then return end
    local payload = Loot.SerializeMySlots()
    if payload == "" then return end
    NS.Sync.BroadcastSlots(payload)
end

-- Réception d'un « E » d'un pair
function Loot.OnPeerSlots(sender, msg)
    local data = msg:match("^E\t(.*)$")
    if not data or not NS.db then return end
    NS.db.peers = NS.db.peers or {}
    local p = NS.db.peers[sender]
    if not p then p = {}; NS.db.peers[sender] = p end
    local slots = {}
    for inv, il in data:gmatch("(%d+):(%d+)") do
        slots[tonumber(inv)] = tonumber(il)
    end
    p.slots = slots
    p.lastSeen = (GetServerTime and GetServerTime()) or time()
    if NS.RefreshLootUI then NS.RefreshLootUI() end
end

-- Quand un pair se signale (hello), on rediffuse nos slots (anti-rafale)
local helloEcho
function Loot.OnPeerHello()
    if helloEcho then return end
    helloEcho = C_Timer.NewTimer(5, function()
        helloEcho = nil
        Loot.BroadcastMySlots()
    end)
end

-- ---------- Roster de comparaison ----------
local KeyOf = NS.UnitKey -- utilitaire partagé (Core.lua)

-- Set des clés présentes dans le groupe (pour ne comparer qu'aux présents)
local function GroupKeySet()
    local set, grouped = {}, false
    local function add(unit)
        if UnitExists and not UnitExists(unit) then return end
        local k = KeyOf(unit)
        if k then set[k] = true; grouped = true end
    end
    if IsInRaid() then
        for i = 1, 40 do add("raid" .. i) end
    elseif IsInGroup() then
        for i = 1, 4 do add("party" .. i) end
    end
    return set, grouped
end

-- Liste { name, class, specID, slots, isMe } : moi + pairs (du groupe si
-- en groupe, sinon tous les pairs connus qui ont partagé leur stuff)
function Loot.RosterForCompare()
    local list = {}
    local mp = NS.db and NS.db.myProfile
    local _, myClass = UnitClass("player")
    local mySpec = (mp and mp.specID) or 0
    if mySpec == 0 and GetSpecialization and GetSpecializationInfo then
        local idx = GetSpecialization()
        if idx then local sid = GetSpecializationInfo(idx); mySpec = sid or 0 end
    end
    list[#list + 1] = {
        name = UnitName("player") or "?", class = myClass,
        specID = mySpec, slots = Loot.GetMySlotIlvls(), isMe = true,
    }
    local set, grouped = GroupKeySet()
    for k, p in pairs((NS.db and NS.db.peers) or {}) do
        if p.slots and (not grouped or set[k]) then
            list[#list + 1] = {
                name = k:match("^(.-)%-") or k, class = p.class,
                specID = p.specID, slots = p.slots,
            }
        end
    end
    return list
end

-- ---------- Équipabilité ----------
local function CanUse(class, specID, equipLoc, classID, subclassID, primary)
    -- Bouclier : Guerrier / Paladin / Chaman uniquement
    if equipLoc == "INVTYPE_SHIELD" then
        if not (class == "WARRIOR" or class == "PALADIN" or class == "SHAMAN") then
            return false
        end
    end
    -- Armure de corps : doit correspondre au type d'armure de la classe
    if classID == CLASS_ARMOR_ID and ARMOR_BODY[equipLoc] and subclassID then
        local need = CLASS_ARMOR[class]
        if need and subclassID ~= need then return false end
    end
    -- Stat primaire : doit correspondre à la spé (si connue et présente)
    if primary then
        local want = SPEC_PRIMARY[specID or 0]
        if want and want ~= primary then return false end
    end
    return true
end

-- ilvl actuel du membre sur cet emplacement (min des slots remplis, ou nil)
local function CurrentIlvl(slotsMap, invList)
    local cur
    for _, inv in ipairs(invList) do
        local v = slotsMap[inv]
        if v and v > 0 and (not cur or v < cur) then cur = v end
    end
    return cur
end

-- Pour qui ce lien d'objet est-il un up ? → { {name, class, delta}, … }
function Loot.UpgradersFor(link)
    local ilvl = ItemLevelOf(link)
    if not ilvl then return nil end
    local _, equipLoc, classID, subclassID = InfoInstant(link)
    local invList = equipLoc and INVTYPE_SLOTS[equipLoc]
    if not invList then return nil end -- objet non comparable (pas un slot suivi)
    local primary = PrimaryStatOf(link)

    local ups = {}
    for _, m in ipairs(Loot.RosterForCompare()) do
        if CanUse(m.class, m.specID, equipLoc, classID, subclassID, primary) then
            local cur = CurrentIlvl(m.slots, invList)
            if cur then
                local delta = ilvl - cur
                if delta > 0 then
                    ups[#ups + 1] = { name = m.name, class = m.class, delta = delta, isMe = m.isMe }
                end
            end
        end
    end
    table.sort(ups, function(a, b) return a.delta > b.delta end)
    return ups, invList
end

-- ---------- Infobulle ----------
local function ClassHex(token)
    if token and C_ClassColor and C_ClassColor.GetClassColor then
        local c = C_ClassColor.GetClassColor(token)
        if c and c.GenerateHexColor then return c:GenerateHexColor() end
    end
    local r = token and RAID_CLASS_COLORS and RAID_CLASS_COLORS[token]
    if r and r.colorStr then return r.colorStr end
    return "ffffffff"
end

local ACCENT = "ff" .. (NS.ACCENT or "a78bfa")

function Loot.RenderTooltip(tt, link)
    if not (tt and link and tt.AddLine) then return end
    if not (NS.db and NS.db.lootTooltip ~= false) then return end
    local ups, invList = Loot.UpgradersFor(link)
    if not invList then return end -- pas un objet équipable comparable
    local _, grouped = GroupKeySet()

    if ups and #ups > 0 then
        tt:AddLine(" ")
        tt:AddLine("|c" .. ACCENT .. NS.L("Asylum — up pour :") .. "|r")
        for i = 1, math.min(#ups, 7) do
            local u = ups[i]
            local nm = u.name .. (u.isMe and (" " .. NS.L("(toi)")) or "")
            tt:AddDoubleLine("|c" .. ClassHex(u.class) .. nm .. "|r",
                "|cff22c55e+" .. u.delta .. "|r")
        end
        if tt.Show then pcall(tt.Show, tt) end
    elseif grouped then
        -- rien à signaler mais on confirme que l'addon a regardé
        tt:AddLine(" ")
        tt:AddLine("|c" .. ACCENT .. NS.L("Asylum|r |cff8b8fa3— aucun up dans le groupe|r"))
        if tt.Show then pcall(tt.Show, tt) end
    end
end

local hooked = false
local function InstallTooltipHook()
    if hooked then return end
    if TooltipDataProcessor and TooltipDataProcessor.AddTooltipPostCall
        and Enum and Enum.TooltipDataType then
        TooltipDataProcessor.AddTooltipPostCall(Enum.TooltipDataType.Item, function(tt, data)
            if tt ~= GameTooltip and tt ~= ItemRefTooltip then return end
            local link = data and data.hyperlink
            if not link and tt.GetItem then
                local ok, _, l = pcall(tt.GetItem, tt)
                if ok then link = l end
            end
            if link then pcall(Loot.RenderTooltip, tt, link) end
        end)
        hooked = true
    end
end

-- ---------- Rapport pour la page Butin ----------
function Loot.GetGroupReport()
    local out = {}
    for _, m in ipairs(Loot.RosterForCompare()) do
        local filled = {}
        for _, inv in ipairs(INV_SLOTS) do
            local v = m.slots[inv]
            if v and v > 0 then filled[#filled + 1] = { inv = inv, ilvl = v } end
        end
        table.sort(filled, function(a, b) return a.ilvl < b.ilvl end)
        local sum = 0
        for _, f in ipairs(filled) do sum = sum + f.ilvl end
        local weak = {}
        for i = 1, math.min(3, #filled) do weak[#weak + 1] = filled[i] end
        out[#out + 1] = {
            name = m.name, class = m.class, isMe = m.isMe,
            avg = (#filled > 0) and math.floor(sum / #filled + 0.5) or 0,
            count = #filled, weak = weak,
        }
    end
    return out
end

-- ---------- Événements ----------
local lf = CreateFrame("Frame")
lf:RegisterEvent("PLAYER_ENTERING_WORLD")
lf:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
local sendTimer
local function QueueSend()
    if sendTimer then return end
    sendTimer = C_Timer.NewTimer(4, function()
        sendTimer = nil
        Loot.BroadcastMySlots()
    end)
end
lf:SetScript("OnEvent", function(_, ev)
    if ev == "PLAYER_ENTERING_WORLD" then
        InstallTooltipHook()
        if not entered then
            entered = true
            C_Timer.After(6, function() Loot.BroadcastMySlots() end)
        end
    elseif ev == "PLAYER_EQUIPMENT_CHANGED" then
        if entered then QueueSend() end
    end
end)
