-- ══════════════════════════════════════════════════════════════════════════════
-- DNLBRIDGE.LUA v2.0 — Vollständig verdrahtet mit allen neuen Modulen
-- Neu in v2: PlayedTime, DeathScreenshot, SurvivalStreak, LastHours, DeathHeatmap
-- ══════════════════════════════════════════════════════════════════════════════

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

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

local DNLBridge = {}
GDL:RegisterModule("DNLBridge", DNLBridge)

DNLBridge.dnlActive       = false
DNLBridge.fallbackActive  = false
DNLBridge.deathsThisSession = 0

-- ── Koordinaten bei eigenem Tod erfassen ──────────────────────────────────────
local function CaptureDeathCoords()
    local mapId = C_Map and C_Map.GetBestMapForUnit and C_Map.GetBestMapForUnit("player")
    if not mapId then return nil, nil end

    local pos = C_Map.GetPlayerMapPosition(mapId, "player")
    if not pos then return mapId, nil end

    return mapId, { x = pos.x, y = pos.y }
end

-- ── Klassen/Race Lookup ───────────────────────────────────────────────────────
local CLASS_ID_TO_NAME = {
    [1]="WARRIOR",[2]="PALADIN",[3]="HUNTER",[4]="ROGUE",[5]="PRIEST",
    [7]="SHAMAN",[8]="MAGE",[9]="WARLOCK",[11]="DRUID",
}
local RACE_ID_TO_NAME = {
    [1]="Mensch",[2]="Orc",[3]="Zwerg",[4]="Nachtelf",[5]="Untoter",
    [6]="Tauren",[7]="Gnom",[8]="Troll",[10]="Blutelf",[11]="Draenei",
}

-- ── Zonen-Name auflösen ───────────────────────────────────────────────────────
local function GetZoneName(map_id, instance_id)
    if instance_id and instance_id > 0 then
        local DNL = _G["DeathNotificationLib"]
        if DNL and DNL.INSTANCE_NAMES and DNL.INSTANCE_NAMES[instance_id] then
            return DNL.INSTANCE_NAMES[instance_id]
        end
        if C_Map and C_Map.GetMapInfo then
            local info = C_Map.GetMapInfo(instance_id)
            if info then return info.name end
        end
        return "Instanz #" .. instance_id
    end
    if map_id and map_id > 0 then
        if C_Map and C_Map.GetMapInfo then
            local info = C_Map.GetMapInfo(map_id)
            if info then return info.name end
        end
    end
    return GetRealZoneText() or "Unbekannte Zone"
end

-- ── Killer-Name auflösen ──────────────────────────────────────────────────────
local function ResolveKiller(source_id)
    if not source_id or source_id == 0 or source_id < 0 then return "Unbekannt", nil end
    local KT = GDL:GetModule("KillerTracker")
    if KT and KT.ResolveNpcName then
        local n = KT:ResolveNpcName(source_id)
        if n then return n, source_id end
    end
    local DNL = _G["DeathNotificationLib"]
    if DNL and DNL.NPC_NAMES and DNL.NPC_NAMES[source_id] then
        return DNL.NPC_NAMES[source_id], source_id
    end
    return "Kreatur #" .. source_id, source_id
end

-- ── DNL PlayerData → GDL deathData ───────────────────────────────────────────
local function ConvertPlayerData(player_data, checksum, peer_report, in_guild)
    if not player_data or not player_data.name then return nil end

    local killerName, killerId = ResolveKiller(player_data.source_id)
    local zoneName = GetZoneName(player_data.map_id, player_data.instance_id)

    return {
        name       = player_data.name,
        level      = player_data.level,
        classId    = player_data.class_id,
        className  = CLASS_ID_TO_NAME[player_data.class_id],
        raceId     = player_data.race_id,
        raceName   = RACE_ID_TO_NAME[player_data.race_id],
        guild      = player_data.guild,
        zone       = zoneName,
        killer     = killerName,
        killerId   = killerId,
        timestamp  = player_data.date or time(),
        lastWords  = player_data.last_words,
        mapId      = player_data.map_id,
        mapPos     = player_data.map_pos,
        instanceId = player_data.instance_id,
        peerReport = peer_report or 0,
        inGuild    = in_guild or false,
        checksum   = checksum,
        _fromDNL   = true,
        isSelf     = (player_data.name == UnitName("player")),
    }
end

-- ══════════════════════════════════════════════════════════════════════════════
-- HAUPT-HANDLER — alle neuen Module hier verdrahtet
-- ══════════════════════════════════════════════════════════════════════════════
function DNLBridge:HandleDeath(deathData)
    if not deathData then return end
    -- Dedup an der Wurzel: DeathNotificationLib feuert HookOnNewAddonEntry oft
    -- MEHRFACH pro Tod (Peer-Bestaetigungen). Ohne Guard laeuft die ganze
    -- Pipeline (Overlay/Announce/Broadcast) mehrfach -> Doppel-Popup + Doppel-Push.
    -- Pro Spieler genau EINMAL innerhalb von 60s (ein Toter stirbt nicht erneut).
    do
        local nm = deathData.name and (strsplit("-", deathData.name)):lower() or "?"
        local now = GetTime()
        DNLBridge._handled = DNLBridge._handled or {}
        local last = DNLBridge._handled[nm]
        if last and (now - last) < 60 then
            if GDL.Log then GDL:Log("DEATH", "DNL Mehrfach-Feuer unterdrueckt: " .. nm) end
            return
        end
        DNLBridge._handled[nm] = now
    end
    DNLBridge.deathsThisSession = DNLBridge.deathsThisSession + 1
    if GDL.Log then GDL:Log("DEATH", "1) HandleDeath OK: " .. (deathData.name or "?") .. " self=" .. tostring(deathData.isSelf) .. " src=" .. tostring(deathData.source)) end

    -- ── Koordinaten für eigenen Tod ergänzen ─────────────────────────────────
    if deathData.isSelf and not deathData.mapPos then
        local mapId, pos = CaptureDeathCoords()
        deathData.mapId  = mapId
        deathData.mapPos = pos
    end

    -- mapPos → coordX/coordY normalisieren (DeathHeatmap liest coordX/coordY)
    if deathData.mapPos then
        deathData.coordX = (deathData.mapPos.x or 0) * 100
        deathData.coordY = (deathData.mapPos.y or 0) * 100
    end

    -- ── 1. PlayedTime + Koordinaten + Screenshot anreichern ──────────────────
    -- PlayedTime:EnrichDeathData ist async → Rest der Pipeline im Callback
    local PT = GDL:GetModule("PlayedTime")
    if PT and deathData.isSelf then
        PT:EnrichDeathData(deathData, function(enriched)
            DNLBridge:_PipelineStep2(enriched)
        end)
    else
        -- Fremder Tod: synchron weiter
        DNLBridge:_PipelineStep2(deathData)
    end
end

-- ── Pipeline Schritt 2: nach PlayedTime-Anreicherung ─────────────────────────
function DNLBridge:_PipelineStep2(deathData)
    -- ── 2. Survival-Streak beenden ────────────────────────────────────────────
    local SS = GDL:GetModule("SurvivalStreak")
    if SS then SS:OnDeath(deathData) end

    -- ── 3. LastHours Snapshot (letzte Stunden vor Tod protokollieren) ─────────
    local LH = GDL:GetModule("LastHours")
    if LH then LH:OnDeath(deathData) end

    -- ── 4. Persistentes Speichern via Sync:RecordLocalDeath ───────────────────
    -- RecordLocalDeath: speichert, broadcastet, aktualisiert UI
    local Sync = GDL:GetModule("Sync")
    if Sync then
        if GDL.Log then GDL:Log("DEATH", "2) PipelineStep2 -> RecordLocalDeath: " .. (deathData.name or "?")) end
        Sync:RecordLocalDeath(deathData)
    else
        if GDL.Log then GDL:Log("DEATH", "2!) Sync-Modul FEHLT -> kein RecordLocalDeath") end
    end

    -- ── 5. Heatmap-Pin aktualisieren wenn Karte offen ──────────────────────────
    if WorldMapFrame and WorldMapFrame:IsShown() and deathData.coordX then
        local HM = GDL:GetModule("DeathHeatmap")
        if HM and HM:IsEnabled() then
            C_Timer.After(0.1, function() HM:UpdateMap() end)
        end
    end
    -- Overlay + Sound wird von Sync:RecordLocalDeath übernommen

    if GDL.debugMode then
        GDL:Print(string.format(
            "|cff00CCFF[DNL]|r %s Lv%s von %s in %s%s",
            deathData.name or "?",
            tostring(deathData.level or "?"),
            deathData.killer or "?",
            deathData.zone or "?",
            deathData.playedTotalStr and (" | gespielt: " .. deathData.playedTotalStr) or ""
        ))
    end
end

-- ── DNL anbinden ──────────────────────────────────────────────────────────────
function DNLBridge:AttachToDNL()
    local DNL = _G["DeathNotificationLib"]
    if not DNL then return false end

    DNL.AttachAddon({
        name          = "GuildDeathLog",
        tag           = "GDL",
        isUnitTracked = function(unit) return UnitIsPlayer and UnitIsPlayer(unit) end,
        db            = GuildDeathLogDB and GuildDeathLogDB.deaths or nil,
        db_map        = GuildDeathLogDB and GuildDeathLogDB.deathMap or nil,
    })

    DNL.HookOnNewAddonEntry("GuildDeathLog", function(player_data, checksum, peer_report, in_guild, source)
        local deathData = ConvertPlayerData(player_data, checksum, peer_report, in_guild)
        if deathData then DNLBridge:HandleDeath(deathData) end
    end)

    DNLBridge.dnlActive = true
    GDL:Print("|cff00FF00[DNLBridge]|r DeathNotificationLib angebunden ✓")
    return true
end

-- ── Fallback-Detektor ─────────────────────────────────────────────────────────
local FALLBACK_DEDUP = {}
local FALLBACK_ORDER = {}
local ENV_DE = {
    ["Drowning"]="Ertrunken",["Falling"]="Absturz",["Fatigue"]="Erschöpfung",
    ["Fire"]="Feuer",["Lava"]="Lava",["Slime"]="Schleim",
}
local _lastEnv = {}

local function FallbackDedup(name, ts)
    local key = name .. ":" .. math_floor(ts / 30)
    if FALLBACK_DEDUP[key] then return true end
    if #FALLBACK_ORDER >= 32 then FALLBACK_DEDUP[table.remove(FALLBACK_ORDER, 1)] = nil end
    FALLBACK_DEDUP[key] = true
    table.insert(FALLBACK_ORDER, key)
    return false
end

local function FallbackCombatLog()
    local _, subevent, _, sGUID, sName, _, _, dGUID, dName, dFlags, _, envType = CombatLogGetCurrentEventInfo()
    if subevent == "ENVIRONMENTAL_DAMAGE" then
        if dGUID then _lastEnv[dGUID] = { t = ENV_DE[envType] or envType, ts = time() } end
        return
    end
    if subevent ~= "UNIT_DIED" and subevent ~= "UNIT_DESTROYED" then return end
    if not dName or dName == "" then return end
    if dFlags and bit.band(dFlags, 0x0400) == 0 and dGUID ~= UnitGUID("player") then return end

    local now = time()
    if FallbackDedup(dName, now) then return end

    -- Killer bestimmen: sGUID=Player → PvP-Kill, sGUID=Creature → NPC
    local killerIsPlayer = sGUID and sGUID:sub(1,6) == "Player"
    local killer = killerIsPlayer and (sName or "Spieler") or (sName or "Unbekannt")
    local env = _lastEnv[dGUID]
    if env and (now - env.ts) < 3 then killer = env.t end
    _lastEnv[dGUID] = nil

    local mapId, mapPos = CaptureDeathCoords()
    local deathData = {
        name = dName, level = (dGUID == UnitGUID("player")) and UnitLevel("player") or nil,
        zone = GetRealZoneText() or "?", killer = killer, timestamp = now,
        isSelf = (dGUID == UnitGUID("player")), peerReport = 0,
        mapId = mapId, mapPos = mapPos, _fromFallback = true,
    }
    if deathData.isSelf then
        local _, cId = UnitClass("player")
        deathData.classId = cId
    end
    DNLBridge:HandleDeath(deathData)
end

local DEATH_PATTERNS = {
    "^(.+) wurde von (.+) get.+ttet%.", "^(.+) ist gestorben%.",
    "^(.+) has been slain by (.+)%.",   "^(.+) has died%.",
}
local function FallbackSystemMsg(self, event, msg)
    if not msg then return end
    for _, pat in ipairs(DEATH_PATTERNS) do
        local name, killer = msg:match(pat)
        if name then
            if name == UnitName("player") then return end
            local now = time()
            if FallbackDedup(name, now) then return end
            DNLBridge:HandleDeath({
                name=name, killer=killer or "Unbekannt",
                zone=GetRealZoneText() or "?", timestamp=now,
                isSelf=false, peerReport=0, _fromFallback=true,
            })
            return
        end
    end
end

function DNLBridge:StartFallback()
    local f1 = CreateFrame("Frame")
    f1:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
    f1:SetScript("OnEvent", FallbackCombatLog)

    local f2 = CreateFrame("Frame")
    f2:RegisterEvent("CHAT_MSG_SYSTEM")
    f2:SetScript("OnEvent", FallbackSystemMsg)

    DNLBridge.fallbackActive = true
    GDL:Print("|cffFFCC00[DNLBridge]|r " .. GDL:T("Fallback-Detektor aktiv (DNL fehlt)"))
end

-- ── Public API ────────────────────────────────────────────────────────────────
function DNLBridge:SendTestDeath()
    local classes = {1,2,3,4,5,7,8,9,11}
    local mapId, mapPos = CaptureDeathCoords()
    DNLBridge:HandleDeath({
        name = "TestHeld-" .. math.random(1000, 9999),
        level = math.random(1, 60),
        classId = classes[math.random(1, #classes)],
        zone = GetRealZoneText() or "Testzone",
        killer = "Bogenmeister Kolt",
        killerId = 6226,  -- Kolt hat eine echte ID
        timestamp = time(),
        lastWords = "Wer hätte das gedacht...",
        peerReport = 0, isSelf = false,
        inGuild = IsInGuild(),
        mapId = mapId, mapPos = mapPos,
        _test = true,
    })
end

function DNLBridge:PrintStatus()
    local status = DNLBridge.dnlActive and ("|cff00FF00" .. GDL:T("AKTIV (DNL)") .. "|r")
                or (DNLBridge.fallbackActive and "|cffFFCC00FALLBACK|r" or ("|cffFF4444" .. GDL:T("INAKTIV") .. "|r"))
    GDL:Print("[DNLBridge] " .. GDL:T("Status: ") .. status)
    GDL:Print("  " .. GDL:T("Tode diese Session: ") .. "|cffFFD100" .. DNLBridge.deathsThisSession .. "|r")
    local HM = GDL:GetModule("DeathHeatmap")
    if HM then
        local d, m = HM.GetStats()
        GDL:Print(string.format("  Gespeicherte Todeskoordinaten: |cffFFD100%d|r auf |cffFFD100%d|r Karten", d, m))
    end
end

-- ── Initialisierung ───────────────────────────────────────────────────────────
function DNLBridge:Initialize()
    if DNLBridge._initialized then return end
    DNLBridge._initialized = true
    local f = CreateFrame("Frame")
    f:RegisterEvent("PLAYER_LOGIN")
    f:SetScript("OnEvent", function(self, event)
        self:UnregisterEvent("PLAYER_LOGIN")
        if not DNLBridge:AttachToDNL() then
            DNLBridge:StartFallback()
        end
    end)
end

DNLBridge:Initialize()
