package dev.latvian.mods.kubejs.core;

import com.google.common.collect.Iterators;
import com.mojang.serialization.Codec;
import dev.latvian.mods.kubejs.color.KubeColor;
import dev.latvian.mods.kubejs.plugin.builtin.wrapper.TextWrapper;
import dev.latvian.mods.kubejs.util.WithCodec;
import dev.latvian.mods.kubejs.util.WrappedJS;
import dev.latvian.mods.rhino.Context;
import dev.latvian.mods.rhino.util.RemapPrefixForJS;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentSerialization;
import net.minecraft.network.chat.FontDescription;
import net.minecraft.network.chat.HoverEvent;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.resources.Identifier;
import org.jspecify.annotations.Nullable;

import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;

/// Extensions for components, will be injected into
/// [MutableComponent] at runtime.
@RemapPrefixForJS("kjs$")
public interface ComponentKJS extends Component, WithCodec, WrappedJS {

	default Iterable<Component> kjs$asIterable() {
		return () -> {
			if (!kjs$hasSiblings()) {
				return Iterators.forArray(kjs$self());
			}

			List<Component> list = new LinkedList<>();
			list.add(kjs$self());

			for (var child : getSiblings()) {
				if (child instanceof ComponentKJS wrapped) {
					wrapped.forEach(list::add);
				} else {
					list.add(child);
				}
			}

			return list.iterator();
		};
	}

	default MutableComponent kjs$self() {
		return (MutableComponent) this;
	}

	@Override
	default Codec<?> getCodec(Context cx) {
		return ComponentSerialization.CODEC;
	}

	default boolean kjs$hasStyle() {
		return getStyle() != null && !getStyle().isEmpty();
	}

	default boolean kjs$hasSiblings() {
		return !getSiblings().isEmpty();
	}

	default void forEach(Consumer<? super Component> action) {
		kjs$asIterable().forEach(action);
	}

	// region ChatFormatting extensions
	default MutableComponent kjs$black() {
		return kjs$self().withStyle(ChatFormatting.BLACK);
	}

	default MutableComponent kjs$darkBlue() {
		return kjs$self().withStyle(ChatFormatting.DARK_BLUE);
	}

	default MutableComponent kjs$darkGreen() {
		return kjs$self().withStyle(ChatFormatting.DARK_GREEN);
	}

	default MutableComponent kjs$darkAqua() {
		return kjs$self().withStyle(ChatFormatting.DARK_AQUA);
	}

	default MutableComponent kjs$darkRed() {
		return kjs$self().withStyle(ChatFormatting.DARK_RED);
	}

	default MutableComponent kjs$darkPurple() {
		return kjs$self().withStyle(ChatFormatting.DARK_PURPLE);
	}

	default MutableComponent kjs$gold() {
		return kjs$self().withStyle(ChatFormatting.GOLD);
	}

	default MutableComponent kjs$gray() {
		return kjs$self().withStyle(ChatFormatting.GRAY);
	}

	default MutableComponent kjs$darkGray() {
		return kjs$self().withStyle(ChatFormatting.DARK_GRAY);
	}

	default MutableComponent kjs$blue() {
		return kjs$self().withStyle(ChatFormatting.BLUE);
	}

	default MutableComponent kjs$green() {
		return kjs$self().withStyle(ChatFormatting.GREEN);
	}

	default MutableComponent kjs$aqua() {
		return kjs$self().withStyle(ChatFormatting.AQUA);
	}

	default MutableComponent kjs$red() {
		return kjs$self().withStyle(ChatFormatting.RED);
	}

	default MutableComponent kjs$lightPurple() {
		return kjs$self().withStyle(ChatFormatting.LIGHT_PURPLE);
	}

	default MutableComponent kjs$yellow() {
		return kjs$self().withStyle(ChatFormatting.YELLOW);
	}

	default MutableComponent kjs$white() {
		return kjs$self().withStyle(ChatFormatting.WHITE);
	}
	// endregion ChatFormatting extensions

	// region Style extensions
	default MutableComponent kjs$color(@Nullable KubeColor c) {
		var col = c == null ? null : c.kjs$createTextColor();
		return kjs$self().setStyle(getStyle().withColor(col));
	}

	default MutableComponent kjs$noColor() {
		return kjs$color(null);
	}

	default MutableComponent kjs$bold(@Nullable Boolean value) {
		return kjs$self().setStyle(getStyle().withBold(value));
	}

	default MutableComponent kjs$bold() {
		return kjs$bold(true);
	}

	default MutableComponent kjs$italic(@Nullable Boolean value) {
		return kjs$self().setStyle(getStyle().withItalic(value));
	}

	default MutableComponent kjs$italic() {
		return kjs$italic(true);
	}

	default MutableComponent kjs$underlined(@Nullable Boolean value) {
		return kjs$self().setStyle(getStyle().withUnderlined(value));
	}

	default MutableComponent kjs$underlined() {
		return kjs$underlined(true);
	}

	default MutableComponent kjs$strikethrough(@Nullable Boolean value) {
		return kjs$self().setStyle(getStyle().withStrikethrough(value));
	}

	default MutableComponent kjs$strikethrough() {
		return kjs$strikethrough(true);
	}

	default MutableComponent kjs$obfuscated(@Nullable Boolean value) {
		return kjs$self().setStyle(getStyle().withObfuscated(value));
	}

	default MutableComponent kjs$obfuscated() {
		return kjs$obfuscated(true);
	}

	default MutableComponent kjs$insertion(@Nullable String s) {
		return kjs$self().setStyle(getStyle().withInsertion(s));
	}

	default MutableComponent kjs$font(@Nullable Identifier s) {
		FontDescription fd = s == null ? FontDescription.DEFAULT : new FontDescription.Resource(s);
		return kjs$self().setStyle(getStyle().withFont(fd));
	}


	default MutableComponent kjs$click(@Nullable ClickEvent s) {
		return kjs$self().setStyle(getStyle().withClickEvent(s));
	}

	default MutableComponent kjs$clickRunCommand(String command) {
		return kjs$click(new ClickEvent.RunCommand(command));
	}

	default MutableComponent kjs$clickSuggestCommand(String command) {
		return kjs$click(new ClickEvent.SuggestCommand(command));
	}

	default MutableComponent kjs$clickCopy(String text) {
		return kjs$click(new ClickEvent.CopyToClipboard(text));
	}

	default MutableComponent kjs$clickChangePage(String page) {
		int p;
		try {
			p = Integer.parseInt(page);
		} catch (NumberFormatException ignored) {
			p = 1;
		}
		return kjs$click(new ClickEvent.ChangePage(Math.max(1, p)));
	}

	default MutableComponent kjs$clickOpenUrl(String url) {
		try {
			return kjs$click(new ClickEvent.OpenUrl(URI.create(url)));
		} catch (IllegalArgumentException ignored) {
			return (MutableComponent) this;
		}
	}

	default MutableComponent kjs$clickOpenFile(String path) {
		return kjs$click(new ClickEvent.OpenFile(path));
	}


	default MutableComponent kjs$hover(@Nullable Component s) {
		return kjs$self().setStyle(getStyle().withHoverEvent(s == null ? null : new HoverEvent.ShowText(s)));
	}

	default boolean kjs$isEmpty() {
		return TextWrapper.isEmpty(kjs$self());
	}

	// endregion Style extensions
}
