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

import de.steamwar.message.Message;
import lombok.Data;
import lombok.NonNull;
import lombok.experimental.Delegate;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.ClickEvent;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;

public class SWPlayer {

    private static final Map<Player, SWPlayer> players = new HashMap<>();

    private static class SWPlayerListener implements Listener {

        @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
        public void onPlayerJoin(PlayerJoinEvent event) {
            SWPlayer.of(event.getPlayer());
        }

        @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
        public void onPlayerQuit(PlayerQuitEvent event) {
            SWPlayer swPlayer = players.remove(event.getPlayer());
            swPlayer.components.forEach((aClass, component) -> {
                component.onUnmount(swPlayer);
            });
        }

        @EventHandler
        public void onCRIUSleep(CRIUSleepEvent event) {
            for (SWPlayer value : players.values()) {
                value.components.forEach((aClass, component) -> {
                    component.onUnmount(value);
                });
            }
            players.clear();
        }
    }

    public interface Component {

        default void onMount(SWPlayer player) {
        }

        default void onUnmount(SWPlayer player) {
        }
    }

    static {
        Bukkit.getPluginManager().registerEvents(new SWPlayerListener(), Core.getInstance());
    }

    public static @NonNull SWPlayer of(@NonNull Player player) {
        return players.computeIfAbsent(player, SWPlayer::new);
    }

    public static @NonNull Stream<SWPlayer> all() {
        return players.values().stream();
    }

    public static <T extends Component> @NonNull Stream<SWPlayerWithComponent<T>> allWithSingleComponent(Class<T> component) {
        return players.values().stream()
                .filter(player -> player.components.containsKey(component))
                .map(player -> new SWPlayerWithComponent<>(player, (T) player.components.get(component)));
    }

    @Data
    public static final class SWPlayerWithComponent<T extends Component> {
        private final SWPlayer player;
        private final T component;
    }

    @Delegate
    private final Player player;
    private final Map<Class<? extends Component>, Component> components = new HashMap<>();

    private SWPlayer(@NonNull Player player) {
        this.player = player;
    }

    public boolean hasComponent(@NonNull Class<? extends Component> component) {
        return components.containsKey(component);
    }

    public <T extends Component> @NonNull Optional<T> getComponent(@NonNull Class<T> clazz) {
        return Optional.ofNullable((T) components.get(clazz));
    }

    public <T extends Component> @NonNull Optional<T> getComponentAndFilter(@NonNull Class<T> clazz, Predicate<T> filter) {
        return Optional.ofNullable((T) components.get(clazz)).filter(filter);
    }

    public <T extends Component> @NonNull T getComponentOrDefault(@NonNull Class<T> clazz, @NonNull Supplier<T> defaultValue) {
        T value = (T) components.get(clazz);
        if (value != null) return value;
        value = defaultValue.get();
        setComponent(value);
        return value;
    }

    public <T extends Component> SWPlayer setComponent(@NonNull T value) {
        Component component = components.put(value.getClass(), value);
        if (component != null) component.onUnmount(this);
        value.onMount(this);
        return this;
    }

    public <T extends Component> SWPlayer removeComponent(@NonNull Class<T> clazz) {
        Component component = components.remove(clazz);
        if (component != null) component.onUnmount(this);
        return this;
    }

    private ThreadLocal<Message> messageThreadLocal = ThreadLocal.withInitial(() -> null);

    public SWPlayer using(Message message) {
        this.messageThreadLocal.set(message);
        return this;
    }

    private Message getMessage() {
        Message message = this.messageThreadLocal.get();
        if (message == null) {
            throw new IllegalStateException("Use #using(Message) before sending or parsing a message!");
        }
        return message;
    }

    public void sendMessage(String message, Object... params) {
        getMessage().send(message, player, ChatMessageType.SYSTEM, params);
    }

    public void sendMessagePrefixless(String message, Object... params) {
        getMessage().sendPrefixless(message, player, ChatMessageType.SYSTEM, params);
    }

    public void sendActionBar(String message, Object... params) {
        getMessage().sendPrefixless(message, player, ChatMessageType.ACTION_BAR, params);
    }

    public void sendMessage(String message, String onHover, ClickEvent onClick, Object... params) {
        getMessage().send(message, true, player, ChatMessageType.SYSTEM, onHover, onClick, params);
    }

    public void sendMessagePrefixless(String message, String onHover, ClickEvent onClick, Object... params) {
        getMessage().send(message, false, player, ChatMessageType.SYSTEM, onHover, onClick, params);
    }

    public String parsePrefixed(String message, Object... params) {
        return getMessage().parsePrefixed(message, player, params);
    }

    public String parse(String message, Object... params) {
        return getMessage().parse(message, player, params);
    }
}
