package ru.dbotthepony.mc.otm.item

import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import net.minecraft.world.item.context.UseOnContext
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.LayeredCauldronBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.material.Fluid
import net.minecraft.world.level.material.Fluids
import net.minecraftforge.common.SoundActions
import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.common.capabilities.ICapabilityProvider
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.fluids.capability.IFluidHandler
import ru.dbotthepony.mc.otm.capability.fluid.ItemMatteryFluidHandler
import ru.dbotthepony.mc.otm.capability.fluidLevel
import ru.dbotthepony.mc.otm.capability.moveFluid
import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.container.stream
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.core.immutableMap
import ru.dbotthepony.mc.otm.core.orNull
import java.util.function.IntSupplier

class FluidCapsuleItem(val capacity: IntSupplier) : Item(Properties().stacksTo(64)) {
	// TODO: Так как использование предмета заблокировано за player.abilities.canBuild
	// капсулу нельзя использовать в режиме приключения
	// почему же можно использовать вёдра на котлах?
	// ИБО КОТЛЫ ЗАХАРДКОЖЕНЫ НА ВЗАИМОДЕЙСТВИЕ С ВЁДРАМИ
	override fun onItemUseFirst(stack: ItemStack, pContext: UseOnContext): InteractionResult {
		val context = Context(pContext.clickedPos, pContext.level.getBlockState(pContext.clickedPos), pContext.clickedFace)

		if (canInteract(pContext.itemInHand, pContext.player ?: return InteractionResult.FAIL, context))
			return interact(pContext.itemInHand, pContext.player!!, context)

		return super.onItemUseFirst(stack, pContext)
	}

	override fun getName(pStack: ItemStack): Component {
		pStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
			it.getFluidInTank(0).also {
				if (!it.isEmpty) {
					return TranslatableComponent("$descriptionId.named", it.displayName)
				}
			}
		}

		return super.getName(pStack)
	}

	override fun appendHoverText(pStack: ItemStack, pLevel: Level?, pTooltipComponents: MutableList<Component>, pIsAdvanced: TooltipFlag) {
		super.appendHoverText(pStack, pLevel, pTooltipComponents, pIsAdvanced)

		pStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
			it.fluidLevel(pTooltipComponents)
		}
	}

	override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider {
		return ItemMatteryFluidHandler(stack, capacity)
	}

	interface Interaction {
		fun canInteract(item: ItemStack, player: Player, context: Context): Boolean
		fun interact(item: ItemStack, player: Player, context: Context): InteractionResult
	}

	private object FillCauldron : Interaction {
		private data class Mapping(val fluid: Fluid, val blockState: BlockState, val soundEvent: SoundEvent)

		private val mapping = immutableList {
			accept(Mapping(Fluids.WATER, Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY))
			accept(Mapping(Fluids.LAVA, Blocks.LAVA_CAULDRON.defaultBlockState(), SoundEvents.BUCKET_EMPTY_LAVA))
		}

		override fun canInteract(item: ItemStack, player: Player, context: Context): Boolean {
			return player.level().getBlockState(context.blockPos).`is`(Blocks.CAULDRON)
		}

		override fun interact(item: ItemStack, player: Player, context: Context): InteractionResult {
			if (item.isEmpty || !context.blockState.`is`(Blocks.CAULDRON)) return InteractionResult.FAIL

			val targetItem = if (item.count == 1) item else item.copyWithCount(1)

			val cap = targetItem.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return InteractionResult.FAIL

			for ((fluid, newState, soundEvent) in mapping) {
				val toDrain = FluidStack(fluid, 1000)
				val drained = cap.drain(toDrain, IFluidHandler.FluidAction.SIMULATE)

				if (!drained.isEmpty && drained.amount == 1000) {
					player.level().playSound(player, context.blockPos, soundEvent, SoundSource.BLOCKS)
					val level = player.level() as? ServerLevel ?: return InteractionResult.SUCCESS
					level.setBlock(context.blockPos, newState, Block.UPDATE_ALL)
					cap.drain(toDrain, IFluidHandler.FluidAction.EXECUTE)

					if (item.count != 1 && !player.level().isClientSide) {
						item.count--

						if (!player.inventory.add(targetItem)) {
							player.spawnAtLocation(targetItem)
						}
					}

					return InteractionResult.CONSUME
				}
			}

			return InteractionResult.FAIL
		}
	}

	private object EmptyCauldron : Interaction {
		private val mapping = immutableMap {
			put(Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), Fluids.WATER to SoundEvents.BUCKET_FILL)
			put(Blocks.LAVA_CAULDRON.defaultBlockState(), Fluids.LAVA to SoundEvents.BUCKET_FILL_LAVA)
		}

		override fun canInteract(item: ItemStack, player: Player, context: Context): Boolean {
			return player.level().getBlockState(context.blockPos) in mapping
		}

		override fun interact(item: ItemStack, player: Player, context: Context): InteractionResult {
			if (item.isEmpty) return InteractionResult.FAIL

			val targetItem = if (item.count == 1) item else item.copyWithCount(1)

			val cap = targetItem.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return InteractionResult.FAIL
			val mapped = mapping[context.blockState] ?: return InteractionResult.FAIL

			val toFill = FluidStack(mapped.first, 1000)

			val fill = cap.fill(toFill, IFluidHandler.FluidAction.SIMULATE)
			if (fill != 1000) return InteractionResult.FAIL

			player.level().playSound(player, context.blockPos, mapped.second, SoundSource.BLOCKS)

			val level = player.level() as? ServerLevel ?: return InteractionResult.SUCCESS
			level.setBlock(context.blockPos, Blocks.CAULDRON.defaultBlockState(), Block.UPDATE_ALL)
			cap.fill(toFill, IFluidHandler.FluidAction.EXECUTE)

			if (item.count != 1 && !player.level().isClientSide) {
				item.count--

				if (!player.inventory.add(targetItem)) {
					player.spawnAtLocation(targetItem)
				}
			}

			return InteractionResult.sidedSuccess(player.level().isClientSide)
		}
	}

	private object FillEmptyCapability : Interaction {
		override fun canInteract(item: ItemStack, player: Player, context: Context): Boolean {
			val target = player.level().getBlockEntity(context.blockPos)?.getCapability(ForgeCapabilities.FLUID_HANDLER, context.side)?.orNull() ?: return false
			val cap = item.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return false
			return target.stream().anyMatch { !it.isEmpty } || cap.stream().anyMatch { !it.isEmpty }
		}

		override fun interact(item: ItemStack, player: Player, context: Context): InteractionResult {
			if (item.isEmpty) return InteractionResult.FAIL

			val targetItem = if (item.count == 1) item else item.copyWithCount(1)

			val itemCap = targetItem.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return InteractionResult.FAIL
			val blockCap = player.level().getBlockEntity(context.blockPos)?.getCapability(ForgeCapabilities.FLUID_HANDLER, context.side)?.orNull() ?: return InteractionResult.FAIL
			val fluid = itemCap[0]

			if (fluid.isEmpty) {
				// заполняем из блока
				val moveResult = moveFluid(source = blockCap, destination = itemCap, actuallyDrain = !player.level().isClientSide, actuallyFill = !player.level().isClientSide)

				if (!moveResult.isEmpty) {
					val sound = moveResult.fluid.fluidType.getSound(moveResult, SoundActions.BUCKET_FILL)

					if (sound != null) {
						player.level().playSound(player, context.blockPos, sound, SoundSource.BLOCKS)
					}

					if (item.count != 1 && !player.level().isClientSide) {
						item.count--

						if (!player.inventory.add(targetItem)) {
							player.spawnAtLocation(targetItem)
						}
					}

					return InteractionResult.sidedSuccess(player.level().isClientSide)
				} else {
					return InteractionResult.FAIL
				}
			} else {
				val moveResult = moveFluid(source = itemCap, destination = blockCap, actuallyDrain = !player.level().isClientSide, actuallyFill = !player.level().isClientSide)

				if (!moveResult.isEmpty) {
					val sound = moveResult.fluid.fluidType.getSound(moveResult, SoundActions.BUCKET_EMPTY)

					if (sound != null) {
						player.level().playSound(player, context.blockPos, sound, SoundSource.BLOCKS)
					}

					if (item.count != 1 && !player.level().isClientSide) {
						item.count--

						if (!player.inventory.add(targetItem)) {
							player.spawnAtLocation(targetItem)
						}
					}

					return InteractionResult.sidedSuccess(player.level().isClientSide)
				} else {
					return InteractionResult.FAIL
				}
			}
		}
	}

	data class Context(val blockPos: BlockPos, val blockState: BlockState, val side: Direction)

	companion object : Interaction {
		val interactions = immutableList {
			accept(FillCauldron)
			accept(EmptyCauldron)
			// accept(FillEmptyCapability) // блоки из модов это сами контролируют
		}

		override fun canInteract(item: ItemStack, player: Player, context: Context): Boolean {
			return interactions.any { it.canInteract(item, player, context) }
		}

		override fun interact(item: ItemStack, player: Player, context: Context): InteractionResult {
			return interactions.firstOrNull { it.canInteract(item, player, context) }?.interact(item, player, context) ?: InteractionResult.PASS
		}
	}
}
