package ru.dbotthepony.mc.otm.triggers

import com.google.common.collect.ImmutableList
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.advancements.critereon.*
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.StringRepresentable
import net.minecraft.world.entity.monster.ElderGuardian
import net.neoforged.neoforge.event.entity.living.LivingDeathEvent
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.capability.MatteryPlayer
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.core.ResourceLocation
import ru.dbotthepony.mc.otm.data.codec.SingletonCodec
import ru.dbotthepony.mc.otm.registry.MRegistry
import java.util.Optional
import java.util.function.Predicate

object KillAsAndroidTrigger : MCriterionTrigger<KillAsAndroidTrigger.Instance>(ResourceLocation(OverdriveThatMatters.MOD_ID, "kill_as_android")) {
	val FEATURE_CODEC: Codec<FeaturePredicate> = StringRepresentable.fromEnum(PredicateType::values).dispatch({ it.type }, { it.codec })

	override val codec: Codec<Instance> = RecordCodecBuilder.create {
		it.group(
			ContextAwarePredicate.CODEC.optionalFieldOf("entityPredicate").forGetter(Instance::predicate),
			FEATURE_CODEC.fieldOf("featurePredicate").forGetter(Instance::featurePredicate),
			ContextAwarePredicate.CODEC.optionalFieldOf("player").forGetter(Instance::playerPredicate)
		).apply(it, ::Instance)
	}

	enum class PredicateType(codec: Lazy<MapCodec<out FeaturePredicate>>) : StringRepresentable {
		ALWAYS(lazy {
			SingletonCodec(Always)
		}),
		HAS(lazy {
			RecordCodecBuilder.mapCodec<Has> {
				it.group(ResourceLocation.CODEC.fieldOf("name").forGetter(Has::name)).apply(it, ::Has)
			}
		}),
		NOT(lazy {
			RecordCodecBuilder.mapCodec<Not> {
				it.group(FEATURE_CODEC.fieldOf("parent").forGetter(Not::parent)).apply(it, ::Not)
			}
		}),
		AND(lazy {
			RecordCodecBuilder.mapCodec<And> {
				it.group(FEATURE_CODEC.listOf().fieldOf("children").forGetter(And::children)).apply(it, ::And)
			}
		}),
		OR(lazy {
			RecordCodecBuilder.mapCodec<OrPredicate> {
				it.group(FEATURE_CODEC.listOf().fieldOf("children").forGetter(OrPredicate::children)).apply(it, ::OrPredicate)
			}
		});

		val codec by codec

		override fun getSerializedName(): String {
			return name.lowercase()
		}
	}

	abstract class FeaturePredicate : Predicate<MatteryPlayer> {
		abstract val type: PredicateType
	}

	object Always : FeaturePredicate() {
		override val type: PredicateType
			get() = PredicateType.ALWAYS

		override fun test(t: MatteryPlayer): Boolean {
			return true
		}
	}

	class Has(val name: ResourceLocation) : FeaturePredicate() {
		private val resolved by lazy { MRegistry.ANDROID_FEATURES.get(name) }

		override val type: PredicateType
			get() = PredicateType.HAS

		override fun test(t: MatteryPlayer): Boolean {
			return t.hasFeature(resolved ?: return false)
		}
	}

	class Not(val parent: FeaturePredicate) : FeaturePredicate() {
		override val type: PredicateType
			get() = PredicateType.NOT

		override fun test(t: MatteryPlayer): Boolean {
			return !parent.test(t)
		}
	}

	class And(children: Iterable<FeaturePredicate>) : FeaturePredicate() {
		val children: ImmutableList<FeaturePredicate> = ImmutableList.copyOf(children)

		override val type: PredicateType
			get() = PredicateType.AND

		override fun test(t: MatteryPlayer): Boolean {
			return children.all { it.test(t) }
		}
	}

	class OrPredicate(children: Iterable<FeaturePredicate>) : FeaturePredicate() {
		val children: ImmutableList<FeaturePredicate> = ImmutableList.copyOf(children)

		override val type: PredicateType
			get() = PredicateType.OR

		override fun test(t: MatteryPlayer): Boolean {
			return children.any { it.test(t) }
		}
	}

	class Instance(
		val predicate: Optional<ContextAwarePredicate> = Optional.empty(),
		val featurePredicate: FeaturePredicate = Always,
		playerPredicate: Optional<ContextAwarePredicate> = Optional.empty(),
	) : AbstractInstance(playerPredicate)

	fun onKill(event: LivingDeathEvent) {
		if (event.entity is ElderGuardian) {
			val killer = event.source.entity

			if (killer is ServerPlayer) {
				val data = killer.matteryPlayer

				if (data.isAndroid) {
					val context = EntityPredicate.createContext(killer, event.entity)

					trigger(killer) {
						it.predicate.map { it.matches(context) }.orElse(true) &&
						it.featurePredicate.test(data)
					}
				}
			}
		}
	}
}
