-- ============================================================
-- Asylum Compagnon — Vault.lua
-- Clés Mythique+ de la semaine + Coffre convoité (Great Vault).
-- Chaque client lit sa clé (C_MythicPlus) et ses 9 cases de coffre
-- (C_WeeklyRewards.GetActivities) et les diffuse (« V ») ; les autres
-- les mettent en cache dans peers[key].keystone / peers[key].vault.
-- La page « Clés & Coffre » liste tout le monde ; le cadre des 9 cases
-- s'affiche au survol du nom.
-- ============================================================

local ADDON_NAME, NS = ...

local Vault = {}
NS.Vault = Vault

-- Libellés des pistes du coffre (Raid / Donjons / Monde / PvP)
local TYPE_LABEL = { [1] = "Donjons", [2] = "PvP", [3] = "Raid", [6] = "Monde", [7] = "Gouffres" }
do
    local E = Enum and Enum.WeeklyRewardChestThresholdType
    if E then
        if E.Raid then TYPE_LABEL[E.Raid] = "Raid" end
        if E.Activities then TYPE_LABEL[E.Activities] = "Donjons" end
        if E.MythicPlus then TYPE_LABEL[E.MythicPlus] = "Donjons" end
        if E.World then TYPE_LABEL[E.World] = "Monde" end
        if E.RankedPvP then TYPE_LABEL[E.RankedPvP] = "PvP" end
    end
end
NS.VAULT_TYPE_LABEL = TYPE_LABEL

-- ---------- Lecture locale ----------
local function ReadKeystone()
    local mapID, level = 0, 0
    if C_MythicPlus then
        if C_MythicPlus.GetOwnedKeystoneMapID then
            local ok, v = pcall(C_MythicPlus.GetOwnedKeystoneMapID)
            if ok and v then mapID = v end
        end
        if C_MythicPlus.GetOwnedKeystoneLevel then
            local ok, v = pcall(C_MythicPlus.GetOwnedKeystoneLevel)
            if ok and v then level = v end
        end
    end
    return mapID, level
end

local function ReadVault()
    local out = {}
    if C_WeeklyRewards and C_WeeklyRewards.GetActivities then
        local ok, acts = pcall(C_WeeklyRewards.GetActivities)
        if ok and type(acts) == "table" then
            for _, a in ipairs(acts) do
                out[#out + 1] = {
                    type = a.type or 0, index = a.index or 0,
                    progress = a.progress or 0, threshold = a.threshold or 0,
                    level = a.level or 0,
                }
            end
        end
    end
    return out
end

function Vault.KeystoneName(mapID)
    if not mapID or mapID == 0 then return nil end
    if C_ChallengeMode and C_ChallengeMode.GetMapUIInfo then
        local ok, name = pcall(C_ChallengeMode.GetMapUIInfo, mapID)
        if ok and name then return name end
    end
    return nil
end

-- ---------- Sérialisation / diffusion ----------
function Vault.SerializeMine()
    local mapID, level = ReadKeystone()
    local acts = ReadVault()
    local parts = {}
    for _, a in ipairs(acts) do
        parts[#parts + 1] = ("%d,%d,%d,%d,%d"):format(a.type, a.index, a.progress, a.threshold, a.level)
    end
    return ("%d:%d\t%s"):format(mapID, level, table.concat(parts, ";"))
end

local entered = false
function Vault.BroadcastMine()
    if not (NS.Sync and NS.Sync.available and NS.Sync.BroadcastVault) then return end
    NS.Sync.BroadcastVault(Vault.SerializeMine())
end

-- Réception d'un « V »
function Vault.OnPeerVault(sender, msg)
    local body = msg:match("^V\t(.*)$")
    if not body or not NS.db then return end
    local keyPart, vaultPart = body:match("^([^\t]*)\t(.*)$")
    if not keyPart then keyPart = body; vaultPart = "" 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 mapID, level = keyPart:match("^(%-?%d+):(%-?%d+)$")
    p.keystone = { mapID = tonumber(mapID) or 0, level = tonumber(level) or 0 }
    local vault = {}
    for chunk in (vaultPart or ""):gmatch("[^;]+") do
        local t, i, pr, th, lv = chunk:match("^(%-?%d+),(%-?%d+),(%-?%d+),(%-?%d+),(%-?%d+)$")
        if t then
            vault[#vault + 1] = {
                type = tonumber(t), index = tonumber(i),
                progress = tonumber(pr), threshold = tonumber(th), level = tonumber(lv),
            }
        end
    end
    p.vault = vault
    p.lastSeen = (GetServerTime and GetServerTime()) or time()
    if NS.RefreshVaultUI then NS.RefreshVaultUI() end
end

-- Écho aux hellos (anti-rafale) — comme le module butin
local helloEcho
function Vault.OnPeerHello()
    if helloEcho then return end
    helloEcho = C_Timer.NewTimer(6, function()
        helloEcho = nil
        Vault.BroadcastMine()
    end)
end

-- ---------- Rapport pour l'UI ----------
local function KeyOf(unit)
    local name, realm = UnitFullName(unit)
    if not name then return nil end
    realm = (realm and realm ~= "" and realm)
        or (GetNormalizedRealmName and GetNormalizedRealmName()) or ""
    return (realm ~= "") and (name .. "-" .. realm) or name
end

-- ---------- Niveaux d'objet du reward (Midnight Saison 1) ----------
-- Source : addon GreatVaultKeyInfo / wago.tools (rewardSeasonID 117).
-- On déduit l'ilvl du reward à partir de (type, niveau) — déjà partagés,
-- donc rien à changer dans la synchro. À MAJ si la saison change.
local RWD_DUNGEON = { [2]=259,[3]=259,[4]=263,[5]=263,[6]=266,[7]=269,[8]=269,[9]=269,[10]=272 }
local RWD_DUNGEON_M0 = 256
local RWD_WORLD = { [1]=233,[2]=237,[3]=240,[4]=243,[5]=246,[6]=253,[7]=256,[8]=259 }
local RWD_RAID  = { [17]=233,[14]=246,[15]=259,[16]=272 } -- LFR / Normal / Héroïque / Mythique
local RAID_ORDER = { 17, 14, 15, 16 }

local T_DUNGEON, T_WORLD, T_RAID = 1, 6, 3
do
    local E = Enum and Enum.WeeklyRewardChestThresholdType
    if E then
        T_DUNGEON = E.Activities or E.MythicPlus or T_DUNGEON
        T_WORLD   = E.World or T_WORLD
        T_RAID    = E.Raid or T_RAID
    end
end

-- ilvl du reward pour (type, niveau), ou nil si inconnu
function Vault.RewardIlvl(typ, level)
    level = level or 0
    if typ == T_RAID then return RWD_RAID[level] end
    if typ == T_WORLD then return RWD_WORLD[math.min(math.max(level, 1), 8)] end
    if typ == T_DUNGEON then
        if level >= 2 then return RWD_DUNGEON[math.min(level, 10)] end
        if level == 1 or level == 0 then return RWD_DUNGEON_M0 end
    end
    return nil
end

-- Prochain palier utile (niveau, ilvl) au-dessus du reward actuel, ou nil si max
function Vault.NextTier(typ, level)
    local cur = Vault.RewardIlvl(typ, level)
    if not cur then return nil end
    if typ == T_DUNGEON then
        for lv = math.max(level or 0, 1) + 1, 10 do
            local il = RWD_DUNGEON[lv]
            if il and il > cur then return lv, il end
        end
    elseif typ == T_WORLD then
        for lv = (level or 0) + 1, 8 do
            local il = RWD_WORLD[lv]
            if il and il > cur then return lv, il end
        end
    elseif typ == T_RAID then
        local seen = false
        for _, d in ipairs(RAID_ORDER) do
            if seen and RWD_RAID[d] and RWD_RAID[d] > cur then return d, RWD_RAID[d] end
            if d == level then seen = true end
        end
    end
    return nil
end

-- Regroupe une liste d'activités par piste (Raid/Donjons/…), 3 cases chacune,
-- avec l'ilvl du reward par case + le palier suivant atteignable par piste.
local function BuildTracks(acts)
    local order, byType = {}, {}
    for _, a in ipairs(acts or {}) do
        if not byType[a.type] then
            byType[a.type] = { label = NS.L(TYPE_LABEL[a.type] or "Piste"), type = a.type, cells = {} }
            order[#order + 1] = a.type
        end
        local filled = (a.progress or 0) >= (a.threshold or 0) and (a.threshold or 0) > 0
        local cell = {
            filled = filled,
            progress = a.progress or 0, threshold = a.threshold or 0, level = a.level or 0,
            ilvl = filled and Vault.RewardIlvl(a.type, a.level) or nil,
        }
        byType[a.type].cells[a.index or (#byType[a.type].cells + 1)] = cell
    end
    local tracks = {}
    for _, t in ipairs(order) do
        local tr = byType[t]
        local best
        for _, c in pairs(tr.cells) do
            if c.filled and c.ilvl and (not best or c.ilvl > best.ilvl) then best = c end
        end
        if best then
            tr.bestIlvl = best.ilvl
            tr.nextLevel, tr.nextIlvl = Vault.NextTier(tr.type, best.level)
        end
        tracks[#tracks + 1] = tr
    end
    return tracks
end

-- Liste { name, class, isMe, keyText, tracks } : moi + pairs ayant partagé
function Vault.GetReport()
    local list = {}
    local mapID, level = ReadKeystone()
    local _, myClass = UnitClass("player")
    local kn = Vault.KeystoneName(mapID)
    list[#list + 1] = {
        name = UnitName("player") or "?", class = myClass, isMe = true,
        keyText = (kn and level > 0) and (kn .. " +" .. level) or NS.L("aucune clé"),
        tracks = BuildTracks(ReadVault()),
    }
    for k, p in pairs((NS.db and NS.db.peers) or {}) do
        if p.keystone or p.vault then
            local mid = p.keystone and p.keystone.mapID or 0
            local lvl = p.keystone and p.keystone.level or 0
            local nm = Vault.KeystoneName(mid)
            list[#list + 1] = {
                name = k:match("^(.-)%-") or k, class = p.class,
                keyText = (nm and lvl > 0) and (nm .. " +" .. lvl) or NS.L("aucune clé"),
                tracks = BuildTracks(p.vault),
            }
        end
    end
    table.sort(list, function(a, b)
        if a.isMe ~= b.isMe then return a.isMe end
        return a.name < b.name
    end)
    return list
end

-- ---------- Événements ----------
local sendTimer
local function QueueSend()
    if sendTimer then return end
    sendTimer = C_Timer.NewTimer(5, function()
        sendTimer = nil
        Vault.BroadcastMine()
    end)
end

local ev = CreateFrame("Frame")
-- RegisterEvent sous pcall : un nom d'événement inconnu (variantes Midnight)
-- ne doit pas casser le chargement du module.
pcall(ev.RegisterEvent, ev, "PLAYER_ENTERING_WORLD")
pcall(ev.RegisterEvent, ev, "WEEKLY_REWARDS_UPDATE")
pcall(ev.RegisterEvent, ev, "CHALLENGE_MODE_COMPLETED")
ev:SetScript("OnEvent", function(_, event)
    if event == "PLAYER_ENTERING_WORLD" then
        if not entered then
            entered = true
            C_Timer.After(8, function() Vault.BroadcastMine() end)
        end
    elseif entered then
        QueueSend()
    end
end)
