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

NS.Core = NS.Core or {}
NS.Core.RuntimeConfigBuilder = NS.Core.RuntimeConfigBuilder or {}

local Builder = NS.Core.RuntimeConfigBuilder
local CONST = NS.Constants or {}

local ALL_CLASSES_ID = CONST.ALL_CLASSES_ID or 0
local ALL_SPECS_ID = CONST.ALL_SPECS_ID or 0
local OBJECT_TYPE_ITEM = CONST.OBJECT_TYPE_ITEM or "item"
local OBJECT_TYPE_SPELL = CONST.OBJECT_TYPE_SPELL or "spell"
local MODE_SOUND = CONST.MODE_SOUND or "sound"

local callbacks = {}

local function SafeCall(name, ...)
    local fn = callbacks[name]
    if type(fn) == "function" then
        return fn(...)
    end
    return nil
end

local function ClearTable(tbl)
    if type(tbl) ~= "table" then
        return
    end
    if type(wipe) == "function" then
        wipe(tbl)
    else
        for key in pairs(tbl) do
            tbl[key] = nil
        end
    end
end

local function GetCurrentClassSpec()
    local classID, specID = SafeCall("getCurrentClassSpec")
    return tonumber(classID) or 0, tonumber(specID) or 0
end

local function GetStoredEntryMap(classID, specID)
    local map = SafeCall("getStoredEntryMap", classID, specID)
    return type(map) == "table" and map or {}
end

local function GetOrderedEntryIndices(map)
    local indices = SafeCall("getOrderedEntryIndices", map)
    return type(indices) == "table" and indices or {}
end

local function GetEntry(map, index)
    local entry = SafeCall("getEntry", map, index)
    return type(entry) == "table" and entry or nil
end

local function ResolveObjectType(objectID, objectType)
    local resolved = SafeCall("resolveObjectType", objectID, objectType)
    if type(resolved) == "string" and resolved ~= "" then
        return resolved
    end
    objectType = tostring(objectType or OBJECT_TYPE_SPELL):lower()
    if objectType ~= OBJECT_TYPE_ITEM then
        objectType = OBJECT_TYPE_SPELL
    end
    return objectType
end

local function ResolveItemTriggerForEntry(entry, quiet)
    local ok = SafeCall("resolveItemTriggerForEntry", entry, quiet)
    if ok ~= nil then
        return ok
    end
    return true
end

local function GetObjectTriggerSpellID(objectID, objectType, triggerSpellID)
    local resolved = SafeCall("getObjectTriggerSpellID", objectID, objectType, triggerSpellID)
    resolved = tonumber(resolved) or 0
    if resolved > 0 then
        return resolved
    end
    return tonumber(triggerSpellID) or tonumber(objectID) or 0
end

local function MakeObjectKey(objectType, objectID)
    local key = SafeCall("makeObjectKey", objectType, objectID)
    if type(key) == "string" and key ~= "" then
        return key
    end
    return tostring(objectType or OBJECT_TYPE_SPELL) .. ":" .. tostring(math.floor(tonumber(objectID) or 0))
end

local function IsTalentSelected(talentId)
    local selected = SafeCall("isTalentSelected", talentId)
    return selected ~= false
end

local function ResolveEntrySoundPath(entry)
    local path = SafeCall("resolveEntrySoundPath", entry)
    return type(path) == "string" and path or ""
end

function Builder:Configure(opts)
    opts = type(opts) == "table" and opts or {}
    callbacks.getCurrentClassSpec = opts.getCurrentClassSpec
    callbacks.getStoredEntryMap = opts.getStoredEntryMap
    callbacks.getOrderedEntryIndices = opts.getOrderedEntryIndices
    callbacks.getEntry = opts.getEntry
    callbacks.resolveObjectType = opts.resolveObjectType
    callbacks.resolveItemTriggerForEntry = opts.resolveItemTriggerForEntry
    callbacks.getObjectTriggerSpellID = opts.getObjectTriggerSpellID
    callbacks.makeObjectKey = opts.makeObjectKey
    callbacks.isTalentSelected = opts.isTalentSelected
    callbacks.resolveEntrySoundPath = opts.resolveEntrySoundPath
    return true
end

function Builder:Rebuild(spellToPrimary, runtimeCfg)
    if type(spellToPrimary) ~= "table" or type(runtimeCfg) ~= "table" then
        return false
    end

    ClearTable(spellToPrimary)
    ClearTable(runtimeCfg)

    local classID, specID = GetCurrentClassSpec()

    local function addEntriesFromMap(entryMap, scopeClassID, scopeSpecID)
        for _, index in ipairs(GetOrderedEntryIndices(entryMap)) do
            local entry = GetEntry(entryMap, index)
            if entry and tostring(entry.entryType or "cooldown") ~= "cast" then
                local objectID = tonumber(entry.spellId) or 0
                local objectType = ResolveObjectType(objectID, entry.objectType)
                if objectType == OBJECT_TYPE_ITEM then
                    ResolveItemTriggerForEntry(entry, true)
                end
                local triggerSpellID = GetObjectTriggerSpellID(objectID, objectType, entry.triggerSpellID or entry.triggerSpellId)
                local talentId = tonumber(entry.talentId) or 0
                local checkTalent = entry.checkTalent == true and talentId > 0
                local talentOK = checkTalent and IsTalentSelected(talentId)
                local fixedCD = tonumber(entry.baseCD) or 0
                local talentCD = tonumber(entry.talentCD) or 0
                local effectiveCD = (talentOK and talentCD > 0) and talentCD or fixedCD
                if objectID > 0 and triggerSpellID > 0 and effectiveCD > 0 then
                    local objectKey = MakeObjectKey(objectType, objectID)
                    -- 触发事件里拿到的是技能ID；物品会映射到该物品“使用时触发的技能ID”。
                    spellToPrimary[triggerSpellID] = objectKey
                    runtimeCfg[objectKey] = {
                        spellId = objectID,
                        objectID = objectID,
                        objectType = objectType,
                        triggerSpellID = triggerSpellID,
                        spellName = tostring(entry.spellName or ""),
                        baseCD = effectiveCD,
                        fixedCD = true,
                        chargeInput = math.max(1, math.floor(tonumber(entry.chargeInput or entry.charge) or 1)),
                        notifyMode = tostring(entry.notifyMode or MODE_SOUND),
                        ttsText = tostring(entry.ttsText or ""),
                        ttsRate = math.max(-10, math.min(10, tonumber(entry.ttsRate) or 0)),
                        soundPath = ResolveEntrySoundPath(entry),
                        soundSource = tostring(entry.soundSource or ""),
                        sharedMediaSound = tostring(entry.sharedMediaSound or entry.sharedMediaName or ""),
                        checkTalent = checkTalent,
                        talentMatched = talentOK == true,
                        talentId = talentId,
                        talentName = tostring(entry.talentName or ""),
                        talentCD = talentCD,
                        fixedBaseCD = fixedCD,
                        index = index,
                        scopeClassID = tonumber(scopeClassID) or 0,
                        scopeSpecID = tonumber(scopeSpecID) or 0,
                    }
                end
            end
        end
    end

    -- 优先级：全职业 < 当前职业全专精 < 当前职业当前专精；后加入的覆盖前面的通用配置。
    addEntriesFromMap(GetStoredEntryMap(ALL_CLASSES_ID, ALL_SPECS_ID), ALL_CLASSES_ID, ALL_SPECS_ID)
    addEntriesFromMap(GetStoredEntryMap(classID, ALL_SPECS_ID), classID, ALL_SPECS_ID)
    if specID ~= ALL_SPECS_ID then
        addEntriesFromMap(GetStoredEntryMap(classID, specID), classID, specID)
    end

    return true
end
