"""
Système de Tickets Simplifié
=============================
Un système de tickets simple et intuitif avec panel intégré.

Fonctionnalités:
- Une seule commande: /ticket
- Création de tickets via boutons
- Gestion via interface interactive
- Stats et configuration accessibles depuis le même panel
"""
import os
import sys
import io
import asyncio
from datetime import datetime, timedelta
from typing import List, Dict, Any, Optional
from collections import defaultdict

# Add parent directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))

import disnake
from disnake.ext import commands, tasks
import traceback
from PIL import Image, ImageDraw, ImageFont

from .data.storage import get_storage
from .data.models import (
    GuildTicketConfig,
    TicketCategory,
    TicketStatus,
    Priority,
    TicketData,
    TicketStats
)
from .utils.formatters import TicketEmbedFormatter
from .staff_analytics import StaffAnalytics, StaffStatsView
from .utils.permissions import is_staff, is_whitelisted, can_access_config
from src.logger import kuby_logger
from utils.premium import check_premium_tier
# V2 RICH COMPONENTS LOADED - 14:10
kuby_logger.info("🎫 [TicketSystem] Chargement de la version V2 Rich Components...")

# Discord limite le nombre de composants par message ; 25 couvre les selects Discord.
MAX_PANEL_CATEGORIES = 25

# Add a task for sending feedback reminders
class FeedbackReminderTask:
    def __init__(self, bot):
        self.bot = bot
        self.reminders = {}  # Store active reminders
        
    async def schedule_feedback_reminder(self, user_id: int, ticket_id: int, delay_hours: int = 24):
        """Schedule a feedback reminder after a ticket is closed"""
        # This would be implemented with a proper task scheduler
        pass


class TicketPanelView(disnake.ui.View):
    """Panel principal avec toutes les options intégrées"""

    def __init__(self, bot, config: GuildTicketConfig, storage=None):
        super().__init__(timeout=None)
        self.bot = bot
        self.config = config
        self.storage = storage or get_storage()
        self.PURPLE = disnake.Color.blurple()

        # Boutons du panel
        self._build_panel()
    
    def _build_panel(self):
        """Construit les boutons du panel selon la configuration"""
        self.clear_items()

        # Créer ticket
        create_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.blurple,
            label="🎫 Créer un ticket",
            custom_id="ticket_create"
        )
        self.add_item(create_btn)

        # Mes tickets (si l'utilisateur a des tickets)
        my_tickets_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.secondary,
            label="📂 Mes tickets",
            custom_id="ticket_my_tickets"
        )
        self.add_item(my_tickets_btn)

        # Analytics (staff only)
        analytics_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.primary,
            label="📊 Analytics",
            custom_id="ticket_analytics"
        )
        self.add_item(analytics_btn)
        
        # Staff Ratings (staff only)
        staff_ratings_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.success,
            label="⭐ Notation du Staff",
            custom_id="ticket_staff_ratings"
        )
        staff_ratings_btn.callback = self._staff_ratings_callback
        self.add_item(staff_ratings_btn)

    def get_components_v2(self):
        """Retourne les composants V2 pour un affichage moderne"""
        return [
            disnake.ui.Container(
                disnake.ui.Section(
                    "# 🎫 Système de Support - Omega Kube",
                    accessory=disnake.ui.Button(
                        label="Créer un ticket", 
                        style=disnake.ButtonStyle.blurple, 
                        custom_id="ticket_create"
                    )
                ),
                disnake.ui.Separator(divider=True),
                disnake.ui.TextDisplay(
                    content="Bienvenue sur le système de support d'**Omega Kube**.\n"
                            "Notre équipe est à votre disposition pour vous aider dans les plus brefs délais."
                ),
                disnake.ui.Separator(divider=True),
                disnake.ui.Section(
                    f"📂 {len(self.config.categories)} Catégories disponibles",
                    accessory=disnake.ui.Button(
                        label="Mes tickets", 
                        style=disnake.ButtonStyle.secondary, 
                        custom_id="ticket_my_tickets"
                    )
                ),
                disnake.ui.Section(
                    "📊 Administration & Staff",
                    accessory=disnake.ui.Button(
                        label="Analytics", 
                        style=disnake.ButtonStyle.primary, 
                        custom_id="ticket_analytics"
                    )
                )
            )
        ]
        
        # Configuration (admin only)
        config_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.secondary,
            label="⚙️ Configuration",
            custom_id="ticket_config"
        )
        config_btn.callback = self._config_callback
        self.add_item(config_btn)
        

    
    async def _create_callback(self, interaction: disnake.Interaction):
        """Affiche les catégories pour créer un ticket"""
        self.storage.invalidate_cache(interaction.guild_id)
        self.config = self.storage.load_config(interaction.guild_id)

        if not self.config.enabled:
            await interaction.response.send_message("Le système de tickets est désactivé.", ephemeral=True)
            return

        if not self.config.categories:
            await interaction.response.send_message("Aucune catégorie configurée. Utilisez la configuration.", ephemeral=True)
            return

        components = build_category_selection_components(self.config)
        try:
            await interaction.response.send_message(
                components=components,
                flags=disnake.MessageFlags(is_components_v2=True),
                ephemeral=True
            )
        except Exception as e:
            import traceback
            traceback.print_exc()
            if interaction.response.is_done():
                await interaction.followup.send(
                    "**🎫 Créer un Ticket**\n\nSélectionnez une catégorie ci-dessous:",
                    view=CategorySelectionView(self.bot, self.config),
                    ephemeral=True
                )
            else:
                await interaction.response.send_message(
                    "**🎫 Créer un Ticket**\n\nSélectionnez une catégorie ci-dessous:",
                    view=CategorySelectionView(self.bot, self.config),
                    ephemeral=True
                )
    
    async def _my_tickets_callback(self, interaction: disnake.Interaction):
        """Affiche les tickets de l'utilisateur"""
        # Reload config
        self.config = self.storage.load_config(interaction.guild_id)

        # Reload config
        self.config = self.storage.load_config(interaction.guild_id)


        storage = get_storage()
        tickets = storage.get_user_tickets(interaction.guild_id, interaction.user.id, open_only=True)
        
        if not tickets:
            await interaction.response.send_message("Vous n'avez aucun ticket ouvert.", ephemeral=True)
            return
        
        # Build V2 Container for user's open tickets
        sections = [
            disnake.ui.TextDisplay(content=f"## 🎫 Vos Tickets Ouverts"),
            disnake.ui.TextDisplay(content=f"Vous avez **{len(tickets)}** ticket(s) ouvert(s):"),
            disnake.ui.Separator(),
        ]
        
        for ticket in tickets[:5]:
            channel = interaction.guild.get_channel(ticket.channel_id)
            channel_mention = channel.mention if channel else "Salon supprimé"
            sections.append(disnake.ui.TextDisplay(
                content=f"**Ticket #{ticket.channel_id}**\nSalon: {channel_mention}\nStatut: {ticket.status.value}\nCréé: {ticket.created_at.strftime('%d/%m/%Y')}"
            ))
        
        await interaction.response.send_message(
            components=[disnake.ui.Container(*sections)],
            ephemeral=True
        )

    async def _analytics_callback(self, interaction: disnake.Interaction):
        """Affiche les analytics (staff uniquement)"""
        # Reload config
        self.config = self.storage.load_config(interaction.guild_id)

        if not self._is_staff(interaction):
            await interaction.response.send_message("❌ Cette fonctionnalité est réservée au staff.", ephemeral=True)
            return

        await interaction.response.defer(ephemeral=True)

        analytics = StaffAnalytics(self.bot)

        # Get staff list for individual stats
        staff_list = await analytics.get_staff_list(interaction.guild_id)

        # Create main embed with overall stats
        embed = disnake.Embed(
            title="📊 Analytics des Tickets",
            description="Statistiques détaillées des tickets et performances du staff",
            color=disnake.Color.blue(),
            timestamp=datetime.now()
        )

        # Calculate overall stats
        tickets = self.storage.get_guild_tickets(interaction.guild_id)
        total_tickets = len(tickets)
        open_tickets = sum(1 for t in tickets if t.status in [TicketStatus.OPEN, TicketStatus.CLAIMED])
        closed_tickets = sum(1 for t in tickets if t.status == TicketStatus.CLOSED)
        claimed_tickets = sum(1 for t in tickets if t.claimed_by is not None)

        # Basic stats
        embed.add_field(
            name="📈 Statistiques Générales",
            value=f"**Total:** {total_tickets}\n"
                  f"**Ouverts:** {open_tickets}\n"
                  f"**Fermés:** {closed_tickets}\n"
                  f"**Réclamés:** {claimed_tickets}",
            inline=True
        )

        # Staff performance summary
        if staff_list:
            total_ratings = sum(s['total_ratings'] for s in staff_list)
            avg_rating = sum(s['average_rating'] for s in staff_list if s['total_ratings'] > 0) / len([s for s in staff_list if s['total_ratings'] > 0]) if any(s['total_ratings'] > 0 for s in staff_list) else 0

            embed.add_field(
                name="👥 Équipe Staff",
                value=f"**Membres actifs:** {len(staff_list)}\n"
                      f"**Évaluations totales:** {total_ratings}\n"
                      f"**Note moyenne globale:** {avg_rating:.1f}⭐" if avg_rating > 0 else "**Note moyenne globale:** N/A",
                inline=True
            )

        # Send main embed
        await interaction.followup.send(embed=embed, ephemeral=True)

        # Create and send staff rating chart
        try:
            if staff_list:
                rating_chart = analytics.create_staff_rating_chart(staff_list)
                await interaction.followup.send(
                    "📊 **Évaluations des Membres du Staff**",
                    file=disnake.File(rating_chart, 'staff_ratings.png'),
                    ephemeral=True
                )

                # Add individual staff stats view
                stats_view = StaffStatsView(self.bot, staff_list, analytics)
                stats_embed = disnake.Embed(
                    title="📈 Statistiques Individuelles",
                    description="Cliquez sur un membre du staff pour voir ses statistiques détaillées:",
                    color=disnake.Color.purple()
                )
                await interaction.followup.send(embed=stats_embed, view=stats_view, ephemeral=True)

        except Exception as e:
            await interaction.followup.send(f"❌ Erreur lors de la génération des graphiques: {e}", ephemeral=True)



    async def _config_callback(self, interaction: disnake.Interaction):
        """Ouvre le panel de configuration (admin ou rôles config uniquement)"""
        # Reload config
        self.config = self.storage.load_config(interaction.guild_id)

        if not self._is_whitelisted(interaction):
            await interaction.response.send_message("❌ Vous n'êtes pas autorisé à utiliser cette fonctionnalité. Veuillez demander à être ajouté à la whitelist.", ephemeral=True)
            return

        if not self._can_access_config(interaction):
            await interaction.response.send_message("❌ Cette fonctionnalité est réservée aux administrateurs ou aux rôles configurés.", ephemeral=True)
            return

        storage = get_storage()
        config_view = ConfigPanelView(self.bot, storage, self.config)
        embed = disnake.Embed(
            title="Configuration des Tickets",
            description="Gérez la configuration du système de tickets",
            color=disnake.Color.blue()
        )

        await interaction.response.send_message(embed=embed, view=config_view, ephemeral=True)

    async def _staff_ratings_callback(self, interaction: disnake.Interaction):
        """Display staff ratings (staff only)"""
        # Reload config
        self.config = self.storage.load_config(interaction.guild_id)

        if not self._is_staff(interaction):
            await interaction.response.send_message("❌ Cette fonctionnalité est réservée au staff.", ephemeral=True)
            return

        await interaction.response.defer(ephemeral=True)

        analytics = StaffAnalytics(self.bot)

        # Get staff list for individual stats
        staff_list = await analytics.get_staff_list(interaction.guild_id)

        # Create main embed with overall stats
        embed = disnake.Embed(
            title="⭐ Notation du Staff",
            description="Performance globale du staff",
            color=disnake.Color.blue(),
            timestamp=datetime.now()
        )

        # Calculate overall stats
        tickets = self.storage.get_guild_tickets(interaction.guild_id)
        total_tickets = len(tickets)
        open_tickets = sum(1 for t in tickets if t.status in [TicketStatus.OPEN, TicketStatus.CLAIMED])
        closed_tickets = sum(1 for t in tickets if t.status == TicketStatus.CLOSED)
        claimed_tickets = sum(1 for t in tickets if t.claimed_by is not None)

        # Basic stats
        embed.add_field(
            name="📈 Statistiques Générales",
            value=f"**Total:** {total_tickets}\n"
                  f"**Ouverts:** {open_tickets}\n"
                  f"**Fermés:** {closed_tickets}\n"
                  f"**Réclamés:** {claimed_tickets}",
            inline=True
        )

        # Staff performance summary
        if staff_list:
            total_ratings = sum(s['total_ratings'] for s in staff_list)
            avg_rating = sum(s['average_rating'] for s in staff_list if s['total_ratings'] > 0) / len([s for s in staff_list if s['total_ratings'] > 0]) if any(s['total_ratings'] > 0 for s in staff_list) else 0

            embed.add_field(
                name="👥 Équipe Staff",
                value=f"**Membres actifs:** {len(staff_list)}\n"
                      f"**Évaluations totales:** {total_ratings}\n"
                      f"**Note moyenne globale:** {avg_rating:.1f}⭐" if avg_rating > 0 else "**Note moyenne globale:** N/A",
                inline=True
            )

        # Send main embed
        await interaction.followup.send(embed=embed, ephemeral=True)

        # Create and send staff rating chart
        try:
            if staff_list:
                rating_chart = analytics.create_staff_rating_chart(staff_list)
                await interaction.followup.send(
                    "📊 **Évaluations des Membres du Staff**",
                    file=disnake.File(rating_chart, 'staff_ratings.png'),
                    ephemeral=True
                )

                # Add individual staff stats view
                stats_view = StaffStatsView(self.bot, staff_list, analytics)
                stats_embed = disnake.Embed(
                    title="📈 Statistiques Individuelles",
                    description="Cliquez sur un membre du staff pour voir ses statistiques détaillées:",
                    color=disnake.Color.purple()
                )
                await interaction.followup.send(embed=stats_embed, view=stats_view, ephemeral=True)

        except Exception as e:
            await interaction.followup.send(f"❌ Erreur lors de la génération des graphiques: {e}", ephemeral=True)


    def _is_staff(self, interaction: disnake.Interaction) -> bool:
        return is_staff(interaction, self.config)

    def _can_access_config(self, interaction: disnake.Interaction) -> bool:
        return can_access_config(interaction, self.config)

    def _is_whitelisted(self, interaction: disnake.Interaction) -> bool:
        return is_whitelisted(interaction, self.config, self.bot)

    def _calculate_stats(self, guild_id: int) -> TicketStats:
        """Calcule les statistiques"""
        storage = get_storage()
        tickets = storage.get_guild_tickets(guild_id)
        
        stats = TicketStats()
        stats.total_tickets = len(tickets)
        
        total_duration = 0
        closed_count = 0
        rating_sum = 0
        rating_count = 0
        
        for ticket in tickets:
            if ticket.status == TicketStatus.OPEN:
                stats.open_tickets += 1
            elif ticket.status == TicketStatus.CLAIMED:
                stats.claimed_tickets += 1
                stats.open_tickets += 1
            elif ticket.status == TicketStatus.CLOSED:
                stats.closed_tickets += 1
                closed_count += 1
            
            if ticket.duration_minutes:
                total_duration += ticket.duration_minutes
            
            if ticket.rating:
                rating_sum += ticket.rating
                rating_count += 1
        
        if closed_count > 0:
            stats.average_duration_minutes = total_duration / closed_count
        
        if rating_count > 0:
            stats.average_rating = rating_sum / rating_count
        
        return stats


async def refresh_ticket_panel(bot, storage, guild_id: int) -> bool:
    """Met à jour le message du panel de création de tickets s'il est enregistré."""
    config = storage.load_config(guild_id)
    if not config.panel_channel_id or not config.panel_message_id:
        return False

    try:
        channel = bot.get_channel(config.panel_channel_id)
        if channel is None:
            channel = await bot.fetch_channel(config.panel_channel_id)
        message = await channel.fetch_message(config.panel_message_id)
        view = TicketPanelView(bot, config, storage)
        await message.edit(components=view.get_components_v2())
        return True
    except Exception as e:
        kuby_logger.warning(f"[TicketSystem] Échec du rafraîchissement du panel: {e}")
        return False


def build_category_selection_components(config: GuildTicketConfig) -> list:
    """Construit le sélecteur de catégories (menu déroulant, jusqu'à 25 options).

    Un Container V2 ne peut contenir que 10 composants enfants ; une Section+Separator
    par catégorie dépassait rapidement cette limite. Le menu déroulant affiche toutes
    les catégories dans un seul composant.
    """
    categories = config.categories[:MAX_PANEL_CATEGORIES]
    if not categories:
        return []

    options = []
    for cat in categories:
        option_kwargs = {
            "label": cat.name[:100],
            "value": str(cat.id)[:100],
        }
        description = (cat.description or "").strip()
        if description:
            option_kwargs["description"] = description[:100]
        if cat.emoji:
            option_kwargs["emoji"] = cat.emoji
        options.append(disnake.SelectOption(**option_kwargs))

    category_select = disnake.ui.StringSelect(
        custom_id="ticket_category_select",
        placeholder="Choisissez une catégorie...",
        options=options,
        min_values=1,
        max_values=1,
    )

    return [
        disnake.ui.Container(
            disnake.ui.TextDisplay(
                content="# 📂 Créer un Ticket\n\n"
                        "Sélectionnez la catégorie correspondant à votre demande."
            ),
            disnake.ui.ActionRow(category_select),
        )
    ]


async def open_ticket_for_category(bot, interaction: disnake.Interaction, config: GuildTicketConfig, category_id: str):
    """Ouvre le modal de création de ticket pour la catégorie choisie."""
    category = config.get_category(category_id)
    if not category:
        await interaction.response.send_message("Catégorie non trouvée.", ephemeral=True)
        return

    storage = get_storage()
    user_tickets = storage.get_user_tickets(interaction.guild_id, interaction.user.id, open_only=True)

    active_tickets = []
    for ticket in user_tickets:
        if interaction.guild.get_channel(ticket.channel_id):
            active_tickets.append(ticket)
        else:
            ticket.status = TicketStatus.CLOSED
            storage.save_ticket(ticket)

    if len(active_tickets) >= config.max_tickets_per_user:
        await interaction.response.send_message(
            f"Vous avez atteint la limite de {config.max_tickets_per_user} tickets.",
            ephemeral=True
        )
        return

    if category.is_recruitment and category.questions:
        modal = RecruitmentQuestionsModal(bot, config, category)
    else:
        modal = TicketPriorityModal(bot, config, category)

    if hasattr(modal, "title") and modal.title:
        modal.title = str(modal.title)[:45]

    await interaction.response.send_modal(modal)


class CategoryButton(disnake.ui.Button):
    def __init__(self, category, view_instance):
        super().__init__(
            style=disnake.ButtonStyle.blurple,
            label=f"{category.emoji} {category.name}",
            custom_id=f"cat_{category.id}_{view_instance.id_gen}" 
        )
        self.category = category
        self.view_instance = view_instance
        # We need unique custom_ids even for ephemeral to avoid any weird collisions/caching
        
    async def callback(self, interaction: disnake.Interaction):
        await self.view_instance._on_category_select(interaction, self.category.id)


class CategorySelectionView(disnake.ui.View):
    """Sélection de catégorie pour créer un ticket"""

    def __init__(self, bot, config: GuildTicketConfig):
        super().__init__(timeout=300) # 5 minutes timeout for ephemeral menu
        self.bot = bot
        self.config = config
        self.id_gen = int(datetime.now().timestamp())

        # Ajouter boutons de catégories
        for category in config.categories[:MAX_PANEL_CATEGORIES]:
            self.add_item(CategoryButton(category, self))

    def _is_staff(self, interaction: disnake.Interaction) -> bool:
        return is_staff(interaction, self.config)

    def _can_access_config(self, interaction: disnake.Interaction) -> bool:
        return can_access_config(interaction, self.config)

    def _is_whitelisted(self, interaction: disnake.Interaction) -> bool:
        return is_whitelisted(interaction, self.config, self.bot)
    
    async def _on_category_select(self, interaction: disnake.Interaction, category_id: str):
        """Handle category selection"""
        try:
            await open_ticket_for_category(self.bot, interaction, self.config, category_id)
        except Exception as e:
            kuby_logger.error(f"[CategorySelection] Error: {e}", exc_info=True)
            try:
                if not interaction.response.is_done():
                    await interaction.response.send_message(f"❌ Erreur interne: {e}", ephemeral=True)
            except:
                pass


class TicketPriorityModal(disnake.ui.Modal):
    """Modal pour spécifier la raison du ticket"""
    def __init__(self, bot, config, category: TicketCategory):
        self.reason_input = disnake.ui.TextInput(
            label="Motif du ticket",
            custom_id="ticket_reason",
            placeholder="Décrivez brièvement votre problème...",
            required=category.require_reason,
            min_length=5 if category.require_reason else 0,
            max_length=250,
            style=disnake.TextInputStyle.paragraph
        )
        super().__init__(title=f"Support: {category.name}", components=[self.reason_input])
        self.bot = bot
        self.config = config
        self.category = category

    async def callback(self, interaction: disnake.ModalInteraction):
        await interaction.response.defer(ephemeral=True)
        try:
            # Map priority
            priority_map = {
                "Urgente": Priority.URGENT,
                "Haute": Priority.HIGH,
                "Normale": Priority.NORMAL
            }
            # Logic updated to use reason input
            priority = Priority.NORMAL 
            reason = interaction.text_values.get("ticket_reason", "Aucun motif fourni")

            # Create ticket
            await self._create_ticket(interaction, priority, reason)
        except Exception as e:
            await interaction.followup.send(f"Erreur lors de la création du ticket: {e}", ephemeral=True)

    async def on_error(self, interaction: disnake.Interaction, error: Exception):
        """Handle errors in the modal"""
        await interaction.response.send_message("Une erreur inattendue s'est produite lors de la création du ticket.", ephemeral=True)

    async def _create_ticket(self, interaction: disnake.Interaction, priority: Priority, reason: str):
        """Crée le ticket"""
        guild = interaction.guild
        user = interaction.user
        
        storage = get_storage()
        
        # Build permissions
        overwrites = {
            guild.default_role: disnake.PermissionOverwrite(read_messages=False),
            user: disnake.PermissionOverwrite(read_messages=True, send_messages=True),
            guild.me: disnake.PermissionOverwrite(read_messages=True, send_messages=True)
        }
        
        # Add staff roles (category-specific roles override global ones)
        staff_roles_to_add = self.category.staff_roles if self.category.staff_roles else self.config.staff_roles
        for role_id in staff_roles_to_add:
            try:
                role = guild.get_role(int(role_id))
                if role and role not in overwrites:
                    overwrites[role] = disnake.PermissionOverwrite(read_messages=True, send_messages=True)
            except (ValueError, TypeError):
                continue
        
        # Create channel name using username and category without numbers
        safe_username = "".join(c for c in user.display_name if not c.isdigit()).strip()
        if not safe_username:
            safe_username = "user"
        safe_category = "".join(c for c in self.category.name if not c.isdigit()).strip()
        channel_name = f"{safe_username}-{safe_category}"
        
        try:
            category_channel = None
            
            # Check if category has a specific Discord category
            if self.category.discord_category_id:
                try:
                    category_channel = guild.get_channel(int(self.category.discord_category_id))
                    if category_channel and not isinstance(category_channel, disnake.CategoryChannel):
                        category_channel = None
                except (ValueError, TypeError):
                    pass
            
            # Fallback to default config category if not found or not set
            if not category_channel and self.config.default_category:
                try:
                    category_channel = guild.get_channel(int(self.config.default_category))
                    if category_channel and not isinstance(category_channel, disnake.CategoryChannel):
                        category_channel = None
                except (ValueError, TypeError):
                    pass
            
            ticket_channel = await guild.create_text_channel(
                name=channel_name,
                category=category_channel,
                overwrites=overwrites,
                topic=f"Ticket de {user} | Catégorie: {self.category.name}"
            )
        except Exception as e:
            await interaction.followup.send(f"Erreur: {e}", ephemeral=True)
            return
        
        # Create ticket data
        ticket_data = TicketData(
            channel_id=ticket_channel.id,
            guild_id=guild.id,
            user_id=user.id,
            category_id=self.category.id,
            priority=priority,
            status=TicketStatus.OPEN,
            reason=reason
        )
        storage.save_ticket(ticket_data)
        
        # Build mentions
        mentions = [user.mention]
        for role_id in self.config.staff_roles:
            mentions.append(f"<@&{role_id}>")
        for user_id in self.config.staff_users:
            mentions.append(f"<@{user_id}>")
        
        mention_content = " ".join(mentions)

        # Build a single V2 container with mention and ticket management components
        mention_section = disnake.ui.TextDisplay(content=mention_content)
        # Retrieve existing management container
        base_container = get_ticket_management_container(ticket_data, self.category, self.config)[0]
        # Build new container merging mention and existing sections
        combined_container = disnake.ui.Container(mention_section, *base_container.children)
        await ticket_channel.send(components=[combined_container])
        
        # Log
        if self.config.log_channel_id:
            log_channel = guild.get_channel(self.config.log_channel_id)
            if log_channel:
                log_embed = disnake.Embed(
                    title="Nouveau Ticket",
                    color=disnake.Color.green(),
                    timestamp=datetime.now()
                )
                log_embed.add_field(name="Utilisateur", value=user.mention, inline=True)
                log_embed.add_field(name="Catégorie", value=self.category.name, inline=True)
                log_embed.add_field(name="Salon", value=ticket_channel.mention, inline=True)
                await log_channel.send(embed=log_embed)
        
        await interaction.followup.send(f"Ticket créé: {ticket_channel.mention}", ephemeral=True)


class RecruitmentQuestionsModal(disnake.ui.Modal):
    """Modal pour les questions de recrutement"""

    def __init__(self, bot, config, category: TicketCategory):
        self.bot = bot
        self.config = config
        self.category = category

        # Construire dynamiquement les inputs de questions
        components = []
        self.question_inputs = {}

        for i, question in enumerate(category.questions):
            # Discord constraints:
            # - placeholder length <= 100
            # We clamp the dynamic placeholder (questions come from config).
            input_field = disnake.ui.TextInput(
                label=f"Question {i+1}",  # <= 45 by construction
                custom_id=f"recruitment_q_{i}",
                placeholder=_clamp_text(question, 100, ""),
                required=True,
                max_length=1000,
                style=disnake.TextInputStyle.paragraph
            )
            self.question_inputs[f"recruitment_q_{i}"] = input_field
            components.append(input_field)

        super().__init__(title=f"Recrutement: {category.name}", components=components)

    async def callback(self, interaction: disnake.ModalInteraction):
        await interaction.response.defer(ephemeral=True)
        try:
            # Collecter les réponses
            responses = {}
            for key, input_field in self.question_inputs.items():
                responses[key] = interaction.text_values.get(key, "")

            # Créer le ticket avec les réponses stockées
            await self._create_recruitment_ticket(interaction, responses)
        except Exception as e:
            await interaction.followup.send(f"Erreur lors de la création du ticket: {e}", ephemeral=True)

    async def _create_recruitment_ticket(self, interaction: disnake.Interaction, responses: Dict[str, str]):
        """Crée le ticket de recrutement"""
        guild = interaction.guild
        user = interaction.user

        storage = get_storage()

        # Build permissions
        overwrites = {
            guild.default_role: disnake.PermissionOverwrite(read_messages=False),
            user: disnake.PermissionOverwrite(read_messages=True, send_messages=True),
            guild.me: disnake.PermissionOverwrite(read_messages=True, send_messages=True)
        }

        # Add staff roles (category-specific roles override global ones)
        staff_roles_to_add = self.category.staff_roles if self.category.staff_roles else self.config.staff_roles
        for role_id in staff_roles_to_add:
            try:
                role = guild.get_role(int(role_id))
                if role and role not in overwrites:
                    overwrites[role] = disnake.PermissionOverwrite(read_messages=True, send_messages=True)
            except (ValueError, TypeError):
                continue

        # Create channel name
        safe_username = "".join(c for c in user.display_name if not c.isdigit()).strip()
        if not safe_username:
            safe_username = "user"
        safe_category = "".join(c for c in self.category.name if not c.isdigit()).strip()
        channel_name = f"recrutement-{safe_username}"

        try:
            category_channel = None
            if self.category.discord_category_id:
                try:
                    category_channel = guild.get_channel(int(self.category.discord_category_id))
                    if category_channel and not isinstance(category_channel, disnake.CategoryChannel):
                        category_channel = None
                except (ValueError, TypeError):
                    pass

            if not category_channel and self.config.default_category:
                try:
                    category_channel = guild.get_channel(int(self.config.default_category))
                    if category_channel and not isinstance(category_channel, disnake.CategoryChannel):
                        category_channel = None
                except (ValueError, TypeError):
                    pass

            ticket_channel = await guild.create_text_channel(
                name=channel_name,
                category=category_channel,
                overwrites=overwrites,
                topic=f"Recrutement de {user} | Catégorie: {self.category.name}"
            )
        except Exception as e:
            await interaction.followup.send(f"Erreur: {e}", ephemeral=True)
            return

        # Create ticket data
        ticket_data = TicketData(
            channel_id=ticket_channel.id,
            guild_id=guild.id,
            user_id=user.id,
            category_id=self.category.id,
            priority=Priority.NORMAL,
            status=TicketStatus.OPEN,
            reason="Recrutement",
            recruitment_responses=responses
        )
        storage.save_ticket(ticket_data)

        # Build mentions for staff
        mentions = [user.mention]
        for role_id in staff_roles_to_add:
            mentions.append(f"<@&{role_id}>")

        mention_content = " ".join(mentions)

        # Send welcome message in ticket channel
        welcome_text = f"Bienvenue {user.mention} !\n\nMerci de votre intérêt pour rejoindre notre équipe.\n\n"
        welcome_text += "Nos responsables vont analyser vos réponses et vous contacteront.\n\n"

        # Afficher les questions et réponses
        welcome_text += "**Vos réponses:**\n"
        for i, question in enumerate(self.category.questions):
            key = f"recruitment_q_{i}"
            response = responses.get(key, "Non répondu")
            welcome_text += f"> **{question}**\n> {response}\n\n"

        # Create container with management buttons and recruitment responses
        sections = [
            disnake.ui.TextDisplay(content=welcome_text),
            disnake.ui.Separator(divider=True)
        ]

        # Add management buttons if staff
        sections.append(
            disnake.ui.Section(
                "Actions",
                accessory=disnake.ui.Button(
                    label="🔒 Fermer (Accepté/Refusé)",
                    style=disnake.ButtonStyle.green,
                    custom_id="recruitment_close"
                )
            )
        )

        container = disnake.ui.Container(*sections)
        await ticket_channel.send(components=[disnake.ui.Container(
            disnake.ui.TextDisplay(content=mention_content),
            disnake.ui.Separator(divider=True),
            *sections
        )])

        # Envoyer les réponses dans le salon staff si configuré
        if self.category.responses_channel_id:
            try:
                responses_channel = guild.get_channel(self.category.responses_channel_id)
                if responses_channel:
                    embed = disnake.Embed(
                        title=f"📝 Nouveau Candidature - {user}",
                        description=f"**Candidature dans** {ticket_channel.mention}",
                        color=disnake.Color.blue(),
                        timestamp=datetime.now()
                    )
                    embed.set_thumbnail(url=user.display_avatar.url)

                    for i, question in enumerate(self.category.questions):
                        key = f"recruitment_q_{i}"
                        response = responses.get(key, "Non répondu")
                        embed.add_field(
                            name=f"Q{i+1}: {question}",
                            value=response,
                            inline=False
                        )

                    embed.add_field(name="Candidat", value=user.mention, inline=True)
                    embed.add_field(name="Discord ID", value=str(user.id), inline=True)

                    await responses_channel.send(embed=embed)
            except Exception as e:
                print(f"Error sending recruitment responses to staff channel: {e}")

        # Log
        if self.config.log_channel_id:
            log_channel = guild.get_channel(self.config.log_channel_id)
            if log_channel:
                log_embed = disnake.Embed(
                    title="🎓 Nouveau Ticket de Recrutement",
                    color=disnake.Color.purple(),
                    timestamp=datetime.now()
                )
                log_embed.add_field(name="Utilisateur", value=user.mention, inline=True)
                log_embed.add_field(name="Catégorie", value=self.category.name, inline=True)
                log_embed.add_field(name="Salon", value=ticket_channel.mention, inline=True)
                await log_channel.send(embed=log_embed)

        await interaction.followup.send(f"Ticket créé: {ticket_channel.mention}", ephemeral=True)


class SetCategoryModal(disnake.ui.Modal):
    """Modal pour définir la catégorie Discord où les tickets seront créés"""
    
    def __init__(self, bot, storage, config: GuildTicketConfig):
        self.category_input = disnake.ui.TextInput(
            label="Catégorie Discord",
            placeholder="Entrez l'ID de la catégorie...",
            required=True
        )
        super().__init__(title="Définir la Catégorie", components=[self.category_input])
        self.bot = bot
        self.storage = storage
        self.config = config
    
    async def callback(self, interaction: disnake.ModalInteraction):
        cat_id = self.category_input.value.strip()
        
        # Verify it's a valid ID
        if not cat_id.isdigit():
             await interaction.response.send_message("❌ Veuillez entrer un ID de catégorie valide (que des chiffres).", ephemeral=True)
             return
             
        # Check if category exists
        category = interaction.guild.get_channel(int(cat_id))
        if not category or not isinstance(category, disnake.CategoryChannel):
            await interaction.response.send_message("❌ Catégorie introuvable ou ce n'est pas une catégorie.", ephemeral=True)
            return
            
        self.config.default_category = cat_id
        self.storage.save_config(interaction.guild_id, self.config)
        
        await interaction.response.send_message(f"📁 Les tickets seront désormais créés dans la catégorie **{category.name}**!", ephemeral=True)


def get_ticket_management_container(ticket: TicketData, category, config: GuildTicketConfig) -> list:
    """Helper V2 pour générer les actions de gestion d'un ticket"""
    sections = []
    
    # Informations du ticket (Titre, description)
    sections.append(disnake.ui.TextDisplay(content=f"# {category.emoji} {category.name}"))
    sections.append(disnake.ui.Separator())
    
    status_emoji = "🟢" if ticket.status == TicketStatus.OPEN else ("🟡" if ticket.status == TicketStatus.CLAIMED else "🔴")
    status_text = "Ouvert" if ticket.status == TicketStatus.OPEN else ("Réclamé" if ticket.status == TicketStatus.CLAIMED else "Fermé")
    
    content = f"Bienvenue <@{ticket.user_id}>!\n\nVotre demande sera traitée sous peu par notre équipe.\n\n"
    content += f"**Motif:** {ticket.reason or 'Aucun'}\n"
    content += f"**Priorité:** {ticket.priority.value.capitalize()}\n"
    content += f"**Statut:** {status_emoji} {status_text}"
    
    if ticket.claimed_by:
        content += f"\n**Pris en charge par:** <@{ticket.claimed_by}>"
        
    sections.append(disnake.ui.TextDisplay(content=content))
    sections.append(disnake.ui.Separator())
    
    # 1. Gestion Staff
    if ticket.status == TicketStatus.OPEN and config.claim_enabled and category and category.allow_claims:
        sections.append(disnake.ui.Section("🤝 Le staff peut prendre en charge le ticket", accessory=disnake.ui.Button(label="Réclamer", style=disnake.ButtonStyle.primary, custom_id="ticket_claim")))
    elif ticket.status == TicketStatus.CLAIMED:
        sections.append(disnake.ui.Section("🤝 Gérer la réclamation", accessory=disnake.ui.Button(label="Abandonner", style=disnake.ButtonStyle.danger, custom_id="ticket_unclaim")))
        sections.append(disnake.ui.Section("🤝 Transférer", accessory=disnake.ui.Button(label="Transférer", style=disnake.ButtonStyle.secondary, custom_id="ticket_claim")))
        
    # 2. Séparateur
    if len(sections) > 0:
        sections.append(disnake.ui.Separator(divider=True))
        
    # 3. Actions générales
    if ticket.status in [TicketStatus.OPEN, TicketStatus.CLAIMED]:
        sections.append(disnake.ui.Section("🔒 Fermer le ticket", accessory=disnake.ui.Button(label="Fermer", style=disnake.ButtonStyle.danger, custom_id="ticket_close")))
    elif ticket.status == TicketStatus.CLOSED:
        sections.append(disnake.ui.Section("🔓 Rouvrir le ticket", accessory=disnake.ui.Button(label="Rouvrir", style=disnake.ButtonStyle.success, custom_id="ticket_reopen")))
        
    sections.append(disnake.ui.Section("📜 Générer un transcript", accessory=disnake.ui.Button(label="Transcript", style=disnake.ButtonStyle.secondary, custom_id="ticket_transcript")))
    
    return [disnake.ui.Container(*sections)]

class TicketManagementView(disnake.ui.View):
    """Gestion du ticket (dans le salon du ticket) - Uniquement pour les callbacks désormais"""

    def __init__(self, bot, storage, config: GuildTicketConfig, ticket: TicketData):
        super().__init__(timeout=None)
        self.bot = bot
        self.storage = storage
        self.config = config
        self.ticket = ticket
        self.category = config.get_category(ticket.category_id)
    
    async def _close_callback(self, interaction: disnake.Interaction):
        """Demande confirmation avant de fermer le ticket"""
        kuby_logger.info(
            f"[TicketSystem] _close_callback: "
            f"guild={getattr(interaction.guild,'id',None)} "
            f"channel={getattr(interaction.channel,'id',None)} "
            f"user={interaction.user.id} "
            f"ticket_user={getattr(self.ticket,'user_id',None)}"
        )
        # Vérifier si l'utilisateur peut fermer le ticket
        # Règle voulue:
        # - Staff: toujours autorisé
        # - Joueur: uniquement si le ticket n'est pas encore pris en charge (OPEN)
        can_close = (
            self._is_staff(interaction) or (
                interaction.user.id == self.ticket.user_id and self.ticket.status == TicketStatus.OPEN
            )
        )
        
        if not can_close:
            await interaction.response.send_message("Vous ne pouvez pas fermer ce ticket.", ephemeral=True)
            return
        
        category = self.config.get_category(self.ticket.category_id)
        duration = self.ticket.duration_minutes
        duration_text = f"{duration:.1f} min" if duration else "N/A"

        # ✅ V2 components (disnake>=2.11): ne pas utiliser embed/content avec components V2
        # (version minimale pour éviter tout problème de parsing)
        # Confirmation UI en V2 (boutons réels) - UI courte.
        # Objectif: éviter les erreurs Invalid Form Body et garder un contenu minimal.
        components = [
            disnake.ui.Container(
                disnake.ui.Section(
                    "🔒 Confirmer la fermeture",
                    accessory=disnake.ui.Button(
                        label="✅ Confirmer",
                        style=disnake.ButtonStyle.green,
                        custom_id="close_confirm"
                    )
                ),
                disnake.ui.Separator(divider=True),
                disnake.ui.Section(
                    "❌ Annuler",
                    accessory=disnake.ui.Button(
                        label="❌ Annuler",
                        style=disnake.ButtonStyle.red,
                        custom_id="close_cancel"
                    )
                )
            )
        ]

        # On ne met PAS de view= ici (sinon Disnake lève TypeError).
        # Les boutons V2 de close_confirm/close_cancel seront routés via on_interaction.
        await interaction.response.send_message(components=components, flags=disnake.MessageFlags(is_components_v2=True), ephemeral=True)
    
    async def _transcript_callback(self, interaction: disnake.Interaction):
        """Génère le transcript"""
        if not self._is_staff(interaction):
            await interaction.response.send_message("Réservé au staff.", ephemeral=True)
            return

        storage = get_storage()
        ticket = storage.load_ticket(interaction.channel.id)
        if not ticket:
            await interaction.response.send_message("Ticket non trouvé.", ephemeral=True)
            return

        # Collect messages
        messages = []
        async for msg in interaction.channel.history(limit=1000, oldest_first=True):
            if msg.author.bot:
                continue
            messages.append({
                "id": str(msg.id),
                "author_id": msg.author.id,
                "author_name": str(msg.author),
                "content": msg.content,
                "timestamp": msg.created_at.isoformat(),
                "is_staff": self._is_staff_member(msg.author)
            })

        # Generate transcript
        from .utils.transcript import get_transcript_generator
        transcript_gen = get_transcript_generator()
        filepath = transcript_gen.save_transcript(ticket, messages, format="html")

        if filepath and os.path.exists(filepath):
            file = disnake.File(filepath, filename=os.path.basename(filepath))
            await interaction.response.send_message("Transcript généré:", file=file, ephemeral=True)
        else:
            await interaction.response.send_message("Erreur lors de la génération du transcript.", ephemeral=True)

    async def _claim_callback(self, interaction: disnake.Interaction):
        """Réclame le ticket"""
        if not self._is_staff(interaction):
            await interaction.response.send_message("Réservé au staff.", ephemeral=True)
            return

        if self.ticket.claimed_by == interaction.user.id:
            await interaction.response.send_message("Vous avez déjà réclamé ce ticket.", ephemeral=True)
            return

        old_claimer = self.ticket.claimed_by
        self.ticket.claimed_by = interaction.user.id
        self.ticket.status = TicketStatus.CLAIMED
        self.storage.save_ticket(self.ticket)

        # Force UI update V2
        try:
            await interaction.response.edit_message(components=get_ticket_management_container(self.ticket, self.category, self.config))
        except disnake.NotFound:
            pass # Message might have been deleted, ignore

        if old_claimer:
            embed = disnake.Embed(
                title="🔄 Ticket Transféré",
                description=f"Le ticket a été transféré de <@{old_claimer}> à {interaction.user.mention}",
                color=disnake.Color.blue()
            )
        else:
            embed = TicketEmbedFormatter.ticket_claimed(self.ticket, interaction.user)
            
        await interaction.channel.send(embed=embed)

    async def _reopen_callback(self, interaction: disnake.Interaction):
        """Rouvre le ticket"""
        if not self._is_staff(interaction):
            await interaction.response.send_message("Réservé au staff.", ephemeral=True)
            return

        self.ticket.status = TicketStatus.OPEN
        self.ticket.closed_at = None
        self.ticket.claimed_by = None
        self.storage.save_ticket(self.ticket)

        # Update channel
        overwrites = interaction.channel.overwrites.copy()
        for target, perms in overwrites.items():
            if isinstance(target, disnake.Member) and target.id != interaction.guild.me.id:
                overwrites[target] = disnake.PermissionOverwrite(
                    read_messages=True,
                    send_messages=True
                )

        await interaction.channel.edit(
            name=interaction.channel.name.replace("closed-", ""),
            overwrites=overwrites
        )

        # Update UI V2
        try:
            await interaction.response.edit_message(components=get_ticket_management_container(self.ticket, self.category, self.config))
        except disnake.NotFound:
            pass

        embed = TicketEmbedFormatter.ticket_reopened(self.ticket, interaction.user)
        await interaction.channel.send(embed=embed)
        # We might have edited the message, so we just send follow up if we can't edit
        # if not interaction.response.is_done():
        #     await interaction.response.send_message("Ticket rouvert!", ephemeral=True)
    
    async def _unclaim_callback(self, interaction: disnake.Interaction):
        """Abandonne le ticket"""
        if not self._is_staff(interaction):
            await interaction.response.send_message("Réservé au staff.", ephemeral=True)
            return

        if self.ticket.claimed_by != interaction.user.id:
            await interaction.response.send_message("Vous n'êtes pas celui qui a réclamé ce ticket.", ephemeral=True)
            return

        self.ticket.claimed_by = None
        self.ticket.status = TicketStatus.OPEN
        self.storage.save_ticket(self.ticket)

        # Update UI V2
        try:
            await interaction.response.edit_message(components=get_ticket_management_container(self.ticket, self.category, self.config))
        except disnake.NotFound:
            pass

        embed = TicketEmbedFormatter.ticket_unclaimed(self.ticket, interaction.user)
        await interaction.channel.send(embed=embed)
    
    def _is_staff(self, interaction: disnake.Interaction) -> bool:
        return is_staff(interaction, self.config, self.category)
    
    def _is_staff_member(self, member) -> bool:
        """Check if a specific member is staff, considering category roles"""
        if not member:
            return False
            
        # Handle cases where member is a User (left the guild)
        if not isinstance(member, disnake.Member):
            return member.id in self.config.staff_users

        if member.guild_permissions.administrator:
            return True
            
        if member.id in self.config.staff_users:
            return True

        # Check category-specific staff roles first
        staff_roles = self.category.staff_roles if self.category and self.category.staff_roles else self.config.staff_roles
        for role_id in staff_roles:
            try:
                role = member.guild.get_role(int(role_id))
                if role and role in member.roles:
                    return True
            except (ValueError, TypeError, AttributeError):
                continue
        return False


class CloseConfirmationView(disnake.ui.View):
    """Demande de confirmation avant la fermeture du ticket"""
    
    def __init__(self, bot, storage, config: GuildTicketConfig, ticket: TicketData):
        super().__init__(timeout=60)
        self.bot = bot
        self.storage = storage
        self.config = config
        self.ticket = ticket
        self.category = config.get_category(ticket.category_id)
    
    @disnake.ui.button(label="✅ Confirmer", style=disnake.ButtonStyle.green, custom_id="close_confirm")
    async def confirm_callback(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
        """L'utilisateur confirme la fermeture"""
        # Vérifier les permissions
        # Règle voulue:
        # - Staff: toujours autorisé
        # - Joueur: uniquement si le ticket n'est pas encore pris en charge (OPEN)
        can_close = (
            self._is_staff(interaction) or (
                interaction.user.id == self.ticket.user_id and self.ticket.status == TicketStatus.OPEN
            )
        )

        if not can_close:
            await interaction.response.send_message("Vous ne pouvez pas fermer ce ticket.", ephemeral=True)
            return

        # Show the delay view with countdown (V2 components, not embeds)
        components = [
            disnake.ui.Container(
                disnake.ui.Section(
                    "# ⏳ Fermeture du Ticket",
                    accessory=disnake.ui.Button(
                        label="Fermeture...",
                        style=disnake.ButtonStyle.secondary,
                        custom_id="close_delay_info"
                    )
                ),
                disnake.ui.Separator(divider=True),
                disnake.ui.TextDisplay(
                    content="Le ticket va être fermé dans quelques secondes...\n\n"
                            "📄 Génération du transcript en cours...\n"
                            "📝 Envoi aux logs...\n"
                            "🗑️ Suppression du canal..."
                )
            )
        ]

        view = CloseDelayView(self.bot, self.storage, self.config, self.ticket)
        await interaction.response.send_message(
            components=components,
            flags=disnake.MessageFlags(is_components_v2=True),
            view=view,
            ephemeral=True
        )

        # Start the countdown
        await view.start_countdown(interaction)
    
    @disnake.ui.button(label="❌ Annuler", style=disnake.ButtonStyle.red, custom_id="close_cancel")
    async def cancel_callback(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
        """L'utilisateur annule la fermeture"""
        await interaction.response.defer()
        await interaction.delete_original_response()
    
    def _is_staff(self, interaction: disnake.Interaction) -> bool:
        return is_staff(interaction, self.config, self.category)


class CloseDelayView(disnake.ui.View):
    """Affiche le délai d'attente avant suppression du ticket"""

    def __init__(self, bot, storage, config: GuildTicketConfig, ticket: TicketData, close_reason: str = ""):
        super().__init__(timeout=300)
        self.bot = bot
        self.storage = storage
        self.config = config
        self.ticket = ticket
        self.close_reason = close_reason
        self.category = config.get_category(ticket.category_id)

        # Ajouter le bouton Annuler
        cancel_btn = disnake.ui.Button(
            label="❌ Annuler",
            style=disnake.ButtonStyle.red,
            custom_id="delay_cancel"
        )
        cancel_btn.callback = self._cancel_callback
        self.add_item(cancel_btn)
    
    async def start_countdown(self, interaction: disnake.Interaction):
        """Démarre le compte à rebours"""
        # Désactiver le bouton Annuler
        for item in self.children:
            item.disabled = True
        await interaction.edit_original_response(view=self)

        # Étapes du processus
        steps = [
            ("⏳ Préparation...", 1),
            ("📄 Génération du transcript...", 1),
            ("📝 Sauvegarde des données...", 1),
            ("🗑️ Suppression du canal...", 1)
        ]

        for step_text, delay in steps:
            embed = disnake.Embed(
                title="🔒 Fermeture du Ticket",
                description=f"**{step_text}**\n\n"
                           f"Merci pour votre confiance!",
                color=disnake.Color.orange()
            )
            embed.set_footer(text=f"Ticket #{self.ticket.channel_id}")

            # IMPORTANT: si la réponse originale a été envoyée avec is_components_v2=True,
            # ne pas modifier en embed (sinon API refuse embeds + components_v2).
            await interaction.edit_original_response(
                components=[disnake.ui.Container(
                    disnake.ui.Section(
                        "# 6ed Fermeture du Ticket",
                        accessory=disnake.ui.Button(
                            label=step_text,
                            style=disnake.ButtonStyle.secondary,
                            custom_id="close_delay_progress"
                        )
                    ),
                    disnake.ui.Separator(divider=True),
                    disnake.ui.TextDisplay(content=f"{step_text}\n\nMerci pour votre confiance!")
                )],
            )

            await asyncio.sleep(delay)

        # Procéder à la fermeture
        await self._close_ticket(interaction)
    
    async def _close_ticket(self, interaction: disnake.Interaction):
        """Effectue la fermeture réelle du ticket"""
        # Collect messages for transcript
        transcript_file = None
        try:
            messages = []
            async for msg in interaction.channel.history(limit=1000, oldest_first=True):
                if msg.author.bot:
                    continue
                messages.append({
                    "id": str(msg.id),
                    "author_id": msg.author.id,
                    "author_name": str(msg.author),
                    "content": msg.content,
                    "timestamp": msg.created_at.isoformat(),
                    "is_staff": self._is_staff_member(msg.author)
                })
            
            from .utils.transcript import get_transcript_generator
            transcript_gen = get_transcript_generator()
            transcript_file = transcript_gen.save_transcript(self.ticket, messages, format="html")
        except Exception as e:
            print(f"Error generating transcript: {e}")
        
        # Close ticket
        self.ticket.status = TicketStatus.CLOSED
        self.ticket.closed_at = datetime.now()
        if self.close_reason:
            self.ticket.feedback = self.close_reason
        self.storage.save_ticket(self.ticket)
        
        duration = self.ticket.duration_minutes
        
        # Send Feedback Request to User via DM
        try:
            user = interaction.guild.get_member(self.ticket.user_id)
            if user:
                from .feedback_view import FeedbackView
                
                dm_embed = disnake.Embed(
                    title="📝 Votre avis nous intéresse !",
                    description=f"Votre ticket **#{self.ticket.channel_id}** a été fermé.\nMerci de prendre un moment pour noter la qualité du support.",
                    color=disnake.Color.green(),
                    timestamp=datetime.now()
                )
                dm_embed.add_field(name="Durée du ticket", value=f"{duration:.1f} minutes", inline=True)
                if self.close_reason:
                    dm_embed.add_field(name="Raison de fermeture", value=self.close_reason, inline=False)
                    
                # Create a special view with a "Noter le staff" button
                view = FeedbackView(self.bot, self.storage, self.ticket)
                
                # Add a button to directly open the feedback form
                note_staff_btn = disnake.ui.Button(
                    label="⭐ Noter le staff",
                    style=disnake.ButtonStyle.primary,
                    custom_id="note_staff_direct"
                )
                note_staff_btn.callback = self._note_staff_callback
                view.add_item(note_staff_btn)
                
                await user.send(embed=dm_embed, view=view)
        except Exception as e:
            print(f"Failed to send feedback DM: {e}")

        # Send transcript to log channel
        if self.config.log_channel_id:
            try:
                log_channel = interaction.guild.get_channel(self.config.log_channel_id)
                if log_channel:
                    log_embed = disnake.Embed(
                        title="📄 Ticket Fermé - Transcript",
                        description=f"Ticket #{self.ticket.channel_id} fermé par {interaction.user.mention}",
                        color=disnake.Color.red(),
                        timestamp=datetime.now()
                    )
                    
                    ticket_creator = interaction.guild.get_member(self.ticket.user_id)
                    creator_mention = ticket_creator.mention if ticket_creator else f"Utilisateur {self.ticket.user_id}"
                    
                    log_embed.add_field(name="Créateur", value=creator_mention, inline=True)
                    log_embed.add_field(name="Catégorie", value=self.category.name if self.category else "N/A", inline=True)
                    log_embed.add_field(name="Durée", value=f"{duration:.1f} min" if duration else "N/A", inline=True)
                    
                    if self.close_reason:
                        log_embed.add_field(name="Raison", value=self.close_reason, inline=False)
                    
                    if transcript_file and os.path.exists(transcript_file):
                        file = disnake.File(transcript_file, filename=os.path.basename(transcript_file))
                        await log_channel.send(embed=log_embed, file=file)
                    else:
                        log_embed.add_field(name="⚠️ Transcript", value="Le transcript n'a pas pu être généré (l'utilisateur a peut-être quitté le serveur).", inline=False)
                        await log_channel.send(embed=log_embed)
            except Exception as e:
                print(f"Error sending transcript to log channel: {e}")
        
        # Delete the ticket channel
        try:
            await interaction.channel.delete(reason=f"Ticket fermé par {interaction.user}")
        except Exception as e:
            print(f"Error deleting channel: {e}")
            embed = disnake.Embed(
                title="❌ Erreur",
                description="Le ticket a été fermé mais la suppression du salon a échoué.",
                color=disnake.Color.red()
            )
            await interaction.channel.send(embed=embed)
            
    async def _note_staff_callback(self, interaction: disnake.Interaction):
        """Handle direct staff rating button"""
        # This would open a direct feedback form
        await interaction.response.send_message("Vous pouvez maintenant noter le staff directement !", ephemeral=True)

    async def _cancel_callback(self, interaction: disnake.Interaction):
        """Annule la fermeture du ticket pendant le délai"""
        await interaction.response.defer()
        await interaction.delete_original_response()

        # Envoyer un message de confirmation d'annulation
        embed = disnake.Embed(
            title="❌ Fermeture Annulée",
            description="La fermeture du ticket a été annulée.",
            color=disnake.Color.red()
        )
        await interaction.channel.send(embed=embed, delete_after=5)

    def _is_staff(self, interaction: disnake.Interaction) -> bool:
        return is_staff(interaction, self.config, self.category)

    def _is_staff_member(self, member) -> bool:
        """Check if a specific member is staff, considering category roles"""
        if not member:
            return False
            
        # Handle cases where member is a User (left the guild)
        if not isinstance(member, disnake.Member):
            return member.id in self.config.staff_users

        if member.guild_permissions.administrator:
            return True
            
        if member.id in self.config.staff_users:
            return True

        # Check category-specific staff roles first
        staff_roles = self.category.staff_roles if self.category and self.category.staff_roles else self.config.staff_roles
        for role_id in staff_roles:
            try:
                role = member.guild.get_role(int(role_id))
                if role and role in member.roles:
                    return True
            except (ValueError, TypeError, AttributeError):
                continue
        return False


class CloseTicketModal(disnake.ui.Modal):
    """Modal pour fermer un ticket"""

    def __init__(self, bot, storage, config: GuildTicketConfig, ticket: TicketData):
        self.reason_input = disnake.ui.TextInput(
            label="Raison de fermeture",
            placeholder="Optionnel...",
            style=disnake.TextInputStyle.paragraph,
            required=False
        )
        super().__init__(title="Fermer le Ticket", components=[self.reason_input])
        self.bot = bot
        self.storage = storage
        self.config = config
        self.ticket = ticket
        self.category = config.get_category(ticket.category_id)
    
    async def callback(self, interaction: disnake.ModalInteraction):
        # Check permissions
        can_close = (
            interaction.user.id == self.ticket.user_id or
            self._is_staff(interaction)
        )

        if not can_close:
            await interaction.response.send_message("Vous ne pouvez pas fermer ce ticket.", ephemeral=True)
            return

        # Send ephemeral response to user first (required for modal interactions)
        await interaction.response.send_message("✅ Fermeture du ticket en cours...", ephemeral=True)

        # Send confirmation message in the ticket channel (V2 components, not embeds)
        confirm_components = [
            disnake.ui.Container(
                disnake.ui.Section(
                    "# ⏳ Fermeture du Ticket",
                    accessory=disnake.ui.Button(
                        label="Fermeture...",
                        style=disnake.ButtonStyle.secondary,
                        custom_id="close_ticket_modal_info"
                    )
                ),
                disnake.ui.Separator(divider=True),
                disnake.ui.TextDisplay(
                    content="Le ticket va être fermé dans quelques secondes...\n\n"
                            "📄 Génération du transcript en cours...\n"
                            "📝 Envoi aux logs...\n"
                            "🗑️ Suppression du canal..."
                )
            )
        ]
        await interaction.channel.send(
            components=confirm_components,
            flags=disnake.MessageFlags(is_components_v2=True)
        )

        # Add small delay for user to see the confirmation
        import asyncio
        await asyncio.sleep(3)

        # Generate transcript before closing
        transcript_file = None
        try:
            # Collect messages
            messages = []
            async for msg in interaction.channel.history(limit=1000, oldest_first=True):
                if msg.author.bot:
                    continue
                messages.append({
                    "id": str(msg.id),
                    "author_id": msg.author.id,
                    "author_name": str(msg.author),
                    "content": msg.content,
                    "timestamp": msg.created_at.isoformat(),
                    "is_staff": self._is_staff_member(msg.author)
                })

            # Generate transcript
            from .utils.transcript import get_transcript_generator
            transcript_gen = get_transcript_generator()
            transcript_file = transcript_gen.save_transcript(self.ticket, messages, format="html")
        except Exception as e:
            print(f"Error generating transcript: {e}")

        # Close ticket
        self.ticket.status = TicketStatus.CLOSED
        self.ticket.closed_at = datetime.now()
        self.storage.save_ticket(self.ticket)

        duration = self.ticket.duration_minutes

        # Send transcript to log channel if configured
        if self.config.log_channel_id:
            try:
                log_channel = interaction.guild.get_channel(self.config.log_channel_id)
                if log_channel:
                    # Create log embed
                    log_embed = disnake.Embed(
                        title="📄 Ticket Fermé - Transcript",
                        description=f"Ticket #{self.ticket.channel_id} fermé par {interaction.user.mention}",
                        color=disnake.Color.red(),
                        timestamp=datetime.now()
                    )

                    # Get ticket creator
                    # Fetch le créateur pour affichage fiable même si absent du cache
                    ticket_creator = interaction.guild.get_member(self.ticket.user_id)
                    if not ticket_creator:
                        try:
                            ticket_creator = await interaction.guild.fetch_member(self.ticket.user_id)
                        except Exception:
                            ticket_creator = None
                    creator_mention = ticket_creator.mention if ticket_creator else f"Utilisateur {self.ticket.user_id}"

                    log_embed.add_field(name="Créateur", value=creator_mention, inline=True)
                    log_embed.add_field(name="Catégorie", value=self.category.name if self.category else "N/A", inline=True)
                    log_embed.add_field(name="Durée", value=f"{duration:.1f} min" if duration else "N/A", inline=True)

                    if self.reason_input.value:
                        log_embed.add_field(name="Raison de fermeture", value=self.reason_input.value, inline=False)

                    # Send transcript file
                    if transcript_file and os.path.exists(transcript_file):
                        file = disnake.File(transcript_file, filename=os.path.basename(transcript_file))
                        await log_channel.send(embed=log_embed, file=file)
                    else:
                        log_embed.add_field(name="⚠️ Transcript", value="Le transcript n'a pas pu être généré (l'utilisateur a peut-être quitté le serveur).", inline=False)
                        await log_channel.send(embed=log_embed)
            except Exception as e:
                print(f"Error sending transcript to log channel: {e}")

        # Delete the ticket channel
        try:
            await interaction.channel.delete(reason=f"Ticket fermé par {interaction.user}")
        except Exception as e:
            print(f"Error deleting channel: {e}")
            # Fallback: just mark as closed if deletion fails
            await interaction.followup.send("Erreur lors de la suppression du canal, mais le ticket a été fermé.", ephemeral=True)
    
    def _is_staff(self, interaction: disnake.Interaction) -> bool:
        return is_staff(interaction, self.config, self.category)

    def _is_staff_member(self, member) -> bool:
        """Check if a specific member is staff, considering category roles"""
        if not member:
            return False
            
        # Handle cases where member is a User (left the guild)
        if not isinstance(member, disnake.Member):
            return member.id in self.config.staff_users

        if member.guild_permissions.administrator:
            return True
            
        if member.id in self.config.staff_users:
            return True

        staff_roles = self.category.staff_roles if self.category and self.category.staff_roles else self.config.staff_roles
        for role_id in staff_roles:
            try:
                role = member.guild.get_role(int(role_id))
                if role and role in member.roles:
                    return True
            except (ValueError, TypeError, AttributeError):
                continue
        return False


class TicketSetupView(disnake.ui.View):
    """Panel de configuration complet du système de tickets"""

    def __init__(self, bot, storage, config: GuildTicketConfig):
        super().__init__(timeout=None)
        self.bot = bot
        self.storage = storage
        self.config = config

        # Toggle enabled
        toggle_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.green if config.enabled else disnake.ButtonStyle.red,
            label="🔄 Activer/Désactiver",
            custom_id="setup_toggle"
        )
        toggle_btn.callback = self._toggle_callback
        self.add_item(toggle_btn)

        # Staff roles
        roles_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.primary,
            label="👥 Rôles Staff",
            custom_id="setup_roles"
        )
        roles_btn.callback = self._roles_callback
        self.add_item(roles_btn)

        # Config roles
        config_roles_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.secondary,
            label="🔑 Rôles Config",
            custom_id="setup_config_roles"
        )
        config_roles_btn.callback = self._config_roles_callback
        self.add_item(config_roles_btn)

        # Categories
        cat_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.secondary,
            label="📂 Catégories",
            custom_id="setup_categories"
        )
        cat_btn.callback = self._categories_callback
        self.add_item(cat_btn)

        # Log channel
        log_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.secondary,
            label="📝 Canal de Logs",
            custom_id="setup_log_channel"
        )
        log_btn.callback = self._log_channel_callback
        self.add_item(log_btn)

        # Advanced settings
        advanced_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.secondary,
            label="⚙️ Paramètres Avancés",
            custom_id="setup_advanced"
        )
        advanced_btn.callback = self._advanced_callback
        self.add_item(advanced_btn)

    async def _toggle_callback(self, interaction: disnake.Interaction):
        self.config.enabled = not self.config.enabled
        self.storage.save_config(interaction.guild_id, self.config)

        status = "activé" if self.config.enabled else "désactivé"
        embed = disnake.Embed(
            title="🔄 État du Système",
            description=f"Le système de tickets a été **{status}**!",
            color=disnake.Color.green() if self.config.enabled else disnake.Color.red()
        )
        await interaction.response.send_message(embed=embed, ephemeral=True)

        # Update button
        for item in self.children:
            if item.custom_id == "setup_toggle":
                item.style = disnake.ButtonStyle.green if self.config.enabled else disnake.ButtonStyle.red
        await interaction.message.edit(view=self)

    async def _roles_callback(self, interaction: disnake.Interaction):
        view = StaffRolesSetupView(self.bot, self.storage, self.config, interaction.guild)

        embed = disnake.Embed(
            title="👥 Configuration des Rôles Staff",
            description="Définissez les rôles qui peuvent gérer les tickets et accéder aux fonctionnalités avancées.\n\n"
                       "**Permissions des rôles staff:**\n"
                       "• Gérer les tickets (réclamer, fermer, etc.)\n"
                       "• Accéder aux analytics et statistiques\n"
                       "• Générer des transcripts\n"
                       "• Fermer les tickets des autres",
            color=disnake.Color.blue()
        )

        role_mentions = []
        for role_id in self.config.staff_roles:
            role = interaction.guild.get_role(int(role_id))
            if role:
                role_mentions.append(role.mention)

        embed.add_field(
            name="Rôles Staff Actuels",
            value=", ".join(role_mentions) if role_mentions else "❌ Aucun rôle configuré",
            inline=False
        )

        await interaction.response.send_message(embed=embed, view=view, ephemeral=True)

    async def _categories_callback(self, interaction: disnake.Interaction):
        view = CategoriesSetupView(self.bot, self.storage, self.config)

        embed = disnake.Embed(
            title="📂 Gestion des Catégories",
            description="Créez et gérez les catégories de tickets pour organiser vos demandes.",
            color=disnake.Color.blue()
        )

        if self.config.categories:
            cat_list = []
            for cat in self.config.categories[:10]:  # Limit to 10 for display
                cat_list.append(f"{cat.emoji} {cat.name}")
            embed.add_field(
                name=f"Catégories ({len(self.config.categories)})",
                value="\n".join(cat_list),
                inline=False
            )
        else:
            embed.add_field(
                name="Aucune catégorie",
                value="Créez votre première catégorie pour commencer!",
                inline=False
            )

        await interaction.response.send_message(embed=embed, view=view, ephemeral=True)

    async def _config_roles_callback(self, interaction: disnake.Interaction):
        view = ConfigRolesSetupView(self.bot, self.storage, self.config, interaction.guild)

        embed = disnake.Embed(
            title="🔑 Configuration des Rôles Config",
            description="Définissez les rôles qui peuvent accéder au panel de configuration du système de tickets.\n\n"
                       "**Permissions des rôles config:**\n"
                       "• Accéder à la commande /ticketconfig\n"
                       "• Modifier tous les paramètres du système\n"
                       "• Gérer les catégories et rôles staff",
            color=disnake.Color.purple()
        )

        role_mentions = []
        for role_id in self.config.config_roles:
            role = interaction.guild.get_role(int(role_id))
            if role:
                role_mentions.append(role.mention)

        embed.add_field(
            name="Rôles Config Actuels",
            value=", ".join(role_mentions) if role_mentions else "❌ Aucun rôle configuré",
            inline=False
        )

        await interaction.response.send_message(embed=embed, view=view, ephemeral=True)

    async def _log_channel_callback(self, interaction: disnake.Interaction):
        modal = SetChannelModal(self.bot, self.storage, self.config, "log")
        await interaction.response.send_modal(modal)

    async def _advanced_callback(self, interaction: disnake.Interaction):
        view = AdvancedSetupView(self.bot, self.storage, self.config)

        embed = disnake.Embed(
            title="⚙️ Paramètres Avancés",
            description="Configuration fine du système de tickets.",
            color=disnake.Color.purple()
        )

        embed.add_field(
            name="Paramètres Actuels",
            value=f"Système de réclamation: {'✅ Activé' if self.config.claim_enabled else '❌ Désactivé'}\n"
                  f"Sondages: {'✅ Activé' if self.config.survey_enabled else '❌ Désactivé'}\n"
                  f"Limite par utilisateur: {self.config.max_tickets_per_user}",
            inline=False
        )

        await interaction.response.send_message(embed=embed, view=view, ephemeral=True)


class StaffRolesSetupView(disnake.ui.View):
    """Configuration des rôles staff"""

    def __init__(self, bot, storage, config: GuildTicketConfig, guild: disnake.Guild):
        super().__init__(timeout=None)
        self.bot = bot
        self.storage = storage
        self.config = config
        self.guild = guild
        self._build_select()

    def _build_select(self):
        """Build the role select menu"""
        options = []
        for role in self.guild.roles:
            if role.id != self.guild.id:  # Exclude @everyone
                options.append(
                    disnake.SelectOption(
                        label=role.name,
                        value=str(role.id),
                        default=str(role.id) in self.config.staff_roles
                    )
                )

        if options:
            select = disnake.ui.Select(
                placeholder="Sélectionnez les rôles staff...",
                options=options[:25],
                min_values=0,
                max_values=min(25, len(options))
            )
            select.callback = self._select_callback
            self.add_item(select)

    async def _select_callback(self, interaction: disnake.Interaction):
        selected = list(interaction.data.get("values", []))
        self.config.staff_roles = selected

        self.storage.save_config(interaction.guild_id, self.config)

        role_mentions = []
        for role_id in selected:
            role = interaction.guild.get_role(int(role_id))
            if role:
                role_mentions.append(role.mention)

        embed = disnake.Embed(
            title="✅ Rôles Staff Mis à Jour",
            description=f"**{len(selected)}** rôle(s) configuré(s) comme staff.",
            color=disnake.Color.green()
        )

        embed.add_field(
            name="Rôles Staff",
            value=", ".join(role_mentions) if role_mentions else "Aucun",
            inline=False
        )

        await interaction.response.send_message(embed=embed, ephemeral=True)


class ConfigRolesSetupView(disnake.ui.View):
    """Configuration des rôles config"""

    def __init__(self, bot, storage, config: GuildTicketConfig, guild: disnake.Guild):
        super().__init__(timeout=None)
        self.bot = bot
        self.storage = storage
        self.config = config
        self.guild = guild
        self._build_select()

    def _build_select(self):
        """Build the role select menu"""
        options = []
        for role in self.guild.roles:
            if role.id != self.guild.id:  # Exclude @everyone
                options.append(
                    disnake.SelectOption(
                        label=role.name,
                        value=str(role.id),
                        default=str(role.id) in self.config.config_roles
                    )
                )

        if options:
            select = disnake.ui.Select(
                placeholder="Sélectionnez les rôles config...",
                options=options[:25],
                min_values=0,
                max_values=min(25, len(options))
            )
            select.callback = self._select_callback
            self.add_item(select)

    async def _select_callback(self, interaction: disnake.Interaction):
        selected = list(interaction.data.get("values", []))
        self.config.config_roles = selected

        self.storage.save_config(interaction.guild_id, self.config)

        role_mentions = []
        for role_id in selected:
            role = interaction.guild.get_role(int(role_id))
            if role:
                role_mentions.append(role.mention)

        embed = disnake.Embed(
            title="✅ Rôles Config Mis à Jour",
            description=f"**{len(selected)}** rôle(s) configuré(s) pour accéder à la configuration.",
            color=disnake.Color.green()
        )

        embed.add_field(
            name="Rôles Config",
            value=", ".join(role_mentions) if role_mentions else "Aucun",
            inline=False
        )

        embed.add_field(
            name="Permissions",
            value="• Accéder à /ticketconfig\n• Modifier la configuration\n• Gérer les catégories et rôles",
            inline=False
        )

        await interaction.response.send_message(embed=embed, ephemeral=True)


class CategoriesSetupView(disnake.ui.View):
    """Gestion des catégories"""

    def __init__(self, bot, storage, config: GuildTicketConfig):
        super().__init__(timeout=None)
        self.bot = bot
        self.storage = storage
        self.config = config

        # Add category
        add_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.green,
            label="➕ Ajouter",
            custom_id="cat_add"
        )
        add_btn.callback = self._add_callback
        self.add_item(add_btn)

        # Remove category (only if categories exist)
        if config.categories:
            remove_btn = disnake.ui.Button(
                style=disnake.ButtonStyle.red,
                label="➖ Supprimer",
                custom_id="cat_remove"
            )
            remove_btn.callback = self._remove_callback
            self.add_item(remove_btn)

    async def _add_callback(self, interaction: disnake.Interaction):
        modal = AddCategoryModal(self.bot, self.storage, self.config)
        await interaction.response.send_modal(modal)

    async def _remove_callback(self, interaction: disnake.Interaction):
        if not self.config.categories:
            await interaction.response.send_message("Aucune catégorie à supprimer.", ephemeral=True)
            return

        # Create select with current categories
        options = []
        for cat in self.config.categories[:25]:  # Discord limit
            options.append(
                disnake.SelectOption(
                    label=cat.name,
                    value=cat.id,
                    emoji=cat.emoji
                )
            )

        select = disnake.ui.Select(
            placeholder="Sélectionnez une catégorie à supprimer...",
            options=options
        )
        select.callback = self._remove_select_callback

        view = disnake.ui.View()
        view.add_item(select)

        embed = disnake.Embed(
            title="Supprimer une Catégorie",
            description="Sélectionnez la catégorie à supprimer:",
            color=disnake.Color.red()
        )

        await interaction.response.send_message(embed=embed, view=view, ephemeral=True)

    async def _remove_select_callback(self, interaction: disnake.Interaction):
        cat_id = interaction.data["values"][0]

        # Remove category
        self.config.categories = [cat for cat in self.config.categories if cat.id != cat_id]
        self.storage.save_config(interaction.guild_id, self.config)
        await refresh_ticket_panel(self.bot, self.storage, interaction.guild_id)

        await interaction.response.send_message("✅ Catégorie supprimée!", ephemeral=True)


class AdvancedSetupView(disnake.ui.View):
    """Paramètres avancés"""

    def __init__(self, bot, storage, config: GuildTicketConfig):
        super().__init__(timeout=None)
        self.bot = bot
        self.storage = storage
        self.config = config

        # Toggle claim system
        claim_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.green if config.claim_enabled else disnake.ButtonStyle.red,
            label=f"{'✅' if config.claim_enabled else '❌'} Système de Réclamation",
            custom_id="advanced_claim"
        )
        claim_btn.callback = self._claim_callback
        self.add_item(claim_btn)

        # Toggle survey system
        survey_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.green if config.survey_enabled else disnake.ButtonStyle.red,
            label=f"{'✅' if config.survey_enabled else '❌'} Sondages",
            custom_id="advanced_survey"
        )
        survey_btn.callback = self._survey_callback
        self.add_item(survey_btn)

        # Max tickets per user
        max_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.secondary,
            label=f"📊 Limite: {config.max_tickets_per_user}",
            custom_id="advanced_max"
        )
        max_btn.callback = self._max_callback
        self.add_item(max_btn)

    async def _claim_callback(self, interaction: disnake.Interaction):
        self.config.claim_enabled = not self.config.claim_enabled
        self.storage.save_config(interaction.guild_id, self.config)

        status = "activé" if self.config.claim_enabled else "désactivé"
        embed = disnake.Embed(
            title="🔄 Système de Réclamation",
            description=f"Le système de réclamation a été **{status}**!",
            color=disnake.Color.green() if self.config.claim_enabled else disnake.Color.red()
        )
        await interaction.response.send_message(embed=embed, ephemeral=True)

        # Update button
        for item in self.children:
            if item.custom_id == "advanced_claim":
                item.style = disnake.ButtonStyle.green if self.config.claim_enabled else disnake.ButtonStyle.red
                item.label = f"{'✅' if self.config.claim_enabled else '❌'} Système de Réclamation"
        await interaction.message.edit(view=self)

    async def _survey_callback(self, interaction: disnake.Interaction):
        self.config.survey_enabled = not self.config.survey_enabled
        self.storage.save_config(interaction.guild_id, self.config)

        status = "activé" if self.config.survey_enabled else "désactivé"
        embed = disnake.Embed(
            title="🔄 Système de Sondages",
            description=f"Le système de sondages a été **{status}**!",
            color=disnake.Color.green() if self.config.survey_enabled else disnake.Color.red()
        )
        await interaction.response.send_message(embed=embed, ephemeral=True)

        # Update button
        for item in self.children:
            if item.custom_id == "advanced_survey":
                item.style = disnake.ButtonStyle.green if self.config.survey_enabled else disnake.ButtonStyle.red
                item.label = f"{'✅' if self.config.survey_enabled else '❌'} Sondages"
        await interaction.message.edit(view=self)

    async def _max_callback(self, interaction: disnake.Interaction):
        modal = MaxTicketsModal(self.bot, self.storage, self.config)
        await interaction.response.send_modal(modal)


class MaxTicketsModal(disnake.ui.Modal):
    """Modal pour définir la limite de tickets par utilisateur"""
    def __init__(self, bot, storage, config: GuildTicketConfig):
        self.max_tickets = disnake.ui.TextInput(
            label="Nombre maximum de tickets ouverts par utilisateur",
            placeholder="5",
            default=str(config.max_tickets_per_user),
            required=True,
            max_length=2
        )
        super().__init__(title="Limite de Tickets", components=[self.max_tickets])
        self.bot = bot
        self.storage = storage
        self.config = config

    async def callback(self, interaction: disnake.ModalInteraction):
        try:
            max_tickets = int(self.max_tickets.value)
            if max_tickets < 1 or max_tickets > 50:
                await interaction.response.send_message("La limite doit être entre 1 et 50.", ephemeral=True)
                return

            self.config.max_tickets_per_user = max_tickets
            self.storage.save_config(interaction.guild_id, self.config)

            embed = disnake.Embed(
                title="✅ Limite Mise à Jour",
                description=f"La limite de tickets par utilisateur est maintenant de **{max_tickets}**.",
                color=disnake.Color.green()
            )
            await interaction.response.send_message(embed=embed, ephemeral=True)

        except ValueError:
            await interaction.response.send_message("Veuillez entrer un nombre valide.", ephemeral=True)


class ConfigPanelView(disnake.ui.View):
    """Panel de configuration"""

    def __init__(self, bot, storage, config: GuildTicketConfig):
        super().__init__(timeout=None)
        self.bot = bot
        self.storage = storage
        self.config = config

        # Toggle enabled
        toggle_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.green if config.enabled else disnake.ButtonStyle.red,
            label="Activer/Désactiver",
            custom_id="config_toggle"
        )
        toggle_btn.callback = self._toggle_callback
        self.add_item(toggle_btn)

        # Default category
        self.category_btn = disnake.ui.Button(
            label="📁 Catégorie par défaut",
            style=disnake.ButtonStyle.secondary,
            custom_id="config_category"
        )
        self.category_btn.callback = self._category_callback
        self.add_item(self.category_btn)

        # Gérer les catégories
        self.manage_cat_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.blurple,
            label="📂 Gérer les catégories",
            custom_id="config_manage_cat"
        )
        self.manage_cat_btn.callback = self._manage_cat_callback
        self.add_item(self.manage_cat_btn)

        # Staff permissions
        staff_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.secondary,
            label="Permissions Staff",
            custom_id="config_staff_perms"
        )
        staff_btn.callback = self._staff_perms_callback
        self.add_item(staff_btn)

        # Config permissions
        config_perms_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.secondary,
            label="Permissions Config",
            custom_id="config_config_perms"
        )
        config_perms_btn.callback = self._config_perms_callback
        self.add_item(config_perms_btn)

        # Log channel
        log_btn = disnake.ui.Button(
            style=disnake.ButtonStyle.secondary,
            label="Canal de Logs",
            custom_id="config_log_channel"
        )
        log_btn.callback = self._log_channel_callback
        self.add_item(log_btn)

    async def _toggle_callback(self, interaction: disnake.Interaction):
        self.config.enabled = not self.config.enabled
        self.storage.save_config(interaction.guild_id, self.config)

        status = "activé" if self.config.enabled else "désactivé"
        await interaction.response.send_message(f"Système {status}!", ephemeral=True)

        # Update button
        for item in self.children:
            if item.custom_id == "config_toggle":
                item.style = disnake.ButtonStyle.green if self.config.enabled else disnake.ButtonStyle.red
        await interaction.message.edit(view=self)

    async def _manage_cat_callback(self, interaction: disnake.Interaction):
        view = CategoryManagerView(self.bot, self.storage, self.config)
        await interaction.response.edit_message(embed=view._get_embed(), view=view)

    async def _staff_perms_callback(self, interaction: disnake.Interaction):
        view = PermissionConfigView(self.bot, self.storage, self.config, interaction.guild, "staff")
        
        embed = disnake.Embed(
            title="Permissions Staff",
            description="Configurez qui peut gérer les tickets (répondre, fermer, etc.)",
            color=disnake.Color.blue()
        )
        self._add_current_perms_fields(embed, self.config.staff_roles, self.config.staff_users, interaction.guild)
        
        await interaction.response.send_message(embed=embed, view=view, ephemeral=True)

    async def _config_perms_callback(self, interaction: disnake.Interaction):
        view = PermissionConfigView(self.bot, self.storage, self.config, interaction.guild, "config")
        
        embed = disnake.Embed(
            title="Permissions Configuration",
            description="Configurez qui peut modifier les paramètres du bot (/ticketconfig)",
            color=disnake.Color.orange()
        )
        self._add_current_perms_fields(embed, self.config.config_roles, self.config.config_users, interaction.guild)
        
        await interaction.response.send_message(embed=embed, view=view, ephemeral=True)

    def _add_current_perms_fields(self, embed, roles, users, guild):
        role_mentions = []
        for role_id in roles:
            role = guild.get_role(int(role_id))
            if role:
                role_mentions.append(role.mention)
                
        user_mentions = []
        for user_id in users:
            user = guild.get_member(user_id)
            if user:
                user_mentions.append(user.mention)
            else:
                user_mentions.append(f"<@{user_id}>")
        
        embed.add_field(name="Rôles", value=", ".join(role_mentions) if role_mentions else "Aucun", inline=False)
        embed.add_field(name="Utilisateurs", value=", ".join(user_mentions) if user_mentions else "Aucun", inline=False)

    async def _category_callback(self, interaction: disnake.Interaction):
        modal = SetCategoryModal(self.bot, self.storage, self.config)
        await interaction.response.send_modal(modal)

    async def _log_channel_callback(self, interaction: disnake.Interaction):
        modal = SetChannelModal(self.bot, self.storage, self.config, "log")
        await interaction.response.send_modal(modal)


def _clamp_text(s: Any, max_len: int, default: str = "") -> str:
    """Discord TextInput constraints:
    - label: 1..45
    - placeholder: <=100
    """
    if s is None:
        return default
    s = str(s)
    if max_len <= 0:
        return ""
    if len(s) > max_len:
        s = s[:max_len]
    return s


class AddCategoryModal(disnake.ui.Modal):
    """Modal pour ajouter une catégorie"""
    def __init__(self, bot, storage, config: GuildTicketConfig):
        # label max 45, placeholder max 100
        name_label = _clamp_text("Nom", 45, "Nom")
        name_placeholder = _clamp_text("Ex: Support", 100, "")

        desc_label = _clamp_text("Description", 45, "Description")
        desc_placeholder = _clamp_text("Description...", 100, "")

        emoji_label = _clamp_text("Emoji", 45, "Emoji")
        emoji_placeholder = _clamp_text("🎫", 100, "🎫")

        recruitment_label = _clamp_text("Recrutement? (oui/non)", 45, "Recrutement?")
        recruitment_placeholder = _clamp_text("non", 100, "non")

        questions_label = _clamp_text(
            "Questions recrutement (+ optionnel: ID salon réponses)",
            45,
            "Questions recrutement"
        )
        # This was the most likely culprit: multi-line placeholder very long
        questions_placeholder = _clamp_text(
            "Quel âge avez-vous? / Pourquoi vouloir rejoindre? / Expériences? / Optionnel: ID salon réponses: 123456789012345678",
            100,
            ""
        )

        self.name_input = disnake.ui.TextInput(
            label=name_label,
            custom_id="add_cat_name",
            placeholder=name_placeholder,
            required=True,
        )

        self.desc_input = disnake.ui.TextInput(
            label=desc_label,
            custom_id="add_cat_desc",
            placeholder=desc_placeholder,
            required=False,
            style=disnake.TextInputStyle.paragraph,
        )

        self.emoji_input = disnake.ui.TextInput(
            label=emoji_label,
            custom_id="add_cat_emoji",
            placeholder=emoji_placeholder,
            required=False,
            max_length=5,
        )

        # Recruitment fields
        self.is_recruitment_input = disnake.ui.TextInput(
            label=recruitment_label,
            custom_id="add_cat_recruitment",
            placeholder=recruitment_placeholder,
            required=False,
            max_length=5,
        )

        # IMPORTANT:
        # Disnake Modal has a maximum number of components.
        # Keep this modal <= 5 components by merging "questions" + "responses channel id" into one input.
        self.questions_input = disnake.ui.TextInput(
            label=questions_label,  # <=45 enforced
            custom_id="add_cat_questions",
            placeholder=questions_placeholder,  # <=100 enforced
            required=False,
            style=disnake.TextInputStyle.paragraph,
        )

        super().__init__(
            title="Ajouter une Catégorie",
            components=[
                self.name_input,
                self.desc_input,
                self.emoji_input,
                self.is_recruitment_input,
                self.questions_input,
            ]
        )

        self.bot = bot
        self.storage = storage
        self.config = config

    async def callback(self, interaction: disnake.ModalInteraction):
        import uuid

        cat_id = str(uuid.uuid4())[:8]

        name_val = interaction.text_values.get("add_cat_name", "Nouvelle Catégorie")
        desc_val = interaction.text_values.get("add_cat_desc", "")
        emoji_val = interaction.text_values.get("add_cat_emoji", "🎫")
        is_recruitment_val = interaction.text_values.get("add_cat_recruitment", "non")
        questions_val = interaction.text_values.get("add_cat_questions", "")

        is_recruitment = is_recruitment_val.lower() in ["oui", "yes", "o", "y", "1", "true"]

        # Parse:
        # - questions are all non-empty lines
        # - if the LAST non-empty line is a digit-only id => treat it as responses_channel_id
        raw_lines = [ln.strip() for ln in (questions_val or "").split("\n") if ln.strip()]
        responses_channel_id = None

        if raw_lines:
            last = raw_lines[-1]
            if last.isdigit():
                # treat last line as channel id, remove it from questions
                responses_channel_id = int(last)
                raw_lines = raw_lines[:-1]

        questions = raw_lines if raw_lines else []

        category = TicketCategory(
            id=cat_id,
            name=name_val,
            description=desc_val,
            emoji=emoji_val,
            is_recruitment=is_recruitment,
            questions=questions,
            responses_channel_id=responses_channel_id if is_recruitment else None
        )

        self.storage.invalidate_cache(interaction.guild_id)
        config = self.storage.load_config(interaction.guild_id)
        config.categories.append(category)
        self.storage.save_config(interaction.guild_id, config)
        self.config = config

        recruitment_info = ""
        if is_recruitment:
            recruitment_info = f"\n✅ Mode recrutement activé avec {len(questions)} question(s)"
            if responses_channel_id:
                recruitment_info += f"\n📝 Salon réponses: <#{responses_channel_id}>"

        await interaction.response.defer(ephemeral=True)

        if interaction.message:
            try:
                manager_view = CategoryManagerView(self.bot, self.storage, config)
                await interaction.message.edit(components=manager_view.get_components_v2())
            except Exception as e:
                kuby_logger.warning(f"[AddCategoryModal] Impossible de rafraîchir la liste des catégories: {e}")

        await refresh_ticket_panel(self.bot, self.storage, interaction.guild_id)

        await interaction.followup.send(
            f"Catégorie **{name_val}** créée!{recruitment_info}",
            ephemeral=True
        )


class EditCategoryModal(disnake.ui.Modal):
    """Modal pour modifier une catégorie existante"""
    
    def __init__(self, bot, storage, config, category: TicketCategory, parent_view):
        self.name_input = disnake.ui.TextInput(label="Nom", custom_id="edit_cat_name", value=category.name, placeholder="Ex: Support", required=True)
        self.desc_input = disnake.ui.TextInput(label="Description", custom_id="edit_cat_desc", value=category.description, placeholder="Description...", required=False, style=disnake.TextInputStyle.paragraph)
        self.emoji_input = disnake.ui.TextInput(label="Emoji", custom_id="edit_cat_emoji", value=category.emoji, placeholder="🎫", required=False, max_length=5)
        super().__init__(title=f"Modifier: {category.name}", components=[self.name_input, self.desc_input, self.emoji_input])
        self.bot = bot
        self.storage = storage
        self.config = config
        self.category = category
        self.parent_view = parent_view

    async def callback(self, interaction: disnake.ModalInteraction):
        try:
            self.category.name = interaction.text_values.get("edit_cat_name", self.category.name)
            self.category.description = interaction.text_values.get("edit_cat_desc", "")
            self.category.emoji = interaction.text_values.get("edit_cat_emoji", "🎫")
            self.storage.save_config(interaction.guild_id, self.config)
            await refresh_ticket_panel(self.bot, self.storage, interaction.guild_id)
            
            container = disnake.ui.Container(
                disnake.ui.TextDisplay(content=f"✅ Catégorie **{self.category.name}** mise à jour !"),
                disnake.ui.Section("Navigation", accessory=disnake.ui.Button(label="Retour", style=disnake.ButtonStyle.secondary, custom_id=f"ticket_config_cat_manage_{self.category.id}"))
            )
            
            await interaction.response.edit_message(components=[container])
        except Exception as e:
            import traceback
            kuby_logger.error(f"[EditCategoryModal] Error: {e}\n{traceback.format_exc()}")
            if not interaction.response.is_done():
                await interaction.response.send_message(f"❌ Erreur critique : {e}", ephemeral=True)

class CategoryManagerView(disnake.ui.View):
    """Vue pour lister et gérer les catégories (Modernisée V2)"""
    def __init__(self, bot, storage, config: GuildTicketConfig):
        super().__init__(timeout=None)
        self.bot = bot
        self.storage = storage
        self.config = config

    def get_components_v2(self):
        sections = [
            disnake.ui.Section(
                "# 📂 Gestion des Catégories",
                accessory=disnake.ui.Button(label="➕ Ajouter", style=disnake.ButtonStyle.green, custom_id="ticket_config_cat_add")
            ),
            disnake.ui.Separator(divider=True)
        ]
        
        if not self.config.categories:
            sections.append(disnake.ui.TextDisplay(content="Aucune catégorie configurée."))
        else:
            for cat in self.config.categories:
                sections.append(
                    disnake.ui.Section(
                        f"{cat.emoji} **{cat.name}**\n*{cat.description or 'Pas de description'}*",
                        accessory=disnake.ui.Button(label="Gérer", style=disnake.ButtonStyle.secondary, custom_id=f"ticket_config_cat_manage_{cat.id}")
                    )
                )
        
        sections.append(disnake.ui.Separator(divider=True))
        sections.append(disnake.ui.Section(
            "Retour au menu principal", 
            accessory=disnake.ui.Button(label="⬅️ Retour", style=disnake.ButtonStyle.secondary, custom_id="ticket_config_main")
        ))
        
        return [disnake.ui.Container(*sections)]

    def _get_embed(self):
        embed = disnake.Embed(
            title="📂 Gestion des Catégories",
            description=f"Il y a actuellement **{len(self.config.categories)}** catégorie(s) configurée(s).\n\n"
                        "Sélectionnez une catégorie dans le menu pour la modifier ou la supprimer.",
            color=disnake.Color.blue()
        )
        
        if not self.config.categories:
            embed.description += "\n\n⚠️ **Aucune catégorie configurée.** Commencez par en ajouter une !"
            
        for cat in self.config.categories[:10]:
            embed.add_field(
                name=f"{cat.emoji} {cat.name}",
                value=cat.description or "Pas de description",
                inline=False
            )
            
        return embed

    async def _category_select_callback(self, interaction: disnake.Interaction):
        # En utilisant interaction.data.get('values') pour obtenir la valeur sélectionnée
        cat_id = interaction.data.get('values', [None])[0]
        category = self.config.get_category(cat_id)
        
        if not category:
            await interaction.response.send_message("Catégorie introuvable.", ephemeral=True)
            return
            
        view = CategoryActionView(self.bot, self.storage, self.config, category, self)
        await interaction.response.edit_message(embed=view._get_embed(), view=view)

    async def _add_cat_callback(self, interaction: disnake.Interaction):
        # Utilisation de la classe AddCategoryModal existante
        class AddCategoryManagerModal(AddCategoryModal):
            def __init__(self, bot, storage, config, parent_view):
                super().__init__(bot, storage, config)
                self.parent_view = parent_view
                
            async def callback(self, interaction: disnake.ModalInteraction):
                import uuid
                cat_id = str(uuid.uuid4())[:8]
                name_val = interaction.text_values.get("add_cat_name", "Nouvelle Catégorie")
                desc_val = interaction.text_values.get("add_cat_desc", "")
                emoji_val = interaction.text_values.get("add_cat_emoji", "🎫")
                
                category = TicketCategory(
                    id=cat_id,
                    name=name_val,
                    description=desc_val,
                    emoji=emoji_val
                )
                self.storage.invalidate_cache(interaction.guild_id)
                config = self.storage.load_config(interaction.guild_id)
                config.categories.append(category)
                self.storage.save_config(interaction.guild_id, config)
                self.config = config
                self.parent_view.config = config
                
                # Refresh parent view
                self.parent_view._add_category_select()
                await refresh_ticket_panel(self.bot, self.storage, interaction.guild_id)
                # On doit utiliser edit_message sur l'interaction d'origine ou renvoyer un embed
                await interaction.response.edit_message(
                    embed=self.parent_view._get_embed(),
                    view=self.parent_view
                )
                await interaction.followup.send(f"Catégorie **{name_val}** créée!", ephemeral=True)

        modal = AddCategoryManagerModal(self.bot, self.storage, self.config, self)
        await interaction.response.send_modal(modal)

    async def _back_callback(self, interaction: disnake.Interaction):
        view = ConfigPanelView(self.bot, self.storage, self.config)
        embed = disnake.Embed(
            title="Configuration des Tickets",
            description="Gérez la configuration du système de tickets",
            color=disnake.Color.blue()
        )
        await interaction.response.edit_message(embed=embed, view=view)


class CategoryActionView(disnake.ui.View):
    """Actions pour une catégorie spécifique"""
    
    def __init__(self, bot, storage, config, category, parent_view):
        super().__init__(timeout=None)
        self.bot = bot
        self.storage = storage
        self.config = config
        self.category = category
        self.parent_view = parent_view
        
    def _get_embed(self):
        embed = disnake.Embed(
            title=f"Gestion: {self.category.name}",
            description=f"**ID:** `{self.category.id}`\n"
                        f"**Emoji:** {self.category.emoji}\n"
                        f"**Description:** {self.category.description or 'Aucune'}\n"
                        f"**Couleur:** `{self.category.color}`\n"
                        f"**Staff Spécifique:** {', '.join([f'<@&{r}>' for r in self.category.staff_roles]) if self.category.staff_roles else 'Global (Tous les staffs)'}\n"
                        f"**Catégorie Discord:** {f'<#{self.category.discord_category_id}>' if self.category.discord_category_id else 'Par défaut'}",
            color=disnake.Color.blue()
        )
        return embed

    @disnake.ui.button(label="📝 Modifier", style=disnake.ButtonStyle.primary)
    async def edit_callback(self, interaction: disnake.MessageInteraction):
        modal = EditCategoryModal(self.bot, self.storage, self.config, self.category, self)
        await interaction.response.send_modal(modal)

    @disnake.ui.button(label="🛡️ Permissions", style=disnake.ButtonStyle.success)
    async def permissions_callback(self, interaction: disnake.MessageInteraction):
        view = CategoryPermissionsView(self.bot, self.storage, self.config, self.category, self)
        embed = disnake.Embed(
            title=f"Permissions: {self.category.name}",
            description="Choisissez les rôles qui auront accès à cette catégorie de tickets.\n\n"
                        "⚠️ **Si vous sélectionnez des rôles ici, seuls ces rôles (et les admins) auront accès aux tickets de cette catégorie.** "
                        "Les rôles staff globaux n'y auront plus accès.",
            color=disnake.Color.green()
        )
        await interaction.response.edit_message(embed=embed, view=view)

    @disnake.ui.button(label="📁 Catégorie Discord", style=disnake.ButtonStyle.secondary)
    async def discord_category_callback(self, interaction: disnake.MessageInteraction):
        view = CategoryDiscordCategoryView(self.bot, self.storage, self.config, self.category, self)
        embed = disnake.Embed(
            title=f"Catégorie Discord: {self.category.name}",
            description="Choisissez la catégorie Discord (dossier) dans laquelle les tickets de ce type seront créés.\n\n"
                        "Si aucune n'est sélectionnée, la catégorie par défaut du serveur sera utilisée.",
            color=disnake.Color.blue()
        )
        await interaction.response.edit_message(embed=embed, view=view)

    @disnake.ui.button(label="🗑️ Supprimer", style=disnake.ButtonStyle.danger)
    async def delete_callback(self, interaction: disnake.MessageInteraction):
        view = DeleteCategoryConfirmationView(self.bot, self.storage, self.config, self.category, self.parent_view)
        embed = disnake.Embed(
            title="⚠️ Confirmer la suppression",
            description=f"Êtes-vous sûr de vouloir supprimer la catégorie **{self.category.name}** ?\n"
                        "Cette action est irréversible.",
            color=disnake.Color.red()
        )
        await interaction.response.edit_message(embed=embed, view=view)

    @disnake.ui.button(label="⬅️ Retour", style=disnake.ButtonStyle.secondary)
    async def back_callback(self, interaction: disnake.MessageInteraction):
        self.parent_view._add_category_select()
        await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view)

class CategoryPermissionsView(disnake.ui.View):
    """Vue pour configurer les rôles staff d'une catégorie"""
    
    def __init__(self, bot, storage, config, category, parent_view):
        super().__init__(timeout=None)
        self.bot = bot
        self.storage = storage
        self.config = config
        self.category = category
        self.parent_view = parent_view

    @disnake.ui.select(cls=disnake.ui.RoleSelect, placeholder="Sélectionner les rôles staff (Gestion)", min_values=0, max_values=10)
    async def role_select_callback(self, interaction: disnake.MessageInteraction):
        role_ids = [str(role.id) for role in interaction.values]
        self.category.staff_roles = role_ids
        self.storage.save_config(interaction.guild_id, self.config)
        
        # Refresh embed
        await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view)

    @disnake.ui.button(label="⬅️ Annuler", style=disnake.ButtonStyle.secondary)
    async def back_callback(self, interaction: disnake.MessageInteraction):
        await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view)

class CategoryDiscordCategoryView(disnake.ui.View):
    """Vue pour configurer la catégorie Discord d'une catégorie de ticket"""
    
    def __init__(self, bot, storage, config, category, parent_view):
        super().__init__(timeout=None)
        self.bot = bot
        self.storage = storage
        self.config = config
        self.category = category
        self.parent_view = parent_view

    @disnake.ui.select(cls=disnake.ui.ChannelSelect, channel_types=[disnake.ChannelType.category], placeholder="Sélectionner la catégorie Discord")
    async def select_callback(self, interaction: disnake.MessageInteraction):
        val = interaction.values[0]
        self.category.discord_category_id = val.id if hasattr(val, 'id') else int(val)
        self.storage.save_config(interaction.guild_id, self.config)
        await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view)

    @disnake.ui.button(label="❌ Réinitialiser", style=disnake.ButtonStyle.danger)
    async def reset_callback(self, interaction: disnake.MessageInteraction):
        self.category.discord_category_id = None
        self.storage.save_config(interaction.guild_id, self.config)
        await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view)
        
    @disnake.ui.button(label="⬅️ Retour", style=disnake.ButtonStyle.secondary)
    async def back_callback(self, interaction: disnake.MessageInteraction):
        await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view)


class DeleteCategoryConfirmationView(disnake.ui.View):
    def __init__(self, bot, storage, config, category, parent_view):
        super().__init__(timeout=None)
        self.bot = bot
        self.storage = storage
        self.config = config
        self.category = category
        self.parent_view = parent_view

    @disnake.ui.button(label="✅ Confirmer", style=disnake.ButtonStyle.danger)
    async def confirm_callback(self, interaction: disnake.MessageInteraction):
        if self.category in self.config.categories:
            self.config.categories.remove(self.category)
            self.storage.save_config(interaction.guild_id, self.config)
            await refresh_ticket_panel(self.bot, self.storage, interaction.guild_id)
            
        self.parent_view._add_category_select()
        await interaction.response.edit_message(
            embed=self.parent_view._get_embed(), 
            view=self.parent_view
        )
        await interaction.followup.send(f"✅ Catégorie **{self.category.name}** supprimée.", ephemeral=True)

    @disnake.ui.button(label="❌ Annuler", style=disnake.ButtonStyle.secondary)
    async def cancel_callback(self, interaction: disnake.MessageInteraction):
        view = CategoryActionView(self.bot, self.storage, self.config, self.category, self.parent_view)
        await interaction.response.edit_message(embed=view._get_embed(), view=view)


class TicketChannelConfigView(disnake.ui.View):
    """Vue moderne pour configurer un salon via un sélecteur (V2)"""
    def __init__(self, bot, storage, config: GuildTicketConfig, channel_type: str = "log"):
        super().__init__(timeout=None)
        self.bot = bot
        self.storage = storage
        self.config = config
        self.channel_type = channel_type

        # Sélecteur de salon
        # (Fix NameError: c_types doit être défini avant d'être utilisé)
        c_types = [disnake.ChannelType.text] if self.channel_type == "log" else [disnake.ChannelType.category]

        select = disnake.ui.ChannelSelect(
            placeholder="Sélectionnez la catégorie dans la liste...",
            channel_types=c_types,
            min_values=1,
            max_values=1,
            custom_id="ticket_config_chan_select"
        )
        # Utiliser uniquement l'instance 'select' (elle sera incluse dans get_components_v2)
        self.add_item(select)

    def get_components_v2(self):
        title = "📝 Configuration des Logs" if self.channel_type == "log" else "📁 Catégorie de Destination"
        desc = "Choisissez la catégorie où les tickets seront créés." if self.channel_type == "log" else "Choisissez la catégorie où les tickets seront créés."
        
        sections = [
            disnake.ui.TextDisplay(content=f"# {title}\n{desc}"),
            disnake.ui.Separator(divider=True),
            disnake.ui.TextDisplay(content="Utilisez le menu déroulant ci-dessous pour choisir un salon."),
            disnake.ui.Separator(divider=True),
            disnake.ui.Section("Navigation", accessory=disnake.ui.Button(label="⬅️ Retour", style=disnake.ButtonStyle.secondary, custom_id="ticket_config_main"))
        ]
        
        c_types = [disnake.ChannelType.text] if self.channel_type == "log" else [disnake.ChannelType.category]
        
        # Include the select menu in the components list
        return [disnake.ui.Container(*sections), disnake.ui.ChannelSelect(placeholder="Choisir une catégorie...", channel_types=c_types, custom_id=f"ticket_config_select_{self.channel_type}")]

    async def _select_callback(self, inter: disnake.MessageInteraction):
        print(f"DEBUG: _select_callback called. Values: {inter.values}")
        try:
            val = inter.values[0]
            channel_id = val.id if hasattr(val, "id") else int(val)
            print(f"DEBUG: channel_id determined: {channel_id}")
            
            if self.channel_type == "log":
                self.config.log_channel_id = channel_id
            elif self.channel_type.startswith("cat_"):
                cat_id = self.channel_type.replace("cat_", "")
                category = next((c for c in self.config.categories if c.id == cat_id), None)
                if category:
                    category.discord_category_id = channel_id
            
            self.storage.save_config(inter.guild_id, self.config)
            print(f"DEBUG: Config saved for guild {inter.guild_id}")
            
            # Success message V2
            container = disnake.ui.Container(
                disnake.ui.TextDisplay(content=f"✅ Salon/Catégorie configuré(e) sur <#{channel_id}>"),
                disnake.ui.Section("Navigation", accessory=disnake.ui.Button(label="Retour", style=disnake.ButtonStyle.secondary, custom_id="ticket_config_main"))
            )
            print("DEBUG: Sending success message...")
            await inter.response.edit_message(components=[container])
            print("DEBUG: Success message sent.")
        except Exception as e:
            print(f"DEBUG ERROR in _select_callback: {e}")
            import traceback
            traceback.print_exc()
            kuby_logger.error(f"❌ Erreur dans _select_callback: {e}", exc_info=True)
            try:
                await inter.response.send_message(f"❌ Erreur lors de la configuration du salon : {e}", ephemeral=True)
            except:
                pass

class SetChannelModal(disnake.ui.Modal):

    async def callback(self, interaction: disnake.ModalInteraction):
        try:
            channel_id = int(self.channel_input.value.strip())
            channel = interaction.guild.get_channel(channel_id)
            if not channel:
                await interaction.response.send_message("❌ Canal introuvable.", ephemeral=True)
                return
            
            self.config.log_channel_id = channel_id
            self.storage.save_config(interaction.guild_id, self.config)
            
            embed = disnake.Embed(
                title="✅ Canal de Logs Mis à Jour",
                description=f"Le canal de logs a été défini sur {channel.mention}.",
                color=disnake.Color.green()
            )
            await interaction.response.send_message(embed=embed, ephemeral=True)
        except ValueError:
            await interaction.response.send_message("❌ ID invalide.", ephemeral=True)


class StaffRolesModal(disnake.ui.Modal):
    """Modal pour ajouter/supprimer les rôles staff"""
    
    def __init__(self, bot, storage, config: GuildTicketConfig):
        super().__init__(title="Gérer les Rôles Staff")
        self.bot = bot
        self.storage = storage
        self.config = config
        
        # Champ pour entrer les rôles (mention ou ID)
        self.roles = disnake.ui.TextInput(
            label="Rôles Staff (mention ou ID)",
            placeholder="@Staff @Modérateurs ou 123456789 987654321",
            required=True,
            max_length=500
        )
        self.add_item(self.roles)
    
    async def callback(self, interaction: disnake.ModalInteraction):
        try:
            role_input = self.roles.value.strip()
            role_ids = []

            # Parse role mentions (@role) or IDs
            import re
            # Match <@&ROLE_ID> mentions
            mention_matches = re.findall(r'<@&(\d+)>', role_input)
            # Match raw numbers (IDs)
            id_matches = re.findall(r'\b(\d{17,20})\b', role_input)

            all_ids = set(mention_matches + id_matches)

            if not all_ids:
                await interaction.response.send_message(
                    "❌ Format invalide. Utilisez les mentions (@Rôle) ou les IDs.",
                    ephemeral=True
                )
                return

            # Validate roles exist
            valid_roles = []
            for role_id in all_ids:
                role = interaction.guild.get_role(int(role_id))
                if role:
                    valid_roles.append(role)
                else:
                    await interaction.response.send_message(
                        f"❌ Le rôle {role_id} n'existe pas sur ce serveur.",
                        ephemeral=True
                    )
                    return

            # Update config
            self.config.staff_roles = list(all_ids)
            self.storage.save_config(interaction.guild_id, self.config)

            # Build success embed
            embed = disnake.Embed(
                title="✅ Rôles Staff Mis à Jour",
                description=f"**{len(valid_roles)}** rôle(s) configuré(s) comme staff.",
                color=disnake.Color.green()
            )

            role_mentions = [role.mention for role in valid_roles]
            embed.add_field(
                name="Rôles Staff",
                value=", ".join(role_mentions) if role_mentions else "Aucun",
                inline=False
            )

            embed.add_field(
                name="Permissions",
                value="• Gérer les tickets\n• Accéder aux analytics\n• Générer des transcripts\n• Fermer les tickets des autres",
                inline=False
            )

            await interaction.response.send_message(embed=embed, ephemeral=True)
        except Exception as e:
            await interaction.response.send_message(f"Erreur lors de la configuration des rôles: {e}", ephemeral=True)

    async def on_error(self, interaction: disnake.Interaction, error: Exception):
        """Handle errors in the modal"""
        await interaction.response.send_message("Une erreur inattendue s'est produite lors de la configuration des rôles.", ephemeral=True)


class PermissionConfigView(disnake.ui.View):
    """View to configure permissions (Staff/Config Roles & Users)"""

    def __init__(self, bot, storage, config: GuildTicketConfig, guild: disnake.Guild, config_type: str = "staff"):
        super().__init__(timeout=None)
        self.bot = bot
        self.storage = storage
        self.config = config
        self.guild = guild
        self.config_type = config_type # "staff" or "config"
        
        # Add components
        self._add_role_select()
        self._add_user_select()
        self._add_remove_select()
    
    def _add_role_select(self):
        select = disnake.ui.RoleSelect(
            placeholder=f"Sélectionnez les rôles {self.config_type}",
            min_values=0,
            max_values=25,
            row=0,
            custom_id=f"ticket_config_perms_role_{self.config_type}"
        )
        select.callback = self._role_callback
        self.add_item(select)
        
    def _add_user_select(self):
        select = disnake.ui.UserSelect(
            placeholder=f"Sélectionnez les utilisateurs {self.config_type}",
            min_values=0,
            max_values=25,
            row=1,
            custom_id=f"ticket_config_perms_user_{self.config_type}"
        )
        select.callback = self._user_callback
        self.add_item(select)
        
    async def _role_callback(self, interaction: disnake.Interaction):
        try:
            # Determine which list to update
            if self.config_type == "staff":
                target_list = self.config.staff_roles
            elif self.config_type == "config":
                target_list = self.config.config_roles
            else:
                # Category-specific staff roles (config_type = "cat_{cat_id}")
                cat_id = self.config_type.replace("cat_", "")
                category = self.config.get_category(cat_id)
                if category:
                    target_list = category.staff_roles
                else:
                    await interaction.response.send_message("Catégorie introuvable.", ephemeral=True)
                    return

            # Get new selected IDs
            new_ids = interaction.data.get('values', [])
            added_count = 0

            for new_id in new_ids:
                if new_id not in target_list:
                    target_list.append(new_id)
                    added_count += 1

            self.storage.save_config(interaction.guild_id, self.config)

            
            await self._refresh_view(interaction)
        except Exception as e:
            # Assuming kuby_logger is defined elsewhere, if not, replace with print or logging.error
            # import kuby_logger # Uncomment if kuby_logger is not globally available
            # kuby_logger.error(f"Error in _role_callback: {e}")
            import traceback
            traceback.print_exc()
            await interaction.response.send_message("Une erreur est survenue lors de la mise à jour des rôles.", ephemeral=True)

    async def _user_callback(self, interaction: disnake.Interaction):
        try:
            if self.config_type == "staff":
                target_list = self.config.staff_users
            elif self.config_type == "config":
                target_list = self.config.config_users
            else:
                # Category-specific staff - users not separately managed per category, use global staff_users
                target_list = self.config.staff_users

            new_ids = [int(uid) for uid in interaction.data.get('values', [])]
            added_count = 0

            for new_id in new_ids:
                if new_id not in target_list:
                    target_list.append(new_id)
                    added_count += 1

            self.storage.save_config(interaction.guild_id, self.config)

            
            await self._refresh_view(interaction)
        except Exception as e:
            # Assuming kuby_logger is defined elsewhere
            # import kuby_logger
            # kuby_logger.error(f"Error in _user_callback: {e}")
            import traceback
            traceback.print_exc()
            await interaction.response.send_message("Une erreur est survenue lors de la mise à jour des utilisateurs.", ephemeral=True)

    async def _refresh_view(self, interaction):
        self.clear_items()
        self._add_role_select()
        self._add_user_select()
        self._add_remove_select()
        await interaction.response.edit_message(components=self.get_components_v2())

    def get_components_v2(self):
        if self.config_type == "staff":
            title = "🛡️ Permissions Staff"
            desc = "Configurez qui peut gérer les tickets."
        elif self.config_type == "config":
            title = "⚙️ Permissions Config"
            desc = "Configurez qui peut modifier les paramètres."
        else:
            # Category-specific staff roles
            cat_id = self.config_type.replace("cat_", "")
            category = self.config.get_category(cat_id)
            cat_name = category.name if category else f"Catégorie {cat_id}"
            title = f"🛡️ Permissions Staff - {cat_name}"
            desc = f"Configurez les rôles staff ayant accès à la catégorie '{cat_name}'."

        sections = [
            disnake.ui.TextDisplay(content=f"# {title}\n{desc}"),
            disnake.ui.Separator(divider=True),
            disnake.ui.TextDisplay(content="Utilisez les sélecteurs ci-dessous pour modifier les permissions."),
            disnake.ui.Separator(divider=True),
            disnake.ui.Section("Navigation", accessory=disnake.ui.Button(label="⬅️ Retour", style=disnake.ButtonStyle.secondary, custom_id="ticket_config_main"))
        ]
        
        # Combine container with view children (RoleSelect, UserSelect, etc.)
        return [disnake.ui.Container(*sections)] + self.children

    def _add_remove_select(self):
        # Build options from current config
        options = []

        if self.config_type == "staff":
            roles = self.config.staff_roles
            users = self.config.staff_users
        elif self.config_type == "config":
            roles = self.config.config_roles
            users = self.config.config_users
        else:
            # Category-specific staff roles
            cat_id = self.config_type.replace("cat_", "")
            category = self.config.get_category(cat_id)
            if category:
                roles = category.staff_roles
            else:
                roles = []
            users = self.config.staff_users  # Category uses global staff_users

        # Limit to 25 items for removal dropdown (API Limit)
        # If more, we might need pagination or just show first 25.

        count = 0
        for r_id in roles:
            if count >= 25: break
            role = self.guild.get_role(int(r_id))
            name = role.name if role else f"Role {r_id}"
            options.append(disnake.SelectOption(
                label=f"Role: {name[:90]}",
                value=f"r_{r_id}",
                emoji="🛡️",
                description="Cliquez pour retirer"
            ))
            count += 1

        for u_id in users:
            if count >= 25: break
            user = self.guild.get_member(u_id)
            name = user.display_name if user else f"User {u_id}"
            options.append(disnake.SelectOption(
                label=f"User: {name[:90]}",
                value=f"u_{u_id}",
                emoji="👤",
                description="Cliquez pour retirer"
            ))
            count += 1

        if options:
            select = disnake.ui.Select(
                placeholder="Sélectionnez pour RETIRER des permissions...",
                options=options,
                min_values=1,
                max_values=min(len(options), 25),
                row=2,
                custom_id=f"ticket_config_perms_remove_{self.config_type}"
            )
            select.callback = self._remove_callback
            self.add_item(select)
            
    async def _remove_callback(self, interaction: disnake.Interaction):
        try:
            values = interaction.data.get('values', [])

            removed_count = 0
            for val in values:
                prefix, id_str = val.split('_')
                id_val = int(id_str)

                if prefix == 'r':  # Role
                    if self.config_type == "staff":
                        target_list = self.config.staff_roles
                    elif self.config_type == "config":
                        target_list = self.config.config_roles
                    else:
                        # Category-specific staff roles
                        cat_id = self.config_type.replace("cat_", "")
                        category = self.config.get_category(cat_id)
                        if category:
                            target_list = category.staff_roles
                        else:
                            continue
                    if str(id_val) in target_list:
                        target_list.remove(str(id_val))
                        removed_count += 1
                elif prefix == 'u':  # User
                    if self.config_type == "staff":
                        target_list = self.config.staff_users
                    elif self.config_type == "config":
                        target_list = self.config.config_users
                    else:
                        # Category-specific staff uses global staff_users
                        target_list = self.config.staff_users
                    if id_val in target_list:
                        target_list.remove(id_val)
                        removed_count += 1

            self.storage.save_config(interaction.guild_id, self.config)

            
            await self._refresh_view(interaction)
        except Exception as e:
            # Assuming kuby_logger is defined elsewhere
            # import kuby_logger
            # kuby_logger.error(f"Error in _remove_callback: {e}")
            import traceback
            traceback.print_exc()
            await interaction.response.send_message("Une erreur est survenue lors de la suppression des permissions.", ephemeral=True)


class TicketCommands(commands.Cog):
    """Cog principal des tickets"""

    def __init__(self, bot):
        self.bot = bot
        self.storage = get_storage()

    async def cog_unload(self):
        pass

    async def cog_load(self):
        await self._register_all_views()
    
    @commands.Cog.listener()
    async def on_interaction(self, inter: disnake.Interaction):
        """Gère les interactions des composants V2 (Container/Section)"""
        # Logs ultra détaillés pour tracer "interaction failed" / mauvais payload
        try:
            inter_data = getattr(inter, "data", None)
            inter_custom_id = getattr(inter_data, "custom_id", None) if inter_data else None
            inter_values = getattr(inter_data, "values", None) if inter_data else None
        except Exception:
            inter_custom_id = None
            inter_values = None

        kuby_logger.info(
            f"[TicketSystem] on_interaction: type={getattr(inter, 'type', None)} "
            f"custom_id={inter_custom_id} values={inter_values} "
            f"guild_id={getattr(getattr(inter,'guild',None),'id',None)} channel_id={getattr(getattr(inter,'channel',None),'id',None)}"
        )

        if inter.type != disnake.InteractionType.component:
            return

        if not inter.guild:
            return

        custom_id = inter.data.custom_id
        # Debug: savoir si on rentre dans le handler ticket_*
        kuby_logger.info(f"[TicketSystem] Routing check: custom_id={custom_id} startswith 'ticket_'? {bool(custom_id and str(custom_id).startswith('ticket_'))}")

        if not custom_id:
            return

        # Accept only ticket_* / catv2_* / cat_* / close_* custom_ids
        # reports_filter_* are intentionally excluded — handled by ReportsFilterView callbacks
        if not (str(custom_id).startswith("ticket_")
            or str(custom_id).startswith("catv2_")
            or str(custom_id).startswith("cat_")
            or str(custom_id) in {"close_confirm", "close_cancel", "delay_cancel"}):
            return



        if not inter.guild:
            return

        custom_id = inter.data.custom_id

        try:
            # On récupère la config
            config = self.storage.load_config(inter.guild.id)
            if not config:
                kuby_logger.warning(f"[TicketSystem] No config for guild_id={inter.guild.id}")
                return
        except Exception as e:
            kuby_logger.error(f"[TicketSystem] Failed to load config: {e}", exc_info=True)
            if not inter.response.is_done():
                await inter.response.send_message("Erreur interne (config)", ephemeral=True)
            return

        try:

            # Routage manuel
            # ✅ BUGFIX: Sélection de catégorie en V2 => custom_id = catv2_{cat.id}_{snowflake}
            if custom_id.startswith("catv2_"):
                try:
                    parts = custom_id.split("_")
                    cat_id = "_".join(parts[1:-1]) if len(parts) >= 3 else (parts[1] if len(parts) > 1 else None)
                    await open_ticket_for_category(self.bot, inter, config, cat_id)
                except Exception as e:
                    kuby_logger.error(f"[TicketSystem] catv2_ routing error: {e}", exc_info=True)
                    if not inter.response.is_done():
                        await inter.response.send_message(f"Erreur interne: {e}", ephemeral=True)
            
            # backward compatibility (legacy cat_<id>_<suffix>)

            elif custom_id.startswith("cat_"):
                try:
                    parts = str(custom_id).split("_")
                    cat_id = "_".join(parts[1:-1]) if len(parts) >= 3 else (parts[1] if len(parts) > 1 else None)
                    await open_ticket_for_category(self.bot, inter, config, cat_id)
                except Exception as e:
                    kuby_logger.error(f"[TicketSystem] cat_ routing error: {e}", exc_info=True)
                    if not inter.response.is_done():
                        await inter.response.send_message(f"Erreur interne: {e}", ephemeral=True)

            elif custom_id == "ticket_create":
                # Debug stockage/cache (pour diagnostiquer "catégorie non trouvée")
                try:
                    kuby_logger.info(f"[TicketSystem][ticket_create] guild.id={getattr(inter.guild,'id',None)} guild_id={getattr(inter,'guild_id',None)}")
                except Exception:
                    pass

                self.storage.invalidate_cache(inter.guild.id)
                config = self.storage.load_config(inter.guild.id)

                try:
                    cats = getattr(config, "categories", None) or []
                    # Affiche aussi des infos pour diagnostiquer le storage
                    storage_obj = self.storage
                    config_path = getattr(storage_obj, "config_path", None)
                    kuby_logger.info(
                        f"[TicketSystem][ticket_create] storage.config_path={config_path} "
                        f"guild_id={inter.guild.id} "
                        f"categories_len={len(cats)} categories_ids={[getattr(c,'id',None) for c in cats[:5]]}"
                    )
                except Exception:
                    pass

                if not config.categories:
                    # Au cas où cache désynchronisé => invalider aussi avec guild_id
                    try:
                        self.storage.invalidate_cache(inter.guild_id)
                        config = self.storage.load_config(inter.guild_id)
                        cats = getattr(config, "categories", None) or []
                        kuby_logger.info(f"[TicketSystem][ticket_create] (after invalidate guild_id) categories_len={len(cats)}")
                    except Exception:
                        pass

                    if not config.categories:
                        await inter.response.send_message(
                            "Aucune catégorie configurée (data/tickets/config.json semble vide pour cette guild). "
                            "Allez dans la configuration et ajoutez au moins 1 catégorie.",
                            ephemeral=True
                        )
                        return

                components = build_category_selection_components(config)
                try:
                    await inter.response.send_message(
                        components=components,
                        flags=disnake.MessageFlags(is_components_v2=True),
                        ephemeral=True
                    )
                except Exception as e:
                    kuby_logger.error(f"[TicketSystem] ticket_create UI error: {e}", exc_info=True)
                    if inter.response.is_done():
                        await inter.followup.send(
                            "**🎫 Créer un Ticket**\n\nSélectionnez une catégorie ci-dessous:",
                            view=CategorySelectionView(self.bot, config),
                            ephemeral=True
                        )
                    else:
                        await inter.response.send_message(
                            "**🎫 Créer un Ticket**\n\nSélectionnez une catégorie ci-dessous:",
                            view=CategorySelectionView(self.bot, config),
                            ephemeral=True
                        )

            elif custom_id == "ticket_category_select":
                self.storage.invalidate_cache(inter.guild.id)
                config = self.storage.load_config(inter.guild.id)
                cat_id = inter.values[0] if inter.values else None
                if not cat_id:
                    await inter.response.send_message("Catégorie non trouvée.", ephemeral=True)
                    return
                await open_ticket_for_category(self.bot, inter, config, cat_id)
            elif custom_id == "ticket_my_tickets":
                # Afficher les tickets de l'utilisateur
                user_tickets = self.storage.get_user_tickets(inter.guild.id, inter.user.id)
                if not user_tickets:
                    await inter.response.send_message("Vous n'avez aucun ticket ouvert.", ephemeral=True)
                    return
                
                # Créer un message avec la liste des tickets
                ticket_list = []
                for ticket in user_tickets:
                    channel = self.bot.get_channel(ticket.channel_id)
                    if channel:
                        ticket_list.append(f"• {channel.mention} - {ticket.status.value}")
                    else:
                        ticket_list.append(f"• #{ticket.channel_id} - {ticket.status.value}")
                
                embed = disnake.Embed(
                    title="📂 Vos Tickets",
                    description="\n".join(ticket_list) if ticket_list else "Aucun ticket trouvé.",
                    color=0x9B59B6
                )
                await inter.response.send_message(embed=embed, ephemeral=True)
            elif custom_id == "ticket_analytics":
                panel_view = TicketPanelView(self.bot, config, self.storage)
                await panel_view._analytics_callback(inter)
            elif custom_id == "ticket_claim":
                ticket = self.storage.load_ticket(inter.channel.id)
                if ticket:
                    view = TicketManagementView(self.bot, self.storage, config, ticket)
                    await view._claim_callback(inter)
            elif custom_id == "ticket_unclaim":
                ticket = self.storage.load_ticket(inter.channel.id)
                if ticket:
                    view = TicketManagementView(self.bot, self.storage, config, ticket)
                    await view._unclaim_callback(inter)
            elif custom_id == "ticket_close":
                ticket = self.storage.load_ticket(inter.channel.id)
                if ticket:
                    view = TicketManagementView(self.bot, self.storage, config, ticket)
                    await view._close_callback(inter)
            elif custom_id == "ticket_reopen":
                ticket = self.storage.load_ticket(inter.channel.id)
                if ticket:
                    view = TicketManagementView(self.bot, self.storage, config, ticket)
                    await view._reopen_callback(inter)
            elif custom_id == "ticket_transcript":
                ticket = self.storage.load_ticket(inter.channel.id)
                if ticket:
                    view = TicketManagementView(self.bot, self.storage, config, ticket)
                    await view._transcript_callback(inter)

            # === Recrutement: Fermeture avec Accepté/Refusé ===
            elif custom_id == "recruitment_close":
                ticket = self.storage.load_ticket(inter.channel.id)
                if not ticket:
                    await inter.response.send_message("Ticket non trouvé.", ephemeral=True)
                    return

                category = config.get_category(ticket.category_id)
                if not category or not category.is_recruitment:
                    # Si pas un ticket recrutement, utiliser la fermeture normale
                    view = TicketManagementView(self.bot, self.storage, config, ticket)
                    await view._close_callback(inter)
                    return

                # Vérifier que c'est du staff
                # Règle voulue:
                # - Staff: toujours autorisé
                # - Joueur: uniquement si le ticket n'est pas encore pris en charge (OPEN)
                # Ici on impose strictement staff (le bouton est staff dans la UI),
                # et on protège aussi contre un éventuel usage côté user.
                if (
                    not is_staff(inter, config, category) and not (
                        inter.user.id == ticket.user_id and ticket.status == TicketStatus.OPEN
                    )
                ):
                    await inter.response.send_message("Réservé au staff.", ephemeral=True)
                    return

                await inter.response.send_message(
                    "Choisissez la décision pour cette candidature:",
                    components=[
                        disnake.ui.Container(
                            disnake.ui.Section(
                                "# 🎓 Décision de Recrutement",
                                accessory=disnake.ui.Button(
                                    label="✅ Accepter",
                                    style=disnake.ButtonStyle.green,
                                    custom_id="recruitment_accept"
                                )
                            ),
                            disnake.ui.Separator(divider=True),
                            disnake.ui.Section(
                                "Refuser la candidature",
                                accessory=disnake.ui.Button(
                                    label="❌ Refuser",
                                    style=disnake.ButtonStyle.red,
                                    custom_id="recruitment_refuse"
                                )
                            )
                        )
                    ],
                    flags=disnake.MessageFlags(is_components_v2=True),
                    ephemeral=True
                )

            elif custom_id == "recruitment_accept":
                ticket = self.storage.load_ticket(inter.channel.id) if inter.channel else None
                if not ticket:
                    await inter.response.send_message("Ticket non trouvé.", ephemeral=True)
                    return

                category = config.get_category(ticket.category_id)
                if not is_staff(inter, config, category):
                    await inter.response.send_message("Réservé au staff.", ephemeral=True)
                    return

                await inter.response.defer()
                await self._send_recruitment_decision(inter, ticket, category, accepted=True)

            elif custom_id == "recruitment_refuse":
                ticket = self.storage.load_ticket(inter.channel.id) if inter.channel else None
                if not ticket:
                    await inter.response.send_message("Ticket non trouvé.", ephemeral=True)
                    return

                category = config.get_category(ticket.category_id)
                if not is_staff(inter, config, category):
                    await inter.response.send_message("Réservé au staff.", ephemeral=True)
                    return

                await inter.response.defer()
                await self._send_recruitment_decision(inter, ticket, category, accepted=False)

            # === Fermeture via composants V2 (sans view=, donc custom_id) ===
            
            elif custom_id.startswith("reports_filter_"):
                # No direct response; let the view's callback handle the interaction
                return


            elif custom_id == "close_confirm":
                try:
                    kuby_logger.info(f"[TicketSystem] close_confirm clicked channel={getattr(inter.channel,'id',None)} user={inter.user.id if inter.user else None}")

                    # Permission check identique à CloseConfirmationView.confirm_callback
                    ticket = self.storage.load_ticket(inter.channel.id) if inter.channel else None
                    if not ticket:
                        await inter.response.send_message("Ticket non trouvé.", ephemeral=True)
                        return

                    # Règle voulue:
                    # - Staff: toujours autorisé
                    # - Joueur: uniquement si le ticket n'est pas encore pris en charge (OPEN)
                    can_close = (
                        is_staff(inter, config, config.get_category(ticket.category_id)) or (
                            inter.user.id == ticket.user_id and ticket.status == TicketStatus.OPEN
                        )
                    )
                    if not can_close:
                        await inter.response.send_message("Vous ne pouvez pas fermer ce ticket.", ephemeral=True)
                        return

                    await inter.response.defer()

                    close_view = CloseDelayView(self.bot, self.storage, config, ticket)

                    # Compte à rebours dans le CHANNEL (pas sur l'ephemeral), puis fermeture.
                    steps = [
                        ("⏳ Préparation...", 1),
                        ("📄 Génération du transcript...", 1),
                        ("📝 Sauvegarde des données...", 1),
                        ("🗑️ Suppression du canal...", 1)
                    ]

                    channel = inter.channel
                    if not channel:
                        await inter.followup.send("Canal introuvable.", ephemeral=True)
                        return

                    countdown_msg = await channel.send(
                        embed=disnake.Embed(
                            title="🔒 Fermeture du Ticket",
                            description="**⏳ Préparation...**\n\nMerci pour votre confiance!",
                            color=disnake.Color.orange()
                        )
                    )

                    for step_text, delay in steps:
                        embed = disnake.Embed(
                            title="🔒 Fermeture du Ticket",
                            description=f"**{step_text}**\n\nMerci pour votre confiance!",
                            color=disnake.Color.orange()
                        )
                        embed.set_footer(text=f"Ticket #{ticket.channel_id}")
                        await countdown_msg.edit(embed=embed)
                        await asyncio.sleep(delay)

                    await close_view._close_ticket(inter)

                    try:
                        await countdown_msg.delete()
                    except Exception:
                        pass

                except Exception as e:
                    kuby_logger.error(f"[TicketSystem] close_confirm failed: {e}", exc_info=True)
                    if not inter.response.is_done():
                        await inter.response.send_message("❌ Échec de la fermeture (voir logs).", ephemeral=True)

            # Configuration
            elif custom_id == "ticket_config_main":
                await self.ticket_config(inter)
            elif custom_id == "ticket_config_toggle":
                config.enabled = not config.enabled
                self.storage.save_config(inter.guild.id, config)
                await self.ticket_config(inter)
            elif custom_id == "ticket_config_manage_cat":
                view = CategoryManagerView(self.bot, self.storage, config)
                await inter.response.edit_message(components=view.get_components_v2())
            elif custom_id == "ticket_config_cat_add":
                modal = AddCategoryModal(self.bot, self.storage, config)
                await inter.response.send_modal(modal)
            elif custom_id.startswith("ticket_config_cat_manage_"):
                # Parser cat_id robuste (cat_id peut contenir '_' => pas de replace)
                parts = str(custom_id).split("_")
                # ticket_config_cat_manage_<cat_id...>
                cat_id = "_".join(parts[4:]) if len(parts) > 4 else ""

                category = next(
                    (c for c in (config.categories if config else []) if str(c.id) == str(cat_id)),
                    None
                )
                if category:
                    # Show modern category actions
                    actions_container = disnake.ui.Container(
                        disnake.ui.TextDisplay(content=f"# 🛠️ Gérer: {category.name}"),
                        disnake.ui.Separator(divider=True),
                        disnake.ui.Section("Modifier les informations de base (Nom, Desc, Emoji)", accessory=disnake.ui.Button(label="✏️ Modifier", style=disnake.ButtonStyle.blurple, custom_id=f"ticket_config_cat_edit_{cat_id}")),
                        disnake.ui.Section("Configurer les permissions staff spécifiques", accessory=disnake.ui.Button(label="🛡️ Permissions", style=disnake.ButtonStyle.secondary, custom_id=f"ticket_config_cat_perms_{cat_id}")),
                        disnake.ui.Section("Définir la catégorie Discord de destination", accessory=disnake.ui.Button(label="📁 Catégorie", style=disnake.ButtonStyle.secondary, custom_id=f"ticket_config_cat_chan_{cat_id}")),
                        disnake.ui.Separator(divider=True),
                        disnake.ui.Section(
                            "Supprimer définitivement cette catégorie",
                            accessory=disnake.ui.Button(
                                label="🗑️ Supprimer",
                                style=disnake.ButtonStyle.danger,
                                custom_id=f"ticket_config_cat_del_idx_{config.categories.index(category) if config and category in config.categories else 0}"
                            )
                        ),
                        disnake.ui.Section("Navigation", accessory=disnake.ui.Button(label="⬅️ Retour", style=disnake.ButtonStyle.secondary, custom_id="ticket_config_manage_cat"))
                    )
                    await inter.response.edit_message(components=[actions_container])
            elif custom_id.startswith("ticket_config_cat_edit_"):
                cat_id = custom_id.replace("ticket_config_cat_edit_", "")
                category = next((c for c in config.categories if c.id == cat_id), None)
                if category:
                    modal = EditCategoryModal(self.bot, self.storage, config, category, None)
                    await inter.response.send_modal(modal)

            # ⚠️ Suppression de catégorie (V2)
            # - Nouveau: ticket_config_cat_del_idx_<idx> (fiable)
            # - Ancien: ticket_config_cat_del_<cat_id...> (garder en fallback, mais invalide si custom_id == confirm_title)
            elif custom_id.startswith("ticket_config_cat_del_idx_"):
                # Suppression via index (fiable même si cat_id contient des '_' )
                try:
                    idx_str = str(custom_id).replace("ticket_config_cat_del_idx_", "")
                    idx = int(idx_str)
                except Exception:
                    idx = None

                if idx is None or idx < 0 or idx >= len(config.categories):
                    await inter.response.send_message("Catégorie non trouvée.", ephemeral=True)
                    return

                category = config.categories[idx]
                view = DeleteCategoryConfirmationView(
                    self.bot,
                    self.storage,
                    config,
                    category,
                    CategoryManagerView(self.bot, self.storage, config)
                )
                await inter.response.send_message(
                    components=[
                        disnake.ui.Container(
                            disnake.ui.Section(
                                "# ⚠️ Confirmer la suppression",
                                accessory=disnake.ui.Button(
                                    label=f"Supprimer: {category.name}",
                                    style=disnake.ButtonStyle.danger,
                                    custom_id="ticket_config_cat_del_confirm_title"
                                )
                            ),
                            disnake.ui.Separator(divider=True),
                            disnake.ui.TextDisplay(
                                content=f"Êtes-vous sûr de vouloir supprimer la catégorie **{category.name}** ?\n"
                                        "Cette action est irréversible."
                            )
                        )
                    ],
                    ephemeral=True
                )
                return

            elif custom_id.startswith("ticket_config_cat_del_"):
                # Ne pas traiter ticket_config_cat_del_confirm_title ici :
                # la suppression est déjà gérée par DeleteCategoryConfirmationView.confirm_callback.
                # Traiter à nouveau ici provoquait une double suppression (2 catégories supprimées).
                if custom_id == "ticket_config_cat_del_confirm_title":
                    # Juste reconstruire l'UI parent à partir de l'état actuel.
                    config = self.storage.load_config(inter.guild.id)
                    if config and config.categories is not None:
                        view = CategoryManagerView(self.bot, self.storage, config)
                        await inter.response.edit_message(components=view.get_components_v2())
                    return

                # Recharger config pour éviter les mismatch (panel vs état storage)
                config = self.storage.load_config(inter.guild.id)

                kuby_logger.warning(
                    f"[TicketSystem] ticket_config_cat_del_ clicked. "
                    f"custom_id={custom_id} "
                    f"guild_id={getattr(inter.guild,'id',None)}"
                )

                # Parser cat_id de manière robuste (cat_id peut contenir des '_' => pas de replace)
                # ticket_config_cat_del_<cat_id...>
                parts = str(custom_id).split("_")
                prefix_parts = ["ticket", "config", "cat", "del"]
                # On s'attend à: ticket config cat del <cat_id...>
                # => cat_id = join(parts[4:])
                cat_id = "_".join(parts[4:]) if len(parts) > 4 else ""

                kuby_logger.warning(
                    f"[TicketSystem] Extracted cat_id={cat_id}. "
                    f"config.categories IDs={[str(c.id) for c in (config.categories if config else [])]}"
                )

                # Normaliser en str pour éviter les mismatches type (int/str/UUID-like)
                category = next(
                    (c for c in (config.categories if config else []) if str(c.id) == str(cat_id)),
                    None
                )
                if not category:
                    kuby_logger.warning(
                        f"[TicketSystem] Catégorie non trouvée pour cat_id={cat_id}. "
                        f"IDs config={[str(c.id) for c in (config.categories if config else [])]}"
                    )
                    await inter.response.send_message("Catégorie non trouvée.", ephemeral=True)
                    return

                view = DeleteCategoryConfirmationView(self.bot, self.storage, config, category, CategoryManagerView(self.bot, self.storage, config))

                # IMPORTANT: si la réponse est en V2 components, ne pas envoyer embed=
                # => on remplace l'affichage "embed" par un Container V2 (UI compat).
                components = [
                    disnake.ui.Container(
                        disnake.ui.Section(
                            "# ⚠️ Confirmer la suppression",
                            accessory=disnake.ui.Button(
                                label=f"Supprimer: {category.name}",
                                style=disnake.ButtonStyle.danger,
                                custom_id="ticket_config_cat_del_confirm_title"
                            )
                        ),
                        disnake.ui.Separator(divider=True),
                        disnake.ui.TextDisplay(
                            content=f"Êtes-vous sûr de vouloir supprimer la catégorie **{category.name}** ?\n"
                                    "Cette action est irréversible."
                        )
                    )
                ]
                # Ne pas mixer view= et components= (Disnake lève TypeError)
                await inter.response.send_message(components=components, ephemeral=True)

            elif custom_id == "ticket_config_log_channel":
                view = TicketChannelConfigView(self.bot, self.storage, config, "log")
                await inter.response.edit_message(components=view.get_components_v2())
            elif custom_id.startswith("ticket_config_cat_chan_"):
                cat_id = custom_id.replace("ticket_config_cat_chan_", "")
                view = TicketChannelConfigView(self.bot, self.storage, config, f"cat_{cat_id}")
                await inter.response.edit_message(components=view.get_components_v2())
            elif custom_id == "ticket_config_staff_perms":
                view = PermissionConfigView(self.bot, self.storage, config, inter.guild, "staff")
                await inter.response.edit_message(components=view.get_components_v2())
            elif custom_id.startswith("ticket_config_cat_perms_"):
                cat_id = custom_id.replace("ticket_config_cat_perms_", "")
                view = PermissionConfigView(self.bot, self.storage, config, inter.guild, f"cat_{cat_id}")
                await inter.response.edit_message(components=view.get_components_v2())
            elif custom_id == "ticket_config_perms_config":
                view = PermissionConfigView(self.bot, self.storage, config, inter.guild, "config")
                await inter.response.edit_message(components=view.get_components_v2())
            elif custom_id.startswith("ticket_config_select_"):
                channel_type = custom_id.replace("ticket_config_select_", "")
                view = TicketChannelConfigView(self.bot, self.storage, config, channel_type)
                await view._select_callback(inter)
            elif custom_id.startswith("ticket_config_perms_role_"):
                config_type = custom_id.replace("ticket_config_perms_role_", "")
                view = PermissionConfigView(self.bot, self.storage, config, inter.guild, config_type)
                await view._role_callback(inter)
            elif custom_id.startswith("ticket_config_perms_user_"):
                config_type = custom_id.replace("ticket_config_perms_user_", "")
                view = PermissionConfigView(self.bot, self.storage, config, inter.guild, config_type)
                await view._user_callback(inter)
            elif custom_id.startswith("ticket_config_perms_remove_"):
                config_type = custom_id.replace("ticket_config_perms_remove_", "")
                view = PermissionConfigView(self.bot, self.storage, config, inter.guild, config_type)
                await view._remove_callback(inter)

        except Exception as e:
            kuby_logger.error(f"❌ Erreur critique dans on_interaction: {e}", exc_info=True)
            try:
                if not inter.response.is_done():
                    await inter.response.send_message(f"❌ Une erreur interne est survenue : {e}", ephemeral=True)
                else:
                    await inter.edit_original_response(content=f"❌ Une erreur interne est survenue : {e}")
            except:
                pass

    
    async def _register_all_views(self):
        """Enregistre les vues pour tous les serveurs"""
        try:
            for guild_id in self.storage.get_all_guild_ids():
                try:
                    config = self.storage.load_config(guild_id)
                    if config and config.enabled:
                        view = TicketPanelView(self.bot, config, self.storage)
                        self.bot.add_view(view)
                        
                        # Register management views for existing tickets
                        tickets = self.storage.get_guild_tickets(guild_id)
                        for ticket in tickets:
                            if ticket.is_open:
                                mgmt_view = TicketManagementView(self.bot, self.storage, config, ticket)
                                self.bot.add_view(mgmt_view)
                        
                        print(f"[Ticket] Views registered for guild {guild_id}")
                except Exception as e:
                    print(f"[Ticket] Error registering views for guild {guild_id}: {e}")
        except Exception as e:
            print(f"[Ticket] Error in _register_all_views: {e}")

    async def _send_recruitment_decision(self, inter: disnake.Interaction, ticket, category, accepted: bool):
        """Envoie la décision de recrutement (Accepté/Refusé) au candidat via DM"""
        try:
            guild = inter.guild
            user = guild.get_member(ticket.user_id)
            if not user:
                try:
                    user = await guild.fetch_member(ticket.user_id)
                except Exception:
                    pass

            # Déterminer le message selon la décision
            if accepted:
                title = "✅ Candidature Acceptée !"
                color = disnake.Color.green()
                description = (
                    f"Félicitations {user.mention} !\n\n"
                    f"Votre candidature pour **Boga Life** a été acceptée.\n\n"
                    f"Un responsable va bientôt vous contacter pour les prochaines étapes.\n\n"
                    f"Bienvenue dans l'équipe ! 🎉"
                )
            else:
                title = "❌ Candidature Refusée"
                color = disnake.Color.red()
                description = (
                    f"Bonjour {user.mention},\n\n"
                    f"Après analyse de votre candidature, nous avons le regret de vous informer "
                    f"que celle-ci n'a pas été retenue pour le poste.\n\n"
                    f"Cette décision ne remet pas en cause vos qualités. N'hésitez pas à repostuler "
                    f"ultérieurement si vous êtes toujours intéressé(e).\n\n"
                    f"Nous vous souhaitons bonne continuation."
                )

            embed = disnake.Embed(
                title=title,
                description=description,
                color=color,
                timestamp=datetime.now()
            )
            embed.set_footer(text="Boga Life - Système de Recrutement")

            # Envoyer le DM à l'utilisateur
            if user:
                try:
                    await user.send(embed=embed)
                    await inter.followup.send("✅ Décision envoyée au candidat par DM.", ephemeral=True)
                except disnake.Forbidden:
                    await inter.followup.send("⚠️ Impossible d'envoyer un DM - l'utilisateur a ses messages privés fermés.", ephemeral=True)
                except Exception as e:
                    await inter.followup.send(f"⚠️ Erreur lors de l'envoi du DM: {e}", ephemeral=True)
            else:
                await inter.followup.send("⚠️ Utilisateur introuvable.", ephemeral=True)

            # Fermer le ticket
            ticket.status = TicketStatus.CLOSED
            ticket.closed_at = datetime.now()
            self.storage.save_ticket(ticket)

            # Envoyer un message dans le salon du ticket
            close_embed = disnake.Embed(
                title=f"🔒 Ticket Fermé - {'Accepté' if accepted else 'Refusé'}",
                description=f"Candidature traitée par {inter.user.mention}",
                color=color
            )
            close_embed.add_field(name="Decision", value="✅ Acceptee" if accepted else "❌ Refusee", inline=True)

            # Supprimer le salon
            try:
                await inter.channel.delete(reason=f"Recrutement termine - {'Accepte' if accepted else 'Refuse'} par {inter.user}")
            except Exception as e:
                await inter.channel.send(embed=close_embed)
                print(f"Error deleting recruitment channel: {e}")

        except Exception as e:
            kuby_logger.error(f"[TicketSystem] _send_recruitment_decision failed: {e}", exc_info=True)
            if not inter.response.is_done():
                await inter.followup.send("❌ Erreur lors de la decision de recrutement.", ephemeral=True)

    # === COMMANDES ===

    @commands.slash_command(name="ticket", description="Ouvre le panel des tickets")
    @check_premium_tier()
    async def ticket_main(self, inter: disnake.ApplicationCommandInteraction):

        """Ouvre le panel des tickets"""
        config = self.storage.load_config(inter.guild.id)
        view = TicketPanelView(self.bot, config, self.storage)
        await inter.response.send_message(components=view.get_components_v2(), ephemeral=True)
    
    @commands.slash_command(name="ticketpanel", description="Envoie le panel de création de tickets dans un canal")
    @check_premium_tier()
    @commands.has_permissions(administrator=True)
    async def ticket_panel(self, inter: disnake.ApplicationCommandInteraction, channel: disnake.TextChannel = None):
        """Envoie le panel de création de tickets dans un canal"""
        await inter.response.defer(ephemeral=True)

        config = self.storage.load_config(inter.guild.id)

        if not config.enabled:
            await inter.edit_original_response(content="Le système est désactivé.")
            return

        target = channel or inter.channel

        view = TicketPanelView(self.bot, config, self.storage)
        panel_message = await target.send(components=view.get_components_v2())

        config.panel_channel_id = target.id
        config.panel_message_id = panel_message.id
        self.storage.save_config(inter.guild.id, config)

        await inter.edit_original_response(content=f"Panel envoyé dans {target.mention}")

    @commands.slash_command(name="ticketconfig", description="Ouvre le panel de configuration complet du système de tickets")
    @check_premium_tier()
    async def ticket_config(self, inter: disnake.ApplicationCommandInteraction):
        """Ouvre le panel de configuration complet du système de tickets"""
        await inter.response.defer(ephemeral=True)

        # Reload config
        config = self.storage.load_config(inter.guild.id)

        # Vérifier permissions (admin ou config roles/users)
        if not can_access_config(inter, config):
            await inter.edit_original_response(content="❌ Cette commande est réservée aux administrateurs ou aux rôles configurés.")
            return

        try:
            # Build config panel with V2 components
            config_container = disnake.ui.Container(
                disnake.ui.Section(
                    "# ⚙️ Configuration des Tickets",
                    accessory=disnake.ui.Button(label="🔄 Activer/Désactiver", style=disnake.ButtonStyle.green if config.enabled else disnake.ButtonStyle.red, custom_id="ticket_config_toggle")
                ),
                disnake.ui.Separator(divider=True),
                disnake.ui.TextDisplay(
                    content=f"**État actuel :** {'✅ Activé' if config.enabled else '❌ Désactivé'}\n"
                            f"**Catégories :** {len(config.categories)}\n"
                            f"**Rôles staff :** {len(config.staff_roles)}"
                ),
                disnake.ui.Separator(divider=True),
                disnake.ui.Section(
                    "📂 Gestion des catégories",
                    accessory=disnake.ui.Button(label="Gérer", style=disnake.ButtonStyle.blurple, custom_id="ticket_config_manage_cat")
                ),
                disnake.ui.Section(
                    "📝 Logs & Salons",
                    accessory=disnake.ui.Button(label="Configurer", style=disnake.ButtonStyle.secondary, custom_id="ticket_config_log_channel")
                ),
                disnake.ui.Section(
                    "👥 Permissions Staff",
                    accessory=disnake.ui.Button(label="Rôles", style=disnake.ButtonStyle.secondary, custom_id="ticket_config_staff_perms")
                )
            )

            await inter.edit_original_response(components=[config_container])
        except Exception as e:
            kuby_logger.error(f"❌ Erreur dans ticket_config: {e}", exc_info=True)
            await inter.edit_original_response(content=f"❌ Une erreur interne est survenue : {e}")


def setup(bot):
    """Setup function"""
    bot.add_cog(TicketCommands(bot))
