-- ============================================================
-- Asylum Compagnon — Buffs.lua
-- Check de préparation raid/M+ (esprit Method Raid Tools) :
-- flacon, nourriture, huile d'arme + buffs de groupe par classe.
--
-- · Buffs de classe : lus localement sur chaque unité du groupe
--   via C_UnitAuras (avec garde « secret values » de Midnight).
-- · Flacon / nourriture : chacun scanne SES buffs et l'état voyage
--   dans le « hello » de la synchro (flags "fno" → 1/0).
-- · Huile d'arme : GetWeaponEnchantInfo ne marche que sur soi →
--   uniquement via les flags de la synchro.
-- Tables éditables dans Core.lua : NS.FLASK_AURAS, NS.FOOD_AURAS,
-- NS.RAID_BUFFS. « /asylum auras » liste tes buffs pour aider.
--
-- Check à l'appel : sur READY_CHECK, on rediffuse ses flags au groupe
-- puis on imprime dans son chat qui manque quoi (Buffs.PrintReadyCheckReport).
-- ============================================================

local ADDON_NAME, NS = ...

local Buffs = {}
NS.Buffs = Buffs

-- L'unité a-t-elle l'une des auras du set ? (par spellID UNIQUEMENT)
-- Midnight : lire le NOM ou la VALEUR d'une aura depuis un addon renvoie une
-- valeur « secrète » qui plante à la comparaison ET contamine (taint) tout
-- l'addon — ce qui cassait aussi la synchro (kicks, etc.). On interroge donc par
-- spellID CONNU : sur le joueur via GetPlayerAuraBySpellID (présence seule, rien
-- de secret) ; sur une autre unité, scan par index en ne lisant QUE le spellId.
local function HasAnyAura(unit, ids)
    if not (C_UnitAuras and ids) then return false end
    local C = C_UnitAuras
    if unit == "player" and C.GetPlayerAuraBySpellID then
        for sid in pairs(ids) do
            local ok, aura = pcall(C.GetPlayerAuraBySpellID, sid)
            if ok and aura then return true end
        end
        return false
    end
    if C.GetBuffDataByIndex then
        for i = 1, 60 do
            local ok, aura = pcall(C.GetBuffDataByIndex, unit, i)
            if not ok or not aura then break end
            local sid = aura and aura.spellId
            if sid and not (issecretvalue and issecretvalue(sid)) then
                local ok2, hit = pcall(function() return ids[sid] end)
                if ok2 and hit then return true end
            end
        end
    end
    return false
end
Buffs.HasAnyAura = HasAnyAura

-- Repli par NOM désactivé en Midnight : lire aura.name est « secret » (plante +
-- taint). La détection repose donc uniquement sur les listes de spellID connues
-- (NS.FOOD_AURAS / NS.FLASK_AURAS…). Stub conservé pour la compatibilité d'appel.
local function HasAuraNameHint(_, _)
    return false
end
Buffs.HasAuraNameHint = HasAuraNameHint

-- ---------- Niveau de QUALITÉ d'un consommable ----------
-- 2 = présent / OK (coche verte) · 1 = présent mais qualité inférieure (rond
-- orange) · 0 = absent (croix rouge) · nil = illisible (tiret).
-- Flacon : la qualité (or / inférieur) se lisait dans la VALEUR de l'aura,
-- désormais « secrète » en Midnight → illisible. On ne peut donc plus distinguer
-- la qualité : toute présence = OK (vert). Le rond orange ne subsiste que pour
-- l'HUILE (lue via l'enchantID, non secret).
local function FlaskLevel()
    if HasAnyAura("player", NS.FLASK_AURAS) then return 2 end
    return 0
end

-- Bouffe : « Bien nourri » suffit → OK (vert), quelle que soit la qualité. Le
-- point orange « qualité inférieure » ne concerne QUE les flacons et les huiles.
local function FoodLevel()
    if HasAnyAura("player", NS.FOOD_GOLD_AURAS) then return 2 end
    if HasAnyAura("player", NS.FOOD_AURAS) then return 2 end
    if HasAuraNameHint("player", NS.FOOD_NAME_HINTS) then return 2 end
    return 0
end

-- Huile : enchant d'arme temporaire ; l'enchantID OR (8052) = top.
local function OilLevel()
    if not GetWeaponEnchantInfo then return nil end
    local ok, hasMH, _, _, mhEnchantID = pcall(GetWeaponEnchantInfo)
    if not ok then return nil end
    if not hasMH then return 0 end
    if mhEnchantID and NS.OIL_GOLD_ENCHANTS and NS.OIL_GOLD_ENCHANTS[mhEnchantID] then return 2 end
    -- Paladin : son imbue d'arme (Rite de sanctification) est un sort de NIVEAU
    -- MAX, pas une « huile inférieure » → toute présence = OK (vert).
    local _, class = UnitClass("player")
    if class == "PALADIN" then return 2 end
    return 1 -- enchant présent, mais pas l'OR (ou ID non reconnu)
end

-- Mon état consommables : niveau de qualité flacon / nourriture / huile d'arme
function Buffs.GetSelfStatus()
    return { flask = FlaskLevel(), food = FoodLevel(), oil = OilLevel() }
end

-- Présence d'enchantement sur les 8 slots suivis → chaîne de bits "10110101"
-- (lien d'objet : le 1er champ après l'itemID = l'enchantID ; >0 = enchanté)
function Buffs.SelfEnchantBits()
    local bits = {}
    for _, sl in ipairs(NS.ENCHANT_SLOTS or {}) do
        local link = GetInventoryItemLink and GetInventoryItemLink("player", sl.inv)
        local ench = "0"
        if link then
            local e = link:match("item:%d+:(%d+)")
            if e and (tonumber(e) or 0) > 0 then ench = "1" end
        end
        bits[#bits + 1] = ench
    end
    return table.concat(bits)
end

-- Sérialise l'état pour le « hello » : "fno" (flacon/bouffe/huile, NIVEAU 0/1/2)
-- + bits enchant. Le niveau (0=rien, 1=inférieur, 2=OR) voyage donc aux pairs.
function Buffs.SelfFlags()
    local s = Buffs.GetSelfStatus()
    return ("%d%d%d"):format(s.flask or 0, s.food or 0, s.oil or 0)
        .. Buffs.SelfEnchantBits()
end

local function ParseFlags(flags)
    if type(flags) ~= "string" or #flags < 3 then return nil end
    local ench
    local nslots = #(NS.ENCHANT_SLOTS or {})
    if #flags >= 3 + nslots and nslots > 0 then
        ench = {}
        for i = 1, nslots do ench[i] = flags:sub(3 + i, 3 + i) == "1" end
    end
    return {
        flask = tonumber(flags:sub(1, 1)) or 0,   -- niveau 0/1/2 (rétrocompat : "1" → 1)
        food  = tonumber(flags:sub(2, 2)) or 0,
        oil   = tonumber(flags:sub(3, 3)) or 0,
        ench  = ench,
    }
end

-- Roster : moi + amis (avec l'addon) présents dans le groupe
local function BuildRoster()
    local roster = {}
    local seen = {}
    local function addUnit(unit, forceInclude)
        local guid = UnitGUID(unit)
        if not guid or seen[guid] then return end
        local key = NS.UnitKey(unit)
        if not key then return end
        local name = key:match("^([^%-]+)") or key
        if not forceInclude then
            if not (NS.db and NS.db.peers and NS.db.peers[key]) then return end
        end
        seen[guid] = true
        local _, classToken = UnitClass(unit)
        roster[#roster + 1] = { unit = unit, key = key, name = name,
            class = classToken, isMe = forceInclude and true or false }
    end
    addUnit("player", true)
    if IsInRaid() then
        for i = 1, 40 do
            if #roster >= 8 then break end
            addUnit("raid" .. i)
        end
    elseif IsInGroup() then
        for i = 1, 4 do
            addUnit("party" .. i)
        end
    end
    return roster
end

-- Classes présentes dans TOUT le groupe (pour savoir quels buffs exiger)
local function PresentClasses()
    local present = {}
    local function mark(unit)
        if UnitExists and not UnitExists(unit) then return end
        local _, token = UnitClass(unit)
        if token then present[token] = true end
    end
    mark("player")
    if IsInRaid() then
        for i = 1, 40 do mark("raid" .. i) end
    elseif IsInGroup() then
        for i = 1, 4 do mark("party" .. i) end
    end
    return present
end

-- Rapport complet pour la page « Buffs & auras » :
-- columns = libellés ; rows = { name, class, cells = {true/false/nil…} }
function Buffs.GetReport()
    local roster = BuildRoster()
    local present = PresentClasses()

    local columns = { NS.L("Flacon"), NS.L("Bouffe"), NS.L("Huile") }
    local buffCols = {}
    for _, b in ipairs(NS.RAID_BUFFS or {}) do
        if present[b.class] then
            columns[#columns + 1] = NS.L(b.label)
            buffCols[#buffCols + 1] = b
        end
    end

    local rows = {}
    for _, m in ipairs(roster) do
        local cells = {}
        if m.isMe then
            local s = Buffs.GetSelfStatus()
            cells[1], cells[2], cells[3] = s.flask, s.food, s.oil
        else
            local p = NS.db and NS.db.peers and NS.db.peers[m.key]
            local f = p and ParseFlags(p.flags)
            if f then
                cells[1], cells[2], cells[3] = f.flask, f.food, f.oil
            else
                cells[1], cells[2], cells[3] = nil, nil, nil
            end
        end
        for _, b in ipairs(buffCols) do
            cells[#cells + 1] = HasAnyAura(m.unit, b.ids)
        end
        rows[#rows + 1] = { name = m.name, class = m.class, isMe = m.isMe, cells = cells }
    end

    return { columns = columns, rows = rows }
end

-- Rapport enchantements : même forme que GetReport (colonnes = 8 slots,
-- cells = true enchanté / false manquant / nil inconnu).
function Buffs.GetEnchantReport()
    local roster = BuildRoster()
    local slots = NS.ENCHANT_SLOTS or {}
    local columns = {}
    for _, sl in ipairs(slots) do columns[#columns + 1] = NS.L(sl.label) end
    local rows = {}
    for _, m in ipairs(roster) do
        local cells = {}
        if m.isMe then
            local bits = Buffs.SelfEnchantBits()
            for i = 1, #slots do cells[i] = (bits:sub(i, i) == "1") end
        else
            local p = NS.db and NS.db.peers and NS.db.peers[m.key]
            local f = p and ParseFlags(p.flags)
            if f and f.ench then
                for i = 1, #slots do cells[i] = f.ench[i] end
            else
                for i = 1, #slots do cells[i] = nil end
            end
        end
        rows[#rows + 1] = { name = m.name, class = m.class, isMe = m.isMe, cells = cells }
    end
    return { columns = columns, rows = rows }
end

-- ============================================================
-- Check automatique à l'appel (ready check), esprit MRT
-- Quand un ready check démarre, on rediffuse nos flags au groupe,
-- puis on imprime dans NOTRE chat qui manque quoi (message ciblé,
-- local — rien n'est envoyé dans le canal de groupe).
-- ============================================================

-- Manques par joueur : { name, class, isMe, missing = {libellés},
-- unknown = {libellés} } — colonne fausse = manque, colonne nil = inconnu
function Buffs.GetMissing()
    local rep = Buffs.GetReport()
    local list = {}
    for _, row in ipairs(rep.rows) do
        local miss, unknown = {}, {}
        for j = 1, #rep.columns do
            local v = row.cells[j]
            -- flacon/bouffe/huile = niveau (0=rien, 1=inférieur, 2=OR) ; buffs/
            -- enchant = booléen. « rien » (false/0) = manque ; nil = inconnu.
            -- Le niveau 1 (qualité inférieure) n'est PAS compté comme un manque
            -- ici (le rond orange le signale dans la grille/l'accueil).
            if v == false or v == 0 then
                miss[#miss + 1] = rep.columns[j]
            elseif v == nil then
                unknown[#unknown + 1] = rep.columns[j]
            end
        end
        list[#list + 1] = {
            name = row.name, class = row.class, isMe = row.isMe,
            missing = miss, unknown = unknown,
        }
    end
    return list
end

-- Code couleur de classe pour le chat ("|cAARRGGBB")
local function ClassColor(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" .. c:GenerateHexColor() end
    end
    local r = token and RAID_CLASS_COLORS and RAID_CLASS_COLORS[token]
    if r and r.colorStr then return "|c" .. r.colorStr end
    return "|cffffffff"
end

-- Imprime le rapport ciblé dans le chat local
function Buffs.PrintReadyCheckReport()
    if not NS.Msg then return end
    local list = Buffs.GetMissing()
    local problems = {}
    for _, e in ipairs(list) do
        if #e.missing > 0 or #e.unknown > 0 then problems[#problems + 1] = e end
    end
    if #problems == 0 then
        NS.Msg(NS.L("Appel — tout le monde est prêt |cff22c55e✓|r (flacon / bouffe / huile / buffs OK)"))
        return
    end
    NS.Msg(NS.L("Appel — préparation du groupe :"))
    for _, e in ipairs(problems) do
        local who = ("%s%s|r"):format(
            ClassColor(e.class), e.name)
        local parts = {}
        if #e.missing > 0 then
            parts[#parts + 1] = NS.L("|cffea4850manque|r ") .. table.concat(e.missing, ", ")
        end
        if #e.unknown > 0 then
            parts[#parts + 1] = "|cff8b8fa3? " .. table.concat(e.unknown, ", ") .. "|r"
        end
        print(("   %s : %s"):format(who, table.concat(parts, "  ·  ")))
    end
end

-- ---------- Événement READY_CHECK ----------
local rcFrame = CreateFrame("Frame")
rcFrame:RegisterEvent("READY_CHECK")
local rcPending
rcFrame:SetScript("OnEvent", function()
    -- 1) rafraîchir MON état (flacon/bouffe/huile) et le rediffuser au
    --    groupe → les autres addons voient mes buffs à jour, et moi les leurs
    if NS.db and NS.db.myProfile and Buffs.SelfFlags then
        local ok, flags = pcall(Buffs.SelfFlags)
        if ok then NS.db.myProfile.flags = flags end
    end
    -- la diffusion de MES flags est TOUJOURS faite (mes amis me voient
    -- même si je choisis de ne pas afficher le rapport chez moi)
    if NS.Sync and NS.Sync.available and NS.Sync.AnnounceGroup then
        pcall(NS.Sync.AnnounceGroup)
    end
    -- 2) rapport chat local : seulement si la feature est activée
    if NS.db and NS.db.appelReport == false then return end
    if rcPending then rcPending:Cancel() end
    rcPending = C_Timer.NewTimer(1.2, function()
        rcPending = nil
        local ok, err = pcall(Buffs.PrintReadyCheckReport)
        if not ok and NS.Msg then NS.Msg(NS.L("check appel indisponible (") .. tostring(err) .. ")") end
    end)
end)

-- ---------- Diffusion IMMÉDIATE au changement de buff/huile (0.34.9) ----------
-- Quand MES buffs changent (flacon, bouffe, huile/Rite, buff de classe…), on
-- rediffuse tout de suite mes flags au groupe/guilde au lieu d'attendre le
-- heartbeat → les amis voient l'info en ~1 s. UNIT_AURA peut spammer en combat :
-- on REGROUPE (debounce 0,7 s) et on ne diffuse QUE si les flags ont vraiment
-- changé (présence flacon/bouffe/huile/buffs — stable en combat → ~0 envoi).
local auraWatch = CreateFrame("Frame")
if auraWatch.RegisterUnitEvent then
    pcall(auraWatch.RegisterUnitEvent, auraWatch, "UNIT_AURA", "player")
else
    pcall(auraWatch.RegisterEvent, auraWatch, "UNIT_AURA")
end
local flagsPending
local function BroadcastFlagsIfChanged()
    flagsPending = nil
    if not (NS.db and NS.db.myProfile and Buffs.SelfFlags) then return end
    local ok, flags = pcall(Buffs.SelfFlags)
    if not ok or type(flags) ~= "string" then return end
    if flags == (NS.db.myProfile.flags or "") then return end   -- rien de neuf → aucun envoi
    NS.db.myProfile.flags = flags
    if NS.Sync and NS.Sync.available and NS.Sync.BroadcastHello then
        pcall(NS.Sync.BroadcastHello, false)   -- groupe + guilde, tout de suite
    end
    if NS.RefreshBuffsUI then pcall(NS.RefreshBuffsUI) end   -- maj locale immédiate
end
auraWatch:SetScript("OnEvent", function(_, _, unit)
    if unit and unit ~= "player" then return end
    if flagsPending then return end        -- debounce : une seule vérif par salve
    flagsPending = C_Timer.NewTimer(0.7, BroadcastFlagsIfChanged)
end)
