-- ============================================================
-- Asylum Compagnon — Sync.lua
-- Synchro entre joueurs via messages d'addon (canal invisible).
-- Même principe que KickSync / Northern Sky Raid Tools (AceComm).
-- Protocole :
--   H  = « hello » : présence + méta (classe, niveau, ilvl, or, items)
--   G  = « get »   : envoie-moi ton code SimC
--   C  = « chunk » : morceau i/n du code SimC
-- ============================================================

local ADDON_NAME, NS = ...

local PREFIX     = "AsylumSync"
local CHUNK_SIZE = 200
local MAX_CHUNKS = 120 -- garde-fou (≈ 24 Ko, le profil inclut les sacs)

local Sync = {}
NS.Sync = Sync

-- Enregistrement du préfixe (indispensable pour recevoir les messages)
Sync.available = false
if C_ChatInfo and C_ChatInfo.RegisterAddonMessagePrefix then
    local res = C_ChatInfo.RegisterAddonMessagePrefix(PREFIX)
    Sync.available = (res == true) or (res == 0)
        or (Enum and Enum.RegisterAddonMessagePrefixResult
            and res == Enum.RegisterAddonMessagePrefixResult.Success)
end

-- ---------- Identité ----------
local myFull
local function MyFullName()
    if myFull then return myFull end
    local name, realm = UnitFullName("player")
    if not name then return nil end
    realm = (realm and realm ~= "" and realm)
        or (GetNormalizedRealmName and GetNormalizedRealmName()) or ""
    myFull = (realm ~= "") and (name .. "-" .. realm) or name
    return myFull
end

local function IsMe(sender)
    local me = MyFullName()
    if not me then return false end
    if sender == me then return true end
    return sender == (me:match("^(.-)%-") or me)
end

-- ---------- File d'envoi (anti-déconnexion / throttle Midnight) ----------
local queue = {}
local ticker
local function Pump()
    local item = table.remove(queue, 1)
    if not item then
        if ticker then ticker:Cancel(); ticker = nil end
        return
    end
    C_ChatInfo.SendAddonMessage(PREFIX, item.msg, item.channel, item.target)
end
local function Enqueue(msg, channel, target)
    if not Sync.available then return end
    queue[#queue + 1] = { msg = msg, channel = channel, target = target }
    if not ticker then
        ticker = C_Timer.NewTicker(0.3, Pump)
    end
end

-- ---------- Émission ----------
-- H \t reply \t version \t classe \t niveau \t specID \t ilvl \t ts \t or \t id:count/id:count…
local function BuildHello(wantReply)
    local p = NS.db and NS.db.myProfile
    local _, classToken = UnitClass("player")

    -- seuls les objets déjà possédés au moins une fois sont annoncés
    local items = {}
    for _, t in ipairs(NS.TRACKED_ITEMS or {}) do
        local pid = t.ids and t.ids[1]
        local v = p and p.items and pid and p.items[pid]
        if v ~= nil then
            items[#items + 1] = ("%d:%d"):format(pid, v)
        end
    end

    return ("H\t%d\t%s\t%s\t%d\t%d\t%d\t%d\t%d\t%s\t%s"):format(
        wantReply and 1 or 0,
        NS.VERSION or "?",
        (p and p.class) or classToken or "?",
        (p and p.level) or UnitLevel("player") or 0,
        (p and p.specID) or 0,
        (p and p.ilvl) or 0,
        (p and p.ts) or 0,
        (p and p.gold) or 0,
        table.concat(items, "/"),
        (p and p.flags) or "")
end

-- Annonce sa présence au groupe/raid et à la guilde
function Sync.BroadcastHello(wantReply)
    if not Sync.available or not NS.db then return end
    local msg = BuildHello(wantReply)
    if IsInGuild() then Enqueue(msg, "GUILD") end
    if IsInRaid() then
        Enqueue(msg, "RAID")
    elseif IsInGroup() then
        Enqueue(msg, "PARTY")
    end
end

-- Annonce (hello) au groupe/raid UNIQUEMENT — pas de guilde.
-- Utilisé par le check à l'appel : quand un ready check démarre, chaque
-- client rediffuse ses flags à jour (flacon/bouffe/huile) à ses coéquipiers.
function Sync.AnnounceGroup()
    if not Sync.available or not NS.db then return end
    local msg = BuildHello(false)
    if IsInRaid() then
        Enqueue(msg, "RAID")
    elseif IsInGroup() then
        Enqueue(msg, "PARTY")
    end
end

-- Demande le code SimC d'un joueur (clé = "Nom-Royaume")
function Sync.RequestProfile(peerKey)
    if not Sync.available then return end
    Enqueue("G", "WHISPER", peerKey)
end

-- Kicks : messages courts et urgents → envoi direct (sans file),
-- au groupe/raid uniquement. Depuis Midnight les casts des autres
-- joueurs sont masqués côté client : chaque addon annonce donc ses
-- propres kicks, comme le font KickSync et consorts.
local function SendGroupNow(msg)
    if not Sync.available then return end
    local channel
    if IsInRaid() then
        channel = "RAID"
    elseif IsInGroup() then
        channel = "PARTY"
    end
    if channel then
        pcall(C_ChatInfo.SendAddonMessage, PREFIX, msg, channel)
    end
end

function Sync.BroadcastKick(cd)
    SendGroupNow("K\t" .. tostring(math.floor(cd or 0)))
end

function Sync.BroadcastKickResult(res)
    SendGroupNow("KR\t" .. tostring(res))
end

-- Envoie son code SimC en morceaux
local function StreamProfileTo(target)
    local prof = NS.db and NS.db.myProfile
    if (not prof or not prof.simc) and NS.UpdateMyProfile then
        NS.UpdateMyProfile(false)
        prof = NS.db and NS.db.myProfile
    end
    if not prof or not prof.simc then return end

    local code, ts = prof.simc, prof.ts or 0
    local total = math.ceil(#code / CHUNK_SIZE)
    if total > MAX_CHUNKS then total = MAX_CHUNKS end
    for i = 1, total do
        local part = code:sub((i - 1) * CHUNK_SIZE + 1, i * CHUNK_SIZE)
        Enqueue(("C\t%d\t%d\t%d\t%s"):format(ts, i, total, part), "WHISPER", target)
    end
end

-- ---------- Réception ----------
local function EnsurePeer(sender)
    NS.db.peers = NS.db.peers or {}
    local p = NS.db.peers[sender]
    if not p then p = {}; NS.db.peers[sender] = p end
    return p
end

local function OnHello(sender, msg)
    -- format v0.7 (avec flags buffs), puis v0.3 (or + items), puis v0.2
    local flags
    local reply, ver, class, level, specID, ilvl, ts, gold, itemsStr, flagsStr =
        msg:match("^H\t(%d)\t([^\t]*)\t([^\t]*)\t(%d+)\t(%d+)\t(%d+)\t(%d+)\t(%d+)\t([^\t]*)\t([^\t]*)$")
    if reply then
        flags = flagsStr
    else
        reply, ver, class, level, specID, ilvl, ts, gold, itemsStr =
            msg:match("^H\t(%d)\t([^\t]*)\t([^\t]*)\t(%d+)\t(%d+)\t(%d+)\t(%d+)\t(%d+)\t([^\t]*)$")
    end
    if not reply then
        reply, ver, class, level, specID, ilvl, ts =
            msg:match("^H\t(%d)\t([^\t]*)\t([^\t]*)\t(%d+)\t(%d+)\t(%d+)\t(%d+)$")
    end
    if not reply then return end

    local p = EnsurePeer(sender)
    p.ver, p.class = ver, class
    p.level  = tonumber(level)
    p.specID = tonumber(specID)
    p.ilvl   = tonumber(ilvl)
    p.profileTs = tonumber(ts) -- version du profil annoncée par le pair
    p.online = true
    p.lastSeen = (GetServerTime and GetServerTime()) or time()

    if gold then
        p.gold = tonumber(gold)
        p.items = {}
        if itemsStr then
            for id, count in itemsStr:gmatch("(%d+):(%d+)") do
                p.items[tonumber(id)] = tonumber(count)
            end
        end
    end
    if flags and flags ~= "" then p.flags = flags end

    if reply == "1" then
        Enqueue(BuildHello(false), "WHISPER", sender)
    end
    if NS.RefreshOptimUI then NS.RefreshOptimUI("roster", sender) end
    if NS.Kicks and NS.Kicks.RefreshRoster then NS.Kicks.RefreshRoster() end
end

local incoming = {}
local function OnChunk(sender, msg)
    local ts, i, n, data = msg:match("^C\t(%d+)\t(%d+)\t(%d+)\t(.*)$")
    if not ts then return end
    i, n = tonumber(i), tonumber(n)
    if not i or not n or n < 1 or n > MAX_CHUNKS or i < 1 or i > n then return end

    local key = sender .. "#" .. ts
    local buf = incoming[key]
    if not buf then
        buf = { parts = {}, count = 0, n = n }
        incoming[key] = buf
    end
    if not buf.parts[i] then
        buf.parts[i] = data
        buf.count = buf.count + 1
    end

    if buf.count >= buf.n then
        incoming[key] = nil
        local p = EnsurePeer(sender)
        p.simc = table.concat(buf.parts, "", 1, buf.n)
        p.ts = tonumber(ts)
        p.online = true
        if NS.RefreshOptimUI then NS.RefreshOptimUI("peerdata", sender) end
    end
end

local f = CreateFrame("Frame")
f:RegisterEvent("CHAT_MSG_ADDON")
f:SetScript("OnEvent", function(_, _, prefix, msg, _, sender)
    if prefix ~= PREFIX or type(msg) ~= "string" or not sender then return end
    if not NS.db or IsMe(sender) then return end

    if msg:sub(1, 2) == "KR" then
        local res = msg:match("^KR\t(%a+)$")
        if res and NS.Kicks and NS.Kicks.OnPeerKickResult then
            NS.Kicks.OnPeerKickResult(sender, res)
        end
        return
    end

    local t = msg:sub(1, 1)
    if t == "H" then
        OnHello(sender, msg)
    elseif t == "G" then
        StreamProfileTo(sender)
    elseif t == "C" then
        OnChunk(sender, msg)
    elseif t == "K" then
        local cd = msg:match("^K\t(%d+)$")
        if cd and NS.Kicks and NS.Kicks.OnPeerKick then
            NS.Kicks.OnPeerKick(sender, tonumber(cd))
        end
    end
end)
