-- ══════════════════════════════════════════════════════════════
-- 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", KONDOLENZ="KON", BULLETIN="BTIN"}

local syncedUsers = {}
local lastSyncRequest = 0
local recentDeaths = {}
-- Anti-Doppel-Popup: merkt sich, fuer welchen Tod schon Overlay+Sound+Chat lief.
-- Greift quer ueber ALLE Eingangspfade (DNL-Hook, DNL-Channel, GUILD-Addon,
-- Blizzard-/System-Channel), unabhaengig von der DB-Dedup.
local recentNotify = {}
-- WICHTIG: deletedDeaths ist jetzt NUR ein Cache!
-- Die persistenten Daten sind in GuildDeathLogDB.guilds[guildName].deletedDeaths

-- Realm-Suffix entfernen + lowercase fuer stabile Schluessel/Vergleiche
local function CanonName(name)
    if not name or name == "" then return "" end
    return (strsplit("-", name)):lower()
end

-- Genau EINE Benachrichtigung pro Tod. Erste Quelle gewinnt, jede weitere
-- Quelle desselben Spielers innerhalb von NOTIFY_WINDOW wird stumm geschluckt.
-- Fenster statt Zeit-Bucket -> immun gegen Bucket-Grenzen und gegen Pfade,
-- die unterschiedliche (arrival-)Zeitstempel liefern (Blizzard/System-Retry).
local NOTIFY_WINDOW = 60  -- Sekunden
function Sync:ShouldNotify(death)
    if not death then return false end
    local nm = CanonName(death.name)
    if nm == "" then return true end
    local now = GetTime()
    local last = recentNotify[nm]
    if last and (now - last) < NOTIFY_WINDOW then return false end
    recentNotify[nm] = now
    return true
end

-- 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 — DEBOUNCED. GUILD_ROSTER_UPDATE feuert
        -- in Classic salvenweise (jeder GuildRoster()-Call, jedes On/Off).
        -- Vorher stapelten sich die Timer -> PING-Salven -> jeder Empfaenger
        -- antwortete brav mehrfach (die 3x-PONGs im Debug-Log).
        if not self._quickPingPending and (time() - (self._lastQuickPing or 0)) > 30 then
            self._quickPingPending = true
            C_Timer.After(3, function()
                self._quickPingPending = false
                self._lastQuickPing = time()
                if IsInGuild() then
                    self:SendPing()
                end
            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:BroadcastKondolenz(dKey, text)
    if not IsInGuild() then return end
    if not dKey or not text or text == "" then return end
    local msg = COMM.KONDOLENZ .. COMM_DELIM .. dKey .. COMM_DELIM .. text
    C_ChatInfo.SendAddonMessage(ADDON_PREFIX, msg, "GUILD")
end

function Sync:BroadcastBulletin(post)
    if not IsInGuild() then return end
    if not post or not post.text or post.text == "" then return end
    local msg = COMM.BULLETIN .. COMM_DELIM ..
        (post.author or "") .. COMM_DELIM ..
        tostring(post.timestamp or time()) .. COMM_DELIM ..
        (post.tag or "info") .. COMM_DELIM ..
        post.text
    C_ChatInfo.SendAddonMessage(ADDON_PREFIX, msg, "GUILD")
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)
    elseif cmd == COMM.KONDOLENZ or cmd == "KON" then
        -- Format: deathKey|text
        if data then
            local dKey, text = strsplit(COMM_DELIM, data, 2)
            if dKey and text and text ~= "" then
                GuildDeathLogDB = GuildDeathLogDB or {}
                GuildDeathLogDB.kondolenze = GuildDeathLogDB.kondolenze or {}
                GuildDeathLogDB.kondolenze[dKey] = text
                GDL:Debug("Kondolenz von " .. sender .. " empfangen fuer " .. dKey)
                local UI = GDL:GetModule("UI")
                if UI and UI.activeChapter then
                    local f = UI.mainFrame
                    if f and f:IsShown() then UI:RenderChapter("condolences") end
                end
            end
        end
    elseif cmd == COMM.BULLETIN or cmd == "BTIN" then
        -- Format: author|timestamp|tag|text
        if data then
            local author, ts, tag, text = strsplit(COMM_DELIM, data, 4)
            if author and text and text ~= "" then
                GuildDeathLogDB = GuildDeathLogDB or {}
                GuildDeathLogDB.bulletinPosts = GuildDeathLogDB.bulletinPosts or {}
                local posts = GuildDeathLogDB.bulletinPosts
                -- Duplikat-Pruefung via timestamp
                local exists = false
                for _, p in ipairs(posts) do
                    if p.author == author and tostring(p.timestamp) == ts then exists = true; break end
                end
                if not exists then
                    table.insert(posts, 1, { author=author, text=text,
                        timestamp=tonumber(ts) or time(), tag=tag or "info",
                        guild = GDL.currentGuildName })   -- Gilden-Bindung (Isolation)
                    while #posts > 15 do table.remove(posts) end
                    GDL:Debug("Bulletin von " .. author .. " empfangen")
                    -- Einmalige Push-Benachrichtigung (DE/EN, Auto-Erkennung)
                    GDL:Print((GetLocale() == "deDE")
                        and "|cffFFD100Neuer Eintrag am Schwarzen Brett.|r"
                        or  "|cffFFD100New entry on the bulletin board.|r")
                    local UI = GDL:GetModule("UI")
                    if UI and UI.mainFrame and UI.mainFrame:IsShown() then
                        UI:RenderChapter("bulletin")
                    end
                end
            end
        end
    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)
    -- Escapen (| -> /) UND laengen-kappen, damit die Gesamtnachricht
    -- ("DEATH|"+...) sicher unter dem 255-Byte-Limit von SendAddonMessage
    -- bleibt (sonst stille Trunkierung -> kaputte Felder beim Empfaenger).
    local lastWords  = (death.lastWords or ""):gsub(COMM_DELIM, "/"):sub(1, 80)
    local killerName = (death.killerName or ""):gsub(COMM_DELIM, "/"):sub(1, 40)
    local zone       = (death.zone or ""):gsub(COMM_DELIM, "/"):sub(1, 40)

    return table.concat({
        death.name or "?", death.level or 0, death.classId or 0,
        zone, 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

-- Tode, die wir bereits via GUILD-Broadcast von einem ANDEREN Mitglied gehoert
-- haben. Verhindert, dass jeder Beobachter denselben fremden Tod nochmal relayed
-- (Storm), OHNE die Verteilung zu gefaehrden: wenn niemand sonst pusht, pushen WIR.
local broadcastSeen = {}

function Sync:_RawBroadcastDeath(death)
    if not IsInGuild() then 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:BroadcastDeath(death)
    if not IsInGuild() then
        GDL:Debug("BroadcastDeath: Nicht in Gilde!")
        return
    end
    if not death then return end

    -- Eigene Tode + lokal selbst erkannte (Fallback) IMMER sofort senden — das ist
    -- der primaere Push und muss ohne Verzoegerung raus.
    local isRelay = (not death.isSelf) and not death._fromFallback and
        (death._fromDNL or death.fromDeathlog or death.fromBlizzard or death.fromSystem or death.syncedFrom)

    if not isRelay then
        self:_RawBroadcastDeath(death)
        return
    end

    -- Relayter Fremd-Tod: kurz mit Jitter warten. Hat ihn in der Zwischenzeit ein
    -- anderes Mitglied schon in die Gilde gepusht (broadcastSeen), sparen wir uns
    -- das Duplikat. Pusht NIEMAND, feuert unser Timer -> Tod kommt garantiert an.
    local key = CanonName(death.name) .. "@" .. (death.timestamp or 0)
    if broadcastSeen[key] then return end
    C_Timer.After(0.3 + math.random() * 2.0, function()
        if broadcastSeen[key] then return end
        self:_RawBroadcastDeath(death)
    end)
end

function Sync:HandleDeath(sender, data)
    -- Eigenes Broadcast-Echo NICHT erneut verarbeiten -> sonst Doppel-Eintrag/-Anzeige
    -- (der Tod wurde lokal bereits via RecordLocalDeath verarbeitet & angezeigt).
    if sender and GDL.playerName and CanonName(sender) == CanonName(GDL.playerName) then
        return
    end
    local death = self:DeserializeDeath(data)
    if death then
        -- Ein anderes Mitglied hat diesen Tod bereits in die Gilde gepusht ->
        -- unseren evtl. ausstehenden Relay-Broadcast abblasen (kein Duplikat).
        local key = CanonName(death.name) .. "@" .. (death.timestamp or 0)
        broadcastSeen[key] = true
        C_Timer.After(300, function() broadcastSeen[key] = nil end)
        -- 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)
    -- Name kanonisieren (Realm-Suffix weg) -> Dedup & Anzeige konsistent
    if death and death.name then death.name = (strsplit("-", death.name)) end
    if GDL.Log then GDL:Log("DEATH", "P) ProcessIncomingDeath ENTRY: " .. (death and death.name or "?") .. " src=" .. tostring(source) .. " silent=" .. tostring(isSilent)) end
    -- 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
    
    -- (DeathBell wird GENAU EINMAL im ShouldNotify-Gate weiter unten gespielt.
    --  Frueher hier ein zweiter, ungeschuetzter Play -> Doppel-Sound/-Banner. Entfernt.)

    -- 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
    
    -- GuildCompetition: Todes-Strafpunkte
    local GuildComp = GDL:GetModule("GuildCompetition")
    if GuildComp then
        GuildComp:RegisterDeath(death.name)
    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!
    -- ShouldNotify() stellt sicher: genau EIN Overlay/Sound pro Tod, egal
    -- ueber wie viele Pfade derselbe Tod hier landet (DNL-Channel, GUILD-Addon, ...).
    if not isSilent and self:ShouldNotify(death) then
        GDL:Print("|cffFF6666" .. (death.name or "?") .. "|r " .. GDL:T("ist gefallen!") .. " (Lvl " .. (death.level or "?") .. ")")

        local DB = GDL:GetModule("DeathBell")
        if DB and DB:IsEnabled() then DB:Play(death) end
        if GuildDeathLogDB.settings.overlay then
            local UI = GDL:GetModule("UI")
            if UI then
                UI:ShowOverlay(death, true)
            end
        end

        -- Gilden-Chat-Meldung fuer den EIGENEN Tod hier nachholen: kommt der
        -- eigene Tod zuerst ueber den Channel rein, raeumt RecordLocalDeath ihn
        -- als Duplikat ab (Return vor dem Announce). Genau EIN Announce pro Tod,
        -- weil pro Client nur EIN Pfad nicht-dedupliziert ist.
        if death.name == GDL.playerName and GuildDeathLogDB.settings
           and GuildDeathLogDB.settings.announce and IsInGuild() then
            local Condolences = GDL:GetModule("Condolences")
            if Condolences and Condolences.GetDeathAnnouncement then
                local msg = Condolences:GetDeathAnnouncement(
                    death.name, death.level or 0, GDL:GetClassName(death.classId),
                    death.killer or death.source, death.zone)
                if msg and msg ~= "" then
                    SendChatMessage(msg, "GUILD")
                    if GDL.Log then GDL:Log("DEATH", "Gilden-Announce (eigener Tod via Channel): " .. (death.name or "?")) end
                end
            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 .. GDL:T(" wurde durch ") .. sender .. GDL:T(" 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

-- Hat dieser Spieler das Addon? (jemals via Addon-Nachricht gesehen, oder self)
-- Name wird Realm-bereinigt verglichen.
function Sync:HasAddon(name)
    if not name then return false end
    local short = (strsplit("-", name))
    if short == GDL.playerName then return true end
    return syncedUsers[short] ~= nil or syncedUsers[name] ~= nil
end

function Sync:GetLastSyncTime()
    return lastSyncRequest
end

-- ══════════════════════════════════════════════════════════════
-- SOLO-SYNCTEST (/gdl synctest)
-- GUILD-Addon-Messages echoen NICHT zu einem selbst zurueck -> echter Gilden-Sync
-- laesst sich solo nicht pruefen. Dieser Test simuliert einen empfangenen Peer-Tod
-- durch den ECHTEN Empfangs-Pfad (ProcessIncomingDeath) + setzt einen Fake-Peer in
-- die Praesenzliste, damit das Debug-Panel "Addon-User" auf 1 springt.
-- ══════════════════════════════════════════════════════════════
function Sync:RunSelfTest()
    -- 1) Fake-Peer -> "Addon-User" zeigt 1 (laeuft nach 10 Min via lastSeen aus)
    local peer = "SyncTest-Peer"
    syncedUsers[peer] = { lastSeen = time(), version = (GDL.version or "?") }
    GuildDeathLogDB.syncUsers = syncedUsers
    lastSyncRequest = time()

    -- 2) Fake-Tod durch den ECHTEN Empfangs-Pfad schieben (als kaeme er von einem Peer)
    local classes = {1, 2, 3, 4, 5, 7, 8, 9, 11}
    local fake = {
        name      = "SyncTestHeld" .. math.random(100, 999),
        level     = math.random(10, 60),
        classId   = classes[math.random(#classes)],
        zone      = GetRealZoneText() or "Testzone",
        killer    = "SyncTest-Gegner",
        timestamp = time(),
        lastWords = "Empfangstest erfolgreich!",
        _synctest = true,
    }
    self:ProcessIncomingDeath(fake, peer, false)  -- false = nicht silent -> Overlay + Sound

    local n = #self:GetOnlineUsers()
    GDL:Print("|cff00FF00[SyncTest]|r Empfangs-Pfad simuliert (als kaeme es von einem Peer).")
    GDL:Print("  Addon-User jetzt: |cffFFD100" .. n .. "|r  (Fake-Peer, laeuft in 10 Min aus)")
    GDL:Print("  Fake-Tod: |cffFFD100" .. fake.name .. "|r  ->  Overlay + Chronik-Eintrag pruefen.")
    GDL:Print("  Aufraeumen: |cffFFD100/gdl synctest clear|r")
    return fake.name
end

function Sync:ClearSelfTest()
    syncedUsers["SyncTest-Peer"] = nil
    GuildDeathLogDB.syncUsers = syncedUsers
    local gd = GDL:GetGuildData()
    local removed = 0
    if gd and gd.deaths then
        for i = #gd.deaths, 1, -1 do
            local d = gd.deaths[i]
            if d and (d._synctest or (d.name and tostring(d.name):find("^SyncTestHeld"))) then
                table.remove(gd.deaths, i)
                removed = removed + 1
            end
        end
    end
    GDL:Print("|cff00FF00[SyncTest]|r Aufgeraeumt: Fake-Peer entfernt, " .. removed .. " Test-Tod(e) geloescht.")
    local UI = GDL:GetModule("UI")
    if UI and UI.mainFrame and UI.mainFrame:IsShown() and UI.UpdateChronicle then
        UI:UpdateChronicle()
    end
end

-- TEST-Funktion: Sendet einen Fake-Tod für Sync-Test
function Sync:SendTestDeath()
    if not IsInGuild() then 
        GDL:Print("|cffFF0000" .. GDL:T("Fehler:") .. "|r " .. GDL:T("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)

    -- Lokales Overlay sofort anzeigen (eigene Nachrichten werden von HandleDeath ignoriert)
    local UI = GDL:GetModule("UI")
    if UI and GuildDeathLogDB and GuildDeathLogDB.settings and GuildDeathLogDB.settings.overlay then
        UI:ShowOverlay(testDeath, false)
    end

    GDL:Print("|cffFFFF00[TEST]|r Overlay lokal angezeigt + an Gilde gesendet.")
end

function Sync:RecordLocalDeath(deathData)
    -- Name kanonisieren (Realm-Suffix weg) -> Dedup & Anzeige konsistent
    if deathData and deathData.name then deathData.name = (strsplit("-", deathData.name)) end
    if GDL.Log then GDL:Log("DEATH", "3) RecordLocalDeath ENTRY: " .. (deathData.name or "?") .. " self=" .. tostring(deathData.isSelf) .. " inGuild=" .. tostring(deathData.inGuild)) end
    -- 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

    -- ── Gildenmitglied-Check ─────────────────────────────────────────────────
    -- Eigene Tode immer erlaubt; fremde nur wenn Gildenmitglied oder GuildlessMode
    if not deathData.isSelf then
        local db = GuildDeathLogDB
        local guildlessMode = db and db.settings and db.settings.guildlessMode

        if not guildlessMode then
            -- Prüfe ob Gildenmitglied
            local isGuildMember = deathData.inGuild
            if not isGuildMember then
                local Guild = GDL:GetModule("Guild")
                if Guild then isGuildMember = Guild:IsMember(deathData.name) end
            end
            if not isGuildMember then
                -- Gildennamen-Vergleich
                if GDL.currentGuildName and deathData.guild and deathData.guild == GDL.currentGuildName then
                    isGuildMember = true
                end
            end
            if not isGuildMember then
                GDL:Debug("RecordLocalDeath: " .. (deathData.name or "?") .. " nicht in Gilde — ignoriert")
                return
            end
        end
    end

    if self:IsDuplicate(deathData) then
        if GDL.Log then GDL:Log("DEATH", "4!) RecordLocalDeath: IsDuplicate -> ABBRUCH " .. (deathData.name or "?")) end
        return
    end

    local guildData = GDL:GetGuildData()
    -- GuildlessMode: Speichere unter Pseudo-Gilde "_GUILDLESS_" wenn kein Gildenname
    if not guildData then
        local db = GuildDeathLogDB
        if db and db.settings and db.settings.guildlessMode then
            local fallbackKey = "_GUILDLESS_"
            if not db.guilds then db.guilds = {} end
            if not db.guilds[fallbackKey] then
                db.guilds[fallbackKey] = { deaths={}, members={}, pendingSync={}, deletedDeaths={} }
            end
            guildData = db.guilds[fallbackKey]
        else
            GDL:Debug("RecordLocalDeath: kein Gildenname + kein GuildlessMode → ignoriert")
            return
        end
    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
    
    -- ── coordX/coordY ↔ posX/posY harmonisieren ─────────────────────────────
    -- Buch rendert mit d.coordX/coordY (0-100 Prozent)
    -- Sync serialisiert mit posX/posY (0.0-1.0 normiert)
    if deathData.coordX and not deathData.posX then
        deathData.posX = deathData.coordX / 100
        deathData.posY = deathData.coordY / 100
    elseif deathData.posX and deathData.posX ~= 0 and not deathData.coordX then
        deathData.coordX = deathData.posX * 100
        deathData.coordY = deathData.posY * 100
    end
    -- 0,0 bedeutet keine Koordinaten → nil setzen damit Buch nichts zeigt
    if deathData.coordX == 0 and deathData.coordY == 0 then
        deathData.coordX = nil
        deathData.coordY = nil
    end
    if deathData.posX == 0 and deathData.posY == 0 then
        deathData.posX = nil
        deathData.posY = nil
    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
    -- Sound wird zentral von DeathBell gesteuert
    -- ShouldNotify(): teilt sich die Sperre mit ProcessIncomingDeath, damit
    -- DNL-Hook + Channel-Empfang denselben Tod nicht doppelt anzeigen.
    local _notify = self:ShouldNotify(deathData)
    if GDL.Log then GDL:Log("DEATH", "5) RecordLocalDeath notify=" .. tostring(_notify) .. " overlay=" .. tostring(GuildDeathLogDB.settings and GuildDeathLogDB.settings.overlay) .. " announce=" .. tostring(GuildDeathLogDB.settings and GuildDeathLogDB.settings.announce) .. " inGuild=" .. tostring(IsInGuild())) end
    if _notify then
        local DB = GDL:GetModule("DeathBell")
        if DB and DB:IsEnabled() then
            DB:Play(deathData)
        end
        if GuildDeathLogDB.settings.overlay then
            local UI = GDL:GetModule("UI")
            if UI then UI:ShowOverlay(deathData, deathData.isSelf == true) end
        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 _notify and 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")
            if GDL.Log then GDL:Log("DEATH", "Gilden-Announce (RecordLocalDeath): " .. (deathData.name or "?")) end
        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)
