package net.viper.status.modules.chat;

import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.*;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ChatEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import net.viper.status.module.Module;
import net.viper.status.modules.chat.bridge.DiscordBridge;
import net.viper.status.modules.chat.bridge.TelegramBridge;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

/**
 * ChatModule f\u00fcr StatusAPI (BungeeCord)
 *
 * Features:
 *  ✅ Mehrere Kan\u00e4le mit Permissions
 *  ✅ Server-Erkennung im Chat-Format
 *  ✅ /helpop f\u00fcr Spieler
 *  ✅ Emoji-Unterst\u00fctzung (:smile: → 😊)
 *  ✅ Admin-Mute / Spieler-eigener Chat-Mute
 *  ✅ CMI & Plugin-kompatibel (kein Eingriff in SubServer-Befehle)
 *  ✅ Privat-Nachrichten (/msg, /r)
 *  ✅ Spieler-Blocking (/ignore, /unignore)
 *  ✅ Discord & Telegram Integration
 *  ✅ Admin-Bypass (kann nicht gemutet/geblockt werden)
 *  ✅ /broadcast f\u00fcr Admins
 *  ✅ Secure-Chat kompatibel (1.19+ & Bedrock/Geyser)
 *  ✅ Chat-Log mit konfigurierbarer Aufbewahrung (7 / 14 Tage)
 *  ✅ Nachrichten-IDs (klickbar → Zwischenablage)
 *  ✅ Report-System (/report, /reports, /reportclose)
 *  ✅ Admin-Benachrichtigung bei Reports (sofort oder verz\u00f6gert nach Login)
 *  ✅ Server-Farben pro Server (&-Codes und &#RRGGBB HEX)
 *  ✅ Join / Leave Nachrichten (mit Vanish-Support)
 *  ✅ BungeeCord-Vanish-Integration via VanishProvider
 */
public class ChatModule implements Module, Listener {

    private Plugin plugin;
    private Logger logger;

    private ChatConfig        config;
    private MuteManager       muteManager;
    private BlockManager      blockManager;
    private PrivateMsgManager pmManager;
    private EmojiParser       emojiParser;
    private ChatFilter        chatFilter;
    private DiscordBridge     discordBridge;
    private TelegramBridge    telegramBridge;
    private ChatLogger        chatLogger;
    private ReportManager     reportManager;
    private AccountLinkManager linkManager;

    // UUID → aktiver Kanal-ID
    private final Map<UUID, String> playerChannels = new ConcurrentHashMap<>();

    // UUIDs die ihren eigenen Chat-Empfang deaktiviert haben
    private final Set<UUID> selfChatMuted = Collections.newSetFromMap(new ConcurrentHashMap<>());

    // UUIDs die Mentions f\u00fcr sich deaktiviert haben
    private final Set<UUID> mentionsDisabled = Collections.newSetFromMap(new ConcurrentHashMap<>());

    // HelpOp Cooldown: UUID → letzter Zeitstempel (Sekunden)
    private final Map<UUID, Long> helpopCooldowns = new ConcurrentHashMap<>();

    // Report-Cooldown: UUID → letzter Report-Zeitstempel (Sekunden)
    private final Map<UUID, Long> reportCooldowns = new ConcurrentHashMap<>();

    // Letzte Chatnachricht pro Spieler (f\u00fcr Report-Kontext): name.toLowerCase() → message
    private final Map<String, String> lastChatMessages = new ConcurrentHashMap<>();

    // UUIDs die gerade auf Plugin-Chat-Eingabe warten
    private final Set<UUID> awaitingInput = Collections.newSetFromMap(new ConcurrentHashMap<>());

    // Geyser-Pr\u00e4fix f\u00fcr Bedrock-Spieler (Standard: ".")
    private static final String GEYSER_PREFIX = ".";

    @Override
    public String getName() { return "ChatModule"; }

    @Override
    public void onEnable(Plugin plugin) {
        this.plugin = plugin;
        this.logger = plugin.getLogger();

        // Konfiguration laden
        config = new ChatConfig(plugin);
        config.load();

        // Manager initialisieren
        muteManager  = new MuteManager(plugin.getDataFolder(), logger);
        muteManager.load();

        blockManager = new BlockManager(plugin.getDataFolder(), logger);
        blockManager.load();

        pmManager   = new PrivateMsgManager(blockManager);
        emojiParser = new EmojiParser(config.getEmojiMappings(), config.isEmojiEnabled());
        chatFilter  = new ChatFilter(config.getFilterConfig());

        // ChatLogger
        if (config.isChatlogEnabled()) {
            chatLogger = new ChatLogger(plugin.getDataFolder(), logger, config.getChatlogRetentionDays());
            logger.fine("[ChatModule] Chat-Log aktiviert (" + config.getChatlogRetentionDays() + " Tage Aufbewahrung).");
        }

        // ReportManager
        if (config.isReportsEnabled()) {
            reportManager = new ReportManager(plugin.getDataFolder(), logger);
            reportManager.load();
        }

        // AccountLinkManager
        linkManager = new AccountLinkManager(plugin.getDataFolder(), logger);
        linkManager.load();

        // Externe Br\u00fccken
        if (config.isDiscordEnabled() || config.isReportWebhookEnabled()) {
            discordBridge = new DiscordBridge(plugin, config);
            discordBridge.setLinkManager(linkManager);
            if (config.isDiscordEnabled()) {
                discordBridge.start();
            }
        }
        if (config.isTelegramEnabled()) {
            telegramBridge = new TelegramBridge(plugin, config);
            telegramBridge.setLinkManager(linkManager);
            telegramBridge.start();
        }

        // Listener & Befehle registrieren
        ProxyServer.getInstance().getPluginManager().registerListener(plugin, this);
        registerCommands();

        logger.fine("[ChatModule] Aktiviert – " + config.getChannels().size() + " Kan\u00e4le geladen.");
    }

    @Override
    public void onDisable(Plugin plugin) {
        if (discordBridge  != null) discordBridge.stop();
        if (telegramBridge != null) telegramBridge.stop();
        if (muteManager    != null) muteManager.save();
        if (blockManager   != null) blockManager.save();
        if (reportManager  != null) reportManager.save();
        if (linkManager    != null) linkManager.save();
        playerChannels.clear();
        selfChatMuted.clear();
        helpopCooldowns.clear();
        reportCooldowns.clear();
        lastChatMessages.clear();
    }

    // =========================================================
    //  CHAT-EVENTS (BungeeCord original, 1.20+)
    //
    //  Das Bypass-Problem mit Paper:
    //  Wenn BungeeCord eine signierte Nachricht unver\u00e4ndert durchl\u00e4sst
    //  (kein setCancelled), pr\u00fcft Paper die Signatur → ung\u00fcltig → Fehler.
    //  Wenn wir setCancelled(true) setzen und die Nachricht selbst senden,
    //  fehlt die Signatur → Paper lehnt sie ebenfalls ab.
    //
    //  L\u00f6sung: In der paper-global.yml auf JEDEM Sub-Server:
    //    messages:
    //      reject-chat-unsigned: false
    //  Das erlaubt unsignierte Nachrichten vom Proxy durch.
    //  Alternativ: In spigot.yml → settings: bungeecord: true (bereits n\u00f6tig)
    //  kombiniert mit BungeeCord IP-Forwarding deaktiviert Paper die Pr\u00fcfung.
    // =========================================================

    @EventHandler(priority = EventPriority.HIGHEST)
    public void onChat(ChatEvent e) {
        if (e.isCancelled()) return;
        if (e.isCommand()) return;
        if (!(e.getSender() instanceof ProxiedPlayer)) return;
        ProxiedPlayer player = (ProxiedPlayer) e.getSender();

        if (awaitingInput.contains(player.getUniqueId())) {
            awaitingInput.remove(player.getUniqueId());
            return; // Event NICHT cancellen → Nachricht geht mit Originalsignatur durch
        }

        e.setCancelled(true);
        processChat(player, e.getMessage());
    }

    /**
     * Zentrale Chat-Verarbeitungslogik.
     */
    private void processChat(ProxiedPlayer player, String rawMessage) {
        if (rawMessage == null || rawMessage.trim().isEmpty()) return;
        UUID uuid = player.getUniqueId();

        boolean isAdmin = player.hasPermission(config.getAdminBypassPermission());

        if (!isAdmin && muteManager.isMuted(uuid)) {
            String remaining = muteManager.getRemainingTime(uuid);
            player.sendMessage(color(config.getMutedMessage().replace("{time}", remaining)));
            return;
        }

        String channelId = playerChannels.getOrDefault(uuid, config.getDefaultChannelId());
        ChatChannel channel = config.getChannel(channelId);
        if (channel == null) channel = config.getDefaultChannel();

        if (channel.hasPermission() && !player.hasPermission(channel.getPermission())) {
            player.sendMessage(color("&cDu hast keine Berechtigung f\u00fcr den Kanal &f" + channel.getName() + "&c."));
            channelId = config.getDefaultChannelId();
            channel   = config.getDefaultChannel();
            playerChannels.put(uuid, channelId);
        }

        String message = emojiParser.parse(rawMessage);

        // ── Chat-Filter ──
        boolean hasColorPerm  = player.hasPermission("chat.color");
        boolean hasFormatPerm = player.hasPermission("chat.color.format");
        boolean filterBypass  = isAdmin || player.hasPermission("chat.filter.bypass");
        ChatFilter.FilterResponse filterResp = chatFilter.filter(
                uuid, message, filterBypass, hasColorPerm, hasFormatPerm);

        if (filterResp.result == ChatFilter.FilterResult.BLOCKED) {
            player.sendMessage(color(filterResp.denyReason));
            return;
        }
        message = filterResp.message;

        String serverName = player.getServer() != null
                ? player.getServer().getInfo().getName()
                : "Proxy";

        String prefix = getLuckPermsPrefix(player);
        String suffix = getLuckPermsSuffix(player);

        // ── Mentions erkennen (@Spielername) ──
        final Set<UUID> mentionedPlayers = new java.util.HashSet<>();
        final String messageWithMentions;
        if (config.isMentionsEnabled()) {
            String[] words = message.split(" ");
            StringBuilder mentionBuilder = new StringBuilder();
            String highlightColor = config.getMentionsHighlightColor();
            for (int wi = 0; wi < words.length; wi++) {
                String word = words[wi];
                if (word.startsWith("@") && word.length() > 1) {
                    String targetName = word.substring(1);
                    ProxiedPlayer mentioned = ProxyServer.getInstance().getPlayer(targetName);
                    if (mentioned != null && !mentioned.getUniqueId().equals(uuid)) {
                        mentionedPlayers.add(mentioned.getUniqueId());
                        word = translateColors(highlightColor + word + "&r");
                    }
                }
                mentionBuilder.append(word);
                if (wi < words.length - 1) mentionBuilder.append(" ");
            }
            messageWithMentions = mentionBuilder.toString();
        } else {
            messageWithMentions = message;
        }

        final String finalMessage = messageWithMentions;
        final String formatted = buildFormat(channel.getFormat(), serverName, prefix,
                player.getName(), suffix, finalMessage);

        // Nachricht loggen und ID generieren
        final String msgId;
        if (chatLogger != null) {
            msgId = chatLogger.generateMessageId();
            chatLogger.log(msgId, serverName, channel.getId(), player.getName(), finalMessage);
        } else {
            msgId = null;
        }

        // Letzte Nachricht des Spielers speichern (f\u00fcr Report-Kontext)
        lastChatMessages.put(player.getName().toLowerCase(), finalMessage);

        final ChatChannel finalChannel  = channel;
        final String finalFormatted    = formatted;
        final String finalSenderName   = player.getName();

        plugin.getProxy().getScheduler().runAsync(plugin, () -> {
            for (ProxiedPlayer recipient : ProxyServer.getInstance().getPlayers()) {
                boolean isSelf = recipient.getUniqueId().equals(uuid);

                if (!isSelf && selfChatMuted.contains(recipient.getUniqueId())) continue;

                boolean recipientIsAdmin = recipient.hasPermission(config.getAdminBypassPermission());
                if (!recipientIsAdmin && !blockManager.canReceive(recipient.getUniqueId(), uuid)) continue;

                if (finalChannel.isLocalOnly()) {
                    if (player.getServer() == null || recipient.getServer() == null) continue;
                    if (!player.getServer().getInfo().getName()
                            .equals(recipient.getServer().getInfo().getName())) continue;
                }

                if (finalChannel.hasPermission() && !recipient.hasPermission(finalChannel.getPermission())) {
                    if (!recipient.getUniqueId().equals(uuid)) continue;
                }

                // Mention-Benachrichtigung
                boolean isMentioned = mentionedPlayers.contains(recipient.getUniqueId())
                        && config.isMentionsEnabled()
                        && !mentionsDisabled.contains(recipient.getUniqueId());

                if (isMentioned) {
                    recipient.sendMessage(color(config.getMentionsNotifyPrefix()
                            + "&7" + finalSenderName + " &7hat dich erw\u00e4hnt!"));
                    sendMentionSound(recipient, config.getMentionsSound());
                }

                if (msgId != null) {
                    recipient.sendMessage(buildClickableMessage(msgId, finalFormatted, finalSenderName));
                } else {
                    recipient.sendMessage(color(finalFormatted));
                }
            }

            bridgeToDiscord(finalChannel, player.getName(), finalMessage, serverName);
            bridgeToTelegram(finalChannel, player.getName(), finalMessage, serverName);
        });
    }

    // =========================================================
    //  LOGIN-EVENT: Kanal setzen + Join-Nachricht + Report-Info
    // =========================================================

    @EventHandler
    public void onLogin(PostLoginEvent e) {
        ProxiedPlayer player = e.getPlayer();
        UUID uuid = player.getUniqueId();

        // Standard-Kanal setzen
        playerChannels.put(uuid, config.getDefaultChannelId());

        // Join-Nachricht und Report-Benachrichtigung mit kurzem Delay senden,
        // damit alle anderen Proxy-Initialisierungen (inkl. VanishModule) abgeschlossen sind.
        // 2s statt 1s: VanishModule markiert den Spieler oft erst beim ServerConnectedEvent.
        plugin.getProxy().getScheduler().schedule(plugin, () -> {
            if (!player.isConnected()) return;

            // ── Join-Nachricht ──
            if (config.isJoinLeaveEnabled()) {
                broadcastJoinLeave(player, true);
            }

            // ── Offene Reports f\u00fcr Admins anzeigen ──
            if (reportManager != null
                    && (player.hasPermission(config.getAdminNotifyPermission())
                        || player.hasPermission(config.getAdminBypassPermission()))) {
                int count = reportManager.getOpenCount();
                if (count > 0) {
                    player.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));
                    player.sendMessage(color("&c&l⚑ OFFENE REPORTS &8| &f" + count + " ausstehend"));
                    player.sendMessage(color("&7Tippe &f/reports &7f\u00fcr eine \u00dcbersicht."));
                    player.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));
                }
            }
        }, 2, TimeUnit.SECONDS);
    }

    // =========================================================
    //  DISCONNECT-EVENT: Cleanup + Leave-Nachricht
    // =========================================================

    @EventHandler
    public void onDisconnect(PlayerDisconnectEvent e) {
        ProxiedPlayer player = e.getPlayer();
        UUID uuid = player.getUniqueId();

        // Leave-Nachricht
        if (config.isJoinLeaveEnabled()) {
            broadcastJoinLeave(player, false);
        }

        // Cleanup
        chatFilter.cleanup(uuid);
        playerChannels.remove(uuid);
        mentionsDisabled.remove(uuid);
        awaitingInput.remove(uuid);
        VanishProvider.cleanup(uuid); // Vanish-Status bereinigen
    }

    // =========================================================
    //  JOIN / LEAVE NACHRICHTEN (mit Vanish-Support)
    // =========================================================

    /**
     * Sendet eine Join- oder Leave-Nachricht an alle Spieler.
     *
     * Vanish-Logik:
     *  - Unsichtbare Spieler: kein Broadcast an normale Spieler.
     *  - Ist vanish-show-to-admins=true, erhalten Admins (bypass-permission)
     *    eine dezente Vanish-Benachrichtigung.
     *
     * @param player  Der betroffene Spieler
     * @param isJoin  true = Join, false = Leave
     */
    private void broadcastJoinLeave(ProxiedPlayer player, boolean isJoin) {
        boolean isVanished = VanishProvider.isVanished(player);

        String prefix = getLuckPermsPrefix(player);
        String suffix = getLuckPermsSuffix(player);
        String server = (player.getServer() != null)
                ? config.getServerDisplay(player.getServer().getInfo().getName())
                : "Netzwerk";

        String normalFormat = isJoin ? config.getJoinFormat() : config.getLeaveFormat();
        String vanishFormat = isJoin ? config.getVanishJoinFormat() : config.getVanishLeaveFormat();

        // Platzhalter ersetzen
        String normalMsg = normalFormat
                .replace("{player}", player.getName())
                .replace("{prefix}", prefix != null ? prefix : "")
                .replace("{suffix}", suffix != null ? suffix : "")
                .replace("{server}", server);

        String vanishMsg = vanishFormat
                .replace("{player}", player.getName())
                .replace("{prefix}", prefix != null ? prefix : "")
                .replace("{suffix}", suffix != null ? suffix : "")
                .replace("{server}", server);

        for (ProxiedPlayer recipient : ProxyServer.getInstance().getPlayers()) {
            // Spieler sieht seine eigene Join-/Leave-Meldung nie
            if (recipient.getUniqueId().equals(player.getUniqueId())) continue;

            // Admin = bypass-permission ODER notify-permission
            boolean recipientIsAdmin = recipient.hasPermission(config.getAdminBypassPermission())
                                    || recipient.hasPermission(config.getAdminNotifyPermission());

            if (isVanished) {
                // Vanished: Nur Admins sehen eine dezente Meldung (wenn konfiguriert)
                if (recipientIsAdmin && config.isVanishShowToAdmins()) {
                    recipient.sendMessage(color(vanishMsg));
                }
            } else {
                // Normaler Spieler: alle erhalten die Nachricht
                // Vanished Admins sehen Join/Leave-Events anderer Spieler normal
                recipient.sendMessage(color(normalMsg));
            }
        }

        // Konsole immer informieren
        String logMsg = isVanished
                ? "[ChatModule] " + (isJoin ? "JOIN(V)" : "LEAVE(V)") + " " + player.getName()
                : "[ChatModule] " + (isJoin ? "JOIN" : "LEAVE") + " " + player.getName();
        ProxyServer.getInstance().getConsole().sendMessage(color(
                isVanished ? "&8" + logMsg : "&7" + logMsg));

        // Br\u00fccken (nur f\u00fcr nicht-vanished Spieler)
        if (!isVanished) {
            String cleanMsg = ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', normalMsg));
            if (discordBridge != null && !config.getJoinLeaveDiscordWebhook().isEmpty()) {
                discordBridge.sendToDiscord(config.getJoinLeaveDiscordWebhook(),
                        player.getName(), cleanMsg, null);
            }
            if (telegramBridge != null && !config.getJoinLeaveTelegramChatId().isEmpty()) {
                telegramBridge.sendToTelegram(config.getJoinLeaveTelegramChatId(),
                        config.getJoinLeaveTelegramThreadId(), cleanMsg);
            }
        }
    }

    // =========================================================
    //  BEFEHLE REGISTRIEREN
    // =========================================================

    private void registerCommands() {

        // /channel <n> | /ch <n>
        Command chCmd = new Command("channel", null, "ch", "kanal") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(color("&cNur Spieler!")); return; }
                ProxiedPlayer p = (ProxiedPlayer) sender;
                if (args.length == 0) {
                    p.sendMessage(color("&8▸ &eVerf\u00fcgbare Kan\u00e4le:"));
                    for (ChatChannel ch : config.getChannels().values()) {
                        boolean hasPerm = !ch.hasPermission() || p.hasPermission(ch.getPermission());
                        String active   = ch.getId().equals(playerChannels.getOrDefault(p.getUniqueId(), config.getDefaultChannelId())) ? " &a✔" : "";
                        p.sendMessage(color(
                                "  " + ch.getFormattedTag()
                                + " &f" + ch.getName()
                                + (hasPerm ? active : " &8(keine Berechtigung)")));
                    }
                    return;
                }
                String target = args[0].toLowerCase();
                ChatChannel ch = config.getChannel(target);
                if (ch == null) { p.sendMessage(color("&cKanal &f" + args[0] + " &cnicht gefunden.")); return; }
                if (ch.hasPermission() && !p.hasPermission(ch.getPermission())) {
                    p.sendMessage(color("&cDu hast keine Berechtigung f\u00fcr diesen Kanal.")); return;
                }
                playerChannels.put(p.getUniqueId(), ch.getId());
                p.sendMessage(color("&aKanal gewechselt: " + ch.getFormattedTag() + " &a" + ch.getName()));
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, chCmd);

        // /helpop <nachricht>
        Command helpop = new Command("helpop") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(color("&cNur Spieler!")); return; }
                ProxiedPlayer p = (ProxiedPlayer) sender;
                if (args.length == 0) { p.sendMessage(color("&cBenutzung: /helpop <Nachricht>")); return; }

                long now = System.currentTimeMillis() / 1000L;
                Long last = helpopCooldowns.get(p.getUniqueId());
                if (last != null && (now - last) < config.getHelpopCooldown()) {
                    long wait = config.getHelpopCooldown() - (now - last);
                    p.sendMessage(color("&cBitte warte noch &f" + wait + "s &cvor dem n\u00e4chsten HelpOp."));
                    return;
                }
                helpopCooldowns.put(p.getUniqueId(), now);

                String msg    = String.join(" ", args);
                String server = p.getServer() != null ? p.getServer().getInfo().getName() : "Proxy";
                String formatted = buildSimpleFormat(config.getHelpopFormat(),
                        "player", p.getName(), "server", server, "message", msg);

                for (ProxiedPlayer op : ProxyServer.getInstance().getPlayers()) {
                    if (op.hasPermission(config.getHelpopPermission())) {
                        op.sendMessage(color(formatted));
                    }
                }
                ProxyServer.getInstance().getConsole().sendMessage(color(formatted));
                p.sendMessage(color(config.getHelpopConfirm()));

                if (discordBridge != null && !config.getHelpopDiscordWebhook().isEmpty()) {
                    discordBridge.sendEmbedToDiscord(config.getHelpopDiscordWebhook(),
                            "🆘 HelpOp von " + p.getName() + " (" + server + ")", msg, "FFAA00");
                }
                if (telegramBridge != null && !config.getHelpopTelegramChatId().isEmpty()) {
                    telegramBridge.sendFormattedToTelegram(config.getHelpopTelegramChatId(),
                            "🆘 HelpOp: " + p.getName() + " @" + server, msg);
                }
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, helpop);

        // /msg <spieler> <nachricht>
        // Vanish: Vanished Spieler sind f\u00fcr normale Spieler nicht erreichbar.
        // Admins k\u00f6nnen vanished Spieler per PM kontaktieren.
        Command msgCmd = new Command("msg", null, "tell", "w", "whisper") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (!config.isPmEnabled()) { sender.sendMessage(color("&cPrivat-Nachrichten sind deaktiviert.")); return; }
                if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(color("&cNur Spieler!")); return; }
                if (args.length < 2) { sender.sendMessage(color("&cBenutzung: /msg <Spieler> <Nachricht>")); return; }

                ProxiedPlayer from = (ProxiedPlayer) sender;
                boolean fromIsAdmin = from.hasPermission(config.getAdminBypassPermission());

                // Ziel suchen – vanished Spieler sind f\u00fcr Nicht-Admins "nicht gefunden"
                ProxiedPlayer to = findVisiblePlayer(args[0], fromIsAdmin);
                if (to == null || !to.isConnected()) {
                    from.sendMessage(color("&cSpieler &f" + args[0] + " &cnicht gefunden.")); return;
                }

                String message = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
                pmManager.send(from, to, message, config, config.getAdminBypassPermission());
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, msgCmd);

        // /r <nachricht>
        Command replyCmd = new Command("r", null, "reply", "antwort") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (!config.isPmEnabled()) { sender.sendMessage(color("&cPrivat-Nachrichten sind deaktiviert.")); return; }
                if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(color("&cNur Spieler!")); return; }
                if (args.length == 0) { sender.sendMessage(color("&cBenutzung: /r <Nachricht>")); return; }
                ProxiedPlayer p = (ProxiedPlayer) sender;
                pmManager.reply(p, String.join(" ", args), config, config.getAdminBypassPermission());
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, replyCmd);

        // /ignore | /unignore
        Command ignoreCmd = new Command("ignore", null, "block") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(color("&cNur Spieler!")); return; }
                if (args.length == 0) { sender.sendMessage(color("&cBenutzung: /ignore <Spieler>")); return; }
                ProxiedPlayer p      = (ProxiedPlayer) sender;
                ProxiedPlayer target = ProxyServer.getInstance().getPlayer(args[0]);
                if (target == null) { p.sendMessage(color("&cSpieler nicht gefunden.")); return; }
                if (target.hasPermission(config.getAdminBypassPermission())) {
                    p.sendMessage(color("&cAdmins k\u00f6nnen nicht ignoriert werden.")); return;
                }
                if (blockManager.isBlocked(p.getUniqueId(), target.getUniqueId())) {
                    p.sendMessage(color("&cDu hast &f" + target.getName() + " &cbereits ignoriert.")); return;
                }
                blockManager.block(p.getUniqueId(), target.getUniqueId());
                p.sendMessage(color("&aDu ignorierst jetzt &f" + target.getName() + "&a."));
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, ignoreCmd);

        Command unignoreCmd = new Command("unignore", null, "unblock") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(color("&cNur Spieler!")); return; }
                if (args.length == 0) { sender.sendMessage(color("&cBenutzung: /unignore <Spieler>")); return; }
                ProxiedPlayer p      = (ProxiedPlayer) sender;
                ProxiedPlayer target = ProxyServer.getInstance().getPlayer(args[0]);
                if (target == null) {
                    p.sendMessage(color("&cSpieler nicht online."));
                    return;
                }
                if (!blockManager.isBlocked(p.getUniqueId(), target.getUniqueId())) {
                    p.sendMessage(color("&cDu hast diesen Spieler nicht ignoriert.")); return;
                }
                blockManager.unblock(p.getUniqueId(), target.getUniqueId());
                p.sendMessage(color("&aIgnore f\u00fcr &f" + args[0] + " &aaufgehoben."));
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, unignoreCmd);

        // /chatmute | /chatunmute
        Command muteCmd = new Command("chatmute", "chat.mute", "gmute") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (args.length == 0) { sender.sendMessage(color("&cBenutzung: /chatmute <Spieler> [Minuten]")); return; }
                ProxiedPlayer target = ProxyServer.getInstance().getPlayer(args[0]);
                if (target == null) { sender.sendMessage(color("&cSpieler &f" + args[0] + " &cist nicht online.")); return; }
                if (target.hasPermission(config.getAdminBypassPermission())) {
                    sender.sendMessage(color("&cDieser Spieler kann nicht gemutet werden.")); return;
                }
                int duration = config.getDefaultMuteDuration();
                if (args.length >= 2) {
                    try { duration = Integer.parseInt(args[1]); }
                    catch (NumberFormatException ex) { sender.sendMessage(color("&cUng\u00fcltige Dauer. Bitte Zahl eingeben.")); return; }
                }
                muteManager.mute(target.getUniqueId(), duration);
                String durationStr = duration <= 0 ? "permanent" : duration + " Minuten";
                target.sendMessage(color("&cDu wurdest f\u00fcr " + durationStr + " stummgeschaltet."));
                sender.sendMessage(color("&a" + target.getName() + " wurde f\u00fcr " + durationStr + " gemutet."));
                notifyAdmins("&8[&cMute&8] &f" + (sender instanceof ProxiedPlayer ? sender.getName() : "Konsole")
                        + " &7hat &f" + target.getName() + " &7f\u00fcr &f" + durationStr + " &7gemutet.");
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, muteCmd);

        Command unmuteCmd = new Command("chatunmute", "chat.mute", "gunmute") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (args.length == 0) { sender.sendMessage(color("&cBenutzung: /chatunmute <Spieler>")); return; }
                ProxiedPlayer target = ProxyServer.getInstance().getPlayer(args[0]);
                if (target == null) { sender.sendMessage(color("&cSpieler nicht online.")); return; }
                if (!muteManager.isMuted(target.getUniqueId())) {
                    sender.sendMessage(color("&cDieser Spieler ist nicht gemutet.")); return;
                }
                muteManager.unmute(target.getUniqueId());
                target.sendMessage(color("&aDeine Stummschaltung wurde aufgehoben."));
                sender.sendMessage(color("&a" + target.getName() + " wurde entmutet."));
                notifyAdmins("&8[&aUnmute&8] &f" + (sender instanceof ProxiedPlayer ? sender.getName() : "Konsole")
                        + " &7hat &f" + target.getName() + " &7entmutet.");
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, unmuteCmd);

        // /chataus (Selbst-Mute)
        Command selfMuteCmd = new Command("chataus", null, "togglechat", "chaton", "chatoff") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(color("&cNur Spieler!")); return; }
                ProxiedPlayer p = (ProxiedPlayer) sender;
                if (selfChatMuted.contains(p.getUniqueId())) {
                    selfChatMuted.remove(p.getUniqueId());
                    p.sendMessage(color("&aChat &l✔ eingeschaltet."));
                } else {
                    selfChatMuted.add(p.getUniqueId());
                    p.sendMessage(color("&cChat &l✘ ausgeschaltet. &7Mit &f/chataus &7wieder einschalten."));
                }
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, selfMuteCmd);

        // /broadcast
        Command broadcastCmd = new Command("broadcast", config.getBroadcastPermission(), "bc", "alert") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (args.length == 0) { sender.sendMessage(color("&cBenutzung: /broadcast <Nachricht>")); return; }
                String message   = String.join(" ", args);
                String formatted = config.getBroadcastFormat().replace("{message}", message);
                ProxyServer.getInstance().broadcast(color(formatted));

                if (discordBridge != null) {
                    if (!config.getDiscordAdminChannelId().isEmpty()) {
                        discordBridge.sendToChannel(config.getDiscordAdminChannelId(),
                                "📢 **Broadcast:** " + ChatColor.stripColor(
                                        ChatColor.translateAlternateColorCodes('&', message)));
                    }
                }
                if (telegramBridge != null && !config.getTelegramAdminChatId().isEmpty()) {
                    telegramBridge.sendFormattedToTelegram(config.getTelegramAdminChatId(),
                            "📢 Broadcast",
                            ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', message)));
                }
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, broadcastCmd);

        // /emoji
        Command emojiCmd = new Command("emoji", null, "emojis") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                sender.sendMessage(color(emojiParser.buildEmojiList()));
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, emojiCmd);

        // /socialspy
        Command spyCmd = new Command("socialspy", "chat.socialspy", "spy") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(color("&cNur Spieler!")); return; }
                ProxiedPlayer p = (ProxiedPlayer) sender;
                boolean now = pmManager.toggleSpy(p.getUniqueId());
                p.sendMessage(color(now ? "&aSocial-Spy aktiviert." : "&cSocial-Spy deaktiviert."));
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, spyCmd);

        // /chatreload
        Command reloadCmd = new Command("chatreload", "chat.admin.bypass") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                config.load();
                emojiParser = new EmojiParser(config.getEmojiMappings(), config.isEmojiEnabled());
                chatFilter  = new ChatFilter(config.getFilterConfig());
                sender.sendMessage(color("&aChat-Konfiguration neu geladen."));
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, reloadCmd);

        // /chatinfo <spieler> – Admin-Info \u00fcber einen Spieler
        Command chatInfoCmd = new Command("chatinfo", "chat.admin.bypass") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (args.length == 0) { sender.sendMessage(color("&cBenutzung: /chatinfo <Spieler>")); return; }
                ProxiedPlayer target = ProxyServer.getInstance().getPlayer(args[0]);
                if (target == null) { sender.sendMessage(color("&cSpieler &f" + args[0] + " &cnicht online.")); return; }

                UUID tUUID = target.getUniqueId();
                String tServer = target.getServer() != null ? target.getServer().getInfo().getName() : "Proxy";

                String channelId = playerChannels.getOrDefault(tUUID, config.getDefaultChannelId());
                ChatChannel ch = config.getChannel(channelId);
                String channelName = ch != null ? ch.getFormattedTag() + " &f" + ch.getName() : "&f" + channelId;

                String muteStatus = muteManager.isMuted(tUUID)
                        ? "&cJa &8(noch: &f" + muteManager.getRemainingTime(tUUID) + "&8)"
                        : "&aKein";

                Set<UUID> blocked = blockManager.getBlockedBy(tUUID);

                AccountLinkManager.LinkedAccount link = linkManager.getByUUID(tUUID);
                String discordInfo  = (link != null && !link.discordUserId.isEmpty())
                        ? "&a" + link.discordUsername + " &8(" + link.discordUserId + ")" : "&7Nicht verkn\u00fcpft";
                String telegramInfo = (link != null && !link.telegramUserId.isEmpty())
                        ? "&a" + link.telegramUsername + " &8(" + link.telegramUserId + ")" : "&7Nicht verkn\u00fcpft";

                String vanishStatus = VanishProvider.isVanished(target) ? "&eJa" : "&aKein";

                sender.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));
                sender.sendMessage(color("&eChatInfo: &f" + target.getName() + " &8@ &7" + tServer));
                sender.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));
                sender.sendMessage(color("&7Kanal:       " + channelName));
                sender.sendMessage(color("&7Mute:        " + muteStatus));
                sender.sendMessage(color("&7Chat-aus:    " + (selfChatMuted.contains(tUUID) ? "&cJa" : "&aKein")));
                sender.sendMessage(color("&7Mentions:    " + (mentionsDisabled.contains(tUUID) ? "&cDeaktiviert" : "&aAktiv")));
                sender.sendMessage(color("&7Vanish:      " + vanishStatus));
                sender.sendMessage(color("&7Blockiert:   &f" + blocked.size() + " Spieler"));
                if (!blocked.isEmpty()) {
                    for (UUID bUUID : blocked) {
                        ProxiedPlayer bp = ProxyServer.getInstance().getPlayer(bUUID);
                        String bName = bp != null ? bp.getName() : bUUID.toString().substring(0, 8) + "...";
                        sender.sendMessage(color("  &8- &7" + bName));
                    }
                }
                sender.sendMessage(color("&7Discord:     " + discordInfo));
                sender.sendMessage(color("&7Telegram:    " + telegramInfo));
                sender.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, chatInfoCmd);

        // /chathist [spieler] [anzahl] – Chat-History aus dem Logfile
        Command chatHistCmd = new Command("chathist", "chat.admin.bypass") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (chatLogger == null) {
                    sender.sendMessage(color("&cChat-Log ist deaktiviert.")); return;
                }

                String playerFilter = null;
                int lines = config.getHistoryDefaultLines();

                if (args.length >= 1) {
                    try {
                        lines = Math.min(Integer.parseInt(args[0]), config.getHistoryMaxLines());
                    } catch (NumberFormatException ex) {
                        playerFilter = args[0];
                    }
                }
                if (args.length >= 2) {
                    try {
                        lines = Math.min(Integer.parseInt(args[1]), config.getHistoryMaxLines());
                    } catch (NumberFormatException ignored) {}
                }

                final String finalFilter = playerFilter;
                final int    finalLines  = lines;

                plugin.getProxy().getScheduler().runAsync(plugin, () -> {
                    List<String> history = chatLogger.readLastLines(finalFilter, finalLines);

                    sender.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));
                    sender.sendMessage(color("&eChatHistory &8| &f" + history.size() + " Zeilen"
                            + (finalFilter != null ? " &8| &7Spieler: &f" + finalFilter : "")));
                    sender.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));

                    if (history.isEmpty()) {
                        sender.sendMessage(color("&7Keine Eintr\u00e4ge gefunden."));
                    } else {
                        for (String line : history) {
                            sender.sendMessage(color("&8" + line));
                        }
                    }
                    sender.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));
                });
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, chatHistCmd);

        // /mentions – Mentions ein-/ausschalten
        Command mentionsCmd = new Command("mentions", null, "mention") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(color("&cNur Spieler!")); return; }
                if (!config.isMentionsEnabled()) { sender.sendMessage(color("&cMentions sind deaktiviert.")); return; }
                if (!config.isMentionsAllowToggle()) { sender.sendMessage(color("&cDas Deaktivieren von Mentions ist nicht erlaubt.")); return; }

                ProxiedPlayer p = (ProxiedPlayer) sender;
                if (mentionsDisabled.contains(p.getUniqueId())) {
                    mentionsDisabled.remove(p.getUniqueId());
                    p.sendMessage(color("&aMentions &l✔ &aaktiviert. Du wirst benachrichtigt wenn jemand @" + p.getName() + " schreibt."));
                } else {
                    mentionsDisabled.add(p.getUniqueId());
                    p.sendMessage(color("&cMentions &l✘ &cdeaktiviert. Du wirst nicht mehr benachrichtigt."));
                }
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, mentionsCmd);

        // /chatbypass – Chat-Verarbeitung f\u00fcr n\u00e4chste Eingabe(n) \u00fcberspringen
        Command bypassCmd = new Command("chatbypass", null, "cbp") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(color("&cNur Spieler!")); return; }
                ProxiedPlayer p = (ProxiedPlayer) sender;

                if (awaitingInput.contains(p.getUniqueId())) {
                    awaitingInput.remove(p.getUniqueId());
                    p.sendMessage(color("&aChatModule &l✔ &aaktiv."));
                } else {
                    awaitingInput.add(p.getUniqueId());
                    p.sendMessage(color("&eChatModule &l⏸ &e\u00fcberbr\u00fcckt. &7N\u00e4chste Nachricht geht direkt an den Server."));
                    p.sendMessage(color("&7Mit &f/chatbypass &7wieder einschalten."));
                }
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, bypassCmd);

        // /discordlink – Discord-Account verkn\u00fcpfen
        Command discordLinkCmd = new Command("discordlink", null, "dlink") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(color("&cNur Spieler!")); return; }
                if (!config.isDiscordEnabled()) { sender.sendMessage(color("&cDiscord ist nicht aktiviert.")); return; }
                ProxiedPlayer p = (ProxiedPlayer) sender;

                String token = linkManager.generateToken(p.getUniqueId(), p.getName(), "discord");

                p.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));
                p.sendMessage(color("&9&lDiscord-Verkn\u00fcpfung"));
                p.sendMessage(color("&7Schreibe dem Bot auf Discord:"));
                p.sendMessage(color("&f!link &b" + token));
                p.sendMessage(color("&7Token g\u00fcltig f\u00fcr &f10 Minuten&7."));
                p.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, discordLinkCmd);

        // /telegramlink – Telegram-Account verkn\u00fcpfen
        Command telegramLinkCmd = new Command("telegramlink", null, "tlink") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(color("&cNur Spieler!")); return; }
                if (!config.isTelegramEnabled()) { sender.sendMessage(color("&cTelegram ist nicht aktiviert.")); return; }
                ProxiedPlayer p = (ProxiedPlayer) sender;

                String token = linkManager.generateToken(p.getUniqueId(), p.getName(), "telegram");

                p.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));
                p.sendMessage(color("&3&lTelegram-Verkn\u00fcpfung"));
                p.sendMessage(color("&7Schreibe dem Bot auf Telegram:"));
                p.sendMessage(color("&f/link &b" + token));
                p.sendMessage(color("&7Token g\u00fcltig f\u00fcr &f10 Minuten&7."));
                p.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, telegramLinkCmd);

        // /unlink – Verkn\u00fcpfung aufheben
        Command unlinkCmd = new Command("unlink") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(color("&cNur Spieler!")); return; }
                ProxiedPlayer p = (ProxiedPlayer) sender;

                if (args.length == 0) {
                    p.sendMessage(color("&cBenutzung: /unlink <discord|telegram|all>"));
                    return;
                }

                switch (args[0].toLowerCase()) {
                    case "discord":
                        if (linkManager.unlinkDiscord(p.getUniqueId()))
                            p.sendMessage(color("&aDiscord-Verkn\u00fcpfung aufgehoben."));
                        else
                            p.sendMessage(color("&cKein Discord-Account verkn\u00fcpft."));
                        break;
                    case "telegram":
                        if (linkManager.unlinkTelegram(p.getUniqueId()))
                            p.sendMessage(color("&aTelegram-Verkn\u00fcpfung aufgehoben."));
                        else
                            p.sendMessage(color("&cKein Telegram-Account verkn\u00fcpft."));
                        break;
                    case "all":
                        boolean d = linkManager.unlinkDiscord(p.getUniqueId());
                        boolean t = linkManager.unlinkTelegram(p.getUniqueId());
                        if (d || t) p.sendMessage(color("&aAlle Verkn\u00fcpfungen aufgehoben."));
                        else        p.sendMessage(color("&cKeine Verkn\u00fcpfungen vorhanden."));
                        break;
                    default:
                        p.sendMessage(color("&cBenutzung: /unlink <discord|telegram|all>"));
                }
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, unlinkCmd);

        // /report <spieler> <grund>
        Command reportCmd = new Command("report") {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (reportManager == null) { sender.sendMessage(color("&cDas Report-System ist deaktiviert.")); return; }
                if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(color("&cNur Spieler!")); return; }

                ProxiedPlayer p = (ProxiedPlayer) sender;

                String reqPerm = config.getReportPermission();
                if (reqPerm != null && !reqPerm.isEmpty() && !p.hasPermission(reqPerm)) {
                    p.sendMessage(color("&cDu hast keine Berechtigung f\u00fcr /report.")); return;
                }

                if (args.length < 2) {
                    p.sendMessage(color("&cBenutzung: /report <Spieler> <Grund>"));
                    return;
                }

                long now = System.currentTimeMillis() / 1000L;
                Long last = reportCooldowns.get(p.getUniqueId());
                if (last != null && (now - last) < config.getReportCooldown()) {
                    long wait = config.getReportCooldown() - (now - last);
                    p.sendMessage(color("&cBitte warte noch &f" + wait + "s &cvor dem n\u00e4chsten Report."));
                    return;
                }

                String reportedName = args[0];
                String reason       = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
                String server       = p.getServer() != null ? p.getServer().getInfo().getName() : "Proxy";
                String msgContext   = lastChatMessages.getOrDefault(reportedName.toLowerCase(), "(keine Chat-Nachricht bekannt)");

                String reportId = reportManager.createReport(
                    p.getName(), p.getUniqueId(), reportedName, server, msgContext, reason);

                if (chatLogger != null) {
                    String logMsg = "[REPORT] Reporter: " + p.getName() + ", Gemeldet: " + reportedName
                            + ", Grund: " + reason + " | Letzte Nachricht: " + msgContext
                            + " | Report-ID: " + reportId;
                    chatLogger.log(reportId, server, "report", p.getName(), logMsg);
                }

                String reportWebhook = config.getReportDiscordWebhook();
                if (config.isReportWebhookEnabled() && discordBridge != null
                        && reportWebhook != null && !reportWebhook.isEmpty()) {
                    String title = "Neuer Report eingegangen";
                    String desc = "**Reporter:** " + p.getName()
                            + "\n**Gemeldet:** " + reportedName
                            + "\n**Server:** " + server
                            + "\n**Grund:** " + reason
                            + "\n**Letzte Nachricht:** " + msgContext
                            + "\n**Report-ID:** " + reportId;
                    discordBridge.sendEmbedToDiscord(reportWebhook, title, desc, config.getDiscordEmbedColor());
                }

                String reportTgChatId = config.getReportTelegramChatId();
                if (telegramBridge != null && reportTgChatId != null && !reportTgChatId.isEmpty()) {
                    String header = "Neuer Report eingegangen";
                    String content = "Reporter: " + p.getName()
                            + "\nGemeldet: " + reportedName
                            + "\nServer: " + server
                            + "\nGrund: " + reason
                            + "\nLetzte Nachricht: " + msgContext
                            + "\nReport-ID: " + reportId;
                    telegramBridge.sendFormattedToTelegram(reportTgChatId, header, content);
                }

                reportCooldowns.put(p.getUniqueId(), now);

                String confirm = config.getReportConfirm().replace("{id}", reportId);
                p.sendMessage(color(confirm));

                notifyAdminsReport(reportId, p.getName(), reportedName, server, reason, msgContext);
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, reportCmd);

        // /reports [all] – Admin-\u00dcbersicht
        Command reportsCmd = new Command("reports", config.getReportViewPermission()) {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (reportManager == null) { sender.sendMessage(color("&cDas Report-System ist deaktiviert.")); return; }

                boolean showAll = args.length > 0 && args[0].equalsIgnoreCase("all");
                List<ReportManager.ChatReport> list = showAll
                        ? reportManager.getAllReports()
                        : reportManager.getOpenReports();

                sender.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));
                sender.sendMessage(color("&c&l⚑ REPORTS &8| &f" + list.size()
                        + (showAll ? " gesamt" : " offen")
                        + " &8| &7/reports all f\u00fcr alle"));
                sender.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));

                if (list.isEmpty()) {
                    sender.sendMessage(color("&7Keine " + (showAll ? "" : "offenen ") + "Reports vorhanden."));
                    return;
                }

                for (ReportManager.ChatReport r : list) {
                    String statusColor = r.closed ? "&a✔" : "&c✘";

                    if (sender instanceof ProxiedPlayer) {
                        ComponentBuilder line = new ComponentBuilder("");

                        line.append(ChatColor.translateAlternateColorCodes('&', statusColor + " "))
                            .event((ClickEvent) null)
                            .event((HoverEvent) null);

                        line.append(ChatColor.translateAlternateColorCodes('&', "&8[&f" + r.id + "&8]"))
                            .event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, r.id))
                            .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
                                new ComponentBuilder(ChatColor.GRAY + "Klicken zum Kopieren: " + r.id
                                    + "\n" + ChatColor.YELLOW + "Reporter: " + ChatColor.WHITE + r.reporterName
                                    + "\n" + ChatColor.YELLOW + "Gemeldet: " + ChatColor.RED  + r.reportedName
                                    + "\n" + ChatColor.YELLOW + "Server: "   + ChatColor.GREEN + r.server
                                    + "\n" + ChatColor.YELLOW + "Zeit: "     + ChatColor.WHITE + r.getFormattedTime()
                                    + "\n" + ChatColor.YELLOW + "Kontext: "  + ChatColor.GRAY  + r.messageContext
                                    + "\n" + ChatColor.YELLOW + "Grund: "    + ChatColor.RED   + r.reason
                                    + (r.closed ? "\n" + ChatColor.GREEN + "Geschlossen von: " + r.closedBy : "")).create()));

                        line.append(ChatColor.translateAlternateColorCodes('&',
                                " &b" + r.reportedName + " &8← &7" + r.reporterName
                                + " &8@ &a" + r.server
                                + " &8| &e" + r.getFormattedTime()))
                            .event((ClickEvent) null)
                            .event((HoverEvent) null);

                        ((ProxiedPlayer) sender).sendMessage(line.create());
                    } else {
                        sender.sendMessage(color(statusColor + " &8[&f" + r.id + "&8] &b" + r.reportedName
                                + " &8← &7" + r.reporterName + " &8@ &a" + r.server
                                + " &8| &e" + r.getFormattedTime()
                                + " &8| &cGrund: &f" + r.reason));
                    }
                }

                sender.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));
                if (!showAll && sender instanceof ProxiedPlayer) {
                    sender.sendMessage(color("&7Tipp: &f/reportclose <ID> &7zum Schlie\u00dfen."));
                }
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, reportsCmd);

        // /reportclose <ID>
        Command reportCloseCmd = new Command("reportclose", config.getReportClosePermission()) {
            @Override
            public void execute(CommandSender sender, String[] args) {
                if (reportManager == null) { sender.sendMessage(color("&cDas Report-System ist deaktiviert.")); return; }
                if (args.length == 0) { sender.sendMessage(color("&cBenutzung: /reportclose <ID>")); return; }

                String id = args[0].toUpperCase();
                ReportManager.ChatReport report = reportManager.getReport(id);

                if (report == null) {
                    sender.sendMessage(color("&cReport &f" + id + " &cnicht gefunden.")); return;
                }
                if (report.closed) {
                    sender.sendMessage(color("&cReport &f" + id + " &cist bereits geschlossen"
                            + (report.closedBy != null && !report.closedBy.isEmpty()
                               ? " &8(von &7" + report.closedBy + "&8)" : "") + "."));
                    return;
                }

                String adminName = (sender instanceof ProxiedPlayer) ? sender.getName() : "Konsole";
                reportManager.closeReport(id, adminName);

                sender.sendMessage(color("&aReport &f" + id + " &awurde geschlossen."));

                ProxiedPlayer reporter = ProxyServer.getInstance().getPlayer(report.reporterUUID);
                if (reporter != null && reporter.isConnected()) {
                    reporter.sendMessage(color("&8[&aReport&8] &7Dein Report &f" + id
                            + " &7gegen &c" + report.reportedName + " &7wurde von &b" + adminName + " &7bearbeitet."));
                }

                notifyAdmins("&8[&aReport geschlossen&8] &f" + adminName + " &7hat Report &f" + id + " &7geschlossen.");
            }
        };
        ProxyServer.getInstance().getPluginManager().registerCommand(plugin, reportCloseCmd);
    }

    // =========================================================
    //  PLUGIN-INPUT BYPASS
    // =========================================================

    /**
     * \u00d6ffentliche API f\u00fcr Sub-Server-Plugins oder BungeeCord-eigene Plugins.
     * Setzt den Bypass-Status f\u00fcr einen Spieler.
     *
     * Beispiel aus einem anderen BungeeCord-Plugin:
     *   ChatModule chatModule = (ChatModule) proxy.getPluginManager()
     *       .getPlugin("StatusAPI").getModule("ChatModule");
     *   chatModule.setAwaitingInput(player.getUniqueId(), true);
     */
    public void setAwaitingInput(UUID uuid, boolean awaiting) {
        if (awaiting) awaitingInput.add(uuid);
        else          awaitingInput.remove(uuid);
    }

    public boolean isAwaitingInput(UUID uuid) {
        return awaitingInput.contains(uuid);
    }

    // =========================================================
    //  VANISH-HILFSMETHODEN
    // =========================================================

    /**
     * Sucht einen Spieler nach Name und ber\u00fccksichtigt den Vanish-Status.
     *
     * @param name        Spielername (case-insensitiv)
     * @param callerIsAdmin true → Vanished Spieler werden ebenfalls gefunden
     * @return ProxiedPlayer oder null wenn nicht gefunden / vanished (f\u00fcr Nicht-Admins)
     */
    private ProxiedPlayer findVisiblePlayer(String name, boolean callerIsAdmin) {
        ProxiedPlayer target = ProxyServer.getInstance().getPlayer(name);
        if (target == null) return null;
        if (!callerIsAdmin && VanishProvider.isVanished(target)) return null;
        return target;
    }

    // =========================================================
    //  HILFSMETHODEN
    // =========================================================

    private String buildFormat(String format, String server, String prefix,
                               String player, String suffix, String message) {
        String serverColor   = config.getServerColor(server);
        String serverDisplay = config.getServerDisplay(server);
        // Nur den Servernamen-Teil vor\u00fcbersetzen damit &#RRGGBB im Display-Namen
        // korrekt sitzt; der Rest wird am Ausgabepunkt via translateColors() \u00fcbersetzt.
        String coloredServer = serverColor + serverDisplay + "&r";

        return format
                .replace("{server}",  coloredServer)
                .replace("{prefix}",  prefix  != null ? prefix  : "")
                .replace("{player}",  player)
                .replace("{suffix}",  suffix  != null ? suffix  : "")
                .replace("{message}", message);
    }

    private String buildSimpleFormat(String format, String... kvPairs) {
        String result = format;
        for (int i = 0; i + 1 < kvPairs.length; i += 2) {
            result = result.replace("{" + kvPairs[i] + "}", kvPairs[i + 1]);
        }
        return result;
    }

    private TextComponent color(String text) {
        return new TextComponent(translateColors(text));
    }

    /**
     * \u00dcbersetzt sowohl klassische &-Farbcodes als auch HEX-Codes im Format &#RRGGBB.
     */
    private String translateColors(String text) {
        if (text == null) return "";

        StringBuilder sb = new StringBuilder();
        int i = 0;
        while (i < text.length()) {
            if (i + 7 < text.length()
                    && text.charAt(i) == '&'
                    && text.charAt(i + 1) == '#') {
                String hex = text.substring(i + 2, i + 8);
                boolean validHex = hex.chars().allMatch(c ->
                        (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
                if (validHex) {
                    try {
                        sb.append(ChatColor.of("#" + hex).toString());
                        i += 8;
                        continue;
                    } catch (Exception ignored) {
                        // Ung\u00fcltige Farbe → als normalen Text behandeln
                    }
                }
            }
            sb.append(text.charAt(i));
            i++;
        }

        return ChatColor.translateAlternateColorCodes('&', sb.toString());
    }

    private void notifyAdmins(String message) {
        for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
            if (p.hasPermission(config.getAdminNotifyPermission())) {
                p.sendMessage(color(message));
            }
        }
    }

    /**
     * Benachrichtigt alle online Admins \u00fcber einen neuen Report.
     */
    private void notifyAdminsReport(String reportId, String reporter, String reported,
                                     String server, String reason, String msgContext) {
        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
        String zeit = sdf.format(new java.util.Date());

        for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
            if (!p.hasPermission(config.getAdminNotifyPermission())
                    && !p.hasPermission(config.getAdminBypassPermission())) continue;

            p.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));
            p.sendMessage(color("&8[&cReport&8] &7Zeit: &e" + zeit));
            p.sendMessage(color("&7Reporter: &b" + reporter));
            p.sendMessage(color("&7Server: &a" + server));
            p.sendMessage(color("&7Letzte Nachricht: &f" + msgContext));
            p.sendMessage(color("&7Grund: &c" + reason));

            ComponentBuilder idLine = new ComponentBuilder(ChatColor.GRAY + "ID: ");
            idLine.append(ChatColor.WHITE + "" + ChatColor.BOLD + reportId)
                  .event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, reportId))
                  .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
                          new ComponentBuilder(ChatColor.GRAY + "Klicken zum Kopieren · "
                                  + ChatColor.YELLOW + "/reportclose " + reportId).create()))
                  .append(ChatColor.GRAY + " (klicken zum Kopieren)", ComponentBuilder.FormatRetention.NONE)
                  .event((ClickEvent) null)
                  .event((HoverEvent) null);
            p.sendMessage(idLine.create());

            p.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"));
        }

        ProxyServer.getInstance().getConsole().sendMessage(color(
                "&8[&cReport " + reportId + "&8] &7Reporter: &b" + reporter
                + " &7→ &c" + reported + " &7@ &a" + server + " &8| &cGrund: &f" + reason));
    }

    /**
     * Baut eine BungeeCord-Nachricht mit klickbarem [⚑] Melden-Button.
     */
    private BaseComponent[] buildClickableMessage(String msgId, String formatted, String senderName) {
        ComponentBuilder builder = new ComponentBuilder("");

        builder.append(translateColors(formatted),
                       ComponentBuilder.FormatRetention.NONE)
               .event((ClickEvent) null)
               .event((HoverEvent) null);

        if (msgId != null && senderName != null && reportManager != null) {
            builder.append(" ", ComponentBuilder.FormatRetention.NONE)
                   .event((ClickEvent) null)
                   .event((HoverEvent) null);
            builder.append(ChatColor.DARK_GRAY + "[" + ChatColor.RED + "⚑" + ChatColor.DARK_GRAY + "]",
                           ComponentBuilder.FormatRetention.NONE)
                   .event(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND,
                           "/report " + senderName + " "))
                   .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
                           new ComponentBuilder(ChatColor.GRAY + "Spieler melden\n"
                                   + ChatColor.YELLOW + "/report " + senderName + " <Grund>").create()));
        }

        return builder.create();
    }

    private boolean isBedrock(ProxiedPlayer player) {
        return player.getName().startsWith(GEYSER_PREFIX);
    }

    /**
     * Sendet einen Sound-Hinweis via Actionbar (Mention-Feedback).
     */
    private void sendMentionSound(ProxiedPlayer player, String soundName) {
        if (soundName == null || soundName.isEmpty()) return;
        try {
            net.md_5.bungee.api.chat.TextComponent actionBar =
                    new net.md_5.bungee.api.chat.TextComponent(
                            ChatColor.translateAlternateColorCodes('&', "&e♪ Mention!"));
            player.sendMessage(net.md_5.bungee.api.ChatMessageType.ACTION_BAR, actionBar);
        } catch (Exception ignored) {}
    }

    private String getLuckPermsPrefix(ProxiedPlayer player) {
        try {
            net.luckperms.api.LuckPerms lp = net.luckperms.api.LuckPermsProvider.get();
            net.luckperms.api.model.user.User user = lp.getUserManager().getUser(player.getUniqueId());
            if (user == null) return "";
            String prefix = user.getCachedData().getMetaData().getPrefix();
            if (prefix == null) return "";
            return ChatColor.translateAlternateColorCodes('&', prefix);
        } catch (Exception e) {
            return "";
        }
    }

    private String getLuckPermsSuffix(ProxiedPlayer player) {
        try {
            net.luckperms.api.LuckPerms lp = net.luckperms.api.LuckPermsProvider.get();
            net.luckperms.api.model.user.User user = lp.getUserManager().getUser(player.getUniqueId());
            if (user == null) return "";
            String suffix = user.getCachedData().getMetaData().getSuffix();
            if (suffix == null) return "";
            return ChatColor.translateAlternateColorCodes('&', suffix);
        } catch (Exception e) {
            return "";
        }
    }

    // =========================================================
    //  EXTERNE BR\u00dcCKEN
    // =========================================================

    private void bridgeToDiscord(ChatChannel channel, String playerName, String message, String server) {
        if (discordBridge == null) return;
        String cleanMessage = "[" + server + "] " + playerName + ": "
                + ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', message));
        if (!channel.getDiscordWebhook().isEmpty()) {
            discordBridge.sendToDiscord(channel.getDiscordWebhook(), playerName, cleanMessage, null);
        }
        if (!channel.getDiscordChannelId().isEmpty()) {
            discordBridge.sendToChannel(channel.getDiscordChannelId(), cleanMessage);
        }
        if (channel.isUseAdminBridge() && !config.getDiscordAdminChannelId().isEmpty()) {
            discordBridge.sendToChannel(config.getDiscordAdminChannelId(), cleanMessage);
        }
    }

    private void bridgeToTelegram(ChatChannel channel, String playerName, String message, String server) {
        if (telegramBridge == null) return;
        String cleanMessage = "[" + server + "] " + playerName + ": "
                + ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', message));
        if (!channel.getTelegramChatId().isEmpty()) {
            telegramBridge.sendToTelegram(channel.getTelegramChatId(),
                    channel.getTelegramThreadId(), cleanMessage);
        }
        if (channel.isUseAdminBridge() && !config.getTelegramAdminChatId().isEmpty()) {
            telegramBridge.sendToTelegram(config.getTelegramAdminChatId(),
                    config.getTelegramAdminTopicId(), cleanMessage);
        }
    }
}