-- ══════════════════════════════════════════════════════════════════════════════
-- SURVIVALSTREAK.LUA — Überlebens-Streak-System
-- Modul: SurvivalStreak | Interface: 11508
--
-- Features:
--   • Aktueller Streak pro Charakter (Zeit seit letztem Tod / erstem Login)
--   • Gilden-Bestenliste: "Wer lebt am längsten?"
--   • Meilenstein-Warnungen: "Du lebst seit 7 Tagen — pass auf!"
--   • Buchseite: Kapitel "Die Ausdauernden"
--   • Sync mit anderen GDL-Nutzern via GDL-Data-Kanal
-- ══════════════════════════════════════════════════════════════════════════════

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

local UnitName   = UnitName
local UnitGUID   = UnitGUID
local UnitClass  = UnitClass
local UnitLevel  = UnitLevel
local IsInGuild  = IsInGuild
local time       = time
local math_floor = math.floor

-- Klassen-Dateiname -> ID (robust, da UnitClass in Classic nicht zuverlaessig
-- eine classId als 3. Rueckgabe liefert)
local CLASS_FILE_TO_ID = {
    WARRIOR=1, PALADIN=2, HUNTER=3, ROGUE=4, PRIEST=5, DEATHKNIGHT=6,
    SHAMAN=7, MAGE=8, WARLOCK=9, DRUID=11,
}

-- ── Modul ─────────────────────────────────────────────────────────────────────
local SurvivalStreak = {}
GDL:RegisterModule("SurvivalStreak", SurvivalStreak)

-- ── Streak-Meilensteine (in Tagen) ───────────────────────────────────────────
local STREAK_MILESTONES = {
    { days = 1,   label = "Einen Tag überlebt",      color = "|cff1A5A24" },
    { days = 3,   label = "Drei Tage überlebt",      color = "|cff1A5A24" },
    { days = 7,   label = "Eine Woche überlebt",     color = "|cff7A5510" },
    { days = 14,  label = "Zwei Wochen überlebt",    color = "|cff7A5510" },
    { days = 30,  label = "Einen Monat überlebt",    color = "|cff7A3A08" },
    { days = 60,  label = "Zwei Monate überlebt",    color = "|cffFF4500" },
    { days = 100, label = "100 Tage Überlebt",       color = "|cffFF0000" },
}

-- ── Hilfsfunktionen ───────────────────────────────────────────────────────────
local function FormatStreak(seconds)
    if not seconds or seconds < 0 then return "Neu" end
    local days    = math_floor(seconds / 86400)
    local hours   = math_floor((seconds % 86400) / 3600)
    local minutes = math_floor((seconds % 3600) / 60)

    if days >= 30 then
        return string.format("%dd", days)
    elseif days > 0 then
        return string.format("%dd %dh", days, hours)
    elseif hours > 0 then
        return string.format("%dh %dm", hours, minutes)
    else
        return string.format("%dm", minutes)
    end
end

local function GetCharKey()
    local name = UnitName("player")
    local guid = UnitGUID("player")
    -- GUID-basiert (HC-sicher: gleicher Name nach Neuerstellung = anderer GUID)
    return guid and (name .. ":" .. guid:sub(-8)) or name
end

-- ── Streak berechnen ──────────────────────────────────────────────────────────
function SurvivalStreak:GetCurrentStreak(charKey)
    charKey = charKey or GetCharKey()
    local db = GuildDeathLogDB
    if not (db and db.streaks) then return 0, 0 end

    local entry = db.streaks[charKey]
    if not entry then return 0, 0 end

    local streakStart = entry.streakStart or entry.firstSeen or time()
    local elapsed     = time() - streakStart
    local days        = math_floor(elapsed / 86400)

    return elapsed, days
end

-- ── Streak starten/resetten ───────────────────────────────────────────────────
function SurvivalStreak:StartStreak(charKey)
    charKey = charKey or GetCharKey()
    local db = GuildDeathLogDB
    if not db then return end
    if not db.streaks then db.streaks = {} end

    local now = time()
    local existing = db.streaks[charKey]

    if not existing then
        db.streaks[charKey] = {
            name        = UnitName("player"),
            charKey     = charKey,
            streakStart = now,
            firstSeen   = now,
            bestStreak  = 0,
            totalDeaths = 0,
        }
    end

    -- Gilde/Klasse/Level am eigenen Eintrag aktuell halten (fuer Sync + Filter)
    local e = db.streaks[charKey]
    e.guild   = GDL.currentGuildName
    e.classId = CLASS_FILE_TO_ID[select(2, UnitClass("player"))] or e.classId or 0
    e.level   = UnitLevel("player") or e.level

    -- Beim Login: Streak läuft weiter (nicht neu starten)
    -- Nur bei Tod: ResetsStreak aufrufen
end

function SurvivalStreak:ResetStreak(charKey, deathData)
    charKey = charKey or GetCharKey()
    local db = GuildDeathLogDB
    if not (db and db.streaks) then return end
    if not db.streaks[charKey] then return end

    local entry   = db.streaks[charKey]
    local elapsed = time() - (entry.streakStart or time())

    -- Besten Streak speichern
    if elapsed > (entry.bestStreak or 0) then
        entry.bestStreak = elapsed
    end

    entry.totalDeaths  = (entry.totalDeaths or 0) + 1
    entry.streakStart  = time()  -- Neuer Start sofort nach Tod
    entry.lastDeath    = time()
    entry.lastDeathBy  = deathData and deathData.killer or "Unbekannt"
    entry.lastDeathZone = deathData and deathData.zone or "?"

    db.streaks[charKey] = entry
end

-- ── Gilden-Bestenliste (gildenweit, nur Addon-Nutzer der eigenen Gilde) ───────
function SurvivalStreak:GetGuildLeaderboard(limit)
    limit = limit or 10
    local db = GuildDeathLogDB
    local now = time()
    -- Gilde robust ermitteln: GDL.currentGuildName kann beim Login noch nil sein.
    local myGuild = GDL.currentGuildName
    if (not myGuild or myGuild == "") and IsInGuild() and GetGuildInfo then
        myGuild = GetGuildInfo("player")
    end
    local myKey = GetCharKey()
    local byName = {}   -- Dedup nach Name; eigener Char schlaegt Remote-Eintrag

    -- 1) Eigene Charaktere: aktuelle Gilde ODER der gerade gespielte Char (immer "ich").
    --    Eigenen Eintrag frisch stempeln, falls die Gilde beim Login noch nicht da war.
    if db and db.streaks then
        local mine = db.streaks[myKey]
        if mine and myGuild then
            mine.guild = myGuild
            mine.name  = mine.name or UnitName("player")
        end
        for key, data in pairs(db.streaks) do
            if data.name and data.streakStart
               and (data.guild == myGuild or key == myKey) then
                byName[data.name] = {
                    name        = data.name,
                    charKey     = key,
                    elapsed     = now - data.streakStart,
                    bestStreak  = data.bestStreak or 0,
                    totalDeaths = data.totalDeaths or 0,
                    lastDeath   = data.lastDeath,
                    classId     = data.classId,
                    level       = data.level,
                    isSelf      = true,
                }
            end
        end
    end

    -- 2) Gildenmitglieder (empfangen via Sync): nur aktuelle Gilde, max 30 Tage alt.
    --    Wer sendet, nutzt das Addon -> Anforderung "nur Addon-Nutzer" erfuellt.
    if myGuild and db and db.guildStreaks then
        for nm, data in pairs(db.guildStreaks) do
            if data.guild == myGuild and (now - (data.lastUpdate or 0)) < 2592000 then
                if not byName[nm] then
                    byName[nm] = {
                        name        = nm,
                        elapsed     = now - (data.streakStart or now),
                        bestStreak  = data.bestStreak or 0,
                        totalDeaths = data.totalDeaths or 0,   -- Default: verhindert nil-Vergleich im Render
                        classId     = data.classId,
                        level       = data.level,
                        remote      = true,
                    }
                end
            end
        end
    end

    local entries = {}
    for _, e in pairs(byName) do
        e.days      = math_floor((e.elapsed or 0) / 86400)
        e.formatted = FormatStreak(e.elapsed)
        entries[#entries + 1] = e
    end
    table.sort(entries, function(a, b) return (a.elapsed or 0) > (b.elapsed or 0) end)

    local result = {}
    for i = 1, math.min(limit, #entries) do result[i] = entries[i] end
    return result
end

-- ── Gilden-Sync der Streaks ───────────────────────────────────────────────────
local STREAK_PREFIX = "GDLStrk"

function SurvivalStreak:BroadcastMyStreak()
    if not IsInGuild() then return end
    local db  = GuildDeathLogDB
    local key = GetCharKey()
    local e   = db and db.streaks and db.streaks[key]
    if not e then return end
    local classId = CLASS_FILE_TO_ID[select(2, UnitClass("player"))] or e.classId or 0
    local msg = table.concat({
        UnitName("player") or "?",
        classId,
        UnitLevel("player") or 0,
        e.streakStart or time(),
        e.bestStreak or 0,
        e.totalDeaths or 0,
    }, "~")
    local send = (C_ChatInfo and C_ChatInfo.SendAddonMessage) or SendAddonMessage
    if send then pcall(send, STREAK_PREFIX, msg, "GUILD") end
    if GDL.Debug then GDL:Debug("Streak: -> SENT") end
end

-- Pull: andere Member bitten, ihren Streak zu senden (gegen 5-Min-Luecke bei Login)
function SurvivalStreak:RequestStreaks()
    if not IsInGuild() then return end
    local send = (C_ChatInfo and C_ChatInfo.SendAddonMessage) or SendAddonMessage
    if send then pcall(send, STREAK_PREFIX, "REQUEST", "GUILD") end
    if GDL.Debug then GDL:Debug("Streak: REQUEST sent") end
end

function SurvivalStreak:HandleStreakMessage(msg, sender)
    local short = (strsplit("-", sender))
    if short == GDL.playerName then return end       -- eigenes Echo
    if not GDL.currentGuildName then return end       -- ohne Gilde nichts speichern

    if msg == "REQUEST" then
        -- Mit Jitter antworten (gegen Antwort-Salven); verhindert auch, dass
        -- "REQUEST" faelschlich als Streak-Name gespeichert wird.
        C_Timer.After(math.random() * 2.5, function() self:BroadcastMyStreak() end)
        return
    end

    local name, classId, lvl, streakStart, best, deaths = strsplit("~", msg or "")
    if not name or name == "" then return end
    GuildDeathLogDB = GuildDeathLogDB or {}
    GuildDeathLogDB.guildStreaks = GuildDeathLogDB.guildStreaks or {}
    GuildDeathLogDB.guildStreaks[name] = {
        name        = name,
        classId     = tonumber(classId) or 0,
        level       = tonumber(lvl) or 0,
        streakStart = tonumber(streakStart) or time(),
        bestStreak  = tonumber(best) or 0,
        totalDeaths = tonumber(deaths) or 0,
        guild       = GDL.currentGuildName,           -- GUILD-Channel = unsere Gilde
        lastUpdate  = time(),
        remote      = true,
    }
    if GDL.Debug then GDL:Debug("Streak: <- RECV " .. name) end
end

-- ── Meilenstein-Check & Warnung ───────────────────────────────────────────────
local _lastMilestoneCheck = 0
local _announcedMilestones = {}

function SurvivalStreak:CheckMilestones()
    local now = time()
    if (now - _lastMilestoneCheck) < 3600 then return end  -- Max 1x pro Stunde
    _lastMilestoneCheck = now

    local elapsed, days = SurvivalStreak:GetCurrentStreak()
    local charKey = GetCharKey()
    -- Persistent statt modul-lokal: sonst poppt der Toast bei JEDEM Login erneut.
    GuildDeathLogDB.streakAnnounced = GuildDeathLogDB.streakAnnounced or {}
    GuildDeathLogDB.streakAnnounced[charKey] = GuildDeathLogDB.streakAnnounced[charKey] or {}
    local announced = GuildDeathLogDB.streakAnnounced[charKey]

    for _, milestone in ipairs(STREAK_MILESTONES) do
        if days >= milestone.days and not announced[milestone.days] then
            announced[milestone.days] = true

            -- Toast-Benachrichtigung
            local UI = GDL:GetModule("UI")
            if UI and UI.ShowMilestoneToast then
                UI:ShowMilestoneToast({
                    name     = milestone.label,
                    desc     = string.format("Aktueller Streak: %s", FormatStreak(elapsed)),
                    icon     = "Interface\\Icons\\Spell_Nature_TimeStop",
                    category = "survival",
                })
            end

            -- Chat-Ausgabe
            GDL:Print(string.format(
                "%s|cff7A5510 Meilenstein:|r %s |cff4A3A28(Streak: %s)|r",
                milestone.color, milestone.label, FormatStreak(elapsed)
            ))
        end
    end
end

-- ── UI-Rendering für Buchseite ────────────────────────────────────────────────
-- Wird von UIChapters aufgerufen für das Kapitel "Die Ausdauernden"
function SurvivalStreak:RenderBookPage(scrollChild, yOffset)
    local db  = GuildDeathLogDB
    local now = time()

    -- ── Eigenen Streak ermitteln ──────────────────────────────────────────────
    local charKey   = GetCharKey()
    local selfEntry = db and db.streaks and db.streaks[charKey]

    -- PlayedTime-Fallback: Wenn 0 Tode → /played als Streak verwenden
    local elapsed = 0
    if selfEntry then
        elapsed = now - (selfEntry.streakStart or now)
    end
    local totalDeaths = selfEntry and (selfEntry.totalDeaths or 0) or 0
    if totalDeaths == 0 then
        local PT = GDL:GetModule("PlayedTime")
        if PT then
            local pt = PT:Get()
            if pt and pt > elapsed then
                elapsed = pt
            end
        end
    end
    local bestElapsed = selfEntry and (selfEntry.bestStreak or 0) or 0

    -- ── SECTION: Meine Überlebensdauer ───────────────────────────────────────
    local selfTitle = scrollChild:CreateFontString(nil, "OVERLAY")
    selfTitle:SetFont("Fonts\\MORPHEUS.TTF", 15, "")
    selfTitle:SetTextColor(0.40, 0.28, 0.14)
    selfTitle:SetShadowOffset(0, 0)
    selfTitle:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 20, yOffset)
    selfTitle:SetText(GDL:T("Meine Ueberlebensdauer"))
    yOffset = yOffset - 24

    -- Stat-Zeile: drei Blöcke mit eigenen FontStrings (keine format-Ausrichtung)
    local COL = { 20, 130, 230 }  -- x-Positionen der 3 Blöcke

    local function MakeStat(label, value, valueColor, xPos)
        local lbl = scrollChild:CreateFontString(nil, "OVERLAY")
        lbl:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
        lbl:SetTextColor(0.45, 0.35, 0.22)
        lbl:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", xPos, yOffset)
        lbl:SetText(label .. ":")

        local val = scrollChild:CreateFontString(nil, "OVERLAY")
        val:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
        local r, g, b = unpack(valueColor)
        val:SetTextColor(r, g, b)
        val:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", xPos + 58, yOffset)
        val:SetText(value)
    end

    MakeStat(GDL:T("Aktuell"),  FormatStreak(elapsed),   {0.20, 0.72, 0.30}, COL[1])
    MakeStat(GDL:T("Bester"),   FormatStreak(bestElapsed), {0.72, 0.58, 0.15}, COL[2])
    MakeStat(GDL:T("Tode"),
        totalDeaths > 0 and ("|cffAA3333" .. totalDeaths .. "|r") or "|cff444444" .. totalDeaths .. "|r",
        {1,1,1}, COL[3])
    yOffset = yOffset - 18

    -- Letzter Tod
    if selfEntry and selfEntry.lastDeath then
        local ago = now - selfEntry.lastDeath
        local deathInfo = scrollChild:CreateFontString(nil, "OVERLAY")
        deathInfo:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        deathInfo:SetTextColor(0.45, 0.35, 0.25)
        deathInfo:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 20, yOffset)
        deathInfo:SetText(string.format(GDL:T("Letzter Tod: vor %s  —  %s (%s)"),
            FormatStreak(ago),
            selfEntry.lastDeathBy or "?",
            selfEntry.lastDeathZone or "?"))
        yOffset = yOffset - 16
    end

    yOffset = yOffset - 10

    -- Trennlinie
    local div = scrollChild:CreateTexture(nil, "ARTWORK")
    div:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 15, yOffset)
    div:SetSize(300, 1)
    div:SetColorTexture(0.45, 0.35, 0.20, 0.35)
    yOffset = yOffset - 14

    -- ── SECTION: Gilden-Bestenliste ──────────────────────────────────────────
    local lbTitle = scrollChild:CreateFontString(nil, "OVERLAY")
    lbTitle:SetFont("Fonts\\MORPHEUS.TTF", 14, "")
    lbTitle:SetTextColor(0.40, 0.28, 0.14)
    lbTitle:SetShadowOffset(0, 0)
    lbTitle:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 20, yOffset)
    lbTitle:SetText(GDL:T("Die Ausdauernden der Gilde"))
    yOffset = yOffset - 22

    -- Spalten-X-Positionen (fixe Pixel, kein string.format Padding)
    local X_RANK   = 20
    local X_NAME   = 42
    local X_STREAK = 178
    local X_BEST   = 228
    local X_DEATHS = 282

    -- Tabellenkopf
    local function MakeHeader(text, xPos)
        local h = scrollChild:CreateFontString(nil, "OVERLAY")
        h:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        h:SetTextColor(0.38, 0.30, 0.20)
        h:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", xPos, yOffset)
        h:SetText(text)
    end
    MakeHeader("#",        X_RANK)
    MakeHeader(GDL:T("Charakter"),X_NAME)
    MakeHeader("Streak",   X_STREAK)
    MakeHeader(GDL:T("Rekord"),   X_BEST)
    MakeHeader(GDL:T("Tode"),     X_DEATHS)
    yOffset = yOffset - 14

    -- Trennlinie unter Header
    local hDiv = scrollChild:CreateTexture(nil, "ARTWORK")
    hDiv:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", X_RANK, yOffset + 2)
    hDiv:SetSize(278, 1)
    hDiv:SetColorTexture(0.40, 0.32, 0.18, 0.28)
    yOffset = yOffset - 6

    local leaders = SurvivalStreak:GetGuildLeaderboard(10)

    if #leaders == 0 then
        local empty = scrollChild:CreateFontString(nil, "OVERLAY")
        empty:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
        empty:SetTextColor(0.42, 0.35, 0.25)
        empty:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 20, yOffset)
        empty:SetText("|cff6A5A48" .. GDL:T("Noch keine Daten vorhanden.") .. "|r")
        yOffset = yOffset - 18
    else
        for i, entry in ipairs(leaders) do
            local rankColor
            if     i == 1 then rankColor = {0.90, 0.75, 0.10}  -- Gold
            elseif i == 2 then rankColor = {0.72, 0.72, 0.72}  -- Silber
            elseif i == 3 then rankColor = {0.72, 0.44, 0.18}  -- Bronze
            else              rankColor = {0.42, 0.38, 0.30}
            end

            local isMe = (entry.charKey == charKey)

            -- Hintergrund für eigenen Eintrag
            if isMe then
                local rowBg = scrollChild:CreateTexture(nil, "BACKGROUND")
                rowBg:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", X_RANK - 2, yOffset + 1)
                rowBg:SetSize(282, 16)
                rowBg:SetColorTexture(0.20, 0.15, 0.05, 0.22)
            end

            -- Rang
            local rankFS = scrollChild:CreateFontString(nil, "OVERLAY")
            rankFS:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
            rankFS:SetTextColor(unpack(rankColor))
            rankFS:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", X_RANK, yOffset)
            rankFS:SetText(i .. ".")

            -- Name
            local nameFS = scrollChild:CreateFontString(nil, "OVERLAY")
            nameFS:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
            if isMe then
                nameFS:SetTextColor(0.16, 0.36, 0.52)
            else
                nameFS:SetTextColor(0.35, 0.28, 0.18)
            end
            nameFS:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", X_NAME, yOffset)
            nameFS:SetText(entry.name:sub(1, 16))

            -- Aktueller Streak
            local streakFS = scrollChild:CreateFontString(nil, "OVERLAY")
            streakFS:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
            streakFS:SetTextColor(0.22, 0.60, 0.30)
            streakFS:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", X_STREAK, yOffset)
            streakFS:SetText(entry.formatted)

            -- Bester Streak
            local bestFS = scrollChild:CreateFontString(nil, "OVERLAY")
            bestFS:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
            bestFS:SetTextColor(0.60, 0.48, 0.18)
            bestFS:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", X_BEST, yOffset)
            bestFS:SetText(FormatStreak(entry.bestStreak))

            -- Tode
            local deathFS = scrollChild:CreateFontString(nil, "OVERLAY")
            deathFS:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
            deathFS:SetTextColor(entry.totalDeaths > 0 and 0.72 or 0.35,
                                 entry.totalDeaths > 0 and 0.22 or 0.28, 0.20)
            deathFS:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", X_DEATHS, yOffset)
            deathFS:SetText(tostring(entry.totalDeaths))

            yOffset = yOffset - 17
        end
    end

    return yOffset
end

-- ── Tod-Ereignis: Streak resetten ────────────────────────────────────────────
-- Wird von DNLBridge nach HandleDeath aufgerufen
function SurvivalStreak:OnDeath(deathData)
    if not deathData then return end

    if deathData.isSelf then
        local charKey = GetCharKey()
        SurvivalStreak:ResetStreak(charKey, deathData)
        GDL:Print(string.format(
            "|cff7A2010[Streak]|r Dein Streak wurde unterbrochen. Bester: %s",
            FormatStreak((GuildDeathLogDB and GuildDeathLogDB.streaks
                and GuildDeathLogDB.streaks[charKey]
                and GuildDeathLogDB.streaks[charKey].bestStreak) or 0)
        ))
    end

    -- Für Gildenmitglieder: Streak in unserer DB updaten
    -- (kommen via DNL Sync, name ist bekannt)
    if deathData.name and not deathData.isSelf then
        local db = GuildDeathLogDB
        if not (db and db.streaks) then return end

        -- Streak-Eintrag für diesen Charakter finden und resetten
        for key, entry in pairs(db.streaks) do
            local entryName = entry.name or ""
            if entryName == deathData.name then
                local elapsed = time() - (entry.streakStart or time())
                if elapsed > (entry.bestStreak or 0) then
                    entry.bestStreak = elapsed
                end
                entry.totalDeaths  = (entry.totalDeaths or 0) + 1
                entry.streakStart  = time()
                entry.lastDeath    = time()
                entry.lastDeathBy  = deathData.killer
                entry.lastDeathZone = deathData.zone
                break
            end
        end
    end
end

-- ── Initialisierung ───────────────────────────────────────────────────────────
function SurvivalStreak:Initialize()
    if SurvivalStreak._initialized then return end
    SurvivalStreak._initialized = true
    -- SavedVariables vorbereiten
    C_Timer.After(0.5, function()
        local db = GuildDeathLogDB
        if not db then return end
        if not db.streaks then db.streaks = {} end

        -- Eigenen Charakter eintragen/updaten
        SurvivalStreak:StartStreak(GetCharKey())

        -- ── Streak-Sync: Prefix registrieren + empfangen ──────────────────────
        local reg = (C_ChatInfo and C_ChatInfo.RegisterAddonMessagePrefix)
        if reg then pcall(reg, STREAK_PREFIX) end
        local sf = CreateFrame("Frame")
        sf:RegisterEvent("CHAT_MSG_ADDON")
        sf:SetScript("OnEvent", function(_, _, prefix, message, channel, sender)
            if prefix == STREAK_PREFIX and channel == "GUILD" then
                SurvivalStreak:HandleStreakMessage(message, sender)
            end
        end)
        -- Eigenen Streak senden: nach 20s + alle 5 Minuten
        C_Timer.After(20, function() SurvivalStreak:BroadcastMyStreak() end)
        C_Timer.NewTicker(300, function() SurvivalStreak:BroadcastMyStreak() end)
        -- Pull: andere bitten, ihren Streak zu senden -> keine 5-Min-Luecke bei Login
        C_Timer.After(24, function() SurvivalStreak:RequestStreaks() end)

        -- Gilde wird oft erst NACH dem Login geladen. Sobald die Roster-Daten da
        -- sind: eigenen Eintrag neu stempeln (guild/level/classId) und einmal
        -- senden -> man taucht selbst in der Bestenliste auf und Mitglieder mit
        -- Addon bekommen die Streak. Greift auch bei Gildenwechsel.
        local gf = CreateFrame("Frame")
        gf:RegisterEvent("GUILD_ROSTER_UPDATE")
        gf:RegisterEvent("PLAYER_GUILD_UPDATE")
        local gStamped = false
        gf:SetScript("OnEvent", function()
            SurvivalStreak:StartStreak(GetCharKey())
            if not gStamped and IsInGuild() then
                gStamped = true
                C_Timer.After(3, function() SurvivalStreak:BroadcastMyStreak() end)
            end
        end)
        -- Roster einmal anfordern, damit das Event sicher feuert
        if C_GuildInfo and C_GuildInfo.GuildRoster then pcall(C_GuildInfo.GuildRoster)
        elseif GuildRoster then pcall(GuildRoster) end

        -- Meilenstein-Ticker: alle 30 Minuten prüfen
        C_Timer.NewTicker(1800, function()
            SurvivalStreak:CheckMilestones()
        end)

        -- Sofort einmal prüfen (nach Login)
        C_Timer.After(30, function()
            SurvivalStreak:CheckMilestones()
        end)
    end)
end

SurvivalStreak:Initialize()

-- ── Public API ────────────────────────────────────────────────────────────────
SurvivalStreak.FormatStreak = FormatStreak

function SurvivalStreak:PrintStatus()
    local elapsed, days = SurvivalStreak:GetCurrentStreak()
    GDL:Print(string.format(
        "|cff7A5510[Streak]|r Aktuell überlebt: |cff00FF00%s|r (%d Tage)",
        FormatStreak(elapsed), days
    ))
    local leaders = SurvivalStreak:GetGuildLeaderboard(3)
    if #leaders > 0 then
        GDL:Print("|cffFFD100" .. GDL:T("Top 3 der Gilde:") .. "|r")
        for i, e in ipairs(leaders) do
            GDL:Print(string.format("  %d. %s — %s", i, e.name, e.formatted))
        end
    end
end
