-- ══════════════════════════════════════════════════════════════
-- MODUL: Sync - Gilden-Synchronisation
-- ══════════════════════════════════════════════════════════════
-- Dieses Modul synct NUR zwischen GILDENMITGLIEDERN!
-- Nutzt GUILD Addon Messages (NICHT server-weit)
-- 
-- Alle Kommunikation ist GILDEN-INTERN:
-- - Todesfaelle
-- - PING/PONG fuer Online-Status
-- - Sync-Requests fuer historische Daten
-- ══════════════════════════════════════════════════════════════

local addonName, addon = ...
local GDL = _G["GuildDeathLog"]
local Sync = {}

local ADDON_PREFIX = "GDLSync"
local COMM_DELIM = "|"
local COMM = {PING="PING", PONG="PONG", DEATH="DEATH", SYNC_REQ="SYNCREQ", SYNC_DATA="SYNCDAT", DELETE="DELETE"}

local syncedUsers = {}
local lastSyncRequest = 0
local recentDeaths = {}
-- WICHTIG: deletedDeaths ist jetzt NUR ein Cache!
-- Die persistenten Daten sind in GuildDeathLogDB.guilds[guildName].deletedDeaths

-- WICHTIG: Prefix SOFORT registrieren beim Laden!
C_ChatInfo.RegisterAddonMessagePrefix(ADDON_PREFIX)

-- ══════════════════════════════════════════════════════════════
-- BLACKLIST HELPER - Persistent in GuildDeathLogDB!
-- ══════════════════════════════════════════════════════════════

function Sync:GetBlacklist()
    local guildData = GDL:GetGuildData()
    if not guildData then return {} end
    guildData.deletedDeaths = guildData.deletedDeaths or {}
    return guildData.deletedDeaths
end

function Sync:IsBlacklisted(name, timestamp)
    local blacklist = self:GetBlacklist()
    
    -- Exakter Key-Match (Name + Timestamp)
    local exactKey = (name or "") .. ":" .. (timestamp or 0)
    if blacklist[exactKey] then return true end
    
    -- Tages-basierter Match (gleicher Name, gleicher Tag)
    -- Verhindert dass verschiedene Tode desselben Namens geblockt werden
    if timestamp and timestamp > 0 then
        local dayKey = (name or "") .. ":" .. math.floor(timestamp / 86400)
        for key, _ in pairs(blacklist) do
            local keyName, keyTs = key:match("^([^:]+):(%d+)")
            if keyName and keyName:lower() == (name or ""):lower() and keyTs then
                local keyDay = math.floor(tonumber(keyTs) / 86400)
                if keyDay == math.floor(timestamp / 86400) then
                    return true
                end
            end
        end
    end
    
    return false
end

function Sync:AddToBlacklist(name, timestamp)
    local guildData = GDL:GetGuildData()
    if not guildData then return end
    
    guildData.deletedDeaths = guildData.deletedDeaths or {}
    local key = (name or "") .. ":" .. (timestamp or 0)
    guildData.deletedDeaths[key] = time()
    
    GDL:Debug("Blacklist: " .. name .. " hinzugefuegt (persistent)")
end

function Sync:Initialize()
    -- Verhindere doppelte Initialisierung
    if self.initialized then return end
    self.initialized = true
    
    -- Prefix wurde bereits oben registriert
    GDL:Debug("Sync Initialize gestartet")
    
    self.eventFrame = CreateFrame("Frame")
    self.eventFrame:RegisterEvent("CHAT_MSG_ADDON")
    self.eventFrame:RegisterEvent("CHAT_MSG_CHANNEL")
    self.eventFrame:RegisterEvent("CHAT_MSG_SYSTEM")
    self.eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
    self.eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
    self.eventFrame:RegisterEvent("GUILD_ROSTER_UPDATE")
    self.eventFrame:SetScript("OnEvent", function(_, event, ...) self:HandleEvent(event, ...) end)
    
-- HardcoreDeaths Channel automatisch joinen (nach kurzer Verzoegerung)
    -- Nur Classic Era: Blizzard HC-Channel existiert nicht in TBC/Wrath
    if GDL.hasHCChannel ~= false then
        C_Timer.After(5, function() self:JoinHardcoreChannel() end)
    else
        -- TBC/Wrath: nur DNL-Channel joinen (kein Blizzard HC-Channel)
        C_Timer.After(5, function() self:JoinDNLChannel() end)
    end
    
    syncedUsers = GuildDeathLogDB.syncUsers or {}
    GDL:Debug("Sync: " .. self:CountTable(syncedUsers) .. " gespeicherte User geladen")
    
    -- Blacklist-Info beim Start
    local blacklist = self:GetBlacklist()
    local blCount = 0
    for _ in pairs(blacklist) do blCount = blCount + 1 end
    if blCount > 0 then
        GDL:Debug("Blacklist: " .. blCount .. " geloeschte Eintraege geladen (persistent)")
    end
    
    -- ════════════════════════════════════════════════════════
    -- SYNC-TIMER: v6.0.3 - Balance zwischen Speed und Spam
    -- HandleSyncRequest hat per-sender 30s Throttle!
    -- ════════════════════════════════════════════════════════
    
    -- Ping alle 3 Minuten
    C_Timer.NewTicker(180, function() 
        if IsInGuild() then 
            self:SendPing() 
        end
    end)
    
    -- Automatischer Sync alle 5 Minuten
    C_Timer.NewTicker(300, function() 
        if IsInGuild() then
            self:RequestFullSync()
        end
    end)
    
    -- Proaktiver Push alle 10 Minuten
    C_Timer.NewTicker(600, function()
        if IsInGuild() then
            self:BroadcastRecentDeaths(20)
        end
    end)
    
    -- Beim Start: Sofort-Sync
    C_Timer.After(3, function() 
        if IsInGuild() then 
            self:SendPing()
            self:RequestFullSync() 
        end
    end)
    
    -- Nach 15 Sekunden: Broadcast fuer Leute die spaeter online kommen
    C_Timer.After(15, function()
        if IsInGuild() then
            self:BroadcastRecentDeaths(20)
        end
    end)
    
    GDL:Debug("Sync: Gilden-Sync aktiv!")
end

function Sync:CountTable(t)
    local count = 0
    for _ in pairs(t or {}) do count = count + 1 end
    return count
end

function Sync:HandleEvent(event, ...)
    if event == "PLAYER_ENTERING_WORLD" then
        GDL:Debug("PLAYER_ENTERING_WORLD - Sync wird gestartet")
        C_Timer.After(3, function()
            if GDL.hasHCChannel then
                self:JoinHardcoreChannel()
            else
                self:JoinDNLChannel()
            end
        end)
    elseif event == "GROUP_ROSTER_UPDATE" or event == "GUILD_ROSTER_UPDATE" then
        -- Bei Gilden-Aenderung: Member-Liste sofort aktualisieren!
        local Guild = GDL:GetModule("Guild")
        if Guild then
            Guild:QueueScan()
        end
        -- Quick Ping nach 3 Sekunden (neue Mitglieder erkennen)
        C_Timer.After(3, function()
            if IsInGuild() then 
                self:SendPing()
            end
        end)
    elseif event == "CHAT_MSG_ADDON" then
        local prefix, message, channel, sender = ...
        if prefix == ADDON_PREFIX and channel == "GUILD" then
            self:HandleAddonMessage(message, sender)
        end
    elseif event == "CHAT_MSG_CHANNEL" then
        local message, sender, _, _, _, _, _, _, channelName = ...
        if channelName then
            local cn = channelName:lower()
            if cn:find("hardcoredeaths") then
                -- Blizzard HC-Channel: "[Name] has died at level X"
                GDL:Debug("HC-Channel: " .. (message or "?"):sub(1,50))
                self:HandleBlizzardDeath(message, sender)
            elseif cn:find("hcdeathalerts") then
                -- DeathNotificationLib Channel: "cmd~name~guild~...~checksum"
                GDL:Debug("DNL-Channel: " .. (message or "?"):sub(1,50))
                self:HandleDNLChannelDeath(message, sender)
            end
        end
    elseif event == "CHAT_MSG_SYSTEM" then
        local message = ...
        if message then
            self:HandleSystemDeath(message)
        end
    end
end

-- HardcoreDeaths Channel joinen (Classic Era only – Blizzard HC-Mode)
function Sync:JoinHardcoreChannel()
    if not GDL.hasHCChannel then
        GDL:Debug("Sync: JoinHardcoreChannel übersprungen (kein HC-Mode in " .. (GDL.expansionLabel or "?") .. ")")
        self:JoinDNLChannel()
        return
    end

    local channelList = {GetChannelList()}
    local hasHC, hasDNL = false, false
    for i = 1, #channelList, 3 do
        local name = channelList[i + 1]
        if name then
            local nl = name:lower()
            if nl:find("hardcoredeaths") then hasHC  = true end
            if nl:find("hcdeathalerts")  then hasDNL = true end
        end
    end

    if not hasHC then
        local id = JoinChannelByName("hardcoredeaths")
        if id then
            GDL:Debug("HC-Channel: hardcoredeaths beigetreten (ID: " .. id .. ")")
        else
            GDL:Debug("HC-Channel: Konnte hardcoredeaths nicht beitreten")
        end
    else
        GDL:Debug("HC-Channel: hardcoredeaths bereits aktiv")
    end

    self:JoinDNLChannel()
end

-- DNL-Channel joinen (alle Expansionen – DNL unterstuetzt Classic/TBC/Wrath)
function Sync:JoinDNLChannel()
    local channelList = {GetChannelList()}
    local hasDNL = false
    for i = 1, #channelList, 3 do
        local name = channelList[i + 1]
        if name then
            if name:lower():find("hcdeathalerts") then hasDNL = true end
        end
    end

    if not hasDNL then
        local id = JoinChannelByName("hcdeathalertschannel", "hcdeathalertschannelpw")
        if id then
            GDL:Debug("DNL-Channel: hcdeathalertschannel beigetreten (ID: " .. id .. ")")
        end
    else
        GDL:Debug("DNL-Channel: hcdeathalertschannel bereits aktiv")
    end
end

-- System-Tod-Nachrichten erkennen (Classic HC Server-Meldungen)
function Sync:HandleSystemDeath(message)
    if not message or not GDL.currentGuildName then return end
    
    -- Alle bekannten HC Tod-Muster (DE / EN / FR / ES)
    -- Classic HC 1.15.x: "Name has died." / "Name ist gestorben." / "Name hat die Stufe X nicht ueberlebt."
    local name = message:match("^(.+) has died") or 
                 message:match("^(.+) ist gestorben") or
                 message:match("^(.+) hat die Stufe") or
                 message:match("^(.+) hat Stufe") or
                 message:match("^(.+) hat Level") or
                 message:match("^(.+) hat das Level") or
                 message:match("^(.+) hat es nicht") or
                 message:match("^(.+) died at level") or
                 message:match("^(.+) did not survive") or
                 message:match("^(.+) est mort") or
                 message:match("^(.+) wurde besiegt")
    
    if not name then return end
    
    -- Realm-Suffix entfernen
    name = strsplit("-", name)
    name = name:match("^%s*(.-)%s*$") -- Trim
    
    GDL:Debug("System-Tod erkannt: '" .. name .. "' - Pruefe Gildenzugehoerigkeit...")
    
    local Guild = GDL:GetModule("Guild")
    if not Guild then return end
    
    if Guild:IsMember(name) then
        GDL:Debug("System-Tod: " .. name .. " ist Gildenmitglied!")
        self:ProcessIncomingDeath({name=name, level=0, timestamp=time(), fromSystem=true}, "System")
    else
        -- Retry: Roster koennte veraltet sein - neu laden und nach 2s nochmal pruefen
        GDL:Debug("System-Tod: " .. name .. " nicht im Roster - aktualisiere...")
        if GuildRoster then GuildRoster() end
        C_Timer.After(2, function()
            Guild:QueueScan()
            if Guild:IsMember(name) then
                GDL:Debug("System-Tod (Retry): " .. name .. " ist doch Gildenmitglied!")
                self:ProcessIncomingDeath({name=name, level=0, timestamp=time(), fromSystem=true}, "System")
            else
                GDL:Debug("System-Tod: " .. name .. " ist kein Gildenmitglied")
            end
        end)
    end
end

function Sync:SendPing()
    if not IsInGuild() then 
        GDL:Debug("SendPing: Nicht in Gilde")
        return 
    end
    local msg = COMM.PING .. COMM_DELIM .. GDL.version
    local success = C_ChatInfo.SendAddonMessage(ADDON_PREFIX, msg, "GUILD")
    GDL:Debug("-> PING")
end

function Sync:HandleAddonMessage(message, sender)
    sender = strsplit("-", sender)
    if sender == GDL.playerName then return end
    
    -- Unterstütze beide Protokoll-Formate (v1.2 und v4.0)
    local cmd, data
    
    -- Neues Format: COMMAND|data
    if message:find(COMM_DELIM) then
        cmd, data = strsplit(COMM_DELIM, message, 2)
    -- Altes v1.2 Format: COMMAND:data
    elseif message:find(":") then
        cmd, data = strsplit(":", message, 2)
        -- Konvertiere alte Kommandos zu neuen
        if cmd == "SYNC_REQ" then cmd = "SYNCREQ" end
        if cmd == "SYNC_DATA" then cmd = "SYNCDAT" end
    else
        cmd = message
    end
    
    -- DEBUG: Nur SYNCREQ loggen (PING/PONG/SYNCDAT werden separat behandelt)
    if cmd == COMM.SYNC_REQ or cmd == "SYNCREQ" then
        GDL:Debug("<- SYNCREQ " .. sender)
    end
    
    -- Ist dieser User neu? (noch nie gesehen oder lange offline)
    local isNewUser = not syncedUsers[sender] or 
                      not syncedUsers[sender].lastSeen or 
                      (time() - (syncedUsers[sender].lastSeen or 0)) > 3600  -- 1 Stunde offline
    
    -- JEDE Nachricht registriert den User (v1.2 Kompatibilitaet!)
    syncedUsers[sender] = syncedUsers[sender] or {}
    syncedUsers[sender].lastSeen = time()
    syncedUsers[sender].version = syncedUsers[sender].version or "1.x"
    GuildDeathLogDB.syncUsers = syncedUsers
    
    if cmd == COMM.PING or cmd == "PING" then
        syncedUsers[sender].version = data or "?"
        -- Sende PONG zurück
        C_ChatInfo.SendAddonMessage(ADDON_PREFIX, COMM.PONG .. COMM_DELIM .. GDL.version, "GUILD")
        -- Debug: Kurz und knapp
        GDL:Debug("<- PING " .. sender .. " v" .. (data or "?"))
        
        -- ══════════════════════════════════════════════════════
        -- WICHTIG: Bei PING von neuem User - sende unsere Daten!
        -- ══════════════════════════════════════════════════════
        if isNewUser then
            GDL:Debug("Neuer User: " .. sender .. " - sende Daten")
            C_Timer.After(2, function()
                self:SendRecentDeaths(sender, 20)
            end)
        end
        
    elseif cmd == COMM.PONG or cmd == "PONG" then
        syncedUsers[sender].version = data or "?"
        GDL:Debug("<- PONG " .. sender .. " v" .. (data or "?"))
        
    elseif cmd == COMM.DEATH or cmd == "DEATH" then
        self:HandleDeath(sender, data)
        
    elseif cmd == COMM.SYNC_REQ or cmd == "SYNCREQ" then
        self:HandleSyncRequest(sender, data)
        
    elseif cmd == COMM.SYNC_DATA or cmd == "SYNCDAT" then
        self:HandleSyncData(sender, data)
        
    elseif cmd == COMM.DELETE or cmd == "DELETE" then
        self:HandleDeleteSync(sender, data)
    end
end

function Sync:HandleBlizzardDeath(message, sender)
    GDL:Debug("HC-Channel Nachricht: " .. (message or "nil"):sub(1, 80))
    
    if not GuildDeathLogDB.settings.useBlizzardChannel then 
        GDL:Debug("HC-Channel: useBlizzardChannel ist DEAKTIVIERT!")
        return 
    end
    
    local name = message:match("%[(.-)%]")
    local level = message:match("level (%d+)")
    
    if not name then 
        GDL:Debug("HC-Channel: Kein Name gefunden in Nachricht")
        return 
    end
    
    GDL:Debug("HC-Channel: Name=" .. name .. ", Level=" .. (level or "?"))
    
    local Guild = GDL:GetModule("Guild")
    if not Guild then
        GDL:Debug("HC-Channel: Guild-Modul nicht geladen!")
        return
    end
    
    if Guild:IsMember(name) then 
        GDL:Debug("HC-Channel: " .. name .. " ist Gildenmitglied - verarbeite Tod!")
        self:ProcessIncomingDeath({name=name, level=tonumber(level) or 0, timestamp=time(), fromBlizzard=true}, "Blizzard")
    else
        -- Race-Condition: Roster koennte veraltet sein (neues Mitglied)
        -- Roster neu laden und nach 2s nochmal pruefen
        GDL:Debug("HC-Channel: " .. name .. " nicht gefunden - Roster wird aktualisiert...")
        GuildRoster()
        C_Timer.After(2, function()
            Guild:QueueScan()
            if Guild:IsMember(name) then
                GDL:Debug("HC-Channel (Retry): " .. name .. " ist doch Gildenmitglied!")
                self:ProcessIncomingDeath({name=name, level=tonumber(level) or 0, timestamp=time(), fromBlizzard=true}, "Blizzard")
            else
                GDL:Debug("HC-Channel: " .. name .. " ist definitiv kein Gildenmitglied")
            end
        end)
    end
end

-- ══════════════════════════════════════════════════════════════
-- DNL-Channel (hcdeathalertschannel): Format "cmd~name~guild~
-- sourceId~raceId~classId~level~instanceId~mapId~posX~posY~date~
-- lastWords~checksum" – NUR Gildenmitglieder verarbeiten!
-- ══════════════════════════════════════════════════════════════
function Sync:HandleDNLChannelDeath(message, sender)
    if not GDL.currentGuildName then return end

    local parts = {strsplit("~", message)}
    if #parts < 7 then return end

    local cmd = parts[1]
    if cmd ~= "1" and cmd ~= "4" then return end   -- Nur PING und GUILD_NOTIFY

    local name      = parts[2]  or ""
    local guild     = parts[3]  or ""
    local sourceId  = tonumber(parts[4]) or 0
    local classId   = tonumber(parts[6]) or 0
    local level     = tonumber(parts[7]) or 0
    local mapId     = tonumber(parts[9]) or 0
    local posX      = tonumber(parts[10]) or 0
    local posY      = tonumber(parts[11]) or 0
    local timestamp = tonumber(parts[12]) or time()
    local lastWords = parts[13] or ""

    name  = (strsplit("-", name)):match("^%s*(.-)%s*$")
    guild = guild:match("^%s*(.-)%s*$")
    if name == "" then return end

    -- ── Gildenmitglied-Prüfung ───────────────────────────────
    local isGuild = (guild ~= "" and guild == GDL.currentGuildName)
    if not isGuild then
        local GM = GDL:GetModule("Guild")
        isGuild = GM and GM:IsMember(name)
    end
    if not isGuild then
        GDL:Debug("DNL-Channel: " .. name .. " nicht in Gilde – ignoriert")
        return
    end

    local mapInfo = mapId > 0 and C_Map.GetMapInfo(mapId)
    local death = {
        name      = name,
        level     = level,
        classId   = classId,
        zone      = mapInfo and mapInfo.name or GDL:L("UNKNOWN"),
        mapId     = mapId,
        posX      = posX > 1 and (posX / 10000) or posX,
        posY      = posY > 1 and (posY / 10000) or posY,
        timestamp = timestamp,
        lastWords = lastWords,
        killerId  = sourceId > 0 and sourceId or nil,
        fromDeathlog = true,
        source    = "DNL-Channel",
    }

    local isSilent = (time() - timestamp) > 60
    self:ProcessIncomingDeath(death, (strsplit("-", sender)) .. " [DNL-CH]", isSilent)
end

function Sync:SerializeDeath(death)
    -- LastWords und Killer escapen (| durch / ersetzen)
    local lastWords = (death.lastWords or ""):gsub(COMM_DELIM, "/"):sub(1, 100) -- Max 100 chars
    local killerName = (death.killerName or ""):gsub(COMM_DELIM, "/")
    
    return table.concat({
        death.name or "?", death.level or 0, death.classId or 0,
        (death.zone or ""):gsub(COMM_DELIM, "/"), death.timestamp or 0,
        death.mapId or 0, math.floor((death.posX or 0) * 10000), math.floor((death.posY or 0) * 10000),
        lastWords, killerName, death.killerId or 0
    }, COMM_DELIM)
end

-- v1.2 Format: name|level|class_STRING|zone|timestamp|lastWords
-- v4.0 Format: name|level|classId|zone|timestamp|mapId|posX|posY|lastWords|killerName|killerId
function Sync:DeserializeDeath(data)
    local parts = {strsplit(COMM_DELIM, data)}
    if #parts < 5 then return nil end
    
    local death = {
        name = parts[1],
        level = tonumber(parts[2]) or 0,
        zone = parts[4],
        timestamp = tonumber(parts[5]) or 0,
    }
    
    -- Erkennung: ist parts[3] eine Zahl (v4.0) oder ein String (v1.2)?
    local classValue = tonumber(parts[3])
    if classValue then
        -- v4.0 Format
        death.classId = classValue
        death.mapId = tonumber(parts[6]) or 0
        death.posX = (tonumber(parts[7]) or 0) / 10000
        death.posY = (tonumber(parts[8]) or 0) / 10000
        death.lastWords = parts[9] or ""
        death.killerName = parts[10] or ""
        death.killerId = tonumber(parts[11]) or 0
    else
        -- v1.2 Format - class ist ein String wie "Krieger"
        death.classId = self:ClassNameToId(parts[3])
        death.lastWords = parts[6] or ""
    end
    
    return death
end

-- Konvertiert Klassen-Namen zu ID (fuer v1.2 Kompatibilitaet)
function Sync:ClassNameToId(className)
    local classMap = {
        ["Krieger"] = 1, ["Warrior"] = 1,
        ["Paladin"] = 2,
        ["Jaeger"] = 3, ["Jäger"] = 3, ["Hunter"] = 3,
        ["Schurke"] = 4, ["Rogue"] = 4,
        ["Priester"] = 5, ["Priest"] = 5,
        ["Schamane"] = 7, ["Shaman"] = 7,
        ["Magier"] = 8, ["Mage"] = 8,
        ["Hexenmeister"] = 9, ["Warlock"] = 9,
        ["Druide"] = 11, ["Druid"] = 11,
    }
    return classMap[className] or 0
end

function Sync:GetDeathKey(name, timestamp)
    -- Alte Methode für Kompatibilität - 1 Minute Fenster
    return (name or ""):lower() .. "-" .. math.floor((timestamp or 0) / 60)
end

-- NEUE Methode: Prüft ob gleicher Name innerhalb von 5 Minuten
function Sync:IsDuplicateByName(name, timestamp, windowSeconds)
    windowSeconds = windowSeconds or 300 -- Standard: 5 Minuten
    local checkName = (name or ""):lower()
    local checkTime = timestamp or time()
    
    local guildData = GDL:GetGuildData()
    if guildData then
        for _, d in ipairs(guildData.deaths or {}) do
            local deathName = (d.name or ""):lower()
            local deathTime = d.timestamp or 0
            
            -- Gleicher Name UND innerhalb des Zeitfensters?
            if deathName == checkName then
                local timeDiff = math.abs(checkTime - deathTime)
                if timeDiff < windowSeconds then
                    GDL:Debug("Duplikat erkannt: " .. name .. " (Zeitdiff: " .. timeDiff .. "s)")
                    return true
                end
            end
        end
    end
    return false
end

function Sync:IsDuplicate(death)
    -- Erst die neue 5-Minuten-Prüfung
    if self:IsDuplicateByName(death.name, death.timestamp, 300) then
        return true
    end
    
    -- Dann die alte Key-basierte Prüfung
    local key = self:GetDeathKey(death.name, death.timestamp)
    if recentDeaths[key] then return true end
    
    local guildData = GDL:GetGuildData()
    if guildData then
        for _, d in ipairs(guildData.deaths or {}) do
            if self:GetDeathKey(d.name, d.timestamp) == key then return true end
        end
    end
    return false
end

function Sync:BroadcastDeath(death)
    if not IsInGuild() then 
        GDL:Debug("BroadcastDeath: Nicht in Gilde!")
        return 
    end
    
    -- v6.0.0: Nur noch v4.0 Format senden (Empfang beider Formate bleibt)
    local success = C_ChatInfo.SendAddonMessage(ADDON_PREFIX, COMM.DEATH .. COMM_DELIM .. self:SerializeDeath(death), "GUILD")
    GDL:Debug("-> DEATH " .. (death.name or "?") .. " (" .. tostring(success) .. ")")
end

function Sync:HandleDeath(sender, data)
    local death = self:DeserializeDeath(data)
    if death then 
        -- Nur anzeigen wenn der Tod weniger als 30 SEKUNDEN alt ist = wirklich LIVE!
        local isHistorical = death.timestamp and (time() - death.timestamp) > 30
        self:ProcessIncomingDeath(death, sender, isHistorical) 
    end
end

function Sync:ProcessIncomingDeath(death, source, isSilent)
    -- Blacklist-Check: Wurde dieser Eintrag gelöscht? (PERSISTENT!)
    if self:IsBlacklisted(death.name, death.timestamp) then
        GDL:Debug("Geloeschter Eintrag ignoriert (Blacklist): " .. (death.name or "?"))
        return
    end
    
    if self:IsDuplicate(death) then 
        GDL:Debug("Duplikat ignoriert: " .. (death.name or "?"))
        return 
    end
    
    local key = self:GetDeathKey(death.name, death.timestamp)
    recentDeaths[key] = time()
    C_Timer.After(300, function() recentDeaths[key] = nil end)
    
    local guildData = GDL:GetGuildData()
    if guildData then
        death.syncedFrom = source
        death.syncedAt = time()
        local Guild = GDL:GetModule("Guild")
        if Guild and Guild.GetMemberInfo then
            local memberInfo = Guild:GetMemberInfo(death.name)
            if memberInfo then
                if not death.classId and memberInfo.class then
                    local classMap = {WARRIOR=1,PALADIN=2,HUNTER=3,ROGUE=4,PRIEST=5,DEATHKNIGHT=6,SHAMAN=7,MAGE=8,WARLOCK=9,DRUID=11}
                    death.classId = classMap[memberInfo.class] or death.classId
                end
                if (not death.level or death.level == 0) and memberInfo.level then
                    death.level = memberInfo.level
                end
                if (not death.zone or death.zone == "") and memberInfo.zone then
                    death.zone = memberInfo.zone
                end
            end
        end
        table.insert(guildData.deaths, death)
        GDL:Debug("Tod gespeichert: " .. (death.name or "?") .. " von " .. (source or "?"))
    end
    
    local DeathBell = GDL:GetModule("DeathBell")
    if DeathBell and not isSilent then
        DeathBell:Play(death)
    end
    
    -- Memorial: Verstorbene aus Berufe-Liste entfernen
    local Memorial = GDL:GetModule("Memorial")
    if Memorial then
        Memorial:OnDeath(death)
        -- Gedenkhalle aktualisieren wenn offen
        if Memorial.frame and Memorial.frame:IsShown() then
            Memorial:UpdateMemorialList()
        end
    end
    
    -- GuildStats aktualisieren wenn offen
    local GuildStats = GDL:GetModule("GuildStats")
    if GuildStats and GuildStats.frame and GuildStats.frame:IsShown() then
        GuildStats:UpdateDeathsList()
    end
    
    -- Professions aktualisieren wenn offen (Verstorbene entfernen)
    local Professions = GDL:GetModule("Professions")
    if Professions and Professions.frame and Professions.frame:IsShown() then
        Professions:UpdateWindow()
    end
    
    -- Nur bei NEUEN Toden anzeigen, NICHT bei historischem Resync!
    if not isSilent then
        GDL:Print("|cffFF6666" .. (death.name or "?") .. "|r ist gefallen! (Lvl " .. (death.level or "?") .. ")")
        
        if GuildDeathLogDB.settings.sound then 
            PlaySound(8959, "Master") 
        end
        if GuildDeathLogDB.settings.overlay then
            local UI = GDL:GetModule("UI")
            if UI then 
                UI:ShowOverlay(death, true) 
            end
        end
    end
    
    -- UI Update immer (auch silent)
    local UI = GDL:GetModule("UI")
    if UI and UI.mainFrame and UI.mainFrame:IsShown() then UI:UpdateChronicle() end
end

function Sync:RequestFullSync()
    if not IsInGuild() then 
        GDL:Debug("Sync: Nicht in Gilde")
        return 
    end
    -- Cooldown 30 Sekunden
    if time() - lastSyncRequest < 30 then 
        GDL:Debug("Sync: Cooldown aktiv (" .. (30 - (time() - lastSyncRequest)) .. "s)")
        return 
    end
    lastSyncRequest = time()
    
    local guildData = GDL:GetGuildData()
    local lastTimestamp = 0
    for _, death in ipairs(guildData and guildData.deaths or {}) do
        if (death.timestamp or 0) > lastTimestamp then lastTimestamp = death.timestamp end
    end
    
    self:SendPing()
    
    -- v6.0.0: Nur noch v4.0 Format (empfangen beider Formate bleibt)
    C_ChatInfo.SendAddonMessage(ADDON_PREFIX, COMM.SYNC_REQ .. COMM_DELIM .. lastTimestamp, "GUILD")
    
    GDL:Debug("-> SYNCREQ (seit " .. lastTimestamp .. ")")
end

function Sync:HandleSyncRequest(sender, data)
    local theirTimestamp = tonumber(data) or 0
    syncedUsers[sender] = syncedUsers[sender] or {}
    syncedUsers[sender].lastSeen = time()
    
    -- Throttle: Max 1 Response pro Sender alle 30 Sekunden
    self._syncThrottle = self._syncThrottle or {}
    if self._syncThrottle[sender] and (time() - self._syncThrottle[sender]) < 30 then
        GDL:Debug("SYNCREQ von " .. sender .. " gedrosselt")
        return
    end
    self._syncThrottle[sender] = time()
    
    local guildData = GDL:GetGuildData()
    if not guildData then return end
    
    -- Sammle alle Tode die neuer sind als deren Timestamp
    local toSend = {}
    for _, death in ipairs(guildData.deaths or {}) do
        if (death.timestamp or 0) > theirTimestamp then
            table.insert(toSend, death)
        end
    end
    
    -- Sortiere nach Zeit (neueste zuerst)
    table.sort(toSend, function(a, b) return (a.timestamp or 0) > (b.timestamp or 0) end)
    
    -- Max 30 Tode senden (war 50), mit 0.3s Delay (war 0.2)
    local count = 0
    local maxToSend = math.min(#toSend, 30)
    
    GDL:Debug("-> " .. maxToSend .. " Tode an " .. sender)
    
    for i, death in ipairs(toSend) do
        if count >= 30 then break end
        
        C_Timer.After(count * 0.3, function()
            C_ChatInfo.SendAddonMessage(ADDON_PREFIX, COMM.SYNC_DATA .. COMM_DELIM .. self:SerializeDeath(death), "GUILD")
        end)
        count = count + 1
    end
end

-- ══════════════════════════════════════════════════════════════
-- NEUE FUNKTION: Sende die letzten X Tode an die Gilde
-- ══════════════════════════════════════════════════════════════
function Sync:SendRecentDeaths(targetUser, maxCount)
    maxCount = maxCount or 20
    
    local guildData = GDL:GetGuildData()
    if not guildData then return end
    
    local allDeaths = {}
    for _, death in ipairs(guildData.deaths or {}) do
        table.insert(allDeaths, death)
    end
    
    table.sort(allDeaths, function(a, b) return (a.timestamp or 0) > (b.timestamp or 0) end)
    
    local count = 0
    for i, death in ipairs(allDeaths) do
        if count >= maxCount then break end
        
        C_Timer.After(count * 0.4, function()
            C_ChatInfo.SendAddonMessage(ADDON_PREFIX, COMM.SYNC_DATA .. COMM_DELIM .. self:SerializeDeath(death), "GUILD")
        end)
        count = count + 1
    end
    
    if count > 0 then
        GDL:Debug("-> " .. count .. " Tode (Auto)")
    end
end

-- ══════════════════════════════════════════════════════════════
-- NEUE FUNKTION: Broadcast - Pusht Tode an ALLE in der Gilde
-- Wird regelmäßig aufgerufen um sicherzustellen dass alle synced sind
-- ══════════════════════════════════════════════════════════════
function Sync:BroadcastRecentDeaths(maxCount)
    if not IsInGuild() then return end
    maxCount = maxCount or 15
    
    local guildData = GDL:GetGuildData()
    if not guildData or not guildData.deaths then return end
    
    -- Nur Tode der letzten 7 Tage pushen
    local sevenDaysAgo = time() - (7 * 24 * 60 * 60)
    
    local recentDeathsList = {}
    for _, death in ipairs(guildData.deaths) do
        if (death.timestamp or 0) > sevenDaysAgo then
            table.insert(recentDeathsList, death)
        end
    end
    
    table.sort(recentDeathsList, function(a, b) return (a.timestamp or 0) > (b.timestamp or 0) end)
    
    local count = 0
    for i, death in ipairs(recentDeathsList) do
        if count >= maxCount then break end
        
        C_Timer.After(count * 0.4, function()
            C_ChatInfo.SendAddonMessage(ADDON_PREFIX, COMM.SYNC_DATA .. COMM_DELIM .. self:SerializeDeath(death), "GUILD")
        end)
        count = count + 1
    end
    
    if count > 0 then
        GDL:Debug("-> " .. count .. " Tode (Broadcast)")
    end
end

-- Zähler für empfangene Sync-Daten (für Debug-Zusammenfassung)
local syncDataReceived = 0
local syncDataLastSender = nil
local syncDataTimer = nil

function Sync:HandleSyncData(sender, data)
    local death = self:DeserializeDeath(data)
    if death then 
        -- Prüfe ob der Tod älter als 30 SEKUNDEN ist = historischer Sync = SILENT
        local isHistorical = death.timestamp and (time() - death.timestamp) > 30
        self:ProcessIncomingDeath(death, sender .. " (sync)", isHistorical) 
        
        -- Zähle empfangene Daten für Debug-Zusammenfassung
        syncDataReceived = syncDataReceived + 1
        syncDataLastSender = sender
        
        -- Timer für Zusammenfassung (nach 2 Sekunden ohne neue Daten)
        if syncDataTimer then
            syncDataTimer:Cancel()
        end
        syncDataTimer = C_Timer.NewTimer(2, function()
            if syncDataReceived > 0 then
                GDL:Debug(syncDataLastSender .. " -> " .. syncDataReceived .. " Tode empfangen")
                syncDataReceived = 0
                syncDataLastSender = nil
            end
        end)
    end
end

-- ══════════════════════════════════════════════════════════════
-- DELETE SYNC - Löschungen an Gildenmitglieder senden
-- ══════════════════════════════════════════════════════════════

function Sync:BroadcastDelete(deathName, deathTimestamp)
    if not IsInGuild() then return end
    
    -- Zur PERSISTENTEN Blacklist hinzufügen!
    self:AddToBlacklist(deathName, deathTimestamp)
    
    -- Format: DELETE|name|timestamp
    local msg = COMM.DELETE .. COMM_DELIM .. (deathName or "") .. COMM_DELIM .. (deathTimestamp or 0)
    local success = C_ChatInfo.SendAddonMessage(ADDON_PREFIX, msg, "GUILD")
    
    if success then
        GDL:Debug("-> DELETE gesendet: " .. deathName .. " (auf Blacklist)")
    end
end

function Sync:HandleDeleteSync(sender, data)
    if not data then return end
    
    local name, timestamp = strsplit(COMM_DELIM, data)
    timestamp = tonumber(timestamp) or 0
    
    if not name or name == "" then return end
    
    GDL:Debug("<- DELETE von " .. sender .. ": " .. name)
    
    -- Zur PERSISTENTEN Blacklist hinzufügen!
    self:AddToBlacklist(name, timestamp)
    
    -- Finde und lösche den Eintrag lokal
    local guildData = GDL:GetGuildData()
    if not guildData or not guildData.deaths then return end
    
    for i = #guildData.deaths, 1, -1 do
        local death = guildData.deaths[i]
        -- Match by name AND timestamp (falls vorhanden) oder nur name
        if death.name == name then
            if timestamp == 0 or (death.timestamp and math.abs(death.timestamp - timestamp) < 60) then
                table.remove(guildData.deaths, i)
                GDL:Print("|cff888888" .. name .. " wurde durch " .. sender .. " geloescht.|r")
                
                -- UI aktualisieren
                local UI = GDL:GetModule("UI")
                if UI and UI.mainFrame and UI.mainFrame:IsShown() then
                    UI:UpdateChronicle()
                end
                break
            end
        end
    end
end

function Sync:SendPendingSync()
    if not IsInGuild() then return end
    local guildData = GDL:GetGuildData()
    if not guildData or not guildData.pendingSync then return end
    
    for _, death in ipairs(guildData.pendingSync) do
        self:BroadcastDeath(death)
    end
    guildData.pendingSync = {}
end

function Sync:GetOnlineUsers()
    local online = {}
    local now = time()
    for name, data in pairs(syncedUsers) do
        if (now - (data.lastSeen or 0)) < 600 then
            table.insert(online, {name = name, version = data.version, lastSeen = data.lastSeen})
        end
    end
    return online
end

function Sync:GetLastSyncTime()
    return lastSyncRequest
end

-- TEST-Funktion: Sendet einen Fake-Tod für Sync-Test
function Sync:SendTestDeath()
    if not IsInGuild() then 
        GDL:Print("|cffFF0000Fehler:|r Nicht in einer Gilde!")
        return 
    end
    
    local testDeath = {
        name = "TestChar-" .. math.random(1000, 9999),
        level = math.random(1, 60),
        classId = math.random(1, 11),
        zone = "Testzone",
        timestamp = time(),
        lastWords = "Dies ist ein Test!",
    }
    
    GDL:Print("|cffFFFF00[TEST]|r Sende Test-Tod...")
    self:BroadcastDeath(testDeath)
    GDL:Print("|cffFFFF00[TEST]|r Wenn andere Gildenmitglieder dies sehen, funktioniert der Sync!")
end

function Sync:RecordLocalDeath(deathData)
    -- Blacklist-Check: Falls dieser Name geloescht wurde
    if self:IsBlacklisted(deathData.name, deathData.timestamp) then
        GDL:Debug("Lokaler Tod ignoriert (Blacklist): " .. (deathData.name or "?"))
        return
    end
    
    if self:IsDuplicate(deathData) then return end
    
    local guildData = GDL:GetGuildData()
    if not guildData then return end
    
    deathData.timestamp = deathData.timestamp or time()
    
    -- Koordinaten ergänzen falls nicht vorhanden (vom MapMarkers Modul)
    if (not deathData.posX or deathData.posX == 0) and (not deathData.posY or deathData.posY == 0) then
        local MapMarkers = GDL:GetModule("MapMarkers")
        if MapMarkers then
            local lastLoc = MapMarkers:GetLastDeathLocation()
            if lastLoc and (time() - (lastLoc.timestamp or 0)) < 10 then
                deathData.mapId = lastLoc.mapId or deathData.mapId
                deathData.posX = lastLoc.posX or 0
                deathData.posY = lastLoc.posY or 0
            end
        end
    end
    
    -- LastWords hinzufügen (v4.0)
    if not deathData.lastWords or deathData.lastWords == "" then
        local LastWords = GDL:GetModule("LastWords")
        if LastWords then
            local words = LastWords:GetLastWords(deathData.name)
            if words then
                deathData.lastWords = words
            end
        end
    end
    
    -- Killer Info hinzufügen (v4.0)
    if not deathData.killerName then
        local KillerTracker = GDL:GetModule("KillerTracker")
        if KillerTracker then
            local killer = KillerTracker:GetKiller(deathData.name)
            if killer then
                deathData.killerName = killer.name
                deathData.killerId = killer.id
                deathData.killerSpell = killer.spell
            end
        end
    end
    
    table.insert(guildData.deaths, deathData)
    self:BroadcastDeath(deathData)
    
    -- Memorial: Verstorbene aus Berufe-Liste entfernen + Fenster aktualisieren
    local Memorial = GDL:GetModule("Memorial")
    if Memorial then
        Memorial:OnDeath(deathData)
        if Memorial.frame and Memorial.frame:IsShown() then
            Memorial:UpdateMemorialList()
        end
    end
    
    -- GuildStats aktualisieren wenn offen
    local GuildStats = GDL:GetModule("GuildStats")
    if GuildStats and GuildStats.frame and GuildStats.frame:IsShown() then
        GuildStats:UpdateDeathsList()
    end
    
    -- Professions aktualisieren wenn offen
    local Professions = GDL:GetModule("Professions")
    if Professions and Professions.frame and Professions.frame:IsShown() then
        Professions:UpdateWindow()
    end
    
    -- Sound & Overlay für lokale Tode
    if GuildDeathLogDB.settings.sound then PlaySound(8959, "Master") end
    if GuildDeathLogDB.settings.overlay then
        local UI = GDL:GetModule("UI")
        if UI then UI:ShowOverlay(deathData, false) end
    end
    
    -- Update UI wenn offen
    local UI = GDL:GetModule("UI")
    if UI and UI.mainFrame and UI.mainFrame:IsShown() then UI:UpdateChronicle() end
    
    -- Gildenchat Ankündigung mit EPISCHEN Condolences (v4.8)
    if GuildDeathLogDB.settings.announce and IsInGuild() then
        local Condolences = GDL:GetModule("Condolences")
        if Condolences then
            -- Killer extrahieren (kann "Environment", Mob-Name, etc. sein)
            local killer = deathData.killer or deathData.source or nil
            local zone = deathData.zone or nil
            local className = GDL:GetClassName(deathData.classId)
            
            local msg = Condolences:GetDeathAnnouncement(
                deathData.name, 
                deathData.level or 0, 
                className,
                killer,
                zone
            )
            SendChatMessage(msg, "GUILD")
        else
            local L = GDL:GetModule("Locale").L
            local msg = L.DEATH_MESSAGES[math.random(#L.DEATH_MESSAGES)]
            SendChatMessage(string.format(msg, deathData.name, deathData.level or 0, GDL:GetClassName(deathData.classId)), "GUILD")
        end
    end
    
    -- Export zu Custom Channel (v4.0)
    local Export = GDL:GetModule("Export")
    if Export then
        Export:AnnounceToChannel(deathData)
    end
end

-- ======================================================================
-- MODULE INITIALIZATION
-- ======================================================================

local initFrame = CreateFrame("Frame")
initFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
initFrame:SetScript("OnEvent", function(self, event)
    if event == "PLAYER_ENTERING_WORLD" then
        C_Timer.After(1, function()
            Sync:Initialize()
        end)
        self:UnregisterEvent("PLAYER_ENTERING_WORLD")
    end
end)

GDL:RegisterModule("Sync", Sync)
