package ru.dbotthepony.mc.otm.registry

import com.google.common.collect.ImmutableList
import net.minecraft.client.renderer.item.ItemProperties
import net.minecraft.core.BlockPos
import net.minecraft.core.cauldron.CauldronInteraction
import net.minecraft.core.component.DataComponents
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.entity.EntityType
import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.Item
import net.minecraft.world.item.ShieldItem
import net.minecraft.world.item.component.CustomData
import net.minecraft.world.item.component.DyedItemColor
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.state.BlockBehaviour
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument
import net.minecraft.world.level.material.MapColor
import net.neoforged.bus.api.IEventBus
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent
import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent
import net.neoforged.neoforge.client.event.RegisterItemDecorationsEvent
import net.neoforged.neoforge.client.model.DynamicFluidContainerModel
import net.neoforged.neoforge.event.ModifyDefaultComponentsEvent
import net.neoforged.neoforge.registries.RegisterEvent
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.android.AndroidFeatureType
import ru.dbotthepony.mc.otm.android.feature.EnderTeleporterFeature
import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature
import ru.dbotthepony.mc.otm.block.decorative.CargoCrateBlock
import ru.dbotthepony.mc.otm.block.decorative.ComputerTerminalBlock
import ru.dbotthepony.mc.otm.block.decorative.StarChairBlock
import ru.dbotthepony.mc.otm.block.decorative.TritaniumPressurePlate
import ru.dbotthepony.mc.otm.capability.matteryEnergy
import ru.dbotthepony.mc.otm.client.MatteryGUI
import ru.dbotthepony.mc.otm.core.ResourceLocation
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.isClient
import ru.dbotthepony.mc.otm.item.armor.TritaniumArmorItem
import ru.dbotthepony.mc.otm.item.weapon.EnergySwordItem
import ru.dbotthepony.mc.otm.item.weapon.FallingSunItem
import ru.dbotthepony.mc.otm.registry.game.MDataComponentTypes
import ru.dbotthepony.mc.otm.registry.game.MItems
import ru.dbotthepony.mc.otm.registry.game.MStats
import ru.dbotthepony.mc.otm.registry.objects.ColoredDecorativeBlock
import ru.dbotthepony.mc.otm.registry.objects.DecorativeBlock
import ru.dbotthepony.mc.otm.registry.objects.IBlockItemRegistryAcceptor
import ru.dbotthepony.mc.otm.registry.objects.StripedColoredDecorativeBlock

object MRegistry : IBlockItemRegistryAcceptor {
	private val features = RegistryDelegate<AndroidFeatureType<*>>("android_features") { sync(true) }
	val ANDROID_FEATURES by features
	val ANDROID_FEATURES_LOCATION get() = features.location
	val ANDROID_FEATURES_KEY get() = features.key

	val DYE_ORDER: ImmutableList<DyeColor> = ImmutableList.of(
		DyeColor.BLACK,
		DyeColor.BLUE,
		DyeColor.BROWN,
		DyeColor.CYAN,
		DyeColor.GRAY,
		DyeColor.GREEN,
		DyeColor.LIGHT_BLUE,
		DyeColor.LIGHT_GRAY,
		DyeColor.LIME,
		DyeColor.MAGENTA,
		DyeColor.ORANGE,
		DyeColor.PINK,
		DyeColor.PURPLE,
		DyeColor.RED,
		DyeColor.WHITE,
		DyeColor.YELLOW,
	)

	private val decorativeBlocks = ArrayList<IBlockItemRegistryAcceptor>()

	override fun registerItems(registry: MDeferredRegister<Item>) {
		decorativeBlocks.forEach { it.registerItems(registry) }
	}

	override fun registerBlocks(registry: MDeferredRegister<Block>) {
		decorativeBlocks.forEach { it.registerBlocks(registry) }
	}

	val CARGO_CRATES = DecorativeBlock(MNames.CARGO_CRATE, ::CargoCrateBlock).also { decorativeBlocks.add(it) }

	val DECORATIVE_CRATE = DecorativeBlock.simple(MNames.DECORATIVE_CRATE) {
		BlockBehaviour.Properties.of()
			.mapColor(it?.mapColor ?: MapColor.SNOW)
			.sound(SoundType.METAL)
			.requiresCorrectToolForDrops()
			.explosionResistance(10f)
			.destroyTime(1f)
	}.also { decorativeBlocks.add(it) }

	val TRITANIUM_BLOCK = DecorativeBlock.simple(MNames.TRITANIUM_BLOCK) {
		BlockBehaviour.Properties.of()
			.mapColor(it?.mapColor ?: MapColor.COLOR_LIGHT_BLUE)
			.sound(SoundType.DEEPSLATE)
			.requiresCorrectToolForDrops()
			.explosionResistance(80f)
			.destroyTime(2.5f)
	}.also { decorativeBlocks.add(it) }

	val COMPUTER_TERMINAL = DecorativeBlock("computer_terminal", ::ComputerTerminalBlock).also { decorativeBlocks.add(it) }
	val STAR_CHAIR = DecorativeBlock("star_chair", ::StarChairBlock).also { decorativeBlocks.add(it) }

	val TRITANIUM_STAIRS = DecorativeBlock(MNames.TRITANIUM_STAIRS) {
		StairBlock(
			TRITANIUM_BLOCK.allBlocks[it]!!.defaultBlockState(),
			BlockBehaviour.Properties.ofFullCopy(TRITANIUM_BLOCK.allBlocks[it]!!)
		)
	}.also { decorativeBlocks.add(it) }

	val TRITANIUM_SLAB = DecorativeBlock(MNames.TRITANIUM_SLAB) {
		SlabBlock(BlockBehaviour.Properties.ofLegacyCopy(TRITANIUM_BLOCK.allBlocks[it]!!))
	}.also { decorativeBlocks.add(it) }

	val TRITANIUM_WALL = DecorativeBlock(MNames.TRITANIUM_WALL) {
		WallBlock(BlockBehaviour.Properties.ofLegacyCopy(TRITANIUM_BLOCK.allBlocks[it]!!))
	}.also { decorativeBlocks.add(it) }

	val TRITANIUM_PRESSURE_PLATE = DecorativeBlock(MNames.TRITANIUM_PRESSURE_PLATE, ::TritaniumPressurePlate).also { decorativeBlocks.add(it) }

	val VENT = DecorativeBlock.simple(MNames.VENT) {
		BlockBehaviour.Properties.of()
			.mapColor(it?.mapColor ?: MapColor.COLOR_LIGHT_BLUE)
			.sound(SoundType.DEEPSLATE)
			.requiresCorrectToolForDrops()
			.explosionResistance(20f)
			.destroyTime(1.5f)
	}.also { decorativeBlocks.add(it) }

	val VENT_ALTERNATIVE = DecorativeBlock.simple(MNames.VENT_ALTERNATIVE) {
		BlockBehaviour.Properties.of()
			.mapColor(it?.mapColor ?: MapColor.COLOR_LIGHT_BLUE)
			.sound(SoundType.DEEPSLATE)
			.requiresCorrectToolForDrops()
			.explosionResistance(20f)
			.destroyTime(1.5f)
	}.also { decorativeBlocks.add(it) }

	val FLOOR_TILES = ColoredDecorativeBlock.simple(MNames.FLOOR_TILES) {
		BlockBehaviour.Properties.of()
			.mapColor(it.mapColor)
			.sound(SoundType.STONE)
			.requiresCorrectToolForDrops()
			.strength(1.5f, 6f)
	}.also { decorativeBlocks.add(it) }

	val FLOOR_TILES_STAIRS = ColoredDecorativeBlock(MNames.FLOOR_TILES_STAIRS) {
		StairBlock(
			FLOOR_TILES.blocks[it]!!.defaultBlockState(),
			BlockBehaviour.Properties.ofLegacyCopy(FLOOR_TILES.blocks[it]!!)
		)
	}.also { decorativeBlocks.add(it) }

	val FLOOR_TILES_SLAB = ColoredDecorativeBlock(MNames.FLOOR_TILES_SLAB) {
		SlabBlock(BlockBehaviour.Properties.ofLegacyCopy(FLOOR_TILES.blocks[it]!!))
	}.also { decorativeBlocks.add(it) }

	val UNREFINED_FLOOR_TILES = ColoredDecorativeBlock.simple(MNames.UNREFINED_FLOOR_TILES) {
		BlockBehaviour.Properties.of()
			.mapColor(it.mapColor)
			.sound(SoundType.GRAVEL)
			.strength(1f, 2f)
	}.also { decorativeBlocks.add(it) }

	val INDUSTRIAL_GLASS = DecorativeBlock(MNames.INDUSTRIAL_GLASS) { color ->
		val properties =
			BlockBehaviour.Properties.of()
				.mapColor(if (color != null) color.mapColor else MapColor.NONE)
				.instrument(NoteBlockInstrument.HAT)
				.destroyTime(1.5f)
				.explosionResistance(40f)
				.requiresCorrectToolForDrops()
				.sound(SoundType.GLASS)
				.noOcclusion()
				.isValidSpawn { _: BlockState, _: BlockGetter, _: BlockPos, _: EntityType<*>? -> false }
				.isRedstoneConductor { _: BlockState, _: BlockGetter, _: BlockPos -> false }
				.isSuffocating { _: BlockState, _: BlockGetter, _: BlockPos -> false }
				.isViewBlocking { _: BlockState, _: BlockGetter, _: BlockPos -> false }

		if (color != null) {
			return@DecorativeBlock StainedGlassBlock(color, properties)
		}

		return@DecorativeBlock TransparentBlock(properties)
	}.also { decorativeBlocks.add(it) }

	val INDUSTRIAL_GLASS_PANE = DecorativeBlock(MNames.INDUSTRIAL_GLASS_PANE) { color ->
		val properties =
			BlockBehaviour.Properties.of()
				.mapColor(if (color != null) color.mapColor else MapColor.NONE)
				.instrument(NoteBlockInstrument.HAT)
				.strength(1.25f, 5.0f)
				.requiresCorrectToolForDrops()
				.sound(SoundType.GLASS)
				.noOcclusion()

		if (color != null) {
			return@DecorativeBlock StainedGlassPaneBlock(color, properties)
		}

		return@DecorativeBlock IronBarsBlock(properties)
	}.also { decorativeBlocks.add(it) }

	val TRITANIUM_STRIPED_BLOCK = StripedColoredDecorativeBlock(MNames.TRITANIUM_STRIPED_BLOCK, { colorA, _ ->
		Block(BlockBehaviour.Properties.of()
			.mapColor(colorA.mapColor)
			.sound(SoundType.DEEPSLATE)
			.requiresCorrectToolForDrops()
			.explosionResistance(80f)
			.strength(4f))
	}).also { decorativeBlocks.add(it) }

	val TRITANIUM_STRIPED_STAIRS = StripedColoredDecorativeBlock(MNames.TRITANIUM_STRIPED_STAIRS, { colorA, colorB ->
		StairBlock(TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB).defaultBlockState(), BlockBehaviour.Properties.ofFullCopy(TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB)))
	}).also { decorativeBlocks.add(it) }

	val TRITANIUM_STRIPED_SLAB = StripedColoredDecorativeBlock(MNames.TRITANIUM_STRIPED_SLAB, { colorA, colorB ->
		SlabBlock(BlockBehaviour.Properties.ofFullCopy(TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB)))
	}).also { decorativeBlocks.add(it) }

	val TRITANIUM_STRIPED_WALL = StripedColoredDecorativeBlock(MNames.TRITANIUM_STRIPED_WALL, { colorA, colorB ->
		WallBlock(BlockBehaviour.Properties.ofFullCopy(TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB)))
	}).also { decorativeBlocks.add(it) }

	private fun registerEvent(event: RegisterEvent) {
		// mojang moment

		// FIXME
		/*if (event.registryKey == Registries.POINT_OF_INTEREST_TYPE) {
			val reg = event.registry as Registry<PoiType>

			event.register(Registries.POINT_OF_INTEREST_TYPE, PoiTypes.BUTCHER.location()) {
				val old = reg[PoiTypes.BUTCHER] ?: throw IllegalStateException("POI with type ${PoiTypes.ARMORER} does not exist")
				PoiType(Streams.concat(old.matchingStates.stream(), MBlocks.POWERED_SMOKER.values.stream().flatMap { it.stateDefinition.possibleStates.stream() }).collect(ImmutableSet.toImmutableSet()), old.maxTickets, old.validRange)
			}

			event.register(Registries.POINT_OF_INTEREST_TYPE, PoiTypes.ARMORER.location()) {
				val old = reg[PoiTypes.ARMORER] ?: throw IllegalStateException("POI with type ${PoiTypes.ARMORER} does not exist")
				PoiType(Streams.concat(old.matchingStates.stream(), MBlocks.POWERED_BLAST_FURNACE.values.stream().flatMap { it.stateDefinition.possibleStates.stream() }).collect(ImmutableSet.toImmutableSet()), old.maxTickets, old.validRange)
			}
		}*/
	}

	fun initialize(bus: IEventBus) {
		bus.addListener(features::build)
		bus.addListener(this::initializeClient)
		bus.addListener(this::initializeCommon)
		bus.addListener(MStats::registerVanilla)
		bus.addListener(this::registerEvent)
		if (isClient) bus.addListener(this::registerItemColorHandlers)
		if (isClient) bus.addListener(this::registerItemDecorators)
		bus.addListener(this::registerDefaultComponents)

		// call static constructors
		NanobotsArmorFeature.Companion
		EnderTeleporterFeature.Companion
	}

	private fun initializeCommon(event: FMLCommonSetupEvent) {
		registerCauldronHandlers()
	}

	private fun initializeClient(event: FMLClientSetupEvent) {
		event.enqueueWork {
			ItemProperties.register(MItems.TRITANIUM_SHIELD, ResourceLocation(OverdriveThatMatters.MOD_ID, "blocking")) { stack, level, entity, _ ->
				if (entity?.isUsingItem == true && entity.useItem == stack) {
					1f
				} else {
					0f
				}
			}

			ItemProperties.register(MItems.ENERGY_SWORD, ResourceLocation(OverdriveThatMatters.MOD_ID, "is_powered")) { stack, _, _, _ ->
				if ((stack.matteryEnergy?.batteryLevel ?: Decimal.ZERO) >= EnergySwordItem.ENERGY_PER_SWING) {
					1f
				} else {
					0f
				}
			}

			ItemProperties.register(MItems.FALLING_SUN, ResourceLocation(OverdriveThatMatters.MOD_ID, "is_powered")) { stack, _, _, _ ->
				if ((stack.matteryEnergy?.batteryLevel ?: Decimal.ZERO) >= FallingSunItem.ENERGY_PER_SWING) {
					1f
				} else {
					0f
				}
			}

			ItemProperties.register(MItems.EXPLOSIVE_HAMMER, ResourceLocation(OverdriveThatMatters.MOD_ID, "is_primed")) { stack, level, entity, _ ->
				if (MItems.EXPLOSIVE_HAMMER.isPrimed(stack) || entity == null) {
					1f
				} else {
					if (entity.useItemRemainingTicks <= 0) {
						0f
					} else {
						(stack.getUseDuration(entity) - entity.useItemRemainingTicks).toFloat() / stack.getUseDuration(entity)
					}
				}
			}

			for (item in MItems.ESSENCE_STORAGE.values) {
				ItemProperties.register(item, ResourceLocation(OverdriveThatMatters.MOD_ID, "is_filled")) { stack, _, _, _ ->
					val tag = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).unsafe ?: return@register 0f

					if (tag.contains("experienceStored") && tag.getLong("experienceStored") > 0.0) {
						1f
					} else {
						0f
					}
				}
			}

			ItemProperties.register(MItems.CONFIGURATOR, ResourceLocation(OverdriveThatMatters.MOD_ID, "has_configuration_saved")) { stack, _, _, _ ->
				val tag = stack.getOrDefault(MDataComponentTypes.Configurator.CONFIGURATION, CompoundTag())
				if (tag.isEmpty) 0f else 1f
			}
		}
	}

	private fun registerCauldronHandlers() {
		MItems.TRITANIUM_ARMOR.forEach { CauldronInteraction.WATER.map()[it] = CauldronInteraction.DYED_ITEM }
	}

	private fun registerItemColorHandlers(event: RegisterColorHandlersEvent.Item) {
		event.register(DynamicFluidContainerModel.Colors(), MItems.FLUID_CAPSULE)

		event.register({ stack, layer -> if (layer == 0) DyedItemColor.getOrDefault(stack, TritaniumArmorItem.TRITANIUM_COLOR) else -1 },
			*MItems.TRITANIUM_ARMOR.toTypedArray()
		)

		event.register({ _, layer -> if (layer == 1) RGBAColor.AQUA.toBGRA() else -1 },
			*MItems.MachineUpgrades.Creative.LIST.toTypedArray())
	}

	private fun registerItemDecorators(event: RegisterItemDecorationsEvent) {
		BuiltInRegistries.ITEM.forEach {
			if (it is ShieldItem) {
				event.register(it, MatteryGUI::renderShieldCooldownOverlay)
			}
		}
	}

	private fun registerDefaultComponents(event: ModifyDefaultComponentsEvent) {
		MItems.TRITANIUM_ARMOR.forEach {
			event.modify(it) {
				it.set(DataComponents.DYED_COLOR, DyedItemColor(TritaniumArmorItem.TRITANIUM_COLOR, false))
			}
		}
	}
}
