-- ══════════════════════════════════════════════════════════════
-- MODUL: Achievements – Persönliche & Gilden-Errungenschaften
-- ══════════════════════════════════════════════════════════════

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

-- Forward-Deklaration: diese Helfer werden weiter unten definiert, aber bereits
-- in den check-Closures der Achievement-Tabelle referenziert. Ohne diese Zeile
-- zeigen die Closures auf das globale (nil) → "attempt to call a nil value",
-- vom pcall in CheckAll() verschluckt → g_nodeaths_7d/30d & g_old_30d triggerten nie.
local GetNewestDeathTimestamp, GetOldestDeathTimestamp

-- ══════════════════════════════════════════════════════════════
-- PERSÖNLICHE ERRUNGENSCHAFTEN (individuell / als Zeuge)
-- ══════════════════════════════════════════════════════════════
local ACHIEVEMENT_LIST = {
    {id = "first_death",   name = "Erster Zeuge",      desc = "Erster Tod eines Gildenmitglieds miterlebt",  icon = "Interface\\Icons\\Achievement_PVP_A_01",
        check = function() local gd = GDL:GetGuildData(); return gd and gd.deaths and #gd.deaths >= 1 end},
    {id = "ten_deaths",    name = "Chronist des Todes", desc = "10 Tode im Buch verzeichnet",                 icon = "Interface\\Icons\\INV_Misc_Book_09",
        check = function() local gd = GDL:GetGuildData(); return gd and gd.deaths and #gd.deaths >= 10 end},
    {id = "fifty_deaths",  name = "Meister der Trauer", desc = "50 Tode im Buch verzeichnet",                 icon = "Interface\\Icons\\Spell_Shadow_DeathPact",
        check = function() local gd = GDL:GetGuildData(); return gd and gd.deaths and #gd.deaths >= 50 end},
    {id = "survivor_10",   name = "Zäh wie Leder",      desc = "Level 10 erreicht ohne zu sterben",           icon = "Interface\\Icons\\INV_Misc_ArmorKit_17",
        check = function() return (UnitLevel("player") or 0) >= 10 end},
    {id = "survivor_20",   name = "Hartgesotten",        desc = "Level 20 erreicht ohne zu sterben",           icon = "Interface\\Icons\\Spell_Holy_DivineProvidence",
        check = function() return (UnitLevel("player") or 0) >= 20 end},
    {id = "survivor_40",   name = "Veteran",             desc = "Level 40 erreicht ohne zu sterben",           icon = "Interface\\Icons\\INV_Shield_36",
        check = function() return (UnitLevel("player") or 0) >= 40 end},
    {id = "survivor_60",   name = "Unsterblich",         desc = "Level 60 erreicht ohne zu sterben!",          icon = "Interface\\Icons\\Spell_Holy_AuraOfLight",
        check = function() return (UnitLevel("player") or 0) >= 60 end},
    {id = "survivor_70",   name = "Wahrer Unsterblicher", desc = "Level 70 erreicht ohne zu sterben! (TBC)",   icon = "Interface\\Icons\\Spell_Holy_HolyBolt",
        check = function() return GDL.isTBC and (UnitLevel("player") or 0) >= 70 end},
    {id = "danger_zone",   name = "Lebensmüde",          desc = "Eine Zone mit 5+ Toden betreten",             icon = "Interface\\Icons\\Spell_Shadow_DeathScream",
        check = function() return GuildDeathLogDB and GuildDeathLogDB.dangerZoneEntered end},
    {id = "nachruf",       name = "Nachruf-Schreiber",  desc = "Einen eigenen Nachruf verfasst",              icon = "Interface\\Icons\\INV_Scroll_03",
        check = function()
            if GuildDeathLogDB and GuildDeathLogDB.customNachrufe then
                for _ in pairs(GuildDeathLogDB.customNachrufe) do return true end
            end
            return false
        end},
    {id = "last_words",    name = "Letzte Worte",        desc = "Eigene letzte Worte festgelegt",              icon = "Interface\\Icons\\INV_Letter_15",
        check = function() return GuildDeathLogDB and GuildDeathLogDB.myLastWords and GuildDeathLogDB.myLastWords ~= "" end},
}

-- ══════════════════════════════════════════════════════════════
-- GILDEN-ERRUNGENSCHAFTEN (nur gemeinsam erreichbar)
-- ══════════════════════════════════════════════════════════════
local GUILD_ACHIEVEMENT_LIST = {
    -- ── Zusammen gekämpft ───────────────────────────────────
    {id = "g_dungeon_3",   name = "Gilden-Trupp",        desc = "Dungeon mit 3+ Gildenmitgliedern betreten",  icon = "Interface\\Icons\\Ability_Warrior_Cleave",
        check = function()
            local p = GuildDeathLogDB and GuildDeathLogDB.guildProgress
            return p and (p.dungeons_with_guild or 0) >= 1
        end},
    {id = "g_dungeon_10",  name = "Kameraden",           desc = "10 Dungeons als Gilden-Trupp absolviert",   icon = "Interface\\Icons\\Ability_Dualwield",
        check = function()
            local p = GuildDeathLogDB and GuildDeathLogDB.guildProgress
            return p and (p.dungeons_with_guild or 0) >= 10
        end},
    {id = "g_raid_10",     name = "Gilden-Schlachtzug",  desc = "Raid mit 10+ Gildenmitgliedern betreten",   icon = "Interface\\Icons\\Achievement_Boss_Ragnaros",
        check = function()
            local p = GuildDeathLogDB and GuildDeathLogDB.guildProgress
            return p and (p.raids_with_guild or 0) >= 1
        end},
    {id = "g_full_party",  name = "Fünf von Uns",        desc = "Voll belegtes Dungeon mit Gilden-Party (5/5)", icon = "Interface\\Icons\\Spell_Holy_GreaterHeal",
        check = function()
            local p = GuildDeathLogDB and GuildDeathLogDB.guildProgress
            return p and p.full_guild_party_done
        end},
    {id = "g_wipe_back",   name = "Wir kämpfen weiter", desc = "Drei simultane Gildentode – und weitergemacht", icon = "Interface\\Icons\\Ability_Warrior_BattleShout",
        check = function()
            local p = GuildDeathLogDB and GuildDeathLogDB.guildProgress
            return p and p.survived_wipe
        end},

    -- ── Stärke der Gilde ────────────────────────────────────
    {id = "g_nodeaths_7d", name = "Sieben Tage Stille",  desc = "7 Tage kein einziger Gildentod",             icon = "Interface\\Icons\\Spell_Holy_DivinePurpose",
        check = function()
            local gd = GDL:GetGuildData()
            if not gd or not gd.deaths or #gd.deaths == 0 then return false end
            local newestTs = GetNewestDeathTimestamp(gd)
            if not newestTs then return false end
            return (time() - newestTs) >= 604800
        end},
    {id = "g_nodeaths_30d", name = "Eiserne Ordnung",   desc = "30 Tage kein einziger Gildentod",            icon = "Interface\\Icons\\Spell_Holy_AuraOfLight",
        check = function()
            local gd = GDL:GetGuildData()
            if not gd or not gd.deaths or #gd.deaths == 0 then return false end
            local newestTs = GetNewestDeathTimestamp(gd)
            if not newestTs then return false end
            return (time() - newestTs) >= 2592000
        end},
    {id = "g_10_online",   name = "Vollbeschäftigt",     desc = "10+ Gildenmitglieder gleichzeitig online",   icon = "Interface\\Icons\\INV_Misc_GroupNeedMore",
        check = function()
            if not IsInGuild() then return false end
            local online = 0
            for i = 1, GetNumGuildMembers() do
                local _, _, _, _, _, _, _, _, isOnline = GetGuildRosterInfo(i)
                if isOnline then online = online + 1 end
            end
            return online >= 10
        end},
    {id = "g_50_members",  name = "Grosse Koalition",   desc = "50+ Mitglieder in der Gilde",                icon = "Interface\\Icons\\Spell_Holy_Renew",
        check = function() return (GetNumGuildMembers() or 0) >= 50 end},
    {id = "g_5_maxlevel",  name = "Gilden-Veteranen",   desc = "5 Mitglieder auf Maximallevel",              icon = "Interface\\Icons\\INV_Sword_30",
        check = function()
            local maxLvl = GDL.isTBC and 70 or (GDL.isWrath and 80 or 60)
            local count  = 0
            for i = 1, GetNumGuildMembers() do
                local _, _, _, level = GetGuildRosterInfo(i)
                if (level or 0) >= maxLvl then count = count + 1 end
            end
            return count >= 5
        end},

    -- ── Geschichte der Gilde ────────────────────────────────
    {id = "g_old_30d",     name = "Alte Garde",          desc = "Gilde loggt seit 30+ Tagen aktiv",           icon = "Interface\\Icons\\INV_Misc_PocketWatch_01",
        check = function()
            local gd = GDL:GetGuildData()
            if not gd or not gd.deaths or #gd.deaths == 0 then return false end
            local oldestTs = GetOldestDeathTimestamp(gd)
            return oldestTs and (time() - oldestTs) >= 2592000
        end},
    {id = "g_100_deaths",  name = "Jahrhundertchronik", desc = "100 Gildentode dokumentiert",                icon = "Interface\\Icons\\INV_Misc_Book_17",
        check = function() local gd = GDL:GetGuildData(); return gd and gd.deaths and #gd.deaths >= 100 end},
    {id = "g_many_classes", name = "Alle Klassen",       desc = "Alle Klassen in der Gilde vertreten",        icon = "Interface\\Icons\\Achievement_Character_Human_Male",
        check = function()
            local classes = {}
            for i = 1, GetNumGuildMembers() do
                local _, _, _, _, _, _, _, _, _, _, classFileName = GetGuildRosterInfo(i)
                if classFileName and classFileName ~= "" then
                    classes[classFileName:upper()] = true
                end
            end
            local count = 0
            for _ in pairs(classes) do count = count + 1 end
            local needed = GDL.isTBC and 9 or 8
            return count >= needed
        end},
    {id = "g_wipe_3same",  name = "Schwarzer Tag",      desc = "3+ Gildenmitglieder starben am selben Tag",  icon = "Interface\\Icons\\Spell_Shadow_SoulLeech_1",
        check = function()
            local gd = GDL:GetGuildData()
            if not gd or not gd.deaths then return false end
            local dayBuckets = {}
            for _, d in ipairs(gd.deaths) do
                if d.timestamp then
                    local day = math.floor(d.timestamp / 86400)
                    dayBuckets[day] = (dayBuckets[day] or 0) + 1
                    if dayBuckets[day] >= 3 then return true end
                end
            end
            return false
        end},
}

-- ══════════════════════════════════════════════════════════════
-- HELPERS
-- ══════════════════════════════════════════════════════════════

function GetNewestDeathTimestamp(gd)  -- assign to forward-declared upvalue
    if not gd or not gd.deaths then return nil end
    local newest = 0
    for _, d in ipairs(gd.deaths) do
        if d.timestamp and d.timestamp > newest then
            newest = d.timestamp
        end
    end
    return newest > 0 and newest or nil
end

function GetOldestDeathTimestamp(gd)  -- assign to forward-declared upvalue
    if not gd or not gd.deaths then return nil end
    local oldest = math.huge
    for _, d in ipairs(gd.deaths) do
        if d.timestamp and d.timestamp < oldest then
            oldest = d.timestamp
        end
    end
    return oldest < math.huge and oldest or nil
end

-- ══════════════════════════════════════════════════════════════
-- DUNGEON/RAID TRACKING (für g_dungeon_3, g_raid_10 etc.)
-- ══════════════════════════════════════════════════════════════

local instanceEntry = nil  -- { zone, instanceType, guildCount, isFull, t }

local function CountGuildMembersInParty()
    local total, guildCount = 0, 0
    local Guild = GDL:GetModule("Guild")
    local numGroup = GetNumGroupMembers and GetNumGroupMembers() or 0
    if numGroup < 1 then
        -- Alleine
        return 1, IsInGuild() and 1 or 0
    end
    total = numGroup
    for i = 1, numGroup do
        local unit = (IsInRaid and IsInRaid()) and ("raid" .. i) or ("party" .. i)
        local name = UnitName(unit)
        if name and Guild and Guild:IsMember(name) then
            guildCount = guildCount + 1
        end
    end
    -- Spieler selbst hinzufügen
    if IsInGuild() then guildCount = guildCount + 1 end
    total = total + 1
    return total, guildCount
end

local function OnInstanceEnter()
    if not IsInGuild() then return end
    local inInst, instType = IsInInstance()
    if not inInst then return end

    local zone = GetRealZoneText() or GetZoneText() or ""
    local total, guildCount = CountGuildMembersInParty()
    local isFull = (instType == "party") and (total >= 5) and (guildCount >= 5)

    instanceEntry = {
        zone      = zone,
        instType  = instType,
        guildCount = guildCount,
        total      = total,
        isFull     = isFull,
        t          = time(),
    }

    GDL:Debug(string.format("Instance entered: %s [%s] %d/%d guildies%s",
        zone, instType or "?", guildCount, total, isFull and " FULL GUILD" or ""))

    -- Sofort tracken (nicht erst bei Verlassen, da HC-Tode das Verlassen triggern könnten)
    GuildDeathLogDB = GuildDeathLogDB or {}
    GuildDeathLogDB.guildProgress = GuildDeathLogDB.guildProgress or {}
    local p = GuildDeathLogDB.guildProgress

    if instType == "party" and guildCount >= 3 then
        p.dungeons_with_guild = (p.dungeons_with_guild or 0) + 1
        GDL:Debug("Guild dungeon tracked: " .. p.dungeons_with_guild)
    end
    if instType == "raid" and guildCount >= 10 then
        p.raids_with_guild = (p.raids_with_guild or 0) + 1
        GDL:Debug("Guild raid tracked: " .. p.raids_with_guild)
    end
    if isFull and not p.full_guild_party_done then
        p.full_guild_party_done = true
        GDL:Debug("Full guild party tracked!")
    end
end

-- Wipe-Erkennung: 3+ Gildentode innerhalb 120 Sekunden
local function CheckForWipe()
    local gd = GDL:GetGuildData()
    if not gd or not gd.deaths then return end
    local now = time()
    local recent = 0
    for i = #gd.deaths, math.max(1, #gd.deaths - 20), -1 do
        local d = gd.deaths[i]
        if d and d.timestamp and (now - d.timestamp) <= 120 then
            recent = recent + 1
        end
    end
    if recent >= 3 then
        GuildDeathLogDB = GuildDeathLogDB or {}
        GuildDeathLogDB.guildProgress = GuildDeathLogDB.guildProgress or {}
        GuildDeathLogDB.guildProgress.survived_wipe = true
        GDL:Debug("Guild: Wipe erkannt und überlebt (" .. recent .. " Tode in 120s)")
    end
end

-- ══════════════════════════════════════════════════════════════
-- CHARAKTER-KEY (GUID-basiert, identisch zu Milestones.lua)
-- Jeder Charakter bekommt seinen eigenen Errungenschafts-Slot.
-- Stirbt Herbert-1 (HC) und wird Herbert-2 erstellt, hat er eine
-- andere GUID -> sauberer, leerer Slot -> kein Erbe alter Daten.
-- ══════════════════════════════════════════════════════════════

function Achievements:GetCharacterKey()
    local guid = UnitGUID("player")
    if guid then
        GuildDeathLogDB.guidToName = GuildDeathLogDB.guidToName or {}
        local n = UnitName("player")  or "?"
        local r = GetRealmName()      or "?"
        GuildDeathLogDB.guidToName[guid] = n .. "-" .. r
        return guid
    end
    local n = UnitName("player")  or "Unknown"
    local r = GetRealmName()      or "Unknown"
    return n .. "-" .. r
end

-- Migriert altes Flat-Format {achievementId=timestamp} -> {[guid]={achievementId=timestamp}}
function Achievements:MigrateOldData(charKey)
    local db = GuildDeathLogDB.achievements
    if not db then return end
    local isFlat = false
    for _, v in pairs(db) do
        if type(v) == "number" then isFlat = true; break end
    end
    if not isFlat then return end
    GDL:Debug("Achievements: Migriere Flat-Daten -> per-Charakter GUID " .. charKey)
    GuildDeathLogDB.achievements = { [charKey] = db }
    self.unlocked = GuildDeathLogDB.achievements[charKey]
    GDL:Print("|cffFFD100" .. GDL:T("[Errungenschaften]") .. "|r " .. GDL:T("Datenmigration: Fortschritt diesem Charakter zugeordnet."))
end

-- HC-Reset: Falls Char-Level deutlich unter hoechstem Survivor-Meilenstein
-- -> neuer HC-Char nach Tod erkannt -> alte Daten archivieren, frisch starten.
function Achievements:CheckHCReset(charKey)
    local charData = GuildDeathLogDB.achievements[charKey]
    if not charData or not next(charData) then return end
    local currentLevel = UnitLevel("player") or 0
    if currentLevel <= 0 then
        C_Timer.After(8, function()
            local lvl = UnitLevel("player") or 0
            if lvl > 0 then self:CheckHCReset(charKey) end
        end)
        return
    end
    local survivorMap = { survivor_10=10, survivor_20=20, survivor_40=40, survivor_60=60, survivor_70=70 }
    local highestSurvivor = 0
    for id, lvl in pairs(survivorMap) do
        if charData[id] and lvl > highestSurvivor then highestSurvivor = lvl end
    end
    if highestSurvivor > 0 and currentLevel < (highestSurvivor - 10) then
        GDL:Debug("Achievements: HC-RESET! Survivor-Lvl: " .. highestSurvivor .. " > Aktuell: " .. currentLevel)
        GuildDeathLogDB.achievements[charKey .. "_dead_" .. time()] = charData
        GuildDeathLogDB.achievements[charKey] = {}
        self.unlocked = GuildDeathLogDB.achievements[charKey]
        GDL:Print("|cffFFD100" .. GDL:T("[Errungenschaften]") .. "|r " .. GDL:T("Neuer Charakter erkannt - Fortschritt zurueckgesetzt!"))
    end
end

-- ══════════════════════════════════════════════════════════════
-- INITIALISIERUNG
-- ══════════════════════════════════════════════════════════════

function Achievements:Initialize()
    GuildDeathLogDB = GuildDeathLogDB or {}
    GuildDeathLogDB.achievements   = GuildDeathLogDB.achievements   or {}
    GuildDeathLogDB.gAchievements  = GuildDeathLogDB.gAchievements  or {}
    GuildDeathLogDB.guildProgress  = GuildDeathLogDB.guildProgress  or {}

    self.unlocked  = {}   -- temporaer leer bis PLAYER_LOGIN die GUID liefert
    self.gUnlocked = GuildDeathLogDB.gAchievements
    self.charKey   = nil

    local function initCharData()
        local charKey = self:GetCharacterKey()
        self.charKey  = charKey
        self:MigrateOldData(charKey)
        GuildDeathLogDB.achievements[charKey] = GuildDeathLogDB.achievements[charKey] or {}
        self.unlocked = GuildDeathLogDB.achievements[charKey]
        self:CheckHCReset(charKey)
        GDL:Debug("Achievements: Charakter-Key = " .. charKey)
        C_Timer.After(3, function() self:CheckAll() end)
    end

    local eventFrame = CreateFrame("Frame")
    eventFrame:RegisterEvent("PLAYER_LOGIN")
    eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
    eventFrame:SetScript("OnEvent", function(_, event)
        if event == "PLAYER_LOGIN" then
            initCharData()
        elseif event == "PLAYER_ENTERING_WORLD" then
            C_Timer.After(1.5, function() OnInstanceEnter() end)
        end
    end)

    -- 30s-Check als echter Timer statt OnUpdate-Polling pro Frame
    C_Timer.NewTicker(30, function()
        if self.charKey then
            self:CheckAll()
            CheckForWipe()
        end
    end)
end

-- ══════════════════════════════════════════════════════════════
-- CHECK-FUNKTIONEN
-- ══════════════════════════════════════════════════════════════

function Achievements:CheckAll()
    if not self.charKey then return end

    -- Persönliche Errungenschaften (per Charakter / GUID)
    for _, ach in ipairs(ACHIEVEMENT_LIST) do
        if not self.unlocked[ach.id] then
            local ok, result = pcall(ach.check)
            if ok and result then
                self.unlocked[ach.id] = time()
                self:AnnounceUnlock(ach, false)
            end
        end
    end

    -- Gilden-Errungenschaften (account-/gilden-weit, kein Char-Key)
    for _, ach in ipairs(GUILD_ACHIEVEMENT_LIST) do
        if not self.gUnlocked[ach.id] then
            local ok, result = pcall(ach.check)
            if ok and result then
                self.gUnlocked[ach.id] = time()
                self:AnnounceUnlock(ach, true)
            end
        end
    end
end

function Achievements:AnnounceUnlock(ach, isGuild)
    local prefix = isGuild
        and "|cff7A5510" .. GDL:T("[Gilden-Errungenschaft]") .. "|r |cff1A5A24"
        or  "|cff7A5510" .. GDL:T("[Errungenschaft]") .. "|r |cff2A1806"
    local msg = prefix .. GDL:T(ach.name) .. "|r"
    pcall(function() UIErrorsFrame:AddMessage(msg) end)
    GDL:Print(msg .. " |cff888888– " .. GDL:T(ach.desc or "") .. "|r")
    PlaySound(8459)

    -- Cinematic Toast Popup
    local UI = GDL:GetModule("UI")
    if UI and UI.ShowAchievementToast then
        UI:ShowAchievementToast(ach, isGuild)
    end

    if isGuild then
        C_Timer.After(0.5, function()
            pcall(function()
                SendChatMessage(GDL:T("[Gilden-Errungenschaft] ") .. GDL:T(ach.name) .. " – " .. GDL:T(ach.desc or ""), "GUILD")
            end)
        end)
    end
end

-- ══════════════════════════════════════════════════════════════
-- ABFRAGE-API
-- ══════════════════════════════════════════════════════════════

function Achievements:GetAllAchievements()
    return ACHIEVEMENT_LIST
end

function Achievements:GetAllGuildAchievements()
    return GUILD_ACHIEVEMENT_LIST
end

function Achievements:IsUnlocked(achievementId)
    return self.unlocked[achievementId] ~= nil
end

function Achievements:IsGuildUnlocked(achievementId)
    return self.gUnlocked[achievementId] ~= nil
end

function Achievements:GetUnlockedCount()
    local count = 0
    for _ in pairs(self.unlocked) do count = count + 1 end
    return count, #ACHIEVEMENT_LIST
end

function Achievements:GetGuildUnlockedCount()
    local count = 0
    for _ in pairs(self.gUnlocked) do count = count + 1 end
    return count, #GUILD_ACHIEVEMENT_LIST
end

function Achievements:GetGuildPowerLevel()
    local totalLevel, alive = 0, 0
    local numMembers = GetNumGuildMembers()
    for i = 1, numMembers do
        local _, _, _, level = GetGuildRosterInfo(i)
        if level then totalLevel = totalLevel + level; alive = alive + 1 end
    end
    return totalLevel, alive
end

function Achievements:GetGuildMoraleScore()
    local totalLevel, alive = self:GetGuildPowerLevel()
    if alive < 1 then return 0, "neutral", "|cff4A3A28" end
    local Stats      = GDL:GetModule("Statistics")
    local summary    = Stats and Stats:GetSummary() or {}
    local weekDeaths = math.max(summary.thisWeek or 0, 1)
    local score      = math.floor((totalLevel / alive * alive) / weekDeaths)
    local label, color
    if     score >= 500 then label, color = "Eisern",       "|cff1A5A24"
    elseif score >= 250 then label, color = "Stark",        "|cff2A5A1A"
    elseif score >= 100 then label, color = "Stabil",       "|cff7A5510"
    elseif score >= 40  then label, color = "Erschüttert",  "|cff7A3A08"
    else                     label, color = "Gebrochen",    "|cffFF2222"
    end
    return score, label, color
end

GDL:RegisterModule("Achievements", Achievements)
