package ru.dbotthepony.mc.otm.core

import com.google.gson.JsonElement
import com.google.gson.JsonSyntaxException
import com.mojang.serialization.Codec
import com.mojang.serialization.JsonOps
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.core.RegistryAccess
import net.minecraft.core.registries.Registries
import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.Tag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.chat.contents.LiteralContents
import net.minecraft.network.chat.contents.TranslatableContents
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.tags.DamageTypeTags
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.damagesource.DamageType
import net.minecraft.world.item.Item
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.material.Fluid
import net.minecraft.world.phys.Vec3
import net.minecraftforge.registries.ForgeRegistries
import net.minecraftforge.registries.IForgeRegistry
import ru.dbotthepony.mc.otm.core.util.readJson
import ru.dbotthepony.mc.otm.core.util.writeJson

// because doing it inline is ugly
fun <V : Any> Codec<V>.fromJson(value: JsonElement): V? {
	return decode(JsonOps.INSTANCE, value).get().map({ left -> left.first }, { null })
}

fun <V : Any> Codec<V>.fromJsonStrict(value: JsonElement): V {
	return decode(JsonOps.INSTANCE, value).get().map({ left -> left.first }, { throw JsonSyntaxException("Error decoding element: ${it.message()}") })
}

fun <V : Any> Codec<V>.toJson(value: V, prefix: JsonElement = JsonOps.INSTANCE.empty()): JsonElement? {
	return encode(value, JsonOps.INSTANCE, prefix).get().map({ it }, { null })
}

fun <V : Any> Codec<V>.toJsonStrict(value: V, prefix: JsonElement = JsonOps.INSTANCE.empty()): JsonElement {
	return encode(value, JsonOps.INSTANCE, prefix).get().map({ it }, { throw RuntimeException("Error encoding element: ${it.message()}") })
}

fun <V : Any> Codec<V>.fromNbt(value: Tag): V? {
	return decode(NbtOps.INSTANCE, value).get().map({ left -> left.first }, { null })
}

fun <V : Any> Codec<V>.fromNbtStrict(value: Tag): V {
	return decode(NbtOps.INSTANCE, value).get().map({ left -> left.first }, { throw RuntimeException("Error decoding element: ${it.message()}") })
}

fun <V : Any> Codec<V>.toNbt(value: V, prefix: Tag = NbtOps.INSTANCE.empty()): Tag? {
	return encode(value, NbtOps.INSTANCE, prefix).get().map({ it }, { null })
}

fun <V : Any> Codec<V>.toNbtStrict(value: V, prefix: Tag = NbtOps.INSTANCE.empty()): Tag {
	return encode(value, NbtOps.INSTANCE, prefix).get().map({ it }, { throw RuntimeException("Error encoding element: ${it.message()}") })
}

fun <V : Any> Codec<V>.toNetwork(buff: FriendlyByteBuf, value: V) {
	buff.writeJson(toJsonStrict(value))
}

fun <V : Any> Codec<V>.fromNetwork(buff: FriendlyByteBuf): V {
	return fromJsonStrict(buff.readJson())
}

// 1.19 being 1.19
fun TranslatableComponent(key: String, vararg values: Any): MutableComponent = MutableComponent.create(TranslatableContents(key, null, values))
fun TextComponent(value: String): MutableComponent = MutableComponent.create(LiteralContents(value))

fun <T> IForgeRegistry<T>.getKeyNullable(value: T): ResourceLocation? {
	val key = getResourceKey(value)

	if (key.isPresent) {
		return key.get().location()
	}

	return null
}

val Item.registryName get() = ForgeRegistries.ITEMS.getKeyNullable(this)
val Fluid.registryName get() = ForgeRegistries.FLUIDS.getKeyNullable(this)
val Block.registryName get() = ForgeRegistries.BLOCKS.getKeyNullable(this)

fun FriendlyByteBuf.writeRegistryId(value: Item) = writeRegistryId(ForgeRegistries.ITEMS, value)

// 1.19.3 lol
inline val SoundEvent.holder get() = ForgeRegistries.SOUND_EVENTS.getHolder(this).orElse(null) ?: throw NoSuchElementException("$this is missing from ${ForgeRegistries.SOUND_EVENTS}")

// 1.19.4 :thonkang:
inline val DamageSource.isFall get() = `is`(DamageTypeTags.IS_FALL)
inline val DamageSource.isBypassArmor get() = `is`(DamageTypeTags.BYPASSES_ARMOR)
inline val DamageSource.isExplosion get() = `is`(DamageTypeTags.IS_EXPLOSION)
inline val DamageSource.isFire get() = `is`(DamageTypeTags.IS_FIRE)

fun RegistryAccess.damageType(key: ResourceKey<DamageType>): Holder<DamageType> {
	return registryOrThrow(Registries.DAMAGE_TYPE).getHolderOrThrow(key)
}
