-- WarBoard_KillTally.lua
-- Per-player World-PvP kill credit, kept OUT of the activity ledger.
--
-- Why: every enemy killing blow is worth +1 War Credit, and at population that's by far the
-- highest-volume event. Streaming a ledger record per kill (even batched) floods the synced ledger —
-- each add invalidates the derived caches, fans out over comm, sorts on trim, and eats the cap. So
-- kills live here instead: a compact per-player, per-campaign TALLY that syncs as ONE evolving value
-- per player (highest wins), never a growing stream. It still feeds the live faction bar, the leader
-- board, campaign finalization, and personal totals — just without bloating the ledger.
--
-- Correctness properties that matter here:
--   • MONOTONIC merge — a smaller value never overwrites a larger, so a stale/lost message can only
--     leave a peer briefly BEHIND, never undercount below the true value. It can't delete.
--   • DURABLE — my own tally + lifetime total persist in SavedVariables, so my kills are never lost
--     on logout/reload; I re-announce on login and peers converge.
--   • CHECKPOINT-SAFE — finalization bakes the kill WC into the locked campaign result, so pruning a
--     tally later (2h after the campaign ends, matching the ledger trim) can't change a past result.
--
-- Trust tradeoff (accepted by design): a latest-wins aggregate isn't tamper-proof the way a hashed
-- ledger record is. Kills feed the campaign economy, so this trades verification on that slice for
-- scale. Being monotonic, the only abuse is self-inflation by a cheater — never harming other players.

local addonName, WB = ...
WB.KillTally = WB.KillTally or {}
local KT = WB.KillTally

local BROADCAST_DEBOUNCE = 30        -- coalesce rapid kills into one sync every N seconds
local CONVERGE_INTERVAL  = 300       -- periodic re-broadcast so fresh logins catch up
local KEEP_AFTER_END     = 2 * 3600  -- drop a campaign's tally this long after it ends (matches ledger trim)

local db

local function EnsureDB()
    WarBoardCharDB = WarBoardCharDB or {}
    db = WarBoardCharDB
    db.killTally = db.killTally or {}          -- [campaignIndex] = { [guid] = {wc,kills,name,realm,faction} }
    db.myKillWC  = tonumber(db.myKillWC) or 0  -- my LIFETIME kill war-credit (feeds L.MyTotals)
end

local function CurIndex()
    return (WB.Campaign and WB.Campaign.Index and WB.Campaign.Index()) or 0
end

local function Bucket(idx)
    if not db then EnsureDB() end
    db.killTally[idx] = db.killTally[idx] or {}
    return db.killTally[idx]
end

local function BumpScore()
    if WB.Ledger and WB.Ledger.BumpScore then WB.Ledger.BumpScore() end  -- refresh leaderboard / faction-bar caches
end

-- ── Broadcast (debounced) ──────────────────────────────────────────────────────────
local pendingBroadcast = false
local function DoBroadcast()
    pendingBroadcast = false
    if not db then return end
    local idx  = CurIndex()
    local me   = UnitGUID("player")
    local mine = db.killTally[idx] and db.killTally[idx][me]
    if not (mine and WB.Comm and WB.Comm.BroadcastKillTally) then return end
    WB.Comm.BroadcastKillTally({
        campaign = idx, guid = me,
        name = UnitNameUnmodified("player"), realm = GetNormalizedRealmName() or "",
        faction = WB.faction or UnitFactionGroup("player") or "Horde",
        wc = mine.wc or 0, kills = mine.kills or 0,
    })
end
local function ArmBroadcast()
    if pendingBroadcast or not C_Timer then return end
    pendingBroadcast = true
    C_Timer.After(BROADCAST_DEBOUNCE, DoBroadcast)
end
function KT.Flush() DoBroadcast() end   -- push my latest tally now (called before a loading screen)

-- ── Local kill ─────────────────────────────────────────────────────────────────────
-- One enemy killing blow = +1 kill, +1 war credit. Called INSTEAD of writing a pvpkill ledger record.
function KT.NoteKill()
    if not db then EnsureDB() end
    local idx = CurIndex()
    local me  = UnitGUID("player")
    local b   = Bucket(idx)
    local e   = b[me]
    if not e then
        e = { wc = 0, kills = 0, name = UnitNameUnmodified("player"),
              realm = GetNormalizedRealmName() or "",
              faction = WB.faction or UnitFactionGroup("player") or "Horde" }
        b[me] = e
    end
    e.wc        = e.wc + 1
    e.kills     = e.kills + 1
    db.myKillWC = (db.myKillWC or 0) + 1
    if WB.Roster and WB.Roster.AddLifetimeWC then WB.Roster.AddLifetimeWC(1) end   -- keep the rank bar climbing
    BumpScore()
    ArmBroadcast()
end

-- ── Remote merge (monotonic: only a HIGHER value wins → can never undercount) ────────
function KT.OnRemote(d)
    if type(d) ~= "table" or not d.guid or d.campaign == nil then return end
    if not db then EnsureDB() end
    local b  = Bucket(tonumber(d.campaign) or 0)
    local e  = b[d.guid]
    local wc, kills = tonumber(d.wc) or 0, tonumber(d.kills) or 0
    if not e then e = { wc = 0, kills = 0 }; b[d.guid] = e end
    local changed = false
    if wc    > (e.wc    or 0) then e.wc    = wc;    changed = true end
    if kills > (e.kills or 0) then e.kills = kills; changed = true end
    e.name    = d.name    or e.name
    e.realm   = d.realm   or e.realm
    e.faction = d.faction or e.faction
    if changed then BumpScore() end
end

-- ── Views (read by L.Leaderboard / L.FactionWarCredit and by campaign finalization) ──
function KT.FactionWC(faction, idx)
    if not db then EnsureDB() end
    local b = idx and db.killTally[idx]
    if not b then return 0 end
    local sum = 0
    for _, e in pairs(b) do
        if e.faction == faction then sum = sum + (e.wc or 0) end
    end
    return sum
end

-- Fold this campaign's kill tally into a leaderboard aggregation table (same keying as L.Leaderboard:
-- by guid), so kill WC + kill count show per player alongside their ledger activity.
function KT.MergeLeaderboard(agg, idx)
    if not (db and type(agg) == "table") then return end
    local b = idx and db.killTally[idx]
    if not b then return end
    for guid, e in pairs(b) do
        local a = agg[guid]
        if not a then
            a = { name = e.name, realm = e.realm, guid = guid, faction = e.faction,
                  warCredit = 0, gold = 0, kills = 0, count = 0 }
            agg[guid] = a
        end
        a.warCredit = a.warCredit + (e.wc or 0)
        a.kills     = a.kills     + (e.kills or 0)
        a.faction   = a.faction or e.faction
        a.name      = a.name    or e.name
        a.realm     = a.realm   or e.realm
    end
end

function KT.MyLifetimeWC() return (db and tonumber(db.myKillWC)) or 0 end

-- ── Prune old campaign tallies (mirrors the ledger's 2h-after-end trim) ──────────────
function KT.Prune()
    if not (db and WB.Campaign and WB.Campaign.Window) then return end
    local now = time()
    for idx in pairs(db.killTally) do
        local _, endT = WB.Campaign.Window(idx)
        if endT and (now - endT) > KEEP_AFTER_END then db.killTally[idx] = nil end
    end
end

-- ── Events ───────────────────────────────────────────────────────────────────────────
local f = CreateFrame("Frame")
f:RegisterEvent("ADDON_LOADED")
f:RegisterEvent("PLAYER_LOGIN")
f:SetScript("OnEvent", function(_, event, a1)
    if event == "ADDON_LOADED" then
        if a1 == addonName then EnsureDB() end
    elseif event == "PLAYER_LOGIN" then
        EnsureDB()
        KT.Prune()
        if C_Timer then
            C_Timer.After(15, DoBroadcast)                                       -- announce my tally so peers converge
            C_Timer.NewTicker(CONVERGE_INTERVAL, function() KT.Prune(); DoBroadcast() end)
        end
    end
end)
