/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.bausystem.features.util;

import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.event.platform.CommandEvent;
import com.sk89q.worldedit.extension.platform.Actor;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.SWUtils;
import de.steamwar.bausystem.features.script.ScriptRunner;
import de.steamwar.bausystem.features.world.WorldEditListener;
import de.steamwar.bausystem.utils.WorldEditUtils;
import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
import de.steamwar.linkage.Linked;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.command.CommandMap;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataType;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import java.util.stream.Collectors;

@Linked
public class BindCommand extends SWCommand implements Listener {

    private static final boolean hasFAWE = Bukkit.getPluginManager().getPlugin("FastAsyncWorldEdit") != null;

    @Linked
    public static class UnBindCommand extends SWCommand {

        public UnBindCommand() {
            super("unbind");
        }

        @Register
        public void generic(Player player) {
            bindInternal(player);
        }
    }

    private static final CommandMap commandMap;

    static {
        Field knownCommandsField;
        try {
            knownCommandsField = Bukkit.getServer().getClass().getDeclaredField("commandMap");
            knownCommandsField.setAccessible(true);
            commandMap = (CommandMap) knownCommandsField.get(Bukkit.getServer());
        } catch (IllegalAccessException | NoSuchFieldException var2) {
            Bukkit.shutdown();
            throw new SecurityException("Oh shit. Commands cannot be registered.", var2);
        }
    }

    private static final NamespacedKey KEY = SWUtils.getNamespaceKey("command");

    public BindCommand() {
        super("bind");
    }

    @Register(description = "OTHER_BIND_HELP")
    public void bind(@Validator Player player, @Mapper("command") @ErrorMessage("OTHER_BIND_ERROR") String... command) {
        bindInternal(player, command);
    }

    private static void bindInternal(Player player, String... command) {
        ItemStack item = player.getInventory().getItemInMainHand();
        ItemMeta meta = item.getItemMeta();
        if (meta == null) {
            BauSystem.MESSAGE.send("OTHER_BIND_UNBINDABLE", player);
            return;
        }

        if (command.length != 0) {
            String commandText = String.join(" ", command);
            meta.getPersistentDataContainer().set(KEY, PersistentDataType.STRING, commandText);
            meta.setDisplayName(BauSystem.MESSAGE.parse("OTHER_BIND_LORE", player, commandText));
            BauSystem.MESSAGE.send("OTHER_BIND_MESSAGE_BIND", player, commandText);
        } else {
            meta.getPersistentDataContainer().remove(KEY);
            meta.setDisplayName(null);
            BauSystem.MESSAGE.send("OTHER_BIND_MESSAGE_UNBIND", player);
        }
        item.setItemMeta(meta);
        player.getInventory().setItemInMainHand(item);
    }

    @EventHandler
    public void onPlayerInteract(PlayerInteractEvent event) {
        if (!event.hasItem()) return;
        if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
        ItemStack itemStack = event.getItem();
        ItemMeta meta = itemStack.getItemMeta();
        if (meta == null) {
            return;
        }

        String command = meta.getPersistentDataContainer().get(KEY, PersistentDataType.STRING);
        if (command == null) {
            return;
        }

        event.setCancelled(true);
        Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
            PlayerCommandPreprocessEvent preprocessEvent = new PlayerCommandPreprocessEvent(event.getPlayer(), "/" + command);
            Bukkit.getPluginManager().callEvent(preprocessEvent);
            if (preprocessEvent.isCancelled()) return;

            String processedCommand = preprocessEvent.getMessage().substring(1);
            Bukkit.getLogger().log(Level.INFO, event.getPlayer().getName() + " dispatched command: " + processedCommand);
            String[] commandSplit = processedCommand.split(" ");
            if (!commandSplit[0].equals("select") && hasFAWE && WorldEditListener.isWorldEditCommand("/" + commandSplit[0])) {
                EditSession editSession = WorldEditUtils.getEditSession(event.getPlayer());
                Actor actor = BukkitAdapter.adapt(event.getPlayer());
                WorldEdit.getInstance().getPlatformManager().getPlatformCommandManager().handleCommandOnCurrentThread(new CommandEvent(actor, processedCommand, editSession));
                editSession.flushSession();
                WorldEditUtils.addToPlayer(event.getPlayer(), editSession);
            } else {
                Bukkit.getServer().dispatchCommand(event.getPlayer(), processedCommand);
            }
        }, 1);
    }

    @Mapper(value = "command", local = true)
    public TypeMapper<String> getCommandMapper() {
        return new TypeMapper<String>() {
            @Override
            public String map(CommandSender sender, PreviousArguments previousArguments, String s) {
                return s;
            }

            @Override
            public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments, String s) {
                if (previousArguments.mappedArgs.length == 0) return null;
                Object[] args = (Object[]) previousArguments.mappedArgs[previousArguments.mappedArgs.length - 1];
                List<String> tabCompletes;
                if (args.length == 0) {
                    tabCompletes = commandMap.tabComplete(sender, "");
                } else {
                    tabCompletes = commandMap.tabComplete(sender, Arrays.stream(args).map(Objects::toString).collect(Collectors.joining(" ")) + " ");
                }
                if (tabCompletes == null) return null;
                if (args.length == 0) {
                    tabCompletes = tabCompletes.stream()
                            .map(t -> t.substring(1))
                            .filter(t -> !t.equals("bind"))
                            .collect(Collectors.toList());
                    tabCompletes.addAll(ScriptRunner.getCommandsOfPlayer((Player) sender));
                    return tabCompletes.stream()
                            .filter(t -> !t.contains(":"))
                            .collect(Collectors.toList());
                }
                return tabCompletes;
            }
        };
    }
}
