-- ══════════════════════════════════════════════════════════════════════════════
-- LASTHOURS.LUA — "Die letzten Stunden" Charakter-Journal
-- Modul: LastHours | Interface: 11508
--
-- Features:
--   • Trackt Einlogg-Zeit, Zonen-Besuche, Quests während der Session
--   • Bei Tod: rekonstruiert den "letzten Tag" als Erzählung im Buch
--   • Format: "Thorvald loggte um 19:34 ein, bereiste Westfall und
--     Dunkler Wald, und fiel um 21:17 in Stranglethorn..."
--   • Speichert letzten Journal-Eintrag pro Charakter in SavedVariables
-- ══════════════════════════════════════════════════════════════════════════════

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

local UnitName        = UnitName
local UnitGUID        = UnitGUID
local UnitLevel       = UnitLevel
local GetRealZoneText = GetRealZoneText
local GetZoneText     = GetZoneText
local date            = date
local time            = time
local math_floor      = math.floor

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

-- ── Session-State (nur RAM, nicht SavedVariables) ─────────────────────────────
local _session = {
    loginTime    = 0,
    zones        = {},        -- {zoneName, entryTime} chronologisch
    currentZone  = nil,
    questsDone   = 0,
    questsStart  = 0,
    mobsKilled   = 0,         -- approximiert via UNIT_DIED für feindliche Units
    active       = false,
}

-- ── Hilfsfunktionen ───────────────────────────────────────────────────────────
local function FormatTime(ts)
    return date("%H:%M", ts)
end

local function FormatDuration(seconds)
    local h = math_floor(seconds / 3600)
    local m = math_floor((seconds % 3600) / 60)
    if h > 0 then
        return string.format("%dh %dm", h, m)
    else
        return string.format("%dm", m)
    end
end

local function GetUniqueZones(limit)
    local seen = {}
    local result = {}
    for _, entry in ipairs(_session.zones) do
        if not seen[entry.zone] then
            seen[entry.zone] = true
            table.insert(result, entry.zone)
            if limit and #result >= limit then break end
        end
    end
    return result
end

-- ── Zone wechsel tracken ──────────────────────────────────────────────────────
local function OnZoneChanged()
    local zone = GetRealZoneText() or GetZoneText()
    if not zone or zone == "" then return end
    if zone == _session.currentZone then return end

    local now = time()
    _session.currentZone = zone
    table.insert(_session.zones, { zone = zone, entryTime = now })

    -- Nicht zu lang werden lassen (max 50 Einträge)
    if #_session.zones > 50 then
        table.remove(_session.zones, 1)
    end
end

-- ── Quests tracken ────────────────────────────────────────────────────────────
local function OnQuestComplete()
    _session.questsDone = _session.questsDone + 1
end

-- ── Kills tracken (approximiert) ─────────────────────────────────────────────
-- Wir zählen feindliche UNIT_DIED Events im CombatLog
local function OnCombatLog(self, event)
    -- PARTY_KILL feuert beim Todesstoss durch den Spieler (oder dessen Pet)
    -- und traegt einen sourceGUID. UNIT_DIED hat KEINEN Source — der alte
    -- Check "sourceGUID == player" bei UNIT_DIED war immer false.
    local _, subevent, _, sourceGUID, _, _, _, _, _, destFlags = CombatLogGetCurrentEventInfo()
    if subevent ~= "PARTY_KILL" then return end
    if not sourceGUID or sourceGUID ~= UnitGUID("player") then return end
    -- Nur feindliche NPCs (nicht Spieler, nicht Pets)
    if bit.band(destFlags or 0, 0x0040) == 0x0040 then  -- COMBATLOG_OBJECT_TYPE_NPC
        _session.mobsKilled = _session.mobsKilled + 1
    end
end

-- ── Journal-Text generieren ───────────────────────────────────────────────────
function LastHours:GenerateJournal(deathData)
    local name       = deathData and deathData.name or UnitName("player")
    local loginTime  = _session.loginTime
    local deathTime  = deathData and deathData.timestamp or time()
    local sessionLen = deathTime - loginTime

    -- Zonen zusammenfassen
    local zones      = GetUniqueZones(5)
    local zoneStr    = ""
    if #zones == 0 then
        zoneStr = deathData and deathData.zone or "Azeroth"
    elseif #zones == 1 then
        zoneStr = zones[1]
    elseif #zones == 2 then
        zoneStr = zones[1] .. " und " .. zones[2]
    else
        local last = table.remove(zones)
        zoneStr = table.concat(zones, ", ") .. " sowie " .. last
    end

    -- Einlog-Zeit formatieren
    local loginStr = loginTime > 0 and FormatTime(loginTime) or "unbekannter Zeit"
    local deathStr = FormatTime(deathTime)
    local duration = FormatDuration(math.max(0, sessionLen))

    -- Killer
    local killer  = deathData and deathData.killer or "unbekannter Bedrohung"
    local zone    = deathData and deathData.zone or (zones[#zones] or "unbekannter Zone")

    -- Koordinaten
    local coordStr = ""
    if deathData and deathData.coordX and deathData.coordY then
        coordStr = string.format(" (%.1f, %.1f)", deathData.coordX, deathData.coordY)
    end

    -- Quests-Info
    local questStr = ""
    if _session.questsDone > 0 then
        questStr = string.format(
            " %s erfüllte in dieser Zeit %d %s.",
            name, _session.questsDone,
            _session.questsDone == 1 and "Auftrag" or "Aufträge"
        )
    end

    -- Mobs-Info
    local mobStr = ""
    if _session.mobsKilled > 10 then
        mobStr = string.format(
            " %d Feinde fielen vor %ss Klinge.",
            _session.mobsKilled, name
        )
    end

    -- Journal zusammensetzen
    local text = string.format(
        "%s loggte um %s ein und bereiste %s.%s%s " ..
        "Nach %s Uhr fiel %s um %s Uhr in %s%s durch %s.",
        name, loginStr, zoneStr,
        questStr, mobStr,
        duration, name, deathStr, zone, coordStr, killer
    )

    return {
        text        = text,
        loginTime   = loginTime,
        deathTime   = deathTime,
        sessionSecs = sessionLen,
        zones       = GetUniqueZones(10),
        questsDone  = _session.questsDone,
        mobsKilled  = _session.mobsKilled,
    }
end

-- ── Journal beim Tod speichern ────────────────────────────────────────────────
function LastHours:OnDeath(deathData)
    if not deathData then return end

    local journal = LastHours:GenerateJournal(deathData)

    -- In SavedVariables speichern
    local db = GuildDeathLogDB
    if not db then return end
    if not db.journals then db.journals = {} end

    local charKey = (UnitName("player") or "?") .. ":" .. (UnitGUID("player") or ""):sub(-8)
    db.journals[charKey] = {
        journal   = journal,
        timestamp = time(),
        version   = 1,
    }

    -- Auch in deathData einpflegen (für das Popup und die DB)
    deathData.journal = journal

    -- Session resetten für nächste Runde
    _session.loginTime  = 0
    _session.zones      = {}
    _session.currentZone = nil
    _session.questsDone = 0
    _session.mobsKilled = 0
    _session.active     = false
end

-- ── Journal abrufen ───────────────────────────────────────────────────────────
-- Für UIChapters: Journal eines toten Charakters aus DB
function LastHours:GetJournal(charName, deathTimestamp)
    local db = GuildDeathLogDB
    if not (db and db.journals) then return nil end

    -- Versuche via charKey zu finden
    for key, entry in pairs(db.journals) do
        if key:match("^" .. charName .. ":") then
            -- Prüfe ob Timestamp ungefähr stimmt (±5 Minuten)
            if not deathTimestamp or math.abs((entry.timestamp or 0) - deathTimestamp) < 300 then
                return entry.journal
            end
        end
    end
    return nil
end

-- ── Buchseiten-Rendering ──────────────────────────────────────────────────────
function LastHours:RenderForDeath(scrollChild, yOffset, deathEntry)
    local name = deathEntry and deathEntry.name or "Unbekannt"
    local journal = LastHours:GetJournal(name, deathEntry and deathEntry.timestamp)

    if not journal then
        local txt = scrollChild:CreateFontString(nil, "OVERLAY")
        txt:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
        txt:SetTextColor(0.5, 0.5, 0.5)
        txt:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 20, yOffset)
        txt:SetText("|cff4A3A28" .. GDL:T("Keine Aufzeichnungen für diese Session verfügbar.") .. "|r")
        return yOffset - 20
    end

    -- Abschnitts-Titel
    local title = scrollChild:CreateFontString(nil, "OVERLAY")
    title:SetFont("Fonts\\MORPHEUS.TTF", 14, "")
    title:SetTextColor(0.26, 0.18, 0.06)
    title:SetShadowOffset(1, -1)
    title:SetShadowColor(0, 0, 0, 0.7)
    title:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 20, yOffset)
    title:SetText(GDL:T("Die letzten Stunden"))
    yOffset = yOffset - 26

    -- Journal-Text (umgebrochen)
    local text = scrollChild:CreateFontString(nil, "OVERLAY")
    text:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
    text:SetTextColor(0.26, 0.18, 0.08)
    text:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 20, yOffset)
    text:SetWidth(300)
    text:SetJustifyH("LEFT")
    text:SetText(journal.text or "")
    text:SetSpacing(3)
    yOffset = yOffset - text:GetHeight() - 10

    -- Session-Stats
    if journal.sessionSecs and journal.sessionSecs > 0 then
        local stats = scrollChild:CreateFontString(nil, "OVERLAY")
        stats:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
        stats:SetTextColor(0.55, 0.50, 0.40)
        stats:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 20, yOffset)
        stats:SetText(string.format(
            "Session: %s  |  Aufträge: %d  |  Feinde: %d",
            FormatDuration(journal.sessionSecs),
            journal.questsDone or 0,
            journal.mobsKilled or 0
        ))
        yOffset = yOffset - 18
    end

    return yOffset
end

-- ── Initialisierung ───────────────────────────────────────────────────────────
function LastHours:Initialize()
    if LastHours._initialized then return end
    LastHours._initialized = true
    local evFrame = CreateFrame("Frame")

    -- Events registrieren
    evFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
    evFrame:RegisterEvent("ZONE_CHANGED")
    evFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
    evFrame:RegisterEvent("ZONE_CHANGED_INDOORS")
    evFrame:RegisterEvent("QUEST_COMPLETE")

    evFrame:SetScript("OnEvent", function(self, event, ...)
        if event == "PLAYER_ENTERING_WORLD" then
            _session.loginTime   = time()
            _session.active      = true
            _session.questsStart = GetNumQuestsCompleted and GetNumQuestsCompleted() or 0

            -- Startzone
            C_Timer.After(2, function()
                OnZoneChanged()
            end)

        elseif event == "ZONE_CHANGED" or event == "ZONE_CHANGED_NEW_AREA" or event == "ZONE_CHANGED_INDOORS" then
            if _session.active then
                C_Timer.After(0.5, OnZoneChanged)
            end

        elseif event == "QUEST_COMPLETE" then
            if _session.active then
                OnQuestComplete()
            end
        end
    end)

    -- CombatLog für Kills
    local clFrame = CreateFrame("Frame")
    clFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
    clFrame:SetScript("OnEvent", OnCombatLog)

    -- SavedVariables initialisieren
    C_Timer.After(1, function()
        local db = GuildDeathLogDB
        if db and not db.journals then
            db.journals = {}
        end
    end)
end

LastHours:Initialize()
