package com.shanebeestudios.skbee.api.util;

import ch.njol.skript.Skript;
import ch.njol.skript.log.ErrorQuality;
import com.shanebeestudios.skbee.SkBee;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Tag;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * General utility class
 */
public class Util {

    public static final String PREFIX = "&7[&bSk&3Bee&7] ";
    public static final String PREFIX_MINI = "<grey>[<gradient:#19C5E7:#17E9B6>SkBee</gradient><grey>] ";
    public static final String PREFIX_ERROR = "&7[&bSk&3Bee &cERROR&7] ";
    public static final String PREFIX_ERRORR_MINI = "<grey>[<gradient:#19C5E7:#17E9B6>SkBee</gradient> <red>ERROR<grey>] ";
    private static final Pattern HEX_PATTERN = Pattern.compile("<#([A-Fa-f\\d]){6}>");
    private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
    private static final boolean SKRIPT_IS_THERE = Bukkit.getPluginManager().getPlugin("Skript") != null;
    public static final boolean IS_RUNNING_FOLIA = Skript.classExists("io.papermc.paper.threadedregions.FoliaWatchdogThread");

    // QuickLinks
    public static final String MCWIKI_TICK_COMMAND = "See [**Tick Command**](https://minecraft.wiki/w/Commands/tick) on McWiki for more details.";
    public static final String AUTO_GEN_NOTE = "NOTE: These are auto-generated and may differ between server versions, currently generated for Minecraft " + Bukkit.getMinecraftVersion() + ".";

    @SuppressWarnings("deprecation") // Paper deprecation
    public static String getColString(String string) {
        Matcher matcher = HEX_PATTERN.matcher(string);
        if (SKRIPT_IS_THERE) {
            while (matcher.find()) {
                final ChatColor hexColor = ChatColor.of(matcher.group().substring(1, matcher.group().length() - 1));
                final String before = string.substring(0, matcher.start());
                final String after = string.substring(matcher.end());
                string = before + hexColor + after;
                matcher = HEX_PATTERN.matcher(string);
            }
        } else {
            string = HEX_PATTERN.matcher(string).replaceAll("");
        }
        return ChatColor.translateAlternateColorCodes('&', string);
    }

    public static Component getMini(String format, Object... objects) {
        return MINI_MESSAGE.deserialize(String.format(format, objects));
    }

    public static void sendColMsg(CommandSender receiver, String format, Object... objects) {
        receiver.sendMessage(getColString(String.format(format, objects)));
    }

    /**
     * Send a message to a receiver with MiniMessage support.
     *
     * @param receiver CommandSender to get message
     * @param format   Format of message
     * @param objects  Objects to include in format
     */
    public static void sendMini(CommandSender receiver, String format, Object... objects) {
        receiver.sendMessage(getMini(String.format(format, objects)));
    }

    /**
     * Send a prefixed message to a receiver with MiniMessage support.
     *
     * @param receiver CommandSender to get message
     * @param format   Format of message
     * @param objects  Objects to include in format
     */
    public static void sendMiniPrefixed(CommandSender receiver, String format, Object... objects) {
        receiver.sendMessage(getMini(PREFIX_MINI + String.format(format, objects)));
    }

    public static void log(String format, Object... objects) {
        String log = String.format(format, objects);
        Bukkit.getConsoleSender().sendMessage(getColString(PREFIX + log));
    }

    public static void logMini(String format, Object... objects) {
        String log = String.format(format, objects);
        Bukkit.getConsoleSender().sendMessage(getMini(PREFIX_MINI + log));
    }

    public static void skriptError(String format, Object... objects) {
        String error = String.format(format, objects);
        Skript.error(getColString(PREFIX_ERROR + error), ErrorQuality.SEMANTIC_ERROR);
    }

    /**
     * Send an error to admins
     * <p>Permission required: `skbee.admin`</p>
     *
     * @param format  Format of the message
     * @param objects Objects to fill format
     */
    public static void errorForAdmins(String format, Object... objects) {
        String error = PREFIX_ERROR + "&c" + format;
        for (Player player : Bukkit.getOnlinePlayers()) {
            if (player.hasPermission("skbee.admin")) {
                sendColMsg(player, error, objects);
            }
        }
    }

    public static void debug(String format, Object... objects) {
        if (SkBee.isDebug()) {
            String debug = String.format(format, objects);
            Bukkit.getConsoleSender().sendMessage(getColString(PREFIX_ERROR + debug));
        }
    }

    public static void debugMini(String format, Object... objects) {
        if (SkBee.isDebug()) {
            String debug = String.format(format, objects);
            Bukkit.getConsoleSender().sendMessage(getMini(PREFIX_ERRORR_MINI + debug));
        }
    }

    private static final List<String> DEBUGS = new ArrayList<>();

    public static void logLoading(String format, Object... objects) {
        String form = String.format(format, objects);
        DEBUGS.add(form);
        log(form);
    }

    public static void logLoadingMini(String format, Object... objects) {
        String form = String.format(format, objects);
        DEBUGS.add(form);
        logMini(form);
    }

    public static List<String> getDebugs() {
        return DEBUGS;
    }

    public static void clearDebugs() {
        DEBUGS.clear();
    }

    /**
     * Gets a Minecraft NamespacedKey from string
     * <p>If a namespace is not provided, it will default to "minecraft:" namespace</p>
     *
     * @param keyObject Key for new Minecraft NamespacedKey (supports Key/NamespacedKey/String)
     * @param error     Whether to send a skript/console error if one occurs
     * @return new Minecraft NamespacedKey
     */
    @Nullable
    public static NamespacedKey getNamespacedKey(@Nullable Object keyObject, boolean error) {
        if (keyObject instanceof NamespacedKey namespacedKey) {
            return namespacedKey;
        }
        if (keyObject instanceof Key aKey) {
            return NamespacedKey.fromString(aKey.asString());
        }
        if (!(keyObject instanceof String key)) {
            return null;
        }
        if (key == null) return null;
        if (!key.contains(":")) key = "minecraft:" + key;
        if (key.length() > Short.MAX_VALUE) {
            if (error)
                skriptError("An invalid key was provided, key must be less than 32767 characters: %s", key);
            return null;
        }
        key = key.toLowerCase();
        if (key.contains(" ")) {
            key = key.replace(" ", "_");
        }

        NamespacedKey namespacedKey = NamespacedKey.fromString(key);
        if (namespacedKey == null && error)
            skriptError("An invalid key was provided, that didn't follow [a-z0-9/._-:]. key: %s", key);
        return namespacedKey;
    }

    /**
     * Convert a UUID to an int array
     * <p>Used for Minecraft 1.16+</p>
     *
     * @param uuid UUID to convert
     * @return int array from UUID
     */
    public static int[] uuidToIntArray(UUID uuid) {
        long most = uuid.getMostSignificantBits();
        long least = uuid.getLeastSignificantBits();
        return new int[]{(int) (most >> 32), (int) most, (int) (least >> 32), (int) least};
    }

    /**
     * Check if a {@link Tag} is a {@link Material} Tag
     *
     * @param tag tag to check
     * @return True if material tag
     */
    public static boolean isMaterialTag(Tag<?> tag) {
        ParameterizedType superC = (ParameterizedType) tag.getClass().getGenericSuperclass();
        for (Type arg : superC.getActualTypeArguments()) {
            if (arg.equals(Material.class)) return true;
        }
        return false;
    }

    /**
     * Check if a {@link Tag} is an {@link EntityType} Tag
     *
     * @param tag tag to check
     * @return True if EntityType tag
     */
    public static boolean isEntityTypeTag(Tag<?> tag) {
        ParameterizedType superC = (ParameterizedType) tag.getClass().getGenericSuperclass();
        for (Type arg : superC.getActualTypeArguments()) {
            if (arg.equals(EntityType.class)) return true;
        }
        return false;
    }

    /**
     * Check if a dialog input key is valid.
     * Must contain only numbers, letters and underscores
     *
     * @param key Key to check
     * @return Whether valid
     */
    public static boolean isValidDialogInputKey(String key) {
        if (key.equalsIgnoreCase("id")) return false;
        for (int i = 0; i < key.length(); i++) {
            char c = key.charAt(i);
            if (!Character.isLetterOrDigit(c) && c != '_') {
                return false;
            }
        }

        return true;
    }

}
