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

import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.tracer.rendering.BundleFilter;
import de.steamwar.bausystem.features.tracer.rendering.PlayerTraceShowData;
import de.steamwar.bausystem.features.tracer.rendering.ViewFlag;
import de.steamwar.bausystem.features.tracer.rendering.dynamicflags.AtFlag;
import de.steamwar.bausystem.region.Region;
import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
import de.steamwar.linkage.Linked;
import net.md_5.bungee.api.chat.ClickEvent;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Linked
public class TraceCommand extends SWCommand {

    public TraceCommand() {
        super("trace", "trail");
    }

    @Register(value = "start", description = "TRACE_COMMAND_HELP_START")
    public void start(@Validator Player player) {
        Region region = Region.getRegion(player.getLocation());
        TraceRecorder.instance.startRecording(region);
        BauSystem.MESSAGE.send("TRACE_MESSAGE_START", player);
    }

    @Register(value = "stop", description = "TRACE_COMMAND_HELP_STOP")
    public void stop(@Validator Player player) {
        Region region = Region.getRegion(player.getLocation());
        TraceRecorder.instance.stopRecording(region);


        if (TraceRecorder.instance.isAutoTraceEnabledInRegion(region)) {
            TraceRecorder.instance.removeAutoTraceRegion(region);
            BauSystem.MESSAGE.send("TRACE_MESSAGE_AUTO_STOP", player);
        } else {
            BauSystem.MESSAGE.send("TRACE_MESSAGE_STOP", player);
        }
    }

    @Register(value = "auto", description = "TRACE_COMMAND_HELP_AUTO")
    public void auto(@Validator Player player) {
        Region region = Region.getRegion(player.getLocation());
        TraceRecorder.instance.addAutoTraceRegion(region);
        BauSystem.MESSAGE.send("TRACE_MESSAGE_AUTO_START", player);
    }

    @Register(value = "show", description = "TRACE_COMMAND_HELP_SHOW")
    public void show(@Validator Player player, @OptionalValue("DEFAULT") BundleFilter bundleFilter, ViewFlag... flags) {
        showInternal(player, bundleFilter, flags);
        BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW", player);
    }

    @Register(value = {"show", "at"}, description = "TRACE_COMMAND_HELP_SHOW_AT_WITH")
    public void showAt(@Validator Player player, @Min(intValue = 0) int time, @StaticValue("with") String with, @OptionalValue("DEFAULT") BundleFilter bundleFilter, ViewFlag... flags) {
        showInternal(player, time, time, bundleFilter, flags);
        BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_AT", player, time);
    }

    @Register(value = {"show", "from"}, description = "TRACE_COMMAND_HELP_SHOW_FROM_WITH")
    public void showFromTo(@Validator Player player, @Min(intValue = 0) int from, @StaticValue("with") String with, @OptionalValue("DEFAULT") BundleFilter bundleFilter, ViewFlag... flags) {
        showInternal(player, from, Integer.MAX_VALUE, bundleFilter, flags);
        BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_FROM", player, from);
    }

    @Register(value = {"show", "from"}, description = "TRACE_COMMAND_HELP_SHOW_FROM_TO_WITH")
    public void showFromTo(@Validator Player player, @Min(intValue = 0) int from, @StaticValue("to") String toString, int to, @StaticValue("with") String with, @OptionalValue("DEFAULT") BundleFilter bundleFilter, ViewFlag... flags) {
        if (to < from) {
            BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_TO_SMALLER", player);
            return;
        }
        showInternal(player, from, to, bundleFilter, flags);
        BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_FROM_TO", player, from, to);
    }


    @Register(value = {"show", "at"}, description = "TRACE_COMMAND_HELP_SHOW_AT")
    public void showAt(@Validator Player player, @Min(intValue = 0) int time) {
        TraceManager.instance.renderAt(player, time, time);
        BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_AT", player, time);
    }

    @Register(value = {"show", "from"}, description = "TRACE_COMMAND_HELP_SHOW_FROM")
    public void showFrom(@Validator Player player, @Min(intValue = 0) int from) {
        TraceManager.instance.renderAt(player, from, Integer.MAX_VALUE);
        BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_FROM", player, from);
    }

    @Register(value = {"show", "from"}, description = "TRACE_COMMAND_HELP_SHOW_FROM_TO")
    public void showFromTo(@Validator Player player, @Min(intValue = 0) int from, @StaticValue("to") String toString, int to) {
        TraceManager.instance.renderAt(player, from, to);
        BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_FROM_TO", player, from, to);
    }

    private void showInternal(Player player, BundleFilter bundleFilter, ViewFlag... flags) {
        PlayerTraceShowData playerTraceShowData = new PlayerTraceShowData(bundleFilter, flags);
        TraceManager.instance.show(player, playerTraceShowData);
    }

    private void showInternal(Player player, int from, int to, BundleFilter bundleFilter, ViewFlag... flags) {
        PlayerTraceShowData playerTraceShowData = new PlayerTraceShowData(bundleFilter, flags);
        playerTraceShowData.addViewFlag(new AtFlag(from, to));
        TraceManager.instance.show(player, playerTraceShowData);
    }

    @Register(value = "hide", description = "TRACE_COMMAND_HELP_HIDE")
    public void hide(@Validator Player player) {
        TraceManager.instance.hide(player);
        BauSystem.MESSAGE.send("TRACE_MESSAGE_HIDE", player);
    }

    @Register(value = "delete")
    @Register(value = "clear")
    public void clear(@Validator Player player) {
        TraceManager.instance.clear(Region.getRegion(player.getLocation()));
        BauSystem.MESSAGE.send("TRACE_MESSAGE_CLEAR", player);
    }

    @Register(value = "delete", description = "TRACE_COMMAND_HELP_DELETE")
    public void delete(@Validator Player player, Trace trace) {
        TraceManager.instance.remove(trace);
        BauSystem.MESSAGE.send("TRACE_MESSAGE_DELETE", player);
    }

    @Register(value = "isolate", description = "TRACE_COMMAND_HELP_ISOLATE")
    public void isolate(@Validator Player player, Trace trace, @ErrorMessage("TRACE_RECORD_ID_INVALID") TNTPoint... records) {
        TraceManager.instance.isolate(player, records);
        BauSystem.MESSAGE.send("TRACE_MESSAGE_ISOLATE", player);
    }

    @Register(value = "broadcast", description = "TRACE_COMMAND_HELP_BROADCAST")
    public void broadcast(@Validator Player player) {
        BauSystem.MESSAGE.broadcast("TRACE_MESSAGE_BROADCAST", "TRACE_MESSAGE_BROADCAST_HOVER", new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/trace follow " + player.getName()), player.getName());
    }

    @Register(value = "follow", description = "TRACE_COMMAND_HELP_FOLLOW")
    public void follow(@Validator Player player, Player toFollow) {
        if (player == toFollow) {
            BauSystem.MESSAGE.send("TRACE_MESSAGE_FOLLOW_SELF", player);
            return;
        }
        TraceManager.instance.follow(player, toFollow);
        BauSystem.MESSAGE.send("TRACE_MESSAGE_FOLLOW", player, toFollow.getName());
    }

    @Register(value = "unfollow", description = "TRACE_COMMAND_HELP_UNFOLLOW")
    public void unfollow(@Validator Player player) {
        TraceManager.instance.unfollow(player);
        BauSystem.MESSAGE.send("TRACE_MESSAGE_UNFOLLOW", player);
    }

    @ClassMapper(value = Trace.class, local = true)
    public TypeMapper<Trace> traceClassMapper() {
        return new TypeMapper<>() {
            @Override
            public Trace map(CommandSender commandSender, String[] previousArguments, String s) {
                return TraceManager.instance.get(Integer.parseInt(s)).orElse(null);
            }

            @Override
            public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments, String s) {
                return TraceManager.instance.getAllIds().stream()
                        .map(Object::toString)
                        .collect(Collectors.toList());
            }
        };
    }

    @ClassMapper(value = TNTPoint.class, local = true)
    public TypeMapper<TNTPoint> recordMapper() {
        return new TypeMapper<>() {
            @Override
            public TNTPoint map(CommandSender commandSender, PreviousArguments previousArguments, String s) {
                Trace trace = previousArguments.getFirst(Trace.class).orElse(null);
                if (trace == null) return null;

                int id = Integer.parseInt(s);
                return trace.getRecords().stream()
                        .filter(record -> record.getTntId() == id)
                        .findFirst()
                        .orElse(null);
            }

            // TODO change when new command framework update
            @Override
            public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments, String s) {
                Trace trace = previousArguments.getFirst(Trace.class).orElse(null);
                if (trace == null) return null;
                return trace.getUsedIds();
            }
        };
    }

    @ClassMapper(value = BundleFilter.class, local = true)
    public TypeMapper<BundleFilter> bundleFilterClassMapper() {
        return new TypeMapper<>() {
            @Override
            public BundleFilter map(CommandSender commandSender, String[] previousArguments, String s) {
                for (BundleFilter filter : BundleFilter.values()) {
                    if (s.equals(filter.toString())) {
                        return filter;
                    }
                }
                return null;
            }

            @Override
            public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments, String s) {
                if (s.length() == 0) {
                    return new ArrayList<>();
                }

                return Arrays.stream(BundleFilter.values())
                        .map(Enum::toString)
                        .collect(Collectors.toList());
            }
        };
    }

    @ClassMapper(value = ViewFlag.class, local = true)
    public TypeMapper<ViewFlag> viewFlagClassMapper() {
        return new TypeMapper<ViewFlag>() {
            @Override
            public ViewFlag map(CommandSender commandSender, String[] previousArguments, String s) {
                for (ViewFlag flag : ViewFlag.flags) {
                    if (s.equals("-" + flag.name)) {
                        return flag;
                    }

                    for (String alias : flag.aliases) {
                        if (s.equals("-" + alias)) {
                            return flag;
                        }
                    }
                }
                return null;
            }

            @Override
            public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments, String s) {
                return ViewFlag.flags.stream()
                        .flatMap(viewFlag -> Stream.concat(Stream.of("-" + viewFlag.name),
                                Arrays.stream(viewFlag.aliases).map(name -> "-" + name)))
                        .collect(Collectors.toList());
            }
        };
    }
}
