-- core.lua

local addonName = ...
local LSM = LibStub("LibSharedMedia-3.0")

CCS = CCS or {}

-- Set to true to enable the Mythic+ module (tab strip, dungeon switcher, M+ spells).
-- Can also be toggled at runtime with:  /ccs mplus
CCS.MPLUS_ENABLED = true

--------------------------------------------------
-- LSM Registration
--------------------------------------------------

local basePath = "Interface\\AddOns\\" .. addonName .. "\\sounds\\"

local lsmSounds = {
    "break","breath","burn","dot","marked","move","soak","spread","targeted","drop","fixate","pull","stack","absorb","debuff"
}

for _, name in ipairs(lsmSounds) do
    LSM:Register("sound", "CCS: " .. name, basePath .. name .. ".ogg")
end

local publicCountdowns = {
    "2s","3s","4s","5s","6s","7s","8s","9s","10s","11s","12s","13s",
}
for _, name in ipairs(publicCountdowns) do
    LSM:Register("sound", "CCS: " .. name, basePath .. name .. ".ogg")
end

local soundPaths = {}
for _, name in ipairs(lsmSounds) do
    soundPaths[name] = basePath .. name .. ".ogg"
end

local function resolvePath(s)
    if not s then return nil end
    if s:sub(1, 5) == "file:" then
        return basePath .. s:sub(6) .. ".ogg"
    end
    if soundPaths[s] then return soundPaths[s] end
    return LSM:Fetch("sound", s)
end
CCS.ResolvePath = resolvePath
CCS.ResolvePath = resolvePath

--------------------------------------------------
-- Difficulty
--------------------------------------------------

local function getCurrentDifficulty()
    local _, instanceType, difficultyID = GetInstanceInfo()
    if instanceType == "raid" then
        if difficultyID == 16 then return "M"
        elseif difficultyID == 15 then return "H"
        end
    elseif instanceType == "party" then
        -- M+ difficulty IDs: 8 (M+), 9 (Challenge), 10 (scenario), 23 (Timewalking M+)
        if difficultyID == 8 or difficultyID == 9 or difficultyID == 23 then
            return "M"
        end
    end
    return nil
end

local function getInstanceType()
    local _, instanceType = GetInstanceInfo()
    return instanceType  -- "raid", "party", "none", etc.
end

CCS.getCurrentDifficulty = getCurrentDifficulty

--------------------------------------------------
-- AceDB
--------------------------------------------------

local dbDefaults = {
    profile = {},
    char = {
        minimapAngle   = 220,
        minimapHidden  = false,
        module         = "raid",
        activeDungeon  = "__all__",
    },
}

-- Ordered list of active M+ dungeons
CCS_MplusDungeons = {
    { key = "magisters_terrace",       label = "Magister's Terrace",      data = function() return CCS_Spells_Mplus_MagistersTerrace      end },
    { key = "maisara_caverns",         label = "Maisara Caverns",         data = function() return CCS_Spells_Mplus_MaisaraCaverns         end },
    { key = "nexus_point_xenas",       label = "Nexus-Point Xenas",       data = function() return CCS_Spells_Mplus_NexusPointXenas        end },
    { key = "windrunner_spire",        label = "Windrunner Spire",        data = function() return CCS_Spells_Mplus_WindrunnerSpire        end },
    { key = "algethar_academy",        label = "Algeth'ar Academy",       data = function() return CCS_Spells_Mplus_AlgetharAcademy        end },
    { key = "pit_of_saron",            label = "Pit of Saron",            data = function() return CCS_Spells_Mplus_PitOfSaron             end },
    { key = "seat_of_the_triumvirate", label = "Seat of the Triumvirate", data = function() return CCS_Spells_Mplus_SeatOfTheTriumvirate   end },
    { key = "skyreach",                label = "Skyreach",                data = function() return CCS_Spells_Mplus_Skyreach               end },
}

local db

function CCS.GetProfile()
    if not db then return { warnEnabled={}, warnOverride={}, countdownEnabled={}, countdownOverride={} } end
    local p = db.profile
    if rawget(p, "warnEnabled")       == nil then rawset(p, "warnEnabled",       {}) end
    if rawget(p, "warnOverride")      == nil then rawset(p, "warnOverride",       {}) end
    if rawget(p, "countdownEnabled")  == nil then rawset(p, "countdownEnabled",   {}) end
    if rawget(p, "countdownOverride") == nil then rawset(p, "countdownOverride",  {}) end
    return p
end

function CCS.GetChar()
    if not db then return dbDefaults.char end
    return db.char
end

function CCS.GetProfileName()
    if not db then return "Default" end
    return db:GetCurrentProfile()
end

function CCS.GetProfileNames()
    if not db then return {"Default"} end
    local names = {}
    db:GetProfiles(names)
    table.sort(names)
    return names
end

function CCS.SetActiveProfile(name)
    if not db then return end
    db:SetProfile(name)
end

function CCS.NewProfile(name)
    if not db then return end
    db:SetProfile(name)
end

function CCS.CopyProfile(sourceName)
    if not db then return end
    db:CopyProfile(sourceName)
end

function CCS.DeleteProfile(name)
    if not db then return end
    db:DeleteProfile(name)
end

function CCS.ResetProfile()
    if not db then return end
    db:ResetProfile()
end

function CCS.GetDB()
    return db
end

local function applyModule()
    local m = (db and db.char.module) or "raid"
    if not CCS.MPLUS_ENABLED and m == "mplus" then
        if db then db.char.module = "raid" end
        m = "raid"
    end
    if m == "mplus" and CCS.MPLUS_ENABLED then
        local key = (db and db.char.activeDungeon) or "__all__"
        if key == "__all__" then
            local combined = {}
            for _, dungeon in ipairs(CCS_MplusDungeons) do
                for _, entry in ipairs(dungeon.data()) do
                    combined[#combined + 1] = entry
                end
            end
            CCS_Spells = combined
            return
        end
        for _, dungeon in ipairs(CCS_MplusDungeons) do
            if dungeon.key == key then
                CCS_Spells = dungeon.data()
                return
            end
        end
        CCS_Spells = CCS_MplusDungeons[1].data()
    else
        CCS_Spells = CCS_Spells_Raid
    end
end

CCS.ApplyModule = applyModule

function CCS.SetDungeon(key)
    if not db or not CCS.MPLUS_ENABLED then return end
    db.char.activeDungeon = key
    applyModule()
    CCS.RefreshAll()
    if CCS._fullRebuild then CCS._fullRebuild() end
end

function CCS.GetActiveDungeon()
    return (db and db.char.activeDungeon) or "__all__"
end

function CCS.SetModule(name)
    if name ~= "raid" and name ~= "mplus" then
        print("|cffff5555CCS:|r Unknown module '" .. name .. "'. Use 'raid' or 'mplus'.")
        return
    end
    if name == "mplus" and not CCS.MPLUS_ENABLED then
        print("|cffff5555CCS:|r Mythic+ module is not enabled.")
        return
    end
    if not db then return end
    db.char.module = name
    applyModule()
    CCS.RefreshAll()
    if CCS._fullRebuild then CCS._fullRebuild() end
end

function CCS.GetModule()
    if not CCS.MPLUS_ENABLED then return "raid" end
    return (db and db.char.module) or "raid"
end

--------------------------------------------------
-- Profile data accessors
--------------------------------------------------

function CCS.isWarnEnabled(key)
    return CCS.GetProfile().warnEnabled[key] == true
end

function CCS.SetWarnEnabled(key, val)
    CCS.GetProfile().warnEnabled[key] = val
end

function CCS.GetWarnOverride(key)
    return CCS.GetProfile().warnOverride[key]
end

function CCS.SetWarnOverride(key, soundKey)
    CCS.GetProfile().warnOverride[key] = soundKey
end

function CCS.IsCDEnabled(key, diff)
    local t = CCS.GetProfile().countdownEnabled[key]
    return t and t[diff] == true or false
end

function CCS.SetCDEnabled(key, diff, val)
    local p = CCS.GetProfile()
    p.countdownEnabled[key] = p.countdownEnabled[key] or {}
    p.countdownEnabled[key][diff] = val
end

function CCS.GetCountdownOverride(key, diff)
    local t = CCS.GetProfile().countdownOverride[key]
    return t and t[diff] or nil
end

function CCS.SetCountdownOverride(key, diff, soundKey)
    local p = CCS.GetProfile()
    p.countdownOverride[key] = p.countdownOverride[key] or {}
    p.countdownOverride[key][diff] = soundKey
end

-- Iterates abilities for a specific module ("raid" or "mplus")
local function iterateModuleSpells(module, fn)
    if module == "raid" then
        for _, entry in ipairs(CCS_Spells_Raid) do
            if entry.abilities then
                for _, ability in ipairs(entry.abilities) do fn(ability, false) end
            end
        end
    elseif module == "mplus" then
        for _, dungeon in ipairs(CCS_MplusDungeons) do
            for _, entry in ipairs(dungeon.data()) do
                if entry.abilities then
                    for _, ability in ipairs(entry.abilities) do fn(ability, true) end
                end
            end
        end
    end
end

function CCS.SetAllWarn(val, module)
    module = module or CCS.GetModule()
    local p = CCS.GetProfile()
    iterateModuleSpells(module, function(ability)
        local hasDefault = ability.soundH ~= nil or ability.soundM ~= nil
        local hasOverride = p.warnOverride[ability.key] ~= nil
        if not val or hasDefault or hasOverride then
            p.warnEnabled[ability.key] = val
        end
    end)
end

function CCS.SetAllCD(val, module)
    module = module or CCS.GetModule()
    local p = CCS.GetProfile()
    iterateModuleSpells(module, function(ability, isMplus)
        local function hasDefault(diff)
            local s = diff == "M" and ability.soundM or ability.soundH
            return type(s) == "table" and s[2] ~= nil
        end
        local function hasOverride(diff)
            return p.countdownOverride[ability.key] and p.countdownOverride[ability.key][diff] ~= nil
        end
        local function isEnabled(diff)
            return p.countdownEnabled[ability.key] and p.countdownEnabled[ability.key][diff] == true
        end
        if isMplus then
            if not val or hasDefault("M") or hasOverride("M") then
                p.countdownEnabled[ability.key] = p.countdownEnabled[ability.key] or {}
                p.countdownEnabled[ability.key].M = val
            elseif not val and isEnabled("M") then
                p.countdownEnabled[ability.key].M = false
            end
        else
            if ability.soundH ~= nil or hasOverride("H") or isEnabled("H") then
                if not val or hasDefault("H") or hasOverride("H") then
                    p.countdownEnabled[ability.key] = p.countdownEnabled[ability.key] or {}
                    p.countdownEnabled[ability.key].H = val
                end
            end
            if ability.soundM ~= nil or hasOverride("M") or isEnabled("M") then
                if not val or hasDefault("M") or hasOverride("M") then
                    p.countdownEnabled[ability.key] = p.countdownEnabled[ability.key] or {}
                    p.countdownEnabled[ability.key].M = val
                end
            end
        end
    end)
end




--------------------------------------------------
-- Sound resolution
--------------------------------------------------

local function resolveAbilitySounds(ability, diff)
    local paths = {}

    if CCS.isWarnEnabled(ability.key) then
        local warnField = ability.soundH or ability.soundM
        local defaultWarn = type(warnField) == "table" and warnField[1] or warnField
        local warnKey = CCS.GetWarnOverride(ability.key) or defaultWarn
        local p = resolvePath(warnKey)
        if p then
            paths[#paths + 1] = p
        else
            print("|cffff9900CCS:|r Warning — could not resolve sound '" .. tostring(warnKey) .. "' for " .. ability.key)
        end
    end

    if CCS.IsCDEnabled(ability.key, diff) then
        local soundField = diff == "M" and ability.soundM or ability.soundH
        local defaultCD = type(soundField) == "table" and soundField[2] or nil
        local cdKey = CCS.GetCountdownOverride(ability.key, diff) or defaultCD
        if cdKey then
            local p = resolvePath(cdKey)
            if p then
                paths[#paths + 1] = p
            else
                print("|cffff9900CCS:|r Warning — could not resolve countdown sound '" .. tostring(cdKey) .. "' for " .. ability.key)
            end
        end
    end

    return #paths > 0 and paths or nil
end

--------------------------------------------------
-- Private Aura Registration
--------------------------------------------------

local handles = {}

local function unregisterAbility(key)
    if not handles[key] then return end
    for _, id in ipairs(handles[key]) do
        C_UnitAuras.RemovePrivateAuraAppliedSound(id)
    end
    handles[key] = nil
end

local function registerAbility(ability, diff)
    if not ability or not ability.privateID then return end
    if not C_UnitAuras.AuraIsPrivate(ability.privateID) then
        if diff then
            print("|cffff9900CCS:|r " .. ability.key .. " (spellID " .. ability.privateID .. ") is not a private aura — skipping.")
        end
        return
    end
    local paths = resolveAbilitySounds(ability, diff)
    if not paths then return end
    if not handles[ability.key] then handles[ability.key] = {} end
    for _, path in ipairs(paths) do
        local id = C_UnitAuras.AddPrivateAuraAppliedSound({
            unitToken     = "player",
            spellID       = ability.privateID,
            soundFileName = path,
            outputChannel = "master",
        })
        if id then
            handles[ability.key][#handles[ability.key] + 1] = id
        end
    end
end

local function unregisterAll()
    for key in pairs(handles) do
        unregisterAbility(key)
    end
end

local function registerAll()
    local diff = getCurrentDifficulty()
    if not diff then return end
    local instType = getInstanceType()

    if instType == "raid" then
        for _, entry in ipairs(CCS_Spells_Raid) do
            if entry.abilities then
                for _, ability in ipairs(entry.abilities) do
                    registerAbility(ability, diff)
                end
            end
        end
    elseif instType == "party" and CCS.MPLUS_ENABLED then
        for _, dungeon in ipairs(CCS_MplusDungeons) do
            for _, entry in ipairs(dungeon.data()) do
                if entry.abilities then
                    for _, ability in ipairs(entry.abilities) do
                        registerAbility(ability, diff)
                    end
                end
            end
        end
    end
end

--------------------------------------------------
-- Combat-safe refresh queue
--------------------------------------------------

local pendingRefreshAll = false
local pendingRefreshKeys = {}

local function flushPending()
    if pendingRefreshAll then
        pendingRefreshAll = false
        pendingRefreshKeys = {}
        unregisterAll()
        registerAll()
    else
        for key, ability in pairs(pendingRefreshKeys) do
            unregisterAbility(key)
            local diff = getCurrentDifficulty()
            if diff then registerAbility(ability, diff) end
        end
        pendingRefreshKeys = {}
    end
end

--------------------------------------------------
-- Public API
--------------------------------------------------

function CCS.RefreshAbility(key, ability)
    if InCombatLockdown() then
        pendingRefreshKeys[key] = ability
        return
    end
    unregisterAbility(key)
    local diff = getCurrentDifficulty()
    if diff then registerAbility(ability, diff) end
end

function CCS.RefreshSounds()
    if InCombatLockdown() then
        pendingRefreshAll = true
        return
    end
    unregisterAll()
    registerAll()
end

function CCS.RefreshAll()
    if InCombatLockdown() then
        pendingRefreshAll = true
        return
    end
    unregisterAll()
    registerAll()
end

function CCS.EnableAll()
    CCS.SetAllWarn(true)
    CCS.SetAllCD(true)
    CCS.RefreshAll()
end

function CCS.DisableAll()
    CCS.SetAllWarn(false)
    CCS.SetAllCD(false)
    CCS.RefreshAll()
end


--------------------------------------------------
-- Debug
--------------------------------------------------

function CCS.DebugProfile()
    local p = CCS.GetProfile()
    local diff = getCurrentDifficulty()
    print("|cffffff00CCS Debug — Profile:|r " .. CCS.GetProfileName() .. "  |cffaaaaaa(diff: " .. (diff or "none") .. ")|r")

    local warned, counted = 0, 0
    for _, entry in ipairs(CCS_Spells) do
        if entry.abilities then
            for _, ability in ipairs(entry.abilities) do
                local warnOn  = CCS.isWarnEnabled(ability.key)
                local warnOvr = CCS.GetWarnOverride(ability.key)
                local hOn     = CCS.IsCDEnabled(ability.key, "H")
                local mOn     = CCS.IsCDEnabled(ability.key, "M")
                local hOvr    = CCS.GetCountdownOverride(ability.key, "H")
                local mOvr    = CCS.GetCountdownOverride(ability.key, "M")
                if warnOn or warnOvr or hOn or mOn or hOvr or mOvr then
                    local parts = {}
                    if warnOn  then parts[#parts+1] = "|cff00ff00warn|r" end
                    if warnOvr then parts[#parts+1] = "|cffaaaaaarwarn=" .. warnOvr .. "|r" end
                    if hOn     then parts[#parts+1] = "|cff00ff00HC|r" end
                    if hOvr    then parts[#parts+1] = "|cffaaaaaaHC=" .. hOvr .. "|r" end
                    if mOn     then parts[#parts+1] = "|cff00ff00M|r" end
                    if mOvr    then parts[#parts+1] = "|cffaaaaaaM=" .. mOvr .. "|r" end
                    print("  " .. ability.key .. ": " .. table.concat(parts, "  "))
                    if warnOn then warned = warned + 1 end
                    if hOn or mOn then counted = counted + 1 end
                end
            end
        end
    end
    print("|cffaaaaaa  " .. warned .. " warn(s), " .. counted .. " countdown(s) enabled.|r")
end

function CCS.DebugSounds()
    local count = 0
    print("|cffffff00CCS Debug — Registered Sounds:|r")
    for key, ids in pairs(handles) do
        print("  " .. key .. ": " .. #ids .. " handle(s) — " .. table.concat(ids, ", "))
        count = count + 1
    end
    if count == 0 then
        print("|cffaaaaaa  No sounds currently registered.|r")
    else
        print("|cffaaaaaa  " .. count .. " ability/abilities registered.|r")
    end
end

local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
eventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
eventFrame:RegisterEvent("ENCOUNTER_START")
eventFrame:RegisterEvent("ENCOUNTER_END")
eventFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")

local dbReady = false
local pendingEnterWorld = false

eventFrame:SetScript("OnEvent", function(self, event, ...)
    if event == "ADDON_LOADED" then
        local name = ...
        if name ~= addonName then return end

        -- Initialize AceDB
        db = LibStub("AceDB-3.0"):New("CCS_DB", dbDefaults, true)
        db.char.module = "raid"  -- always start on raid module
        applyModule()

        -- Migrate old minimap from CCS_DB_CHAR global
        if CCS_DB_CHAR then
            if CCS_DB_CHAR.minimapAngle  ~= nil then db.char.minimapAngle  = CCS_DB_CHAR.minimapAngle  end
            if CCS_DB_CHAR.minimapHidden ~= nil then db.char.minimapHidden = CCS_DB_CHAR.minimapHidden end
        end

        -- Refresh sounds on any profile change, and notify UI panels
        db.RegisterCallback(CCS, "OnProfileChanged", function()
            CCS.RefreshAll()
            if CCS._onProfileChange then CCS._onProfileChange() end
        end)
        db.RegisterCallback(CCS, "OnProfileCopied",  function()
            CCS.RefreshAll()
            if CCS._onProfileChange then CCS._onProfileChange() end
        end)
        db.RegisterCallback(CCS, "OnProfileReset",   function()
            CCS.RefreshAll()
            if CCS._onProfileChange then CCS._onProfileChange() end
        end)

        dbReady = true
        self:UnregisterEvent("ADDON_LOADED")

        if pendingEnterWorld then
            pendingEnterWorld = false
            unregisterAll()
            registerAll()
        end

    elseif event == "PLAYER_ENTERING_WORLD" or event == "ZONE_CHANGED_NEW_AREA" then
        if not dbReady then
            pendingEnterWorld = true
            return
        end
        unregisterAll()
        registerAll()

    elseif event == "PLAYER_REGEN_ENABLED" then
        if pendingRefreshAll or next(pendingRefreshKeys) then
            flushPending()
            print("|cffffff00CCS:|r Settings applied after combat.")
        end

    elseif event == "ENCOUNTER_START" then
        unregisterAll()
        registerAll()

    elseif event == "ENCOUNTER_END" then
        unregisterAll()
    end
end)