MCDVoiceCooldownDB = (type(MCDVoiceCooldownDB) == "table") and MCDVoiceCooldownDB or {}

local NS = rawget(_G, "MCDVoiceCooldownNS") or {}
_G.MCDVoiceCooldownNS = NS

local CONST = NS.Constants or {}
local ENTRY_MIN = CONST.ENTRY_MIN or 1
local ALL_CLASSES_ID = CONST.ALL_CLASSES_ID or 0
local ALL_SPECS_ID = CONST.ALL_SPECS_ID or 0

local MODE_TTS = CONST.MODE_TTS or "tts"
local MODE_SOUND = CONST.MODE_SOUND or "sound"

local L = NS.L or function(key, ...)
    if select("#", ...) > 0 then
        return string.format(tostring(key), ...)
    end
    return tostring(key)
end
NS.ADDON_DISPLAY_NAME = NS.ADDON_DISPLAY_NAME or L("ADDON_DISPLAY_NAME")
NS.ADDON_SHORT_NAME = NS.ADDON_SHORT_NAME or L("ADDON_SHORT_NAME")
NS.ADDON_CHAT_PREFIX = "|cffff7f50[MCD-CVA]|r"
NS.ADDON_ICON = "Interface\\AddOns\\MCDVoiceCooldown\\AppIcon.png"

local frame = CreateFrame("Frame")

-- 物品支持说明：
-- 真实物品ID用于显示名称/图标；物品使用后触发的法术ID(triggerSpellID)用于 UNIT_SPELLCAST_SUCCEEDED 监听。
-- GetItemSpell / C_Item.GetItemSpell 只在非战斗或资料加载回调里解析；战斗中只使用已保存的 triggerSpellID。
local function GetItemResolveQueue()
    return (NS.Core and NS.Core.ItemResolveQueue) or NS.ItemResolveQueue
end

local function QueueItemResolve(itemID, entry)
    local queue = GetItemResolveQueue()
    if queue and type(queue.Queue) == "function" then
        return queue:Queue(itemID, entry)
    end
    return false
end

local MAPPED_SPELL_TO_PRIMARY = {}
local MAPPED_RUNTIME_CFG = {}

local UPDATE_INTERVAL_COMBAT = CONST.UPDATE_INTERVAL_COMBAT or 0.03
local UPDATE_INTERVAL_IDLE = CONST.UPDATE_INTERVAL_IDLE or 0.08

local function GetScope()
    return (NS.Core and NS.Core.Scope) or NS.Scope
end

local function GetCurrentClassSpec()
    local scope = GetScope()
    if scope and type(scope.GetCurrentClassSpec) == "function" then
        return scope:GetCurrentClassSpec()
    end
    local _, _, classID = UnitClass("player")
    local specIndex = GetSpecialization()
    local specID = specIndex and GetSpecializationInfo(specIndex) or 0
    return classID or 0, specID or 0
end

local function GetEntryMapStore()
    return NS.Core and NS.Core.EntryMap
end

local function EnsureSpecTable(root, classID, specID)
    local store = GetEntryMapStore()
    if store and type(store.EnsureSpecTable) == "function" then
        return store:EnsureSpecTable(root, classID, specID)
    end
    return nil
end

local function GetSpecTable(root, classID, specID)
    local store = GetEntryMapStore()
    if store and type(store.GetSpecTable) == "function" then
        return store:GetSpecTable(root, classID, specID)
    end
    return nil
end

local function IsActiveScopeLoaded(entryClassID, entrySpecID, currentClassID, currentSpecID)
    local scope = GetScope()
    if scope and type(scope.IsActiveScopeLoaded) == "function" then
        return scope:IsActiveScopeLoaded(entryClassID, entrySpecID, currentClassID, currentSpecID)
    end

    entryClassID = tonumber(entryClassID) or 0
    entrySpecID = tonumber(entrySpecID) or 0
    currentClassID = tonumber(currentClassID) or 0
    currentSpecID = tonumber(currentSpecID) or 0

    if entryClassID == ALL_CLASSES_ID then
        return true
    end

    return entryClassID == currentClassID and (entrySpecID == currentSpecID or entrySpecID == ALL_SPECS_ID)
end

local function EnsureDB()
    local db = MCDVoiceCooldownDB
    if type(db) ~= "table" then
        db = {}
        MCDVoiceCooldownDB = db
    end
    if type(db.specConfigs) ~= "table" then
        db.specConfigs = {}
    end
    if type(db.castSuccessConfigs) ~= "table" then
        db.castSuccessConfigs = {}
    end
    if type(db.bloodlustConfig) ~= "table" then
        db.bloodlustConfig = {}
    end
    if type(db.bloodlustConfig.customSoundPaths) ~= "table" then
        db.bloodlustConfig.customSoundPaths = { "", "", "", "", "" }
    end
    if type(db.deletedEntries) ~= "table" then
        db.deletedEntries = {}
    end
    if type(db.languageMode) ~= "string" then
        db.languageMode = "auto"
    end
    return db
end


local function GetDeletedEntryStore()
    return (NS.Core and NS.Core.DeletedEntryStore) or NS.DeletedEntryStore
end

local function MarkEntryDeleted(classID, specID, entry, index)
    local store = GetDeletedEntryStore()
    if store and type(store.Mark) == "function" then
        return store:Mark(EnsureDB(), classID, specID, entry, index)
    end
    return false
end

local function ClearEntryDeletedMarker(classID, specID, entry, index)
    local store = GetDeletedEntryStore()
    if store and type(store.Clear) == "function" then
        return store:Clear(EnsureDB(), classID, specID, entry, index)
    end
    return false
end

local function IsEntryMarkedDeleted(classID, specID, entry, index)
    local store = GetDeletedEntryStore()
    if store and type(store.IsMarked) == "function" then
        return store:IsMarked(EnsureDB(), classID, specID, entry, index)
    end
    return false
end

local function PurgeDeletedEntries()
    local store = GetDeletedEntryStore()
    if store and type(store.Purge) == "function" then
        return store:Purge(EnsureDB())
    end
    return false
end

local function EnsureCastSuccessMap(classID, specID)
    local specMap = EnsureSpecTable(EnsureDB().castSuccessConfigs, classID, specID)
    if type(specMap) ~= "table" then
        return nil
    end
    return specMap
end

local function GetCastSuccessMap(classID, specID)
    local map = GetSpecTable(EnsureDB().castSuccessConfigs, classID, specID)
    if type(map) ~= "table" then
        return {}
    end
    return map
end

local function EnsureEntryMap(classID, specID)
    local specMap = EnsureSpecTable(EnsureDB().specConfigs, classID, specID)
    if type(specMap) ~= "table" then
        return nil
    end
    return specMap
end

local function GetStoredEntryMap(classID, specID)
    local map = GetSpecTable(EnsureDB().specConfigs, classID, specID)
    if type(map) ~= "table" then
        return {}
    end
    return map
end

local function GetEntry(map, index)
    local store = GetEntryMapStore()
    if store and type(store.GetEntry) == "function" then
        return store:GetEntry(map, index)
    end
    return nil
end

local function GetOrderedEntryIndices(map)
    local store = GetEntryMapStore()
    if store and type(store.GetOrderedEntryIndices) == "function" then
        return store:GetOrderedEntryIndices(map)
    end
    return {}
end

local function ResolveSpellName(spellId)
    return NS.Core.ResolverBridge:ResolveSpellName(spellId)
end

local function ResolveSpellIcon(spellId)
    return NS.Core.ResolverBridge:ResolveSpellIcon(spellId)
end

local function ResolveSpellBaseCooldownSeconds(spellId)
    return NS.Core.ResolverBridge:ResolveSpellBaseCooldownSeconds(spellId)
end

local function ResolveItemName(itemID)
    return NS.Core.ResolverBridge:ResolveItemName(itemID)
end

local function ResolveItemIcon(itemID)
    return NS.Core.ResolverBridge:ResolveItemIcon(itemID)
end

local function ResolveItemUseSpellID(itemID, allowInCombat)
    return NS.Core.ResolverBridge:ResolveItemUseSpellID(itemID, allowInCombat)
end

local function ResolveItemTriggerForEntry(entry, quiet, noQueue)
    return NS.Core.ResolverBridge:ResolveItemTriggerForEntry(entry, quiet, noQueue, QueueItemResolve)
end

local function ResolveObjectType(objectID, objectType)
    return NS.Core.ResolverBridge:ResolveObjectType(objectID, objectType)
end

local function ResolveObjectName(objectID, objectType)
    return NS.Core.ResolverBridge:ResolveObjectName(objectID, objectType)
end

local function ResolveObjectIcon(objectID, objectType)
    return NS.Core.ResolverBridge:ResolveObjectIcon(objectID, objectType)
end

local function ResolveObjectBaseCooldownSeconds(objectID, objectType)
    return NS.Core.ResolverBridge:ResolveObjectBaseCooldownSeconds(objectID, objectType)
end

local function GetObjectTriggerSpellID(objectID, objectType, triggerSpellID)
    return NS.Core.ResolverBridge:GetObjectTriggerSpellID(objectID, objectType, triggerSpellID)
end

local function MakeObjectKey(objectType, objectID)
    return NS.Core.ResolverBridge:MakeObjectKey(objectType, objectID)
end

local function ResolveTalentName(talentId)
    return NS.Core.ResolverBridge:ResolveTalentName(talentId)
end

local function IsTalentSelected(talentId)
    return NS.Core.ResolverBridge:IsTalentSelected(talentId)
end

local function GetRuntimeUpdateInterval()
    return UnitAffectingCombat("player") and UPDATE_INTERVAL_COMBAT or UPDATE_INTERVAL_IDLE
end

local function GetNotifier()
    return NS.Core.NotifierBridge:GetNotifier()
end

local function GetRuntime()
    return NS.Core.RuntimeBridge:GetRuntime()
end

local function GetRuntimeConfigBuilder()
    return NS.Core and NS.Core.RuntimeConfigBuilder
end

local function GetBloodlust()
    return (NS.Core and NS.Core.Bloodlust) or NS.Bloodlust
end

local function GetCastSuccess()
    return (NS.Core and NS.Core.CastSuccess) or NS.CastSuccess
end

local function GetProfileController()
    return NS.Core and NS.Core.ProfileController
end

local function GetStartup()
    return NS.Core and NS.Core.Startup
end

local function NormalizeSoundPath(path)
    return NS.Core.NotifierBridge:NormalizeSoundPath(path)
end

local function ResolveEntrySoundPath(entry)
    return NS.Core.NotifierBridge:ResolveEntrySoundPath(entry)
end

local function PlayReadyNotification(cfg)
    return NS.Core.NotifierBridge:PlayReadyNotification(cfg)
end

local function PlayCastSuccessNotification(cfg)
    return NS.Core.NotifierBridge:PlayCastSuccessNotification(cfg)
end

local function PlayBloodlustNotification(cfg)
    local bloodlust = GetBloodlust()
    if bloodlust and type(bloodlust.PlayNotification) == "function" then
        return bloodlust:PlayNotification(cfg, GetNotifier())
    end
    return false
end

local function ConfigureRuntime()
    local runtime = GetRuntime()
    if runtime and type(runtime.Configure) == "function" then
        runtime:Configure({
            getUpdateInterval = GetRuntimeUpdateInterval,
            playReady = PlayReadyNotification,
            playCastSuccess = PlayCastSuccessNotification,
            normalizeSoundPath = NormalizeSoundPath,
            resolveObjectName = ResolveObjectName,
        })
    end
end

local function WipeRuntimeCooldowns()
    return NS.Core.RuntimeBridge:WipeCooldowns()
end

local function ClearDelayedCastSuccessTimers()
    return NS.Core.RuntimeBridge:ClearDelayedCastSuccessTimers()
end

local function QueueCastSuccessNotification(triggerSpellID, cfg)
    return NS.Core.RuntimeBridge:QueueCastSuccessNotification(triggerSpellID, cfg, PlayCastSuccessNotification)
end

local function ConfigureCastSuccess()
    local castSuccess = GetCastSuccess()
    if castSuccess and type(castSuccess.Configure) == "function" then
        castSuccess:Configure({
            getCurrentClassSpec = GetCurrentClassSpec,
            getOrderedEntryIndices = GetOrderedEntryIndices,
            getEntry = GetEntry,
            getStoredEntryMap = GetStoredEntryMap,
            getCastSuccessMap = GetCastSuccessMap,
            resolveObjectType = ResolveObjectType,
            resolveItemTriggerForEntry = ResolveItemTriggerForEntry,
            getObjectTriggerSpellID = GetObjectTriggerSpellID,
            resolveEntrySoundPath = ResolveEntrySoundPath,
            queueNotification = QueueCastSuccessNotification,
        })
    end
end

local function ConfigureRuntimeConfigBuilder()
    local builder = GetRuntimeConfigBuilder()
    if builder and type(builder.Configure) == "function" then
        builder:Configure({
            getCurrentClassSpec = GetCurrentClassSpec,
            getStoredEntryMap = GetStoredEntryMap,
            getOrderedEntryIndices = GetOrderedEntryIndices,
            getEntry = GetEntry,
            resolveObjectType = ResolveObjectType,
            resolveItemTriggerForEntry = ResolveItemTriggerForEntry,
            getObjectTriggerSpellID = GetObjectTriggerSpellID,
            makeObjectKey = MakeObjectKey,
            isTalentSelected = IsTalentSelected,
            resolveEntrySoundPath = ResolveEntrySoundPath,
        })
    end
end

ConfigureRuntime()
ConfigureCastSuccess()
ConfigureRuntimeConfigBuilder()

local function RebuildBloodlustConfig()
    local bloodlust = GetBloodlust()
    if bloodlust and type(bloodlust.Rebuild) == "function" then
        return bloodlust:Rebuild(EnsureDB(), NormalizeSoundPath)
    end
    return false
end

local function HandleBloodlustAura(unit)
    local bloodlust = GetBloodlust()
    if bloodlust and type(bloodlust.HandleUnitAura) == "function" then
        return bloodlust:HandleUnitAura(unit, GetNotifier())
    end
    return false
end

local function RebuildRuntimeConfig()
    local builder = GetRuntimeConfigBuilder()
    if builder and type(builder.Rebuild) == "function" then
        return builder:Rebuild(MAPPED_SPELL_TO_PRIMARY, MAPPED_RUNTIME_CFG)
    end
    return false
end

local function RebuildCastSuccessConfig()
    local castSuccess = GetCastSuccess()
    if castSuccess and type(castSuccess.Rebuild) == "function" then
        return castSuccess:Rebuild()
    end
    return false
end

local function HandleCastSuccessSpellcast(spellId)
    local castSuccess = GetCastSuccess()
    if castSuccess and type(castSuccess.HandleSpellcastSucceeded) == "function" then
        return castSuccess:HandleSpellcastSucceeded(spellId)
    end
    return false
end

local function RefreshRuntimeCooldowns()
    return NS.Core.RuntimeBridge:RefreshRuntimeCooldowns()
end

local function StartCooldown(spellId)
    return NS.Core.RuntimeBridge:StartCooldown(spellId, MAPPED_SPELL_TO_PRIMARY, MAPPED_RUNTIME_CFG)
end

local function GetUsedEntryCount(map)
    local store = GetEntryMapStore()
    if store and type(store.GetUsedEntryCount) == "function" then
        return store:GetUsedEntryCount(map)
    end
    return #GetOrderedEntryIndices(map)
end

local function FindFirstFreeIndex(map)
    local store = GetEntryMapStore()
    if store and type(store.FindFirstFreeIndex) == "function" then
        return store:FindFirstFreeIndex(map)
    end
    local indices = GetOrderedEntryIndices(map)
    local nextIndex = ENTRY_MIN
    for _, index in ipairs(indices) do
        if index > nextIndex then
            return nextIndex
        end
        if index == nextIndex then
            nextIndex = nextIndex + 1
        end
    end
    return nextIndex
end

local function ConfigureProfileController()
    local controller = GetProfileController()
    if controller and type(controller.Configure) == "function" then
        controller:Configure({
            getAceState = function()
                if NS.AceOptions and type(NS.AceOptions.GetState) == "function" then
                    return NS.AceOptions:GetState()
                end
                return nil
            end,
            getCurrentClassSpec = GetCurrentClassSpec,
            getStoredEntryMap = GetStoredEntryMap,
            getUsedEntryCount = GetUsedEntryCount,
            wipeRuntimeCooldowns = WipeRuntimeCooldowns,
            clearDelayedCastSuccessTimers = ClearDelayedCastSuccessTimers,
            rebuildRuntimeConfig = RebuildRuntimeConfig,
            rebuildCastSuccessConfig = RebuildCastSuccessConfig,
            rebuildBloodlustConfig = RebuildBloodlustConfig,
            refreshRuntimeCooldowns = RefreshRuntimeCooldowns,
            refreshPanel = function()
                if NS.AceOptions and type(NS.AceOptions.RefreshUI) == "function" then
                    return NS.AceOptions:RefreshUI()
                end
                return false
            end,
        })
    end
end

ConfigureProfileController()

local function ResolveClassName(classID)
    local scope = GetScope()
    if scope and type(scope.ResolveClassName) == "function" then
        return scope:ResolveClassName(classID)
    end
    return L("FALLBACK_CLASS", tostring(classID or 0))
end

local function ResolveSpecName(classID, specID)
    local scope = GetScope()
    if scope and type(scope.ResolveSpecName) == "function" then
        return scope:ResolveSpecName(classID, specID)
    end
    return L("FALLBACK_SPEC", tostring(specID or 0))
end

local function GetTargetClassSpec()
    local controller = GetProfileController()
    if controller and type(controller.GetTargetClassSpec) == "function" then
        return controller:GetTargetClassSpec()
    end
    return GetCurrentClassSpec()
end

local function BuildSummaryText()
    local controller = GetProfileController()
    if controller and type(controller.BuildSummaryText) == "function" then
        return controller:BuildSummaryText()
    end
    return L("MSG_SCOPE_COUNT", 0)
end

local function GetEntryRange()
    local controller = GetProfileController()
    if controller and type(controller.GetEntryRange) == "function" then
        return controller:GetEntryRange()
    end
    return ENTRY_MIN, ENTRY_MIN
end

function RefreshPanel()
    local controller = GetProfileController()
    if controller and type(controller.RefreshPanel) == "function" then
        return controller:RefreshPanel()
    end
    if NS.AceOptions and type(NS.AceOptions.RefreshUI) == "function" then
        return NS.AceOptions:RefreshUI()
    end
    return false
end

local function OnProfileChanged(resetCooldowns)
    local controller = GetProfileController()
    if controller and type(controller.OnProfileChanged) == "function" then
        return controller:OnProfileChanged(resetCooldowns)
    end
    if resetCooldowns then
        WipeRuntimeCooldowns()
        ClearDelayedCastSuccessTimers()
    end
    RebuildRuntimeConfig()
    RebuildCastSuccessConfig()
    RebuildBloodlustConfig()
    RefreshRuntimeCooldowns()
    RefreshPanel()
end

NS.API = NS.API or {}
NS.API.RefreshPanel = RefreshPanel
local function ConfigureItemResolveQueue()
    local queue = GetItemResolveQueue()
    if queue and type(queue.Configure) == "function" then
        queue:Configure({
            ensureDB = EnsureDB,
            resolveItemTriggerForEntry = ResolveItemTriggerForEntry,
            rebuildRuntimeConfig = RebuildRuntimeConfig,
            rebuildCastSuccessConfig = RebuildCastSuccessConfig,
            refreshRuntimeCooldowns = RefreshRuntimeCooldowns,
            refreshPanel = RefreshPanel,
            chatPrefix = NS.ADDON_CHAT_PREFIX,
        })
    end
end

local function ResolveAllStoredItemTriggers(quiet)
    local queue = GetItemResolveQueue()
    if queue and type(queue.ResolveAllStored) == "function" then
        return queue:ResolveAllStored(quiet)
    end
    return false
end

local function ResolvePendingItems()
    local queue = GetItemResolveQueue()
    if queue and type(queue.ResolvePending) == "function" then
        return queue:ResolvePending()
    end
    return false
end

ConfigureItemResolveQueue()

local PublicAPI = NS.Core and NS.Core.PublicAPI
PublicAPI:Install({
        GetCurrentClassSpec = GetCurrentClassSpec,
        IsActiveScopeLoaded = IsActiveScopeLoaded,
        GetTargetClassSpec = GetTargetClassSpec,
        GetStoredEntryMap = GetStoredEntryMap,
        GetCastSuccessMap = GetCastSuccessMap,
        EnsureCastSuccessMap = EnsureCastSuccessMap,
        GetEntry = GetEntry,
        PlayReadyNotification = PlayReadyNotification,
        PlayCastSuccessNotification = PlayCastSuccessNotification,
        QueueCastSuccessNotification = QueueCastSuccessNotification,
        ClearDelayedCastSuccessTimers = ClearDelayedCastSuccessTimers,
        PlayBloodlustNotification = PlayBloodlustNotification,
        RebuildCastSuccessConfig = RebuildCastSuccessConfig,
        RebuildBloodlustConfig = RebuildBloodlustConfig,
        GetModes = function()
            return MODE_TTS, MODE_SOUND
        end,
        GetEntryRange = function()
            return GetEntryRange()
        end,
        GetSummaryText = BuildSummaryText,
        ResolveClassName = ResolveClassName,
        ResolveSpecName = ResolveSpecName,
        ResolveSpellName = ResolveSpellName,
        ResolveSpellIcon = ResolveSpellIcon,
        ResolveSpellBaseCooldownSeconds = ResolveSpellBaseCooldownSeconds,
        ResolveItemName = ResolveItemName,
        ResolveItemIcon = ResolveItemIcon,
        ResolveItemUseSpellID = ResolveItemUseSpellID,
        ResolveItemTriggerForEntry = ResolveItemTriggerForEntry,
        ResolveAllStoredItemTriggers = ResolveAllStoredItemTriggers,
        ResolvePendingItems = ResolvePendingItems,
        MarkEntryDeleted = MarkEntryDeleted,
        ClearEntryDeletedMarker = ClearEntryDeletedMarker,
        IsEntryMarkedDeleted = IsEntryMarkedDeleted,
        PurgeDeletedEntries = PurgeDeletedEntries,
        ResolveObjectType = ResolveObjectType,
        ResolveObjectName = ResolveObjectName,
        ResolveObjectIcon = ResolveObjectIcon,
        ResolveObjectBaseCooldownSeconds = ResolveObjectBaseCooldownSeconds,
        GetObjectTriggerSpellID = GetObjectTriggerSpellID,
        ResolveTalentName = ResolveTalentName,
        IsTalentSelected = IsTalentSelected,
        EnsureEntryMap = EnsureEntryMap,
        FindFirstFreeIndex = FindFirstFreeIndex,
        GetOrderedEntryIndices = GetOrderedEntryIndices,
        RebuildRuntimeConfig = RebuildRuntimeConfig,
        RefreshRuntimeCooldowns = RefreshRuntimeCooldowns,
    })

local function ConfigureStartup()
    local startup = GetStartup()
    if startup and type(startup.Configure) == "function" then
        startup:Configure({
            initializeDatabase = function()
                if NS.Core and NS.Core.Database and type(NS.Core.Database.Initialize) == "function" then
                    return NS.Core.Database:Initialize()
                end
                return false
            end,
            ensureDB = EnsureDB,
            purgeDeletedEntries = PurgeDeletedEntries,
            resolveAllStoredItemTriggers = ResolveAllStoredItemTriggers,
            rebuildRuntimeConfig = RebuildRuntimeConfig,
            rebuildCastSuccessConfig = RebuildCastSuccessConfig,
            rebuildBloodlustConfig = RebuildBloodlustConfig,
            refreshRuntimeCooldowns = RefreshRuntimeCooldowns,
            initializeAceOptions = function()
                if NS.AceOptions and type(NS.AceOptions.Initialize) == "function" then
                    return NS.AceOptions:Initialize()
                end
                return false
            end,
            initializeConfigPanel = function()
                if NS.Core and NS.Core.ConfigPanel and type(NS.Core.ConfigPanel.Initialize) == "function" then
                    return NS.Core.ConfigPanel:Initialize()
                end
                return false
            end,
            initializeCommands = function()
                if NS.Core and NS.Core.Commands and type(NS.Core.Commands.Initialize) == "function" then
                    return NS.Core.Commands:Initialize()
                end
                return false
            end,
            initializeMinimapButton = function()
                if NS.Core and NS.Core.MinimapButton and type(NS.Core.MinimapButton.Initialize) == "function" then
                    return NS.Core.MinimapButton:Initialize()
                end
                return false
            end,
            showMinimapButton = function()
                if NS.Core and NS.Core.MinimapButton and type(NS.Core.MinimapButton.Show) == "function" then
                    return NS.Core.MinimapButton:Show()
                end
                return false
            end,
            printLoadedMessage = function()
                print("[MCD-CVA] " .. L("MSG_LOADED"))
            end,
            onProfileChanged = OnProfileChanged,
            resolvePendingItems = ResolvePendingItems,
            clearDelayedCastSuccessTimers = ClearDelayedCastSuccessTimers,
            startCooldown = StartCooldown,
            handleCastSuccessSpellcast = HandleCastSuccessSpellcast,
            handleBloodlustAura = HandleBloodlustAura,
        })
    end
end

ConfigureStartup()

local EventHub = NS.Core and NS.Core.EventHub
local Startup = GetStartup()
local eventHandlers = Startup and type(Startup.GetEventHandlers) == "function" and Startup:GetEventHandlers() or {}
EventHub:Initialize(frame, eventHandlers)
