--[[
============================================================================
ETB v10.0 - EventBus.lua
============================================================================
Centralized event handling system.
- Single frame for all WoW events
- Priority-based callback ordering
- Prevents race conditions
- Custom ETB events support
============================================================================
]]--

local ADDON_NAME, NS = ...

-- Create namespace early
local ETB = {}
NS.ETB = ETB
-- NOTE: No _G.ETB — writing to _G taints the global namespace,
-- breaking protected functions like FocusUnit(). All files use NS.ETB.

local EventBus = {}
ETB.EventBus = EventBus

-- Internal state
local callbacks = {}           -- [event] = { {fn, priority, id}, ... }
local eventFrame = nil
local callbackIdCounter = 0
local isProcessing = {}
local pendingUnregisters = {}

-- Stats
local stats = {
    totalFires = 0,
    eventCounts = {},
}

-- =============================================================================
-- INITIALIZATION
-- =============================================================================

local function InitializeFrame()
    if eventFrame then return end
    
    eventFrame = CreateFrame("Frame")
    eventFrame:SetScript("OnEvent", function(_, event, ...)
        EventBus:Fire(event, ...)
    end)
end

-- =============================================================================
-- REGISTRATION
-- =============================================================================

function EventBus:Register(event, callback, priority)
    if not event or type(callback) ~= "function" then return nil end
    
    priority = priority or 50
    InitializeFrame()
    
    if not callbacks[event] then
        callbacks[event] = {}
        -- Register WoW event (skip custom ETB_ events)
        if not event:match("^ETB_") then
            eventFrame:RegisterEvent(event)
        end
    end
    
    callbackIdCounter = callbackIdCounter + 1
    local entry = {
        id = callbackIdCounter,
        fn = callback,
        priority = priority,
    }
    
    table.insert(callbacks[event], entry)
    table.sort(callbacks[event], function(a, b) return a.priority < b.priority end)
    
    return entry.id
end

function EventBus:Unregister(event, callbackId)
    if not callbacks[event] then return false end
    
    if isProcessing[event] then
        pendingUnregisters[event] = pendingUnregisters[event] or {}
        table.insert(pendingUnregisters[event], callbackId)
        return true
    end
    
    for i, entry in ipairs(callbacks[event]) do
        if entry.id == callbackId then
            table.remove(callbacks[event], i)
            if #callbacks[event] == 0 and not event:match("^ETB_") then
                eventFrame:UnregisterEvent(event)
                callbacks[event] = nil
            end
            return true
        end
    end
    return false
end

-- =============================================================================
-- EVENT FIRING
-- =============================================================================

function EventBus:Fire(event, ...)
    if not callbacks[event] then return end
    if isProcessing[event] then return end
    
    isProcessing[event] = true
    stats.totalFires = stats.totalFires + 1
    stats.eventCounts[event] = (stats.eventCounts[event] or 0) + 1
    
    for _, entry in ipairs(callbacks[event]) do
        local success, err = pcall(entry.fn, event, ...)
        if not success and ETB.PrintError then
            ETB:PrintError("EventBus error: " .. tostring(err))
        end
    end
    
    isProcessing[event] = nil
    
    if pendingUnregisters[event] then
        for _, id in ipairs(pendingUnregisters[event]) do
            self:Unregister(event, id)
        end
        pendingUnregisters[event] = nil
    end
end

-- =============================================================================
-- UTILITIES
-- =============================================================================

function EventBus:GetCallbackCount(event)
    if event then
        return callbacks[event] and #callbacks[event] or 0
    end
    local total = 0
    for _, list in pairs(callbacks) do
        total = total + #list
    end
    return total
end

function EventBus:GetStats()
    return {
        totalFires = stats.totalFires,
        currentCallbacks = self:GetCallbackCount(),
        eventCounts = stats.eventCounts,
    }
end

-- Priority constants
EventBus.Priority = {
    FIRST = 1,
    EARLY = 10,
    NORMAL = 50,
    LATE = 90,
    LAST = 100,
}

InitializeFrame()
