-- ══════════════════════════════════════════════════════════════════════════════
-- DEATHHEATMAP.LUA — Todes-Heatmap auf der Weltkarte
-- Modul: DeathHeatmap | Interface: 11508
--
-- FIX v9.3.1: Kein Doppelpin mit MapMarkers.lua mehr.
--   • Einzelne Tode (cluster.count == 1): KEIN Pin zeichnen wenn MapMarkers aktiv
--   • Cluster (2+ Tode nahe beieinander): immer zeigen (MapMarkers macht keine Cluster)
--   • Lebende Spieler (aktuell online) werden aus der Heatmap gefiltert
-- ══════════════════════════════════════════════════════════════════════════════

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

local C_Map      = C_Map
local math_floor = math.floor
local math_sqrt  = math.sqrt
local UnitName   = UnitName

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

-- ── Konstanten ────────────────────────────────────────────────────────────────
local PIN_CLUSTER_RADIUS = 0.03   -- 3% der Map = gleicher Cluster
local PIN_SIZE_BASE      = 12
local PIN_SIZE_MAX       = 26
local MAX_PINS           = 150

-- ── Farben nach Cluster-Dichte ────────────────────────────────────────────────
local DENSITY_COLORS = {
    { min = 2,  max = 4,   r = 1.0, g = 0.85, b = 0.0, a = 0.85 },  -- Gelb  (2–4)
    { min = 5,  max = 9,   r = 1.0, g = 0.50, b = 0.0, a = 0.88 },  -- Orange (5–9)
    { min = 10, max = 24,  r = 0.9, g = 0.10, b = 0.0, a = 0.92 },  -- Rot   (10–24)
    { min = 25, max = 999, r = 0.6, g = 0.00, b = 0.8, a = 0.95 },  -- Lila  (25+)
}

local function GetDensityColor(count)
    for _, c in ipairs(DENSITY_COLORS) do
        if count >= c.min and count <= c.max then
            return c.r, c.g, c.b, c.a
        end
    end
    return 1.0, 0.5, 0.0, 0.88
end

local function GetPinSize(count)
    return math.min(PIN_SIZE_BASE + math_floor(math_sqrt(count) * 4), PIN_SIZE_MAX)
end

-- ── MapMarkers aktiv? ─────────────────────────────────────────────────────────
-- Wenn MapMarkers.lua läuft, überlässt DeathHeatmap ihr die Einzel-Pins.
-- Wir zeigen NUR Cluster (2+ Tode).
local function MapMarkersActive()
    local MM = GDL:GetModule("MapMarkers")
    return MM ~= nil
end

-- ── Lebende Spieler-Namen (online im gleichen Realm) ─────────────────────────
-- Wird beim Map-Update kurz gecacht um GuildRoster nicht 60x/s abzufragen
local _livePlayerCache    = {}
local _liveCacheTimestamp = 0
local LIVE_CACHE_TTL      = 10  -- Sekunden

local function GetLivePlayers()
    local now = time()
    if (now - _liveCacheTimestamp) < LIVE_CACHE_TTL then
        return _livePlayerCache
    end

    local live = {}

    -- Eigener Charakter
    local me = UnitName("player")
    if me then live[me] = true end

    -- Guild-Roster (online-Mitglieder)
    if IsInGuild and IsInGuild() then
        local n = GetNumGuildMembers and GetNumGuildMembers() or 0
        for i = 1, n do
            local name, _, _, _, _, _, _, _, online = GetGuildRosterInfo(i)
            if online and name then
                -- Name kann "Name-Realm" sein → nur ersten Teil nehmen
                local shortName = name:match("^([^%-]+)") or name
                live[shortName] = true
            end
        end
    end

    -- Party/Raid
    for i = 1, GetNumGroupMembers() do
        local unit = "party" .. i
        local n = UnitName(unit)
        if n then live[n] = true end
    end

    _livePlayerCache    = live
    _liveCacheTimestamp = now
    return live
end

-- ── Todes-Daten für aktuelle Karte ────────────────────────────────────────────
local function GetDeathsForMap(mapId)
    local db = GuildDeathLogDB
    if not (db and db.deaths) then return {} end

    local livePlayers = GetLivePlayers()
    local result      = {}

    for _, death in ipairs(db.deaths) do
        -- Kein Pin für aktuell lebende Spieler
        if death.mapId == mapId and death.coordX and death.coordY then
            local name = death.name or ""
            local shortName = name:match("^([^%-]+)") or name
            if not livePlayers[shortName] then
                table.insert(result, {
                    x      = death.coordX / 100,
                    y      = death.coordY / 100,
                    name   = death.name,
                    killer = death.killer,
                    ts     = death.timestamp,
                    zone   = death.zone,
                })
            end
        end
    end
    return result
end

-- ── Cluster-Algorithmus ────────────────────────────────────────────────────────
local function ClusterDeaths(deaths)
    local clusters = {}
    local assigned = {}
    for i, death in ipairs(deaths) do
        if not assigned[i] then
            local cluster = { x=death.x, y=death.y, count=1, deaths={death} }
            assigned[i] = true
            for j = i+1, #deaths do
                if not assigned[j] then
                    local dx   = deaths[j].x - death.x
                    local dy   = deaths[j].y - death.y
                    local dist = math_sqrt(dx*dx + dy*dy)
                    if dist <= PIN_CLUSTER_RADIUS then
                        cluster.count = cluster.count + 1
                        table.insert(cluster.deaths, deaths[j])
                        cluster.x = (cluster.x*(cluster.count-1) + deaths[j].x) / cluster.count
                        cluster.y = (cluster.y*(cluster.count-1) + deaths[j].y) / cluster.count
                        assigned[j] = true
                    end
                end
            end
            table.insert(clusters, cluster)
        end
    end
    return clusters
end

-- ── Pin-Pool ──────────────────────────────────────────────────────────────────
local _pinPool   = {}
local _activePins = {}

local function GetMapCanvas()
    -- Classic WorldMapFrame hat ScrollContainer.Child
    if WorldMapFrame and WorldMapFrame.ScrollContainer and WorldMapFrame.ScrollContainer.Child then
        return WorldMapFrame.ScrollContainer.Child
    end
    return WorldMapFrame
end

local function GetPin()
    local pin = table.remove(_pinPool)
    if not pin then
        local canvas = GetMapCanvas()
        pin = CreateFrame("Button", nil, canvas)
        pin:SetSize(16, 16)
        pin:EnableMouse(true)

        -- Äußerer Glow-Ring
        local ring = pin:CreateTexture(nil, "BACKGROUND")
        ring:SetAllPoints()
        ring:SetColorTexture(1, 0.3, 0.0, 0.25)
        pin._ring = ring

        -- Kern-Farbe
        local dot = pin:CreateTexture(nil, "ARTWORK")
        dot:SetSize(10, 10)
        dot:SetPoint("CENTER")
        dot:SetColorTexture(1, 0.4, 0.0, 0.9)
        pin._dot = dot

        -- Zahl drüber
        local count = pin:CreateFontString(nil, "OVERLAY")
        count:SetFont("Fonts\\FRIZQT__.TTF", 8, "OUTLINE")
        count:SetPoint("CENTER", pin, "CENTER", 0, 0)
        count:SetTextColor(1, 1, 1)
        pin._count = count

        -- Tooltip
        pin:SetScript("OnEnter", function(self)
            if not self._cluster then return end
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:SetText(
                string.format("|cffFF6633" .. GDL:T("%d Todesorte") .. "|r", self._cluster.count), 1, 1, 1)
            GameTooltip:AddLine(" ")
            local shown = 0
            for _, d in ipairs(self._cluster.deaths) do
                if shown >= 5 then
                    GameTooltip:AddLine(string.format(
                        "|cff888888... und %d weitere|r", self._cluster.count - shown))
                    break
                end
                local timeStr = date("%d.%m. %H:%M", d.ts or 0)
                GameTooltip:AddLine(string.format(
                    "|cffFFFFFF%s|r  durch |cffFF8C00%s|r", d.name or "?", d.killer or "?"), 1,1,1)
                GameTooltip:AddLine(string.format("|cff666666  %s  —  %s|r",
                    timeStr, d.zone or "?"))
                shown = shown + 1
            end
            GameTooltip:Show()
        end)
        pin:SetScript("OnLeave", function() GameTooltip:Hide() end)
    end
    table.insert(_activePins, pin)
    return pin
end

local function RecycleAllPins()
    for _, pin in ipairs(_activePins) do
        pin:Hide()
        table.insert(_pinPool, pin)
    end
    _activePins = {}
end

-- ── Karte aktualisieren ───────────────────────────────────────────────────────
function DeathHeatmap:UpdateMap()
    RecycleAllPins()

    local db = GuildDeathLogDB
    if not (db and db.settings and db.settings.heatmap) then return end
    if not WorldMapFrame or not WorldMapFrame:IsVisible() then return end

    local canvas = GetMapCanvas()
    local mapId  = WorldMapFrame.mapID
                   or (WorldMapFrame.GetMapID and WorldMapFrame:GetMapID())
    if not mapId then return end

    local deaths   = GetDeathsForMap(mapId)
    if #deaths == 0 then return end

    local clusters = ClusterDeaths(deaths)
    table.sort(clusters, function(a, b) return a.count > b.count end)

    local mapMarkersActive = MapMarkersActive()
    local mapW = canvas:GetWidth()
    local mapH = canvas:GetHeight()
    local shown = 0

    for _, cluster in ipairs(clusters) do
        if shown >= MAX_PINS then break end

        -- ── KERN-FIX: Einzel-Pins überspringen wenn MapMarkers aktiv ──────────
        -- MapMarkers.lua zeichnet bereits Einzel-Skulls für jeden Tod.
        -- Wir zeigen NUR Cluster (2+ nahe Tode zusammen).
        if cluster.count == 1 and mapMarkersActive then
            -- kein doppelter Pin
        else
            local pin  = GetPin()
            local size = GetPinSize(cluster.count)
            local r, g, b, a = GetDensityColor(cluster.count)

            pin:SetSize(size, size)
            pin._dot:SetColorTexture(r, g, b, a)
            pin._ring:SetColorTexture(r, g, b, 0.2)

            -- Zahl nur bei 2+ zeigen
            if cluster.count >= 2 then
                pin._count:SetText(tostring(cluster.count))
            else
                pin._count:SetText("")
            end

            local px = cluster.x * mapW
            local py = -(cluster.y * mapH)
            pin:SetParent(canvas)
            pin:ClearAllPoints()
            pin:SetPoint("CENTER", canvas, "TOPLEFT", px, py)
            pin._cluster = cluster
            pin:Show()
            shown = shown + 1
        end
    end
end

-- ── Toggle ────────────────────────────────────────────────────────────────────
function DeathHeatmap:Toggle()
    local db = GuildDeathLogDB
    if not (db and db.settings) then return end
    db.settings.heatmap = not db.settings.heatmap
    if db.settings.heatmap then
        GDL:Print(GDL:T("|cff00FF00[Heatmap]|r Aktiviert — öffne die Weltkarte."))
        if WorldMapFrame and WorldMapFrame:IsVisible() then
            C_Timer.After(0.1, function() DeathHeatmap:UpdateMap() end)
        end
    else
        GDL:Print(GDL:T("|cffFF4444[Heatmap]|r Deaktiviert."))
        RecycleAllPins()
    end
end

function DeathHeatmap:IsEnabled()
    local db = GuildDeathLogDB
    return db and db.settings and db.settings.heatmap == true
end

-- ── Legende ───────────────────────────────────────────────────────────────────
local _legendFrame = nil

local function ShowLegend()
    if not DeathHeatmap:IsEnabled() then return end
    if not WorldMapFrame or not WorldMapFrame:IsVisible() then return end
    if not _legendFrame then
        _legendFrame = CreateFrame("Frame", "GDLHeatmapLegend", WorldMapFrame, "BackdropTemplate")
        _legendFrame:SetSize(130, 82)
        _legendFrame:SetPoint("BOTTOMRIGHT", WorldMapFrame, "BOTTOMRIGHT", -8, 28)
        _legendFrame:SetBackdrop({
            bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            edgeSize = 12,
            insets   = {left=3, right=3, top=3, bottom=3},
        })
        _legendFrame:SetBackdropColor(0.05, 0.05, 0.05, 0.88)
        _legendFrame:SetBackdropBorderColor(0.5, 0.4, 0.2, 0.9)
        _legendFrame:SetFrameLevel(1000)

        local title = _legendFrame:CreateFontString(nil, "OVERLAY")
        title:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
        title:SetTextColor(1, 0.85, 0.4)
        title:SetPoint("TOPLEFT", 6, -6)
        title:SetText(GDL:T("|cffFF4444[x]|r Todes-Heatmap"))

        local rows = {
            { r=1.0, g=0.85, b=0.0, label="2–4 Tode (Cluster)"  },
            { r=1.0, g=0.50, b=0.0, label="5–9 Tode"            },
            { r=0.9, g=0.10, b=0.0, label="10–24 Tode"          },
            { r=0.6, g=0.00, b=0.8, label="25+ Tode"            },
        }
        local y = -20
        for _, row in ipairs(rows) do
            local dot = _legendFrame:CreateTexture(nil, "OVERLAY")
            dot:SetSize(9, 9)
            dot:SetPoint("TOPLEFT", 8, y)
            dot:SetColorTexture(row.r, row.g, row.b, 0.95)
            local lbl = _legendFrame:CreateFontString(nil, "OVERLAY")
            lbl:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
            lbl:SetTextColor(0.85, 0.8, 0.7)
            lbl:SetPoint("LEFT", dot, "RIGHT", 4, 0)
            lbl:SetText(GDL:T(row.label))
            y = y - 14
        end
    end
    _legendFrame:Show()
end

local function HideLegend()
    if _legendFrame then _legendFrame:Hide() end
end

-- ── Initialisierung ───────────────────────────────────────────────────────────
function DeathHeatmap:Initialize()
    if DeathHeatmap._initialized then return end
    DeathHeatmap._initialized = true
    -- Default-Setting setzen
    C_Timer.After(0.5, function()
        local db = GuildDeathLogDB
        if db and db.settings and db.settings.heatmap == nil then
            db.settings.heatmap = false
        end
    end)

    -- Weltkarte Hooks
    if WorldMapFrame then
        WorldMapFrame:HookScript("OnShow", function()
            if DeathHeatmap:IsEnabled() then
                C_Timer.After(0.25, function()
                    DeathHeatmap:UpdateMap()
                    ShowLegend()
                end)
            end
        end)
        WorldMapFrame:HookScript("OnHide", function()
            HideLegend()
            RecycleAllPins()
        end)
    end

    -- Karte aktualisieren: WorldMapFrame Hooks (WORLD_MAP_UPDATE existiert nicht in Classic Era)
    if WorldMapFrame then
        WorldMapFrame:HookScript("OnShow", function()
            if DeathHeatmap:IsEnabled() then
                C_Timer.After(0.15, function() DeathHeatmap:UpdateMap() end)
            end
        end)
    end

    -- ZONE_CHANGED als Ersatz-Trigger (Kartenwechsel beim Erkunden)
    local f = CreateFrame("Frame")
    f:RegisterEvent("ZONE_CHANGED_NEW_AREA")
    f:SetScript("OnEvent", function()
        if DeathHeatmap:IsEnabled() and WorldMapFrame and WorldMapFrame:IsVisible() then
            C_Timer.After(0.3, function()
                DeathHeatmap:UpdateMap()
            end)
        end
    end)
end

DeathHeatmap:Initialize()
