local _, addonTable = ...
local WipedQoL = addonTable.WipedQoL
local TRANSLATE = addonTable.TRANSLATE or function(msg) return msg end

local MOUNT_SOUND_DEFAULT_KEY = "WipedQoL: Ante Up"
local KONYGBONY_SOUND_KEY = "WipedQoL: Konygebony"
local DEATH_SOUND_DEFAULT_KEY = "WipedQoL: Garnixgemacht"
local MOUNT_SOUND_LEGACY_KEY = "WipedQoL: Konig Ebony"
local MOUNT_SOUND_DEFAULT_PATH = "Interface\\AddOns\\WipedQoL\\Sounds\\anteup.ogg"
local KONYGBONY_SOUND_PATH = "Interface\\AddOns\\WipedQoL\\Sounds\\konygebony.ogg"
local DEATH_SOUND_DEFAULT_PATH = "Interface\\AddOns\\WipedQoL\\Sounds\\garnixgemacht.ogg"
local BOAHCUS_SOUND_PATH = "Interface\\AddOns\\WipedQoL\\Sounds\\boahcus.ogg"
local DEEZNUTZ_SOUND_PATH = "Interface\\AddOns\\WipedQoL\\Sounds\\deeznutz.ogg"
local DAYUM_SOUND_PATH = "Interface\\AddOns\\WipedQoL\\Sounds\\dayum.ogg"
local ICHHABEFERTIG_SOUND_PATH = "Interface\\AddOns\\WipedQoL\\Sounds\\ichhabefertig.ogg"
local HAHA_SIMPSONS_SOUND_PATH = "Interface\\AddOns\\WipedQoL\\Sounds\\HahaSimpsons.ogg"
local SALAMALEIKUM_SOUND_KEY = "WipedQoL: Salamaleikum"
local SALAMALEIKUM_SOUND_PATH = "Interface\\AddOns\\WipedQoL\\Sounds\\salamaleikum.ogg"
local GUTGENUG_SOUND_KEY = "WipedQoL: Gut genug"
local GUTGENUG_SOUND_PATH = "Interface\\AddOns\\WipedQoL\\Sounds\\gutgenug.ogg"
local WINSHUTDOWN_SOUND_KEY = "WipedQoL: Win Shutdown"
local WINSHUTDOWN_SOUND_PATH = "Interface\\AddOns\\WipedQoL\\Sounds\\winshutdown.ogg"
local CUSTOM_SOUND_DIRECTORY = "Interface\\Sounds\\"
local CUSTOM_SOUND_KEY_PREFIX = "WipedQoL Custom: "
local RANDOM_SOUND_LIST_VERSION = 3
local GROUP_DEATH_SOUND_DEFAULT_KEY = KONYGBONY_SOUND_KEY
local KEYSTONE_COMPLETION_SOUND_DEFAULT_KEY = MOUNT_SOUND_DEFAULT_KEY


local DEFAULT_RANDOM_SOUND_KEYS = {
    KONYGBONY_SOUND_KEY,
    "WipedQoL: Boahcus",
    "WipedQoL: Deez Nutz",
    "WipedQoL: Dayum",
    "WipedQoL: Ich habe fertig",
    "WipedQoL: HahaSimpsons",
    SALAMALEIKUM_SOUND_KEY,
    GUTGENUG_SOUND_KEY,
    WINSHUTDOWN_SOUND_KEY,
}
local ADDON_SOUND_OPTIONS = {
    {
        key = KONYGBONY_SOUND_KEY,
        path = KONYGBONY_SOUND_PATH,
    },
    {
        key = MOUNT_SOUND_DEFAULT_KEY,
        path = "Interface\\AddOns\\WipedQoL\\Sounds\\anteup.ogg",
    },
    {
        key = DEATH_SOUND_DEFAULT_KEY,
        path = DEATH_SOUND_DEFAULT_PATH,
    },
    {
        key = "WipedQoL: Boahcus",
        path = BOAHCUS_SOUND_PATH,
    },
    {
        key = "WipedQoL: Deez Nutz",
        path = DEEZNUTZ_SOUND_PATH,
    },
    {
        key = "WipedQoL: Dayum",
        path = DAYUM_SOUND_PATH,
    },
    {
        key = "WipedQoL: Ich habe fertig",
        path = ICHHABEFERTIG_SOUND_PATH,
    },
    {
        key = "WipedQoL: HahaSimpsons",
        path = HAHA_SIMPSONS_SOUND_PATH,
    },
    {
        key = SALAMALEIKUM_SOUND_KEY,
        path = SALAMALEIKUM_SOUND_PATH,
    },
    {
        key = GUTGENUG_SOUND_KEY,
        path = GUTGENUG_SOUND_PATH,
    },
    {
        key = WINSHUTDOWN_SOUND_KEY,
        path = WINSHUTDOWN_SOUND_PATH,
    },
}
local ADDON_SOUND_PATHS = {}
for _, sound in ipairs(ADDON_SOUND_OPTIONS) do
    ADDON_SOUND_PATHS[sound.key] = sound.path
end

local function GetSharedMedia()
    local lsm = addonTable and addonTable.LSM or nil
    if not lsm and LibStub then
        lsm = LibStub("LibSharedMedia-3.0", true)
        if addonTable then
            addonTable.LSM = lsm
        end
    end

    return lsm
end

local function EnsureSoundMedia()
    local lsm = GetSharedMedia()
    if lsm then
        for _, sound in ipairs(ADDON_SOUND_OPTIONS) do
            if not lsm:IsValid("sound", sound.key) then
                lsm:Register("sound", sound.key, sound.path)
            end
        end
    end

    return lsm
end

local function EnsureRandomSoundSettings(settings)
    settings.random = settings.random == true

    if settings.randomSoundsInitialized ~= true then
        settings.randomSounds = {}
        for _, soundName in ipairs(DEFAULT_RANDOM_SOUND_KEYS) do
            settings.randomSounds[soundName] = true
        end
        settings.randomSoundsInitialized = true
        settings.randomSoundsVersion = RANDOM_SOUND_LIST_VERSION
    elseif type(settings.randomSounds) ~= "table" then
        settings.randomSounds = {}
    end

    local version = tonumber(settings.randomSoundsVersion) or 0
    if version < 1 then
        settings.randomSounds[SALAMALEIKUM_SOUND_KEY] = true
    end
    if version < 2 then
        settings.randomSounds[GUTGENUG_SOUND_KEY] = true
    end
    if version < 3 then
        settings.randomSounds[WINSHUTDOWN_SOUND_KEY] = true
    end
    settings.randomSoundsVersion = RANDOM_SOUND_LIST_VERSION

    return settings
end

local function NormalizeCustomSoundFileName(value)
    value = strtrim(tostring(value or "")):gsub("/", "\\")
    local lowerValue = value:lower()
    local lowerDirectory = CUSTOM_SOUND_DIRECTORY:lower()
    if lowerValue:sub(1, #lowerDirectory) == lowerDirectory then
        value = value:sub(#CUSTOM_SOUND_DIRECTORY + 1)
        lowerValue = value:lower()
    end

    if value == "" or value:find("\\", 1, true) or value:find(":", 1, true) then
        return nil
    end
    if not lowerValue:match("%.ogg$") and not lowerValue:match("%.mp3$") then
        return nil
    end

    return value
end

local function GetCustomSoundKey(fileName)
    return CUSTOM_SOUND_KEY_PREFIX .. tostring(fileName or ""):lower()
end

local function IsSoundAvailable(addon, soundName)
    if ADDON_SOUND_PATHS[soundName] then
        return true
    end

    if addon:GetCustomSoundPath(soundName) then
        return true
    end

    local lsm = EnsureSoundMedia()
    return lsm and lsm:IsValid("sound", soundName) == true
end

local function IsMissingCustomSound(addon, soundName)
    soundName = tostring(soundName or "")
    return soundName:sub(1, #CUSTOM_SOUND_KEY_PREFIX) == CUSTOM_SOUND_KEY_PREFIX
        and addon:GetCustomSoundPath(soundName) == nil
end



local function IsSecretValue(value)
    return issecretvalue and issecretvalue(value) == true
end

local function WasKeystoneCompletedOnTime()
    if not C_ChallengeMode then
        return false
    end

    if C_ChallengeMode.GetChallengeCompletionInfo then
        local ok, info = pcall(C_ChallengeMode.GetChallengeCompletionInfo)
        if not ok or type(info) ~= "table" then
            return false
        end

        local level = info.level
        local onTime = info.onTime
        local practiceRun = info.practiceRun
        if IsSecretValue(level) or IsSecretValue(onTime) or IsSecretValue(practiceRun) then
            return false
        end

        level = tonumber(level)
        return level ~= nil and level > 0 and onTime == true and practiceRun ~= true
    end

    if C_ChallengeMode.GetCompletionInfo then
        local ok, _, level, _, onTime, _, practiceRun = pcall(C_ChallengeMode.GetCompletionInfo)
        if not ok or IsSecretValue(level) or IsSecretValue(onTime) or IsSecretValue(practiceRun) then
            return false
        end

        level = tonumber(level)
        return level ~= nil and level > 0 and onTime == true and practiceRun ~= true
    end

    return false
end



function WipedQoL:GetMountSoundSettings()
    local modules = self.db and self.db.profile and self.db.profile.modules
    if not modules then
        return {}
    end

    if type(modules.mountSound) ~= "table" then
        modules.mountSound = {
            enabled = false,
            sound = MOUNT_SOUND_DEFAULT_KEY,
        }
    end

    local settings = modules.mountSound
    settings.enabled = settings.enabled == true
    settings.sound = tostring(settings.sound or MOUNT_SOUND_DEFAULT_KEY)
    if settings.sound == MOUNT_SOUND_LEGACY_KEY then
        settings.sound = MOUNT_SOUND_DEFAULT_KEY
    end
    if IsMissingCustomSound(self, settings.sound) then
        settings.sound = MOUNT_SOUND_DEFAULT_KEY
    end
    return settings
end

function WipedQoL:GetDeathSoundSettings()
    local modules = self.db and self.db.profile and self.db.profile.modules
    if not modules then
        return {}
    end

    if type(modules.deathSound) ~= "table" then
        modules.deathSound = {
            enabled = false,
            sound = DEATH_SOUND_DEFAULT_KEY,
        }
    end

    local settings = modules.deathSound
    settings.enabled = settings.enabled == true
    settings.sound = tostring(settings.sound or DEATH_SOUND_DEFAULT_KEY)
    EnsureRandomSoundSettings(settings)
    if settings.sound == MOUNT_SOUND_LEGACY_KEY then
        settings.sound = DEATH_SOUND_DEFAULT_KEY
    end
    if IsMissingCustomSound(self, settings.sound) then
        settings.sound = DEATH_SOUND_DEFAULT_KEY
    end
    return settings
end

function WipedQoL:GetGroupDeathSoundSettings()
    local modules = self.db and self.db.profile and self.db.profile.modules
    if not modules then
        return {}
    end

    if type(modules.groupDeathSound) ~= "table" then
        modules.groupDeathSound = {
            enabled = false,
            sound = GROUP_DEATH_SOUND_DEFAULT_KEY,
            random = false,
        }
    end

    local settings = modules.groupDeathSound
    settings.enabled = settings.enabled == true
    settings.sound = tostring(settings.sound or GROUP_DEATH_SOUND_DEFAULT_KEY)
    EnsureRandomSoundSettings(settings)
    if settings.sound == MOUNT_SOUND_LEGACY_KEY then
        settings.sound = GROUP_DEATH_SOUND_DEFAULT_KEY
    end
    if IsMissingCustomSound(self, settings.sound) then
        settings.sound = GROUP_DEATH_SOUND_DEFAULT_KEY
    end
    return settings
end

function WipedQoL:GetKeystoneCompletionSoundSettings()
    local modules = self.db and self.db.profile and self.db.profile.modules
    if not modules then
        return {}
    end

    if type(modules.keystoneCompletionSound) ~= "table" then
        modules.keystoneCompletionSound = {
            enabled = false,
            sound = KEYSTONE_COMPLETION_SOUND_DEFAULT_KEY,
        }
    end

    local settings = modules.keystoneCompletionSound
    settings.enabled = settings.enabled == true
    settings.sound = tostring(settings.sound or KEYSTONE_COMPLETION_SOUND_DEFAULT_KEY)
    EnsureRandomSoundSettings(settings)
    if settings.sound == MOUNT_SOUND_LEGACY_KEY then
        settings.sound = KEYSTONE_COMPLETION_SOUND_DEFAULT_KEY
    end
    if IsMissingCustomSound(self, settings.sound) then
        settings.sound = KEYSTONE_COMPLETION_SOUND_DEFAULT_KEY
    end
    return settings
end

function WipedQoL:GetCustomSounds()
    local global = self.db and self.db.global
    if not global then
        return {}
    end

    if type(global.customSounds) ~= "table" then
        global.customSounds = {}
    end

    return global.customSounds
end

function WipedQoL:GetCustomSoundPath(soundName)
    soundName = tostring(soundName or "")
    for _, entry in ipairs(self:GetCustomSounds()) do
        if type(entry) == "table" and entry.key == soundName then
            local fileName = NormalizeCustomSoundFileName(entry.file)
            if fileName then
                return CUSTOM_SOUND_DIRECTORY .. fileName
            end
        end
    end

    return nil
end




function WipedQoL:AddCustomSound(displayName, fileName)
    displayName = strtrim(tostring(displayName or ""))
    if displayName == "" then
        return false, "missingName"
    end

    fileName = NormalizeCustomSoundFileName(fileName)
    if not fileName then
        return false, "invalidFile"
    end

    local key = GetCustomSoundKey(fileName)
    local sounds = self:GetCustomSounds()
    for _, entry in ipairs(sounds) do
        if type(entry) == "table" and entry.key == key then
            return false, "duplicateFile"
        end
    end

    sounds[#sounds + 1] = {
        key = key,
        name = displayName,
        file = fileName,
    }

    if self.RefreshSoundUI then
        self:RefreshSoundUI()
    end
    if self.RefreshCustomSoundsUI then
        self:RefreshCustomSoundsUI()
    end
    return true
end

function WipedQoL:RemoveCustomSound(soundName)
    soundName = tostring(soundName or "")
    local sounds = self:GetCustomSounds()
    local removed = false
    for index = #sounds, 1, -1 do
        local entry = sounds[index]
        if type(entry) == "table" and entry.key == soundName then
            table.remove(sounds, index)
            removed = true
            break
        end
    end

    if not removed then
        return false
    end

    local settingsAndDefaults = {
        { self:GetMountSoundSettings(), MOUNT_SOUND_DEFAULT_KEY },
        { self:GetDeathSoundSettings(), DEATH_SOUND_DEFAULT_KEY },
        { self:GetGroupDeathSoundSettings(), GROUP_DEATH_SOUND_DEFAULT_KEY },
        { self:GetKeystoneCompletionSoundSettings(), KEYSTONE_COMPLETION_SOUND_DEFAULT_KEY },
    }
    for _, pair in ipairs(settingsAndDefaults) do
        local settings, defaultSound = pair[1], pair[2]
        if settings.sound == soundName then
            settings.sound = defaultSound
        end
        if type(settings.randomSounds) == "table" then
            settings.randomSounds[soundName] = nil
        end
    end

    self:StopSoundPlayback(true)
    self:RefreshSoundUI()
    if self.RefreshCustomSoundsUI then
        self:RefreshCustomSoundsUI()
    end
    return true
end

function WipedQoL:PlayCustomSoundPreview(soundName)
    local path = self:GetCustomSoundPath(soundName)
    if not path then
        return false
    end

    return self:PlaySoundPlayback(path, true) == true
end



function WipedQoL:GetSoundOptions()
    local options = {}
    local seen = {}
    local function AddOption(name)
        if not name or seen[name] then
            return
        end

        seen[name] = true
        options[#options + 1] = {
            value = name,
            label = name,
        }
    end

    for _, sound in ipairs(ADDON_SOUND_OPTIONS) do
        AddOption(sound.key)
    end

    local customSounds = {}
    for _, entry in ipairs(self:GetCustomSounds()) do
        if type(entry) == "table" and entry.key and entry.name and self:GetCustomSoundPath(entry.key) then
            customSounds[#customSounds + 1] = entry
        end
    end
    table.sort(customSounds, function(left, right)
        return tostring(left.name):lower() < tostring(right.name):lower()
    end)
    for _, entry in ipairs(customSounds) do
        if not seen[entry.key] then
            seen[entry.key] = true
            options[#options + 1] = {
                value = entry.key,
                label = string.format("%s: %s", TRANSLATE("Custom"), entry.name),
            }
        end
    end

    local lsm = EnsureSoundMedia()
    if lsm and lsm.List then
        local names = {}
        for _, name in ipairs(lsm:List("sound") or {}) do
            names[#names + 1] = name
        end
        table.sort(names)
        for _, name in ipairs(names) do
            AddOption(name)
        end
    end

    return options
end

function WipedQoL:GetSoundPath(soundName)
    local lsm = EnsureSoundMedia()
    soundName = tostring(soundName or MOUNT_SOUND_DEFAULT_KEY)

    if ADDON_SOUND_PATHS[soundName] then
        return ADDON_SOUND_PATHS[soundName]
    end

    local customPath = self:GetCustomSoundPath(soundName)
    if customPath then
        return customPath
    end

    if lsm and lsm:IsValid("sound", soundName) then
        return lsm:Fetch("sound", soundName, true) or MOUNT_SOUND_DEFAULT_PATH
    end

    return MOUNT_SOUND_DEFAULT_PATH
end

function WipedQoL:GetRandomSoundSelectionText(settings)
    settings = EnsureRandomSoundSettings(settings or {})
    local count = 0
    for soundName, selected in pairs(settings.randomSounds) do
        if selected == true and IsSoundAvailable(self, soundName) then
            count = count + 1
        end
    end

    if count == 0 then
        return TRANSLATE("No sounds selected")
    elseif count == 1 then
        return TRANSLATE("1 sound selected")
    end

    return string.format(TRANSLATE("%d sounds selected"), count)
end

function WipedQoL:GetConfiguredSoundPath(settings)
    settings = EnsureRandomSoundSettings(settings or {})
    if settings.random == true then
        local selectedSounds = {}
        for soundName, selected in pairs(settings.randomSounds) do
            if selected == true and IsSoundAvailable(self, soundName) then
                selectedSounds[#selectedSounds + 1] = soundName
            end
        end

        if #selectedSounds > 0 then
            local soundName = selectedSounds[math.random(#selectedSounds)]
            return self:GetSoundPath(soundName)
        end
    end

    return self:GetSoundPath(settings.sound)
end

local function SetRandomSoundSelected(settings, soundName, selected)
    EnsureRandomSoundSettings(settings)
    soundName = tostring(soundName or "")
    if soundName == "" then
        return
    end

    settings.randomSounds[soundName] = selected == true and true or nil
end



function WipedQoL:SetMountSoundEnabled(enabled)
    local settings = self:GetMountSoundSettings()
    settings.enabled = enabled == true
    if self.ApplyMountLootTracking then
        self:ApplyMountLootTracking()
    end
    if settings.enabled ~= true and self.mountSoundPreviewHandle then
        self:StopMountSoundPreview()
        return
    end

    if self.RefreshMountSoundUI then
        self:RefreshMountSoundUI()
    end
end

function WipedQoL:SetMountSound(soundName)
    local settings = self:GetMountSoundSettings()
    settings.sound = tostring(soundName or MOUNT_SOUND_DEFAULT_KEY)
    if self.mountSoundPreviewHandle then
        self:StopMountSoundPreview()
        return
    end

    if self.RefreshMountSoundUI then
        self:RefreshMountSoundUI()
    end
end

function WipedQoL:SetDeathSoundEnabled(enabled)
    local settings = self:GetDeathSoundSettings()
    settings.enabled = enabled == true
    if settings.enabled ~= true and self.deathSoundPreviewHandle then
        self:StopDeathSoundPreview()
        return
    end

    if self.RefreshDeathSoundUI then
        self:RefreshDeathSoundUI()
    end
end

function WipedQoL:SetDeathSound(soundName)
    local settings = self:GetDeathSoundSettings()
    settings.sound = tostring(soundName or DEATH_SOUND_DEFAULT_KEY)
    if self.deathSoundPreviewHandle then
        self:StopDeathSoundPreview()
        return
    end

    if self.RefreshDeathSoundUI then
        self:RefreshDeathSoundUI()
    end
end

function WipedQoL:SetDeathSoundRandomEnabled(enabled)
    local settings = self:GetDeathSoundSettings()
    settings.random = enabled == true
    if self.deathSoundPreviewHandle then
        self:StopDeathSoundPreview()
        return
    end

    if self.RefreshDeathSoundUI then
        self:RefreshDeathSoundUI()
    end
end

function WipedQoL:SetDeathSoundRandomSound(soundName, selected)
    SetRandomSoundSelected(self:GetDeathSoundSettings(), soundName, selected)
    if self.RefreshDeathSoundUI then
        self:RefreshDeathSoundUI()
    end
end

function WipedQoL:GetDeathSoundPath()
    return self:GetConfiguredSoundPath(self:GetDeathSoundSettings())
end



function WipedQoL:SetGroupDeathSoundEnabled(enabled)
    local settings = self:GetGroupDeathSoundSettings()
    settings.enabled = enabled == true
    if self.UpdateGroupDeathSoundEventRegistration then
        self:UpdateGroupDeathSoundEventRegistration()
    end
    if settings.enabled ~= true and self.groupDeathSoundPreviewHandle then
        self:StopGroupDeathSoundPreview()
        return
    end

    if self.RefreshGroupDeathSoundUI then
        self:RefreshGroupDeathSoundUI()
    end
end

function WipedQoL:SetGroupDeathSound(soundName)
    local settings = self:GetGroupDeathSoundSettings()
    settings.sound = tostring(soundName or GROUP_DEATH_SOUND_DEFAULT_KEY)
    if self.groupDeathSoundPreviewHandle then
        self:StopGroupDeathSoundPreview()
        return
    end

    if self.RefreshGroupDeathSoundUI then
        self:RefreshGroupDeathSoundUI()
    end
end

function WipedQoL:SetGroupDeathSoundRandomEnabled(enabled)
    local settings = self:GetGroupDeathSoundSettings()
    settings.random = enabled == true
    if self.groupDeathSoundPreviewHandle then
        self:StopGroupDeathSoundPreview()
        return
    end

    if self.RefreshGroupDeathSoundUI then
        self:RefreshGroupDeathSoundUI()
    end
end

function WipedQoL:SetGroupDeathSoundRandomSound(soundName, selected)
    SetRandomSoundSelected(self:GetGroupDeathSoundSettings(), soundName, selected)
    if self.RefreshGroupDeathSoundUI then
        self:RefreshGroupDeathSoundUI()
    end
end

function WipedQoL:GetGroupDeathSoundPath()
    return self:GetConfiguredSoundPath(self:GetGroupDeathSoundSettings())
end

function WipedQoL:SetKeystoneCompletionSoundEnabled(enabled)
    local settings = self:GetKeystoneCompletionSoundSettings()
    settings.enabled = enabled == true
    if settings.enabled ~= true and self.keystoneCompletionSoundPreviewHandle then
        self:StopKeystoneCompletionSoundPreview()
        return
    end

    if self.RefreshKeystoneCompletionSoundUI then
        self:RefreshKeystoneCompletionSoundUI()
    end
end

function WipedQoL:SetKeystoneCompletionSound(soundName)
    local settings = self:GetKeystoneCompletionSoundSettings()
    settings.sound = tostring(soundName or KEYSTONE_COMPLETION_SOUND_DEFAULT_KEY)
    if self.keystoneCompletionSoundPreviewHandle then
        self:StopKeystoneCompletionSoundPreview()
        return
    end

    if self.RefreshKeystoneCompletionSoundUI then
        self:RefreshKeystoneCompletionSoundUI()
    end
end

function WipedQoL:SetKeystoneCompletionSoundRandomEnabled(enabled)
    local settings = self:GetKeystoneCompletionSoundSettings()
    settings.random = enabled == true
    if self.keystoneCompletionSoundPreviewHandle then
        self:StopKeystoneCompletionSoundPreview()
        return
    end

    if self.RefreshKeystoneCompletionSoundUI then
        self:RefreshKeystoneCompletionSoundUI()
    end
end

function WipedQoL:SetKeystoneCompletionSoundRandomSound(soundName, selected)
    SetRandomSoundSelected(self:GetKeystoneCompletionSoundSettings(), soundName, selected)
    if self.RefreshKeystoneCompletionSoundUI then
        self:RefreshKeystoneCompletionSoundUI()
    end
end

function WipedQoL:GetKeystoneCompletionSoundPath()
    return self:GetConfiguredSoundPath(self:GetKeystoneCompletionSoundSettings())
end



function WipedQoL:RefreshSoundUI()
    if self.RefreshMountSoundUI then
        self:RefreshMountSoundUI()
    end
    if self.RefreshDeathSoundUI then
        self:RefreshDeathSoundUI()
    end
    if self.RefreshGroupDeathSoundUI then
        self:RefreshGroupDeathSoundUI()
    end
    if self.RefreshKeystoneCompletionSoundUI then
        self:RefreshKeystoneCompletionSoundUI()
    end
end

function WipedQoL:StopSoundPlayback(skipRefresh)
    if self.mountSoundPreviewHandle and StopSound then
        pcall(StopSound, self.mountSoundPreviewHandle)
    end
    if self.deathSoundPreviewHandle and StopSound then
        pcall(StopSound, self.deathSoundPreviewHandle)
    end
    if self.groupDeathSoundPreviewHandle and StopSound then
        pcall(StopSound, self.groupDeathSoundPreviewHandle)
    end
    if self.keystoneCompletionSoundPreviewHandle and StopSound then
        pcall(StopSound, self.keystoneCompletionSoundPreviewHandle)
    end
    if self.soundPlaybackHandle and StopSound then
        pcall(StopSound, self.soundPlaybackHandle)
    end

    self.mountSoundPreviewHandle = nil
    self.mountSoundPreviewToken = nil
    self.deathSoundPreviewHandle = nil
    self.deathSoundPreviewToken = nil
    self.groupDeathSoundPreviewHandle = nil
    self.groupDeathSoundPreviewToken = nil
    self.keystoneCompletionSoundPreviewHandle = nil
    self.keystoneCompletionSoundPreviewToken = nil
    self.soundPlaybackHandle = nil
    self.soundPlaybackToken = nil

    if skipRefresh then
        return
    end

    self:RefreshSoundUI()
end

function WipedQoL:PlaySoundPlayback(path, forceRestart)
    if self.soundPlaybackToken and not forceRestart then
        return false
    end

    self:StopSoundPlayback(true)

    if not PlaySoundFile or not path then
        return false
    end

    local willPlay, soundHandle = PlaySoundFile(path, "Master")
    if type(willPlay) == "number" and soundHandle == nil then
        soundHandle = willPlay
        willPlay = true
    end

    if willPlay then
        self.soundPlaybackHandle = soundHandle
        local soundToken = {}
        self.soundPlaybackToken = soundToken
        if C_Timer and C_Timer.After then
            C_Timer.After(10, function()
                if self.soundPlaybackToken ~= soundToken then
                    return
                end

                self.soundPlaybackHandle = nil
                self.soundPlaybackToken = nil
                if self.mountSoundPreviewToken == soundToken then
                    self.mountSoundPreviewHandle = nil
                    self.mountSoundPreviewToken = nil
                    if self.RefreshMountSoundUI then
                        self:RefreshMountSoundUI()
                    end
                end
                if self.deathSoundPreviewToken == soundToken then
                    self.deathSoundPreviewHandle = nil
                    self.deathSoundPreviewToken = nil
                    if self.RefreshDeathSoundUI then
                        self:RefreshDeathSoundUI()
                    end
                end
                if self.groupDeathSoundPreviewToken == soundToken then
                    self.groupDeathSoundPreviewHandle = nil
                    self.groupDeathSoundPreviewToken = nil
                    if self.RefreshGroupDeathSoundUI then
                        self:RefreshGroupDeathSoundUI()
                    end
                end
                if self.keystoneCompletionSoundPreviewToken == soundToken then
                    self.keystoneCompletionSoundPreviewHandle = nil
                    self.keystoneCompletionSoundPreviewToken = nil
                    if self.RefreshKeystoneCompletionSoundUI then
                        self:RefreshKeystoneCompletionSoundUI()
                    end
                end
            end)
        end
    end

    return willPlay == true
end



function WipedQoL:StopMountSoundPreview()
    self:StopSoundPlayback()
end

function WipedQoL:PlayMountSoundPreview()
    local settings = self:GetMountSoundSettings()
    local path = self:GetSoundPath(settings.sound)
    if not self:PlaySoundPlayback(path, true) then
        self:RefreshSoundUI()
        return
    end

    self.mountSoundPreviewHandle = self.soundPlaybackHandle
    self.mountSoundPreviewToken = self.soundPlaybackToken

    self:RefreshSoundUI()
end

function WipedQoL:IsMountSoundPreviewPlaying()
    return self.mountSoundPreviewHandle ~= nil
end

function WipedQoL:StopDeathSoundPreview()
    self:StopSoundPlayback()
end

function WipedQoL:PlayDeathSoundPreview()
    local path = self:GetDeathSoundPath()
    if not self:PlaySoundPlayback(path, true) then
        self:RefreshSoundUI()
        return
    end

    self.deathSoundPreviewHandle = self.soundPlaybackHandle
    self.deathSoundPreviewToken = self.soundPlaybackToken

    self:RefreshSoundUI()
end

function WipedQoL:IsDeathSoundPreviewPlaying()
    return self.deathSoundPreviewHandle ~= nil
end

function WipedQoL:StopGroupDeathSoundPreview()
    self:StopSoundPlayback()
end

function WipedQoL:PlayGroupDeathSoundPreview()
    local path = self:GetGroupDeathSoundPath()
    if not self:PlaySoundPlayback(path, true) then
        self:RefreshSoundUI()
        return
    end

    self.groupDeathSoundPreviewHandle = self.soundPlaybackHandle
    self.groupDeathSoundPreviewToken = self.soundPlaybackToken

    self:RefreshSoundUI()
end

function WipedQoL:IsGroupDeathSoundPreviewPlaying()
    return self.groupDeathSoundPreviewHandle ~= nil
end

function WipedQoL:StopKeystoneCompletionSoundPreview()
    self:StopSoundPlayback()
end

function WipedQoL:PlayKeystoneCompletionSoundPreview()
    local path = self:GetKeystoneCompletionSoundPath()
    if not self:PlaySoundPlayback(path, true) then
        self:RefreshSoundUI()
        return
    end

    self.keystoneCompletionSoundPreviewHandle = self.soundPlaybackHandle
    self.keystoneCompletionSoundPreviewToken = self.soundPlaybackToken

    self:RefreshSoundUI()
end

function WipedQoL:IsKeystoneCompletionSoundPreviewPlaying()
    return self.keystoneCompletionSoundPreviewHandle ~= nil
end



function WipedQoL:SetupSounds()
    if self.soundEventsRegistered then
        return
    end

    self.soundEventsRegistered = true
    self:RegisterEvent("PLAYER_DEAD", "HandlePlayerDead")
    self:RegisterEvent("GROUP_ROSTER_UPDATE", "RefreshGroupDeathSoundStates")
    self:RegisterEvent("PLAYER_ENTERING_WORLD", "RefreshGroupDeathSoundStates")
    self:RegisterEvent("CHALLENGE_MODE_COMPLETED", "HandleKeystoneCompleted")
    self:UpdateGroupDeathSoundEventRegistration()
end

function WipedQoL:HandlePlayerDead()
    local settings = self:GetDeathSoundSettings()
    if settings.enabled ~= true then
        return
    end

    self:PlaySoundPlayback(self:GetDeathSoundPath())
end

function WipedQoL:HandleKeystoneCompleted()
    local settings = self:GetKeystoneCompletionSoundSettings()
    if settings.enabled ~= true or not WasKeystoneCompletedOnTime() then
        return
    end

    self:PlaySoundPlayback(self:GetKeystoneCompletionSoundPath())
end

function WipedQoL:IsGroupDeathSoundUnit(unit)
    if type(unit) ~= "string" then
        return false
    end

    return string.find(unit, "^party%d+$") ~= nil or string.find(unit, "^raid%d+$") ~= nil
end

function WipedQoL:UpdateGroupDeathSoundEventRegistration()
    local enabled = self:GetGroupDeathSoundSettings().enabled == true
    self.groupDeathSoundRuntimeEnabled = enabled
    if enabled then
        self:RegisterEvent("UNIT_FLAGS", "HandleGroupDeathSoundUnitChanged")
        self:RegisterEvent("UNIT_HEALTH", "HandleGroupDeathSoundUnitChanged")
        self:RefreshGroupDeathSoundStates()
    else
        self:UnregisterEvent("UNIT_FLAGS")
        self:UnregisterEvent("UNIT_HEALTH")
        if self.groupDeathSoundDeadUnits then
            wipe(self.groupDeathSoundDeadUnits)
        end
    end
end

function WipedQoL:UpdateGroupDeathSoundUnit(unit, playOnNewDeath)
    if not self:IsGroupDeathSoundUnit(unit) or not UnitIsDeadOrGhost then
        return
    end

    if UnitIsUnit and UnitIsUnit(unit, "player") then
        return
    end

    self.groupDeathSoundDeadUnits = self.groupDeathSoundDeadUnits or {}
    local isDead = UnitIsDeadOrGhost(unit) == true
    local wasDead = self.groupDeathSoundDeadUnits[unit] == true

    if isDead then
        self.groupDeathSoundDeadUnits[unit] = true
        if playOnNewDeath and not wasDead then
            self:PlaySoundPlayback(self:GetGroupDeathSoundPath())
        end
    else
        self.groupDeathSoundDeadUnits[unit] = nil
    end
end

function WipedQoL:RefreshGroupDeathSoundStates()
    self.groupDeathSoundDeadUnits = self.groupDeathSoundDeadUnits or {}
    wipe(self.groupDeathSoundDeadUnits)
    if self.groupDeathSoundRuntimeEnabled ~= true then
        return
    end
    if not IsInGroup or not IsInGroup() or not GetNumGroupMembers then
        return
    end

    local unitPrefix = IsInRaid and IsInRaid() and "raid" or "party"
    local count = GetNumGroupMembers() or 0
    if unitPrefix == "party" and count > 0 then
        count = count - 1
    end

    for index = 1, count do
        self:UpdateGroupDeathSoundUnit(unitPrefix .. index, false)
    end
end

function WipedQoL:HandleGroupDeathSoundUnitChanged(_, unit)
    if self.groupDeathSoundRuntimeEnabled ~= true then
        return
    end
    self:UpdateGroupDeathSoundUnit(unit, true)
end
