package com.shanebeestudios.skbee.elements.text.type;

import ch.njol.skript.classes.Changer;
import ch.njol.skript.classes.Parser;
import ch.njol.skript.classes.Serializer;
import ch.njol.skript.lang.ParseContext;
import ch.njol.skript.registrations.Classes;
import ch.njol.util.coll.CollectionUtils;
import ch.njol.yggdrasil.Fields;
import com.github.shanebeee.skr.Registration;
import com.shanebeestudios.skbee.api.util.SkriptUtils;
import com.shanebeestudios.skbee.api.wrapper.ComponentWrapper;
import net.kyori.adventure.chat.SignedMessage;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.common.function.DefaultFunction;
import org.skriptlang.skript.common.function.Parameter;
import org.skriptlang.skript.lang.comparator.Comparators;
import org.skriptlang.skript.lang.comparator.Relation;
import org.skriptlang.skript.lang.converter.Converters;

import java.io.StreamCorruptedException;

public class Types {

    @SuppressWarnings("PatternValidation")
    public static void register(Registration reg) {
        // Allow components to be used anywhere a string can
        Converters.registerConverter(ComponentWrapper.class, String.class, ComponentWrapper::toString);

        // Support for text components in Skript
        Converters.registerConverter(ComponentWrapper.class, Component.class, ComponentWrapper::getComponent);
        Converters.registerConverter(Component.class, ComponentWrapper.class, ComponentWrapper::fromComponent);

        Comparators.registerComparator(ComponentWrapper.class, ComponentWrapper.class, (o1, o2) -> Relation.get(o1.equals(o2)));

        Changer<ComponentWrapper> COMP_CHANGER = new Changer<>() {
            @Override
            public Class<?> @Nullable [] acceptChange(ChangeMode mode) {
                if (mode == ChangeMode.ADD)
                    return CollectionUtils.array(HoverEvent.class, ClickEvent.class, ComponentWrapper.class);
                return null;
            }

            @Override
            public void change(ComponentWrapper[] components, @Nullable Object[] delta, ChangeMode mode) {
                if (delta == null || delta.length == 0) return;
                if (mode == ChangeMode.ADD) {
                    for (ComponentWrapper component : components) {
                        if (delta[0] instanceof HoverEvent<?> hoverEvent) {
                            component.setHoverEvent(hoverEvent);
                        } else if (delta[0] instanceof ClickEvent clickEvent) {
                            component.setClickEvent(clickEvent);
                        } else if (delta[0] instanceof ComponentWrapper cw) {
                            component.append(cw);
                        }
                    }
                }
            }
        };

        reg.newType(ComponentWrapper.class, "textcomp")
            .user("text ?comps?")
            .name("TextComponent - Text Component")
            .description("Represents a text component.",
                "Text components are how Minecraft manages formatted text in game.",
                "See [**Text Component**](https://minecraft.wiki/w/Text_component_format) on McWiki for more information.",
                "**NOTE**: These are different than Skript's TextComponent class, as this is a wrapper that holds a component.")
            .examples("set {_t} to text component from \"CLICK FOR OUR DISCORD\"",
                "add hover event showing \"Clicky Clicky!\" to {_t}",
                "add click event to open url \"https://OurDiscord.com\" to {_t}",
                "send component {_t} to player")
            .since("1.5.0")
            .cloner(original -> ComponentWrapper.fromComponent(original.getComponent()))
            .parser(new Parser<>() {
                @Override
                public @NotNull String toString(@NotNull ComponentWrapper o, int flags) {
                    return o.toString();
                }

                @Override
                public boolean canParse(@NotNull ParseContext context) {
                    return false;
                }

                @Override
                public @NotNull String toVariableNameString(@NotNull ComponentWrapper o) {
                    return o.toString();
                }
            })
            .changer(COMP_CHANGER)
            .serializer(new Serializer<>() {
                @Override
                public Fields serialize(ComponentWrapper component) {
                    Fields fields = new Fields();
                    fields.putObject("json", component.toJsonString());
                    return fields;
                }

                @Override
                protected ComponentWrapper deserialize(Fields fields) throws StreamCorruptedException {
                    if (!fields.hasField("json"))
                        throw new StreamCorruptedException("TextComponent is missing the 'json' tag");
                    try {
                        return ComponentWrapper.fromJson(fields.getObject("json", String.class));
                    } catch (StreamCorruptedException exception) {
                        throw new StreamCorruptedException("TextComponent JSON failed to convert back into a component, possibly caused by version changes.");
                    }
                }

                @Override
                public void deserialize(ComponentWrapper component, Fields fields) {
                    assert false : "This should never by called by skript";
                }

                @Override
                public boolean mustSyncDeserialization() {
                    return true;
                }

                @Override
                protected boolean canBeInstantiated() {
                    return false;
                }
            })
            .register();

        if (Classes.getExactClassInfo(SignedMessage.class) == null) {
            reg.newType(SignedMessage.class, "signedmessage")
                .user("signed ?messages?")
                .name("Signed Chat Message")
                .description("Represents a signed chat message.")
                .examples("remove all players from signed chat message # will remove the message from the client")
                .parser(SkriptUtils.getDefaultParser())
                .since("3.5.0")
                .changer(new Changer<>() {
                    @Override
                    public Class<?> @Nullable [] acceptChange(ChangeMode mode) {
                        if (mode == ChangeMode.REMOVE || mode == ChangeMode.REMOVE_ALL)
                            return CollectionUtils.array(Player[].class);
                        return null;
                    }

                    @Override
                    public void change(SignedMessage[] what, @Nullable Object[] delta, ChangeMode mode) {
                        if (delta == null) return;
                        for (SignedMessage signedMessage : what) {
                            if (!signedMessage.canDelete()) continue;

                            for (Object object : delta) {
                                if (object instanceof Player player) {
                                    player.deleteMessage(signedMessage);
                                }
                            }
                        }
                    }
                })
                .register();
        }

        if (Classes.getExactClassInfo(TagResolver.class) == null) {
            reg.newType(TagResolver.class, "tagresolver")
                .name("Tag Resolver")
                .user("tag ?resolvers?")
                .description("Represents an object to replace text in a mini message.")
                .examples("# Create a component",
                    "set {_t} to translate component of player's tool",
                    "add hover event showing player's tool to {_t}",
                    "# Use this component in the resolver to replace \"<item>\" in the mini message",
                    "set {_r} to resolver(\"item\", {_t})",
                    "# setup the mini message with the replacement placeholder",
                    "set {_m} to mini message from \"<rainbow> Hey guys check out my <item> aint she a beaut?\" with {_r}",
                    "send component {_m}")
                .parser(SkriptUtils.getDefaultParser())
                .since("3.5.0")
                .register();
        }

        // Functions
        DefaultFunction<TagResolver> resolverFunc = DefaultFunction.builder(reg.getAddon(), "resolver", TagResolver.class)
            .parameter("placeholder", String.class)
            .parameter("replacement", Object.class)
            .parameter("parsable", Boolean.class, Parameter.Modifier.OPTIONAL)
            .build(params -> {
                String key = params.get("placeholder");
                if (key == null) return null;

                Object object = params.get("replacement");
                Boolean parsable = params.get("parsable");
                if (parsable == null) parsable = false;

                TagResolver resolver;
                if (object instanceof ComponentWrapper component) {
                    resolver = Placeholder.component(key, component.getComponent());
                } else if (object instanceof Component component) {
                    resolver = Placeholder.component(key, component);
                } else if (parsable) {
                    resolver = Placeholder.parsed(key, Classes.toString(object));
                } else {
                    resolver = Placeholder.component(key, ComponentWrapper.fromText(Classes.toString(object)).getComponent());
                }
                return resolver;
            });

        reg.newFunction(resolverFunc)
            .name("TextComponent - Resolver")
            .description("Creates a tag resolver for replacements in mini message.",
                "`placeholder` = The string that will be replaced in the mini message.",
                "In the mini message itself this part needs to be surrounded by <>. See examples!",
                "`replacement` = A string/text component that will replace the first string.",
                "`parsable` = Whether the replacement should be parsed as mini message after being replaced.",
                "For servers that take in user inputs, this should be used with caution.")
            .examples("# Create a component",
                "set {_t} to translate component of player's tool",
                "add hover event showing player's tool to {_t}",
                "# Use this component in the resolver to replace \"<item>\" in the mini message",
                "set {_r} to resolver(\"item\", {_t})",
                "# setup the mini message with the replacement placeholder",
                "set {_m} to mini message from \"<rainbow> Hey guys check out my <item> aint she a beaut?\" with {_r}",
                "send component {_m}",
                "",
                "on async chat:",
                "\tset {_chatColor} to resolver(\"chatcolor\", raw ({chatcolor::%player%} ? raw \"<gray>\"), true)",
                "\tset async chat message to mini message from \"<chatcolor>%async chat message%\" with resolver {_chatColor}")
            .since("3.5.0")
            .register();
    }

}
