the repeat doesnt work in parallel mode. when I turn I use it in parallel mode it should play the sound 2 times when the repeat is set to 2, but after eachother. but also add a optional speed input from the /play command that I can play a sound in 2x when I want to.
output the full updated code:
import discord
from discord import app_commands
from discord.ext import commands
import os
from dotenv import load_dotenv
import asyncio
from pydub import AudioSegment
import requests
import json
import logging
from typing import Optional
from enum import Enum

# Configure logging with timestamps
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s:%(levelname)s:%(name)s: %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

# Load environment variables from .env
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')

# Define intents
intents = discord.Intents.default()
intents.message_content = True
intents.guilds = True
intents.voice_states = True

# Playback modes
class PlaybackMode(Enum):
    QUEUE = 'queue'
    SYNC = 'sync'
    PARALLEL = 'parallel'

# Initialize the bot
class SoundboardBot(commands.Bot):
    def __init__(self):
        super().__init__(
            command_prefix="!",  # Prefix is not used for slash commands but required
            intents=intents,
        )
        self.queue = {}            # Dictionary to hold queues for each guild
        self.sounds = {}           # Dictionary to hold sound metadata
        self.disconnect_tasks = {} # Dictionary to hold disconnect tasks for each guild
        self.guild_durations = {}  # Dictionary to hold disconnect durations per guild
        self.guild_modes = {}      # Dictionary to hold playback modes per guild ('queue' or 'sync' or 'parallel')
        self.parallel_audio_sources = {}  # Dictionary to hold ParallelAudioSource per guild

    async def setup_hook(self):
        # Sync the command tree with Discord
        await self.tree.sync()
        # Load sounds from JSON
        self.load_sounds()

    def load_sounds(self):
        sounds_file = os.path.join("data", "sounds.json")
        if os.path.exists(sounds_file):
            with open(sounds_file, "r") as f:
                self.sounds = json.load(f)
            logging.info("Loaded sounds from sounds.json")
        else:
            self.sounds = {}
            os.makedirs(os.path.dirname(sounds_file), exist_ok=True)
            with open(sounds_file, "w") as f:
                json.dump(self.sounds, f, indent=4)
            logging.info("Created new sounds.json")

    def save_sounds(self):
        sounds_file = os.path.join("data", "sounds.json")
        with open(sounds_file, "w") as f:
            json.dump(self.sounds, f, indent=4)
        logging.info("Saved sounds to sounds.json")

    async def play_sound_via_button(self, interaction: discord.Interaction, sound_name: str):
        """
        Helper function to play sounds when triggered via soundboard buttons.
        Respects the current playback mode ('queue', 'sync', 'parallel').
        """
        # Check if the user is in a voice channel
        if not interaction.user.voice or not interaction.user.voice.channel:
            await interaction.response.send_message("❌ You need to be in a voice channel to use this button.", ephemeral=True)
            return

        voice_channel = interaction.user.voice.channel
        guild_id = interaction.guild.id

        # Locate the sound file
        if sound_name not in self.sounds:
            await interaction.response.send_message(f"❌ Sound `{sound_name}` not found.", ephemeral=True)
            return

        sound_file = self.sounds[sound_name]["file_path"]
        sound_duration = self.sounds[sound_name]["duration"]

        # Get the current mode
        mode = self.guild_modes.get(guild_id, 'queue')

        # Initialize the queue if not present
        if guild_id not in self.queue:
            self.queue[guild_id] = asyncio.Queue()

        # Connect to the voice channel if not already connected
        voice_client = discord.utils.get(self.voice_clients, guild__id=guild_id)

        if not voice_client:
            try:
                voice_client = await voice_channel.connect()
                logging.info(f"Connected to voice channel {voice_channel} in guild {guild_id}.")
            except Exception as e:
                logging.error(f"Failed to connect to voice channel: {e}")
                await interaction.response.send_message(f"❌ Failed to connect to voice channel: {e}", ephemeral=True)
                return
        else:
            if voice_client.channel != voice_channel:
                try:
                    await voice_client.move_to(voice_channel)
                    logging.info(f"Moved to voice channel {voice_channel} in guild {guild_id}.")
                except Exception as e:
                    logging.error(f"Failed to move to voice channel: {e}")
                    await interaction.response.send_message(f"❌ Failed to move to voice channel: {e}", ephemeral=True)
                    return

        if mode == 'queue':
            # Enqueue the sound as usual
            await self.queue[guild_id].put((sound_file, 1, sound_duration))
            logging.info(f"Enqueued sound '{sound_name}' in guild {guild_id} (Mode: Queue).")

            # Cancel any existing disconnect task
            if guild_id in self.disconnect_tasks:
                self.disconnect_tasks[guild_id].cancel()
                del self.disconnect_tasks[guild_id]

            # If not already playing, start playing
            if not voice_client.is_playing():
                await play_next(guild_id)

            await interaction.response.send_message(f"✅ Sound `{sound_name}` enqueued.", ephemeral=True)

        elif mode == 'sync':
            # Sync mode: Play immediately, interrupting current playback
            if voice_client.is_playing():
                voice_client.stop()
                logging.info(f"Stopped current playback in guild {guild_id} for sync mode.")

            # Clear the queue
            if guild_id in self.queue:
                try:
                    while not self.queue[guild_id].empty():
                        self.queue[guild_id].get_nowait()
                except:
                    pass  # Ignore if queue is already empty

            # Enqueue the sound
            await self.queue[guild_id].put((sound_file, 1, sound_duration))
            logging.info(f"Queued sound '{sound_name}' in guild {guild_id} (Mode: Sync).")

            # Start playing immediately
            await play_next(guild_id)

            # Cancel any existing disconnect task
            if guild_id in self.disconnect_tasks:
                self.disconnect_tasks[guild_id].cancel()
                del self.disconnect_tasks[guild_id]

            await interaction.response.send_message(f"✅ Sound `{sound_name}` played immediately (Sync Mode).", ephemeral=True)

        elif mode == 'parallel':
            # Parallel mode: Play sounds simultaneously
            # Prepare the audio source
            before_options = f"-ss 0 -t {sound_duration}"
            source = discord.FFmpegPCMAudio(sound_file, before_options=before_options, options="-vn")

            # Initialize ParallelAudioSource if not exists
            if guild_id not in self.parallel_audio_sources:
                self.parallel_audio_sources[guild_id] = ParallelAudioSource()

            # Add the source to ParallelAudioSource
            self.parallel_audio_sources[guild_id].add_source(source)

            # If not already playing, start playing
            if not voice_client.is_playing():
                def after_playing(error):
                    if error:
                        logging.error(f"Error in parallel playback: {error}")
                    asyncio.run_coroutine_threadsafe(handle_parallel_finish(guild_id), self.loop)

                voice_client.play(self.parallel_audio_sources[guild_id], after=after_playing)
                logging.info(f"Started parallel playback in guild {guild_id}.")

            await interaction.response.send_message(f"✅ Sound `{sound_name}` added to parallel playback.", ephemeral=True)

bot = SoundboardBot()

# Directory to store sound files
SOUND_DIR = "sounds"

# Ensure the sounds and data directories exist
for directory in [SOUND_DIR, "data"]:
    if not os.path.exists(directory):
        os.makedirs(directory)
        logging.info(f"Created directory: {directory}")

# Allowed audio formats
ALLOWED_EXTENSIONS = ('.mp3', '.wav', '.ogg', '.flac')
# Maximum file size (in bytes) for uploads via `/add` command
MAX_FILE_SIZE = 8 * 1024 * 1024  # 8MB

# Helper function to extract duration of audio file in seconds
def get_audio_duration(file_path):
    try:
        audio = AudioSegment.from_file(file_path)
        duration = len(audio) / 1000  # pydub works in milliseconds
        return round(duration, 2)
    except Exception as e:
        logging.error(f"Error getting duration of {file_path}: {e}")
        return None

# Helper function to download a file from a URL
async def download_file(url, dest):
    loop = asyncio.get_event_loop()
    try:
        response = requests.get(url, stream=True, timeout=120)
        response.raise_for_status()
        # Download in chunks to handle large files efficiently
        with open(dest, 'wb') as f:
            for chunk in response.iter_content(chunk_size=8192):
                if chunk:
                    f.write(chunk)
        return True, ""
    except requests.exceptions.RequestException as e:
        return False, str(e)

# Helper function to handle disconnect after duration
async def monitor_queue(guild_id: int):
    duration = bot.guild_durations.get(guild_id, 60)  # Default to 60 seconds
    logging.info(f"Monitoring guild {guild_id} for disconnect in {duration} seconds.")
    await asyncio.sleep(duration)
    voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)
    if voice_client and voice_client.is_connected():
        await voice_client.disconnect()
        logging.info(f"Disconnected from guild {guild_id} after {duration} seconds of inactivity.")

# Function to play the next sound in the queue
async def play_next(guild_id: int):
    if guild_id not in bot.queue:
        return

    if bot.queue[guild_id].empty():
        # Start a disconnect timer
        if guild_id in bot.disconnect_tasks:
            bot.disconnect_tasks[guild_id].cancel()
            del bot.disconnect_tasks[guild_id]
        disconnect_task = asyncio.create_task(monitor_queue(guild_id))
        bot.disconnect_tasks[guild_id] = disconnect_task
        return

    sound_file, repeat, duration_play = await bot.queue[guild_id].get()
    voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)

    if not voice_client or not voice_client.is_connected():
        logging.warning(f"No active voice client in guild {guild_id}.")
        return

    if voice_client.is_playing():
        # Re-enqueue the sound if it's still playing
        await bot.queue[guild_id].put((sound_file, repeat, duration_play))
        return

    # Play the sound with specified duration
    try:
        # Prepare FFmpeg options to limit playback duration
        before_options = f"-ss 0 -t {duration_play}"
        source = discord.FFmpegPCMAudio(sound_file, before_options=before_options, options="-vn")
        def after_playing(error):
            if error:
                logging.error(f"Error playing sound: {error}")
            asyncio.run_coroutine_threadsafe(play_next(guild_id), bot.loop)
        voice_client.play(source, after=after_playing)
        sound_name = os.path.splitext(os.path.basename(sound_file))[0]
        logging.info(f"Playing sound '{sound_name}' in guild {guild_id}. Repeat left: {repeat}")
    except Exception as e:
        logging.error(f"Error playing sound {sound_file} in guild {guild_id}: {e}")
        # Proceed to next sound
        await play_next(guild_id)

# Handle finishing of parallel playback
async def handle_parallel_finish(guild_id: int):
    if guild_id in bot.parallel_audio_sources:
        if bot.parallel_audio_sources[guild_id].finished:
            # Start a disconnect timer
            if guild_id in bot.disconnect_tasks:
                bot.disconnect_tasks[guild_id].cancel()
                del bot.disconnect_tasks[guild_id]
            disconnect_task = asyncio.create_task(monitor_queue(guild_id))
            bot.disconnect_tasks[guild_id] = disconnect_task
            del bot.parallel_audio_sources[guild_id]
            logging.info(f"Parallel playback finished in guild {guild_id}.")

# Class for mixing audio sources for parallel playback
class ParallelAudioSource(discord.AudioSource):
    def __init__(self):
        self.sources = []
        self.finished = False

    def add_source(self, source):
        self.sources.append(source)

    def is_opus(self):
        return False

    def read(self):
        if not self.sources:
            self.finished = True
            return b''

        frames = []
        for source in self.sources[:]:
            frame = source.read()
            if frame:
                frames.append(frame)
            else:
                # EOF for this source
                self.sources.remove(source)

        if not frames:
            self.finished = True
            return b''

        # Assume all frames are the same size
        frame_size = len(frames[0])
        mixed_frame = bytearray(frame_size)

        # Mixing frames
        for i in range(0, frame_size, 2):
            sample_sum = 0
            for frame in frames:
                # Get the sample from the frame
                sample = int.from_bytes(frame[i:i+2], 'little', signed=True)
                sample_sum += sample
            # Prevent clipping
            sample_sum = max(min(sample_sum, 32767), -32768)
            # Write back to mixed_frame
            mixed_frame[i:i+2] = int(sample_sum).to_bytes(2, 'little', signed=True)
        return bytes(mixed_frame)

# 🔹 Slash Command: Set Playback Mode
@bot.tree.command(name="set_mode", description="Set the playback mode for the bot.")
@app_commands.describe(
    mode='Playback mode to set: "queue", "sync", or "parallel"'
)
async def set_mode(interaction: discord.Interaction, mode: PlaybackMode):
    await interaction.response.defer(ephemeral=True)
    guild_id = interaction.guild.id
    bot.guild_modes[guild_id] = mode.value
    logging.info(f"Guild {guild_id} mode changed to {mode.value.capitalize()}.")
    await interaction.followup.send(f"✅ Playback mode set to '{mode.value}'.", ephemeral=True)

# 🔹 Slash Command: Add a sound via file upload
@bot.tree.command(name="add", description="Add a new sound to the soundboard.")
@app_commands.describe(
    name="Name for the sound",
    file="Audio file to upload (MP3, WAV, OGG, FLAC)"
)
async def add_sound(interaction: discord.Interaction, name: str, file: discord.Attachment):
    await interaction.response.defer(ephemeral=True)

    # Validate file extension
    if not any(file.filename.lower().endswith(ext) for ext in ALLOWED_EXTENSIONS):
        await interaction.followup.send(
            f"❌ Unsupported file format. Please upload a file with one of the following extensions: {', '.join(ALLOWED_EXTENSIONS)}.",
            ephemeral=True
        )
        return

    # Validate file size
    if file.size > MAX_FILE_SIZE:
        await interaction.followup.send(
            f"❌ File size exceeds the 8MB limit.",
            ephemeral=True
        )
        return

    # Sanitize the sound name to prevent filesystem issues
    safe_name = "".join([c if c.isalnum() or c in (' ', '_') else '_' for c in name]).strip()
    if not safe_name:
        await interaction.followup.send(
            "❌ Invalid sound name. Please use alphanumeric characters, spaces, or underscores.",
            ephemeral=True
        )
        return

    file_extension = os.path.splitext(file.filename)[1]
    file_path = os.path.join(SOUND_DIR, f"{safe_name}{file_extension}")

    # Check for name conflicts
    if any(safe_name.lower() == existing.lower() for existing in bot.sounds.keys()):
        await interaction.followup.send(
            f"❌ A sound with the name `{safe_name}` already exists.",
            ephemeral=True
        )
        return

    try:
        # Save the uploaded file
        await file.save(file_path)

        # Extract duration
        duration = get_audio_duration(file_path)
        if duration is None:
            os.remove(file_path)
            await interaction.followup.send(
                "❌ Failed to extract duration from the audio file. Ensure it's a valid audio file.",
                ephemeral=True
            )
            return

        # Add sound metadata
        bot.sounds[safe_name] = {
            "file_path": file_path,
            "duration": duration
        }
        bot.save_sounds()

        # Confirm addition with an embed
        embed = discord.Embed(
            title="✅ Sound Added ✅",
            description=f"Sound `{safe_name}` has been added successfully.",
            color=discord.Color.green()
        )
        if interaction.user.avatar:
            embed.set_thumbnail(url=interaction.user.avatar.url)
        embed.add_field(name="File", value=f"[{file.filename}]({file.url})", inline=False)
        embed.add_field(name="Duration", value=f"{duration} seconds", inline=False)
        await interaction.followup.send(embed=embed, ephemeral=True)
    except Exception as e:
        logging.error(f"Failed to add sound: {e}")
        await interaction.followup.send(f"❌ Failed to add sound: {e}", ephemeral=True)

# 🔹 Slash Command: Add a sound from a URL (no file upload limit)
@bot.tree.command(name="add_from_url", description="Add a new sound to the soundboard by downloading from a URL.")
@app_commands.describe(
    name="Name for the sound",
    url="Direct URL to the audio file (MP3, WAV, OGG, FLAC)"
)
async def add_from_url(interaction: discord.Interaction, name: str, url: str):
    await interaction.response.defer(ephemeral=True)

    # Validate file extension
    if not any(url.lower().endswith(ext) for ext in ALLOWED_EXTENSIONS):
        await interaction.followup.send(
            f"❌ Unsupported file format. Please provide a URL ending with one of the following extensions: {', '.join(ALLOWED_EXTENSIONS)}.",
            ephemeral=True
        )
        return

    # Sanitize the sound name
    safe_name = "".join([c if c.isalnum() or c in (' ', '_') else '_' for c in name]).strip()
    if not safe_name:
        await interaction.followup.send(
            "❌ Invalid sound name. Please use alphanumeric characters, spaces, or underscores.",
            ephemeral=True
        )
        return

    file_extension = os.path.splitext(url)[1]
    file_path = os.path.join(SOUND_DIR, f"{safe_name}{file_extension}")

    # Check for name conflicts
    if any(safe_name.lower() == existing.lower() for existing in bot.sounds.keys()):
        await interaction.followup.send(
            f"❌ A sound with the name `{safe_name}` already exists.",
            ephemeral=True
        )
        return

    try:
        # Download the file
        success, error = await download_file(url, file_path)
        if not success:
            await interaction.followup.send(
                f"❌ Failed to download the file: {error}",
                ephemeral=True
            )
            return

        # Extract duration
        duration = get_audio_duration(file_path)
        if duration is None:
            os.remove(file_path)
            await interaction.followup.send(
                "❌ Failed to extract duration from the audio file. Ensure it's a valid audio file.",
                ephemeral=True
            )
            return

        # Add sound metadata
        bot.sounds[safe_name] = {
            "file_path": file_path,
            "duration": duration
        }
        bot.save_sounds()

        # Confirm addition with an embed
        embed = discord.Embed(
            title="✅ Sound Added from URL ✅",
            description=f"Sound `{safe_name}` has been added successfully from the provided URL.",
            color=discord.Color.green()
        )
        embed.add_field(name="URL", value=f"[Download Here]({url})", inline=False)
        embed.add_field(name="Duration", value=f"{duration} seconds", inline=False)
        await interaction.followup.send(embed=embed, ephemeral=True)
    except Exception as e:
        logging.error(f"Failed to add sound from URL: {e}")
        # Clean up if download failed
        if os.path.exists(file_path):
            os.remove(file_path)
        await interaction.followup.send(f"❌ Failed to add sound from URL: {e}", ephemeral=True)

# 🔹 Slash Command: Play a sound with optional repeat and play duration
@bot.tree.command(name="play", description="Play a sound in your current voice channel.")
@app_commands.describe(
    name="Name of the sound to play",
    repeat="Number of times to play the sound (default: 1)",
    play_duration="Time in seconds to play the sound (default: full duration)"
)
async def play_sound(interaction: discord.Interaction, name: str, repeat: int = 1, play_duration: Optional[float] = None):
    await interaction.response.defer()
    
    # Validate repeat parameter
    if repeat < 1:
        await interaction.followup.send("❌ Repeat count must be at least 1.", ephemeral=True)
        return

    # Check if the user is in a voice channel
    if not interaction.user.voice or not interaction.user.voice.channel:
        await interaction.followup.send("❌ You need to be in a voice channel to use this command.", ephemeral=True)
        return

    voice_channel = interaction.user.voice.channel
    guild_id = interaction.guild.id

    # Locate the sound file
    if name not in bot.sounds:
        await interaction.followup.send(f"❌ Sound `{name}` not found.", ephemeral=True)
        return

    sound_file = bot.sounds[name]["file_path"]
    sound_duration = bot.sounds[name]["duration"]

    # Determine play duration
    if play_duration is None:
        play_duration = sound_duration
    elif play_duration > sound_duration:
        play_duration = sound_duration
    elif play_duration <= 0:
        await interaction.followup.send("❌ Play duration must be a positive number.", ephemeral=True)
        return
    play_duration = round(play_duration, 2)  # Ensure it's rounded to 2 decimal places

    # Get the current mode
    mode = bot.guild_modes.get(guild_id, 'queue')

    # Initialize the queue if not present
    if guild_id not in bot.queue:
        bot.queue[guild_id] = asyncio.Queue()

    # Connect to the voice channel if not already connected
    voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)

    if not voice_client:
        try:
            voice_client = await voice_channel.connect()
            logging.info(f"Connected to voice channel {voice_channel} in guild {guild_id}.")
        except Exception as e:
            logging.error(f"Failed to connect to voice channel: {e}")
            await interaction.followup.send(f"❌ Failed to connect to voice channel: {e}", ephemeral=True)
            return
    else:
        if voice_client.channel != voice_channel:
            try:
                await voice_client.move_to(voice_channel)
                logging.info(f"Moved to voice channel {voice_channel} in guild {guild_id}.")
            except Exception as e:
                logging.error(f"Failed to move to voice channel: {e}")
                await interaction.followup.send(f"❌ Failed to move to voice channel: {e}", ephemeral=True)
                return

    if mode == 'queue':
        # Enqueue the sound with repeat count and play duration
        for _ in range(repeat):
            await bot.queue[guild_id].put((sound_file, 1, play_duration))  # Each enqueue has repeat=1
        logging.info(f"Enqueued sound '{name}' {repeat} time(s) in guild {guild_id} (Mode: Queue).")

        # Cancel any existing disconnect task
        if guild_id in bot.disconnect_tasks:
            bot.disconnect_tasks[guild_id].cancel()
            del bot.disconnect_tasks[guild_id]

        # If the voice client is not playing, start playing the next sound
        if not voice_client.is_playing():
            await play_next(guild_id)

        # Send confirmation with embed
        queue_length = bot.queue[guild_id].qsize()
        embed = discord.Embed(
            title="🎶 Sound Enqueued 🎶",
            description=f"Sound `{name}` has been added to the queue.",
            color=discord.Color.blue()
        )
        if interaction.user.avatar:
            embed.set_thumbnail(url=interaction.user.avatar.url)
        embed.add_field(name="Repeat Count", value=f"{repeat}", inline=False)
        embed.add_field(name="Play Duration", value=f"{play_duration} seconds", inline=False)
        embed.add_field(name="Position in Queue", value=f"{queue_length}", inline=False)
        embed.set_footer(text=f"Total in Queue: {queue_length}")
        await interaction.followup.send(embed=embed)

    elif mode == 'sync':
        # Sync mode: Play immediately, interrupting current playback
        if voice_client.is_playing():
            voice_client.stop()
            logging.info(f"Stopped current playback in guild {guild_id} for sync mode.")

        # Clear the queue
        if guild_id in bot.queue:
            try:
                while not bot.queue[guild_id].empty():
                    bot.queue[guild_id].get_nowait()
            except:
                pass  # Ignore if queue is already empty

        # Enqueue the sound with repeat count and play duration
        for _ in range(repeat):
            await bot.queue[guild_id].put((sound_file, 1, play_duration))  # Each enqueue has repeat=1
        logging.info(f"Enqueued sound '{name}' {repeat} time(s) in guild {guild_id} (Mode: Sync).")

        # Start playing immediately
        await play_next(guild_id)

        # Cancel any existing disconnect task
        if guild_id in bot.disconnect_tasks:
            bot.disconnect_tasks[guild_id].cancel()
            del bot.disconnect_tasks[guild_id]

        # Send confirmation with embed
        embed = discord.Embed(
            title="🎶 Sound Played 🎶",
            description=f"Sound `{name}` has been played immediately (Sync Mode).",
            color=discord.Color.orange()
        )
        if interaction.user.avatar:
            embed.set_thumbnail(url=interaction.user.avatar.url)
        embed.add_field(name="Repeat Count", value=f"{repeat}", inline=False)
        embed.add_field(name="Play Duration", value=f"{play_duration} seconds", inline=False)
        await interaction.followup.send(embed=embed)

    elif mode == 'parallel':
        # Parallel mode: Play sounds simultaneously
        # Prepare the audio source
        before_options = f"-ss 0 -t {play_duration}"
        source = discord.FFmpegPCMAudio(sound_file, before_options=before_options, options="-vn")

        # Initialize ParallelAudioSource if not exists
        if guild_id not in bot.parallel_audio_sources:
            bot.parallel_audio_sources[guild_id] = ParallelAudioSource()

        # Add the source to ParallelAudioSource
        for _ in range(repeat):
            bot.parallel_audio_sources[guild_id].add_source(source)

        # If not already playing, start playing
        if not voice_client.is_playing():
            def after_playing(error):
                if error:
                    logging.error(f"Error in parallel playback: {error}")
                asyncio.run_coroutine_threadsafe(handle_parallel_finish(guild_id), bot.loop)

            voice_client.play(bot.parallel_audio_sources[guild_id], after=after_playing)
            logging.info(f"Started parallel playback in guild {guild_id}.")

        # Send confirmation with embed
        embed = discord.Embed(
            title="🎶 Sound Added to Parallel Playback 🎶",
            description=f"Sound `{name}` has been added to parallel playback.",
            color=discord.Color.green()
        )
        if interaction.user.avatar:
            embed.set_thumbnail(url=interaction.user.avatar.url)
        embed.add_field(name="Repeat Count", value=f"{repeat}", inline=False)
        embed.add_field(name="Play Duration", value=f"{play_duration} seconds", inline=False)
        await interaction.followup.send(embed=embed)

# 🔹 Slash Command: Set Disconnect Duration After Queue Empty (Admin Only)
@bot.tree.command(name="set_disconnect_duration", description="Set how long the bot stays connected after the queue is empty. (Admin only)")
@app_commands.describe(
    duration="Time in seconds to stay connected after queue is empty (minimum: 10)"
)
@app_commands.default_permissions(administrator=True)
async def set_disconnect_duration(interaction: discord.Interaction, duration: int):
    await interaction.response.defer(ephemeral=True)

    # Validate duration parameter
    if duration < 10:
        await interaction.followup.send("❌ Duration must be at least 10 seconds.", ephemeral=True)
        return

    guild_id = interaction.guild.id
    bot.guild_durations[guild_id] = duration

    # If there's an existing disconnect task, cancel it
    if guild_id in bot.disconnect_tasks:
        bot.disconnect_tasks[guild_id].cancel()
        del bot.disconnect_tasks[guild_id]

    # Confirm setting with an embed
    embed = discord.Embed(
        title="⏰ Disconnect Duration Set ⏰",
        description=f"The bot will stay connected for {duration} seconds after the queue is empty.",
        color=discord.Color.blue()
    )
    if interaction.user.avatar:
        embed.set_thumbnail(url=interaction.user.avatar.url)
    await interaction.followup.send(embed=embed, ephemeral=True)

# 🔹 Slash Command: Skip the Currently Playing Sound
@bot.tree.command(name="skip", description="Skip the currently playing sound.")
async def skip_sound(interaction: discord.Interaction):
    await interaction.response.defer()
    guild_id = interaction.guild.id
    voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)

    if voice_client and voice_client.is_playing():
        voice_client.stop()
        logging.info(f"Skipped sound in guild {guild_id}.")
        await interaction.followup.send("✅ Skipped the current sound.")
    else:
        await interaction.followup.send("❌ No sound is currently playing.", ephemeral=True)

# 🔹 Slash Command: Stop Playback and Clear Queue
@bot.tree.command(name="stop", description="Stop playback and clear the queue.")
async def stop_sound(interaction: discord.Interaction):
    await interaction.response.defer(ephemeral=True)
    guild_id = interaction.guild.id
    voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)

    if guild_id in bot.queue:
        while not bot.queue[guild_id].empty():
            bot.queue[guild_id].get_nowait()

    if guild_id in bot.parallel_audio_sources:
        del bot.parallel_audio_sources[guild_id]

    if voice_client and voice_client.is_connected():
        voice_client.stop()
        await voice_client.disconnect()
        logging.info(f"Stopped playback and disconnected from guild {guild_id}.")
        await interaction.followup.send("✅ Stopped playback and cleared the queue.")
    else:
        await interaction.followup.send("❌ No active voice connection found.", ephemeral=True)

# 🔹 Slash Command: Clear the Entire Queue
@bot.tree.command(name="clear", description="Clear the entire sound queue.")
async def clear_queue(interaction: discord.Interaction):
    await interaction.response.defer(ephemeral=True)
    guild_id = interaction.guild.id

    if guild_id in bot.queue and not bot.queue[guild_id].empty():
        while not bot.queue[guild_id].empty():
            bot.queue[guild_id].get_nowait()
        # Cancel any disconnect tasks as the queue is now empty
        if guild_id in bot.disconnect_tasks:
            bot.disconnect_tasks[guild_id].cancel()
            del bot.disconnect_tasks[guild_id]
        await interaction.followup.send("✅ The queue has been cleared.")
        logging.info(f"Cleared the queue in guild {guild_id}.")
    else:
        await interaction.followup.send("❌ The queue is already empty.", ephemeral=True)

# 🔹 Slash Command: List All Sounds with Durations
@bot.tree.command(name="list", description="List all available sounds.")
async def list_sounds(interaction: discord.Interaction):
    await interaction.response.defer(ephemeral=True)

    if not bot.sounds:
        embed = discord.Embed(
            title="❌ No Sounds Available ❌",
            description="Add some sounds using the `/add` or `/add_from_url` commands.",
            color=discord.Color.red()
        )
        await interaction.followup.send(embed=embed)
        return

    embed = discord.Embed(
        title="🎶 Available Sounds 🎶",
        description="Here are the sounds you can play:",
        color=discord.Color.green()
    )

    # Discord embeds have a limit of 25 fields; handle accordingly
    sounds_sorted = sorted(bot.sounds.items(), key=lambda item: item[0].lower())
    for name, meta in sounds_sorted:
        embed.add_field(
            name=name,
            value=f"Duration: {meta['duration']} seconds",
            inline=False
        )
        if len(embed.fields) == 25:
            break  # Prevent exceeding Discord's embed field limit

    await interaction.followup.send(embed=embed)

# 🔹 Slash Command: Display the Current Sound Queue
@bot.tree.command(name="queue", description="Display the current sound queue.")
async def display_queue(interaction: discord.Interaction):
    await interaction.response.defer(ephemeral=True)
    guild_id = interaction.guild.id

    if guild_id not in bot.queue or bot.queue[guild_id].empty():
        embed = discord.Embed(
            title="🔄 Sound Queue 🔄",
            description="The queue is currently empty.",
            color=discord.Color.orange()
        )
        await interaction.followup.send(embed=embed)
        return

    # Peek into the queue without removing items
    temp_list = list(bot.queue[guild_id].queue)

    embed = discord.Embed(
        title="🔄 Sound Queue 🔄",
        description="Here's the current queue:",
        color=discord.Color.purple()
    )

    for idx, (sound, repeat, duration_play) in enumerate(temp_list, start=1):
        sound_name = os.path.splitext(os.path.basename(sound))[0]
        embed.add_field(
            name=f"{idx}. {sound_name}",
            value=f"Repeat: {repeat} time(s)\nPlay Duration: {duration_play} seconds",
            inline=False
        )
        if len(embed.fields) == 25:
            break  # Prevent exceeding Discord's embed field limit

    await interaction.followup.send(embed=embed)

# 🔹 Slash Command: Delete a Sound (Admin Only)
@bot.tree.command(name="delete", description="Delete a sound from the soundboard. (Admin only)")
@app_commands.describe(name="Name of the sound to delete")
@app_commands.default_permissions(administrator=True)
async def delete_sound(interaction: discord.Interaction, name: str):
    await interaction.response.defer(ephemeral=True)

    # Locate the sound file
    if name not in bot.sounds:
        await interaction.followup.send(f"❌ Sound `{name}` not found.", ephemeral=True)
        return

    sound_file = bot.sounds[name]["file_path"]

    try:
        os.remove(sound_file)
        del bot.sounds[name]
        bot.save_sounds()

        # Also remove the sound from all guild queues if present
        for guild_id, queue in bot.queue.items():
            temp_queue = asyncio.Queue()
            while not queue.empty():
                item = await queue.get()
                if item[0] != sound_file:
                    await temp_queue.put(item)
            bot.queue[guild_id] = temp_queue

        # Confirm deletion with an embed
        embed = discord.Embed(
            title="🗑️ Sound Deleted 🗑️",
            description=f"Sound `{name}` has been deleted successfully.",
            color=discord.Color.dark_red()
        )
        if interaction.user.avatar:
            embed.set_thumbnail(url=interaction.user.avatar.url)
        await interaction.followup.send(embed=embed, ephemeral=True)
    except Exception as e:
        logging.error(f"Failed to delete sound: {e}")
        await interaction.followup.send(f"❌ Failed to delete sound: {e}", ephemeral=True)

# 🔹 Error handling for delete command permissions
@delete_sound.error
async def delete_sound_error(interaction: discord.Interaction, error):
    if isinstance(error, app_commands.errors.MissingPermissions):
        await interaction.response.send_message("🔒 You don't have permission to delete sounds.", ephemeral=True)
    else:
        await interaction.response.send_message("⚠️ An error occurred while trying to delete the sound.", ephemeral=True)

# 🔹 New Feature: Soundboard Command with Buttons (without mode switch)
# Utilizing discord.ui for interactive components
class SoundboardView(discord.ui.View):
    def __init__(self, bot: SoundboardBot, guild_id: int):
        super().__init__(timeout=None)
        self.bot = bot
        self.guild_id = guild_id

        # Add sound buttons
        for sound_name in sorted(bot.sounds.keys()):
            self.add_item(SoundButton(sound_name, bot))

class SoundButton(discord.ui.Button):
    def __init__(self, sound_name: str, bot: SoundboardBot):
        super().__init__(
            style=discord.ButtonStyle.primary,
            label=sound_name,
            custom_id=f'soundboard_sound_{sound_name}'
        )
        self.sound_name = sound_name
        self.bot = bot

    async def callback(self, interaction: discord.Interaction):
        await self.bot.play_sound_via_button(interaction, self.sound_name)
    
# 🔹 Slash Command: Soundboard
@bot.tree.command(name="soundboard", description="Display the soundboard with clickable buttons for sounds.")
async def soundboard(interaction: discord.Interaction):
    await interaction.response.defer()

    if not bot.sounds:
        embed = discord.Embed(
            title="❌ No Sounds Available ❌",
            description="Add some sounds using the `/add` or `/add_from_url` commands.",
            color=discord.Color.red()
        )
        await interaction.followup.send(embed=embed)
        return

    # Create the view with sound buttons
    view = SoundboardView(bot, interaction.guild.id)

    embed = discord.Embed(
        title="🎵 Soundboard 🎵",
        description="Click the buttons below to play sounds.",
        color=discord.Color.blue()
    )

    await interaction.followup.send(embed=embed, view=view)

# 🔹 Event: Bot Ready
@bot.event
async def on_ready():
    print(f'Bot connected as {bot.user}')  # Removed Unicode emojis to prevent encoding errors
    logging.info(f'✅ Bot connected as {bot.user} ✅')

# 🔹 Run the bot
bot.run(TOKEN)