package ru.dbotthepony.mc.otm.block.entity.decorative

import net.minecraft.advancements.CriteriaTriggers
import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.LongTag
import net.minecraft.nbt.StringTag
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundSource
import net.minecraft.world.Container
import net.minecraft.world.Containers
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.level.storage.loot.LootContext
import net.minecraft.world.level.storage.loot.LootParams
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets
import net.minecraft.world.level.storage.loot.parameters.LootContextParams
import net.minecraft.world.phys.Vec3
import ru.dbotthepony.mc.otm.block.decorative.CargoCrateBlock
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.nbt.map
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.menu.decorative.CargoCrateMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.registry.MSoundEvents

class CargoCrateBlockEntity(
	p_155229_: BlockPos,
	p_155230_: BlockState
) : MatteryDeviceBlockEntity(MBlockEntities.CARGO_CRATE, p_155229_, p_155230_) {
	val container = MatteryContainer(this::setChanged, CAPACITY).also(::addDroppableContainer)

	private var interactingPlayers = 0

	val handler = container.handler(object : HandlerFilter {
		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
			return lootTable == null
		}

		override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
			return lootTable == null
		}
	})

	override fun beforeDroppingItems(oldBlockState: BlockState, level: Level, blockPos: BlockPos, newBlockState: BlockState, movedByPiston: Boolean) {
		unpackLootTable()
	}

	override fun beforeDestroyedByPlayer(level: Level, blockPos: BlockPos, blockState: BlockState, player: Player) {
		unpackLootTable(player)
	}

	var lootTable: ResourceLocation? = null
	var lootTableSeed: Long? = null

	fun onPlayerOpen() {
		val level = level

		if (interactingPlayers++ == 0 && level != null && !isRemoved && level.getBlockState(blockPos).block is CargoCrateBlock) {
			level.setBlock(blockPos, blockState.setValue(CargoCrateBlock.IS_OPEN, true), Block.UPDATE_CLIENTS)
			level.playSound(null, blockPos, MSoundEvents.CARGO_CRATE_OPEN, SoundSource.BLOCKS, 1f, 0.8f + level.random.nextFloat() * 0.2f)
			level.gameEvent(GameEvent.CONTAINER_OPEN, blockPos, GameEvent.Context.of(blockState))
		}
	}

	fun onPlayerClose() {
		val level = level

		if (--interactingPlayers == 0 && level != null && !isRemoved && level.getBlockState(blockPos).block is CargoCrateBlock) {
			level.setBlock(blockPos, blockState.setValue(CargoCrateBlock.IS_OPEN, false), Block.UPDATE_CLIENTS)
			level.gameEvent(GameEvent.CONTAINER_CLOSE, blockPos, GameEvent.Context.of(blockState))
		}
	}

	init {
		exposeItemsGlobally(handler)
		savetablesLevel.stateful(::container, INVENTORY_KEY)
	}

	override fun saveLevel(nbt: CompoundTag) {
		super.saveLevel(nbt)
		lootTable?.let { nbt[LOOT_TABLE_KEY] = it.toString() }
		lootTableSeed?.let { nbt[LOOT_TABLE_SEED_KEY] = it }
	}

	override fun load(nbt: CompoundTag) {
		super.load(nbt)
		lootTable = nbt.map(LOOT_TABLE_KEY) { it: StringTag -> ResourceLocation.tryParse(it.asString) }
		lootTableSeed = (nbt[LOOT_TABLE_SEED_KEY] as LongTag?)?.asLong
	}

	fun unpackLootTable(ply: Player? = null) {
		val lootTable = lootTable ?: return
		val lootTableSeed = lootTableSeed ?: 0L
		val server = level?.server ?: return

		val loot = server.lootData.getLootTable(lootTable)

		if (ply is ServerPlayer) {
			CriteriaTriggers.GENERATE_LOOT.trigger(ply, lootTable)
		}

		this.lootTable = null
		this.lootTableSeed = null

		val params = LootParams.Builder(level as ServerLevel)
			.withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(this.worldPosition))

		if (ply != null) {
			params.withLuck(ply.luck).withParameter(LootContextParams.THIS_ENTITY, ply)
		}

		Containers.dropContents(level as ServerLevel, blockPos, container)
		loot.fill(container, params.create(LootContextParamSets.CHEST), lootTableSeed)
	}

	override val defaultDisplayName: Component
		get() = MACHINE_NAME

	override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? {
		if (lootTable != null && ply.isSpectator)
			return null

		unpackLootTable(ply)
		return CargoCrateMenu(containerID, inventory, this)
	}

	companion object {
		private val MACHINE_NAME = TranslatableComponent("block.overdrive_that_matters.cargo_crate")
		const val CAPACITY = 9 * 6
	}
}
