/*
 *   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.script.lua;

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.features.script.ScriptRunner;
import de.steamwar.bausystem.features.script.lua.libs.LuaLib;
import de.steamwar.bausystem.features.world.WorldEditListener;
import de.steamwar.bausystem.utils.WorldEditUtils;
import de.steamwar.inventory.SWAnvilInv;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.luaj.vm2.LuaFunction;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Varargs;
import org.luaj.vm2.lib.*;

import java.util.*;
import java.util.logging.Level;

public class SteamWarLuaPlugin extends TwoArgFunction {

    private static final boolean hasFAWE = Bukkit.getPluginManager().getPlugin("FastAsyncWorldEdit") != null;
    private static final Set<String> ignoreCommandFromWorldEdit = Set.of("select", "schem", "/schem", "schematic", "/schematic");

    protected static final Map<Class<?>, List<LuaLib>> LUA_LIBS = new HashMap<>();

    public static void add(LuaLib luaLib) {
        LUA_LIBS.computeIfAbsent(luaLib.parent(), l -> new ArrayList<>()).add(luaLib);
    }

    private final Player player;

    public SteamWarLuaPlugin(Player player) {
        this.player = player;
    }

    @Override
    public LuaValue call(LuaValue modname, LuaValue env) {
        LuaValue materialLib = tableOf();
        for (Material mat : Material.values()) {
            materialLib.set(mat.name().toLowerCase(), valueOf(mat.name()));
        }

        initialize(env, null);

        env.set("print", new Print());
        env.set("input", new TwoArgFunction() {
            @Override
            public LuaValue call(LuaValue arg1, LuaValue arg2) {
                String message = arg1.tojstring();
                LuaFunction callback = arg2.checkfunction();

                SWAnvilInv inv = new SWAnvilInv(player, message);
                inv.setCallback(s -> ScriptRunner.catchScript("SCRIPT_ERROR_CLICK", player, () -> callback.call(valueOf(s))));
                inv.open();

                return LuaValue.NIL;
            }
        });
        env.set("delayed", new TwoArgFunction() {
            @Override
            public LuaValue call(LuaValue arg1, LuaValue arg2) {
                long time = arg1.checklong();
                LuaFunction callback = arg2.checkfunction();

                Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> ScriptRunner.catchScript("SCRIPT_ERROR_CLICK", player, callback::call), time);
                return LuaValue.NIL;
            }
        });
        env.set("pos", new ThreeArgFunction() {
            @Override
            public LuaValue call(LuaValue arg1, LuaValue arg2, LuaValue arg3) {
                double x = arg1.checkdouble();
                double y = arg2.checkdouble();
                double z = arg3.checkdouble();
                return pos(x, y, z);
            }
        });
        env.set("exec", new VarArgFunction() {
            @Override
            public Varargs invoke(Varargs args) {
                String command = varArgsToString(args);
                PlayerCommandPreprocessEvent preprocessEvent = new PlayerCommandPreprocessEvent(player, "/" + command);
                Bukkit.getServer().getPluginManager().callEvent(preprocessEvent);
                if (preprocessEvent.isCancelled()) {
                    return LuaValue.NIL;
                }

                command = preprocessEvent.getMessage().substring(1);
                Bukkit.getLogger().log(Level.INFO, player.getName() + " dispatched command: " + command);
                String[] commandSplit = command.split(" ");
                if (!ignoreCommandFromWorldEdit.contains(commandSplit[0]) && hasFAWE && WorldEditListener.isWorldEditCommand("/" + commandSplit[0])) {
                    EditSession editSession = WorldEditUtils.getEditSession(player);
                    Actor actor = BukkitAdapter.adapt(player);
                    WorldEdit.getInstance().getPlatformManager().getPlatformCommandManager().handleCommandOnCurrentThread(new CommandEvent(actor, command, editSession));
                    editSession.flushSession();
                    WorldEditUtils.addToPlayer(player, editSession);
                } else {
                    Bukkit.getServer().dispatchCommand(player, command);
                }
                return LuaValue.NIL;
            }
        });
        env.set("length", new OneArgFunction() {
            @Override
            public LuaValue call(LuaValue arg) {
                return arg.len();
            }
        });
        env.set("join", new TwoArgFunction() {
            @Override
            public LuaValue call(LuaValue arg1, LuaValue arg2) {
                String separator = arg1.checkjstring();
                LuaTable table = arg2.checktable();

                StringBuilder builder = new StringBuilder();
                for (int i = 1; i <= table.length(); i++) {
                    if (builder.length() != 0) builder.append(separator);
                    builder.append(table.get(i).tojstring());
                }
                return valueOf(builder.toString());
            }
        });

        env.set("collectgarbage", NIL);
        env.set("dofile", NIL);
        env.set("load", NIL);
        env.set("loadfile", NIL);
        env.set("pcall", NIL);
        env.set("rawequal", NIL);
        env.set("rawget", NIL);
        env.set("rawlen", NIL);
        env.set("rawset", NIL);
        env.set("xpcall", NIL);
        env.set("require", NIL);
        env.set("package", NIL);
        return null;
    }

    public static LuaTable pos(double x, double y, double z) {
        LuaTable position = new LuaTable();
        position.set("x", x);
        position.set("y", y);
        position.set("z", z);

        position.set("add", new OneArgFunction() {
            @Override
            public LuaValue call(LuaValue luaValue) {
                LuaTable table = luaValue.checktable();
                double dx = table.get("x").checkdouble();
                double dy = table.get("y").checkdouble();
                double dz = table.get("z").checkdouble();
                return pos(x + dx, y + dy, z + dz);
            }
        });

        position.set("subtract", new OneArgFunction() {
            @Override
            public LuaValue call(LuaValue luaValue) {
                LuaTable table = luaValue.checktable();
                double dx = table.get("x").checkdouble();
                double dy = table.get("y").checkdouble();
                double dz = table.get("z").checkdouble();
                return pos(x - dx, y - dy, z - dz);
            }
        });

        position.set("addX", new OneArgFunction() {
            @Override
            public LuaValue call(LuaValue luaValue) {
                return pos(x + luaValue.checkdouble(), y, z);
            }
        });
        position.set("subtractX", new OneArgFunction() {
            @Override
            public LuaValue call(LuaValue luaValue) {
                return pos(x - luaValue.checkdouble(), y, z);
            }
        });
        position.set("addY", new OneArgFunction() {
            @Override
            public LuaValue call(LuaValue luaValue) {
                return pos(x, y + luaValue.checkdouble(), z);
            }
        });
        position.set("subtractY", new OneArgFunction() {
            @Override
            public LuaValue call(LuaValue luaValue) {
                return pos(x, y - luaValue.checkdouble(), z);
            }
        });
        position.set("addZ", new OneArgFunction() {
            @Override
            public LuaValue call(LuaValue luaValue) {
                return pos(x, y, z + luaValue.checkdouble());
            }
        });
        position.set("subtractZ", new OneArgFunction() {
            @Override
            public LuaValue call(LuaValue luaValue) {
                return pos(x, y, z - luaValue.checkdouble());
            }
        });

        position.set("blockPos", new ZeroArgFunction() {
            @Override
            public LuaValue call() {
                Location location = new Location(Bukkit.getWorlds().get(0), x, y, z);
                return pos(location.getBlockX(), location.getBlockY(), location.getBlockZ());
            }
        });

        return position;
    }

    public static String varArgsToString(Varargs args) {
        StringBuilder builder = new StringBuilder();
        for (int i = 1; i <= args.narg(); i++) {
            if (builder.length() != 0) builder.append(" ");
            LuaValue arg = args.arg(i);
            builder.append(arg.tojstring());
        }
        return builder.toString();
    }

    private void initialize(LuaValue parent, Class<? extends LuaLib> clazz) {
        LUA_LIBS.getOrDefault(clazz, Collections.emptyList()).forEach(luaLib -> {
            LuaTable luaTable = luaLib.get(player);
            parent.set(luaLib.name(), luaTable);
            initialize(luaTable, luaLib.getClass());
        });
    }

    class Print extends VarArgFunction {
        @Override
        public Varargs invoke(Varargs args) {
            player.sendMessage(ChatColor.translateAlternateColorCodes('&', varArgsToString(args)));
            return LuaValue.NIL;
        }
    }
}
