import { Direction, EquipmentSlot, ItemStack, system } from "@minecraft/server";
import { directionState, getLeftBlock, getRightBlock } from "../util/globalVariables";
import { decrementItemInHand, spawnEntityRotatedByBlock } from "../util/utils";

export function equipEntityWithItem(player, entity, item, sound = "block.itemframe.add_item") {
	entity.getComponent("inventory").container.setItem(0, new ItemStack(item, 1));
	entity.runCommand(`replaceitem entity @s slot.weapon.mainhand 0 ${item}`);
	player.dimension.playSound(sound, entity.location);
	decrementItemInHand(player);
}
export function updateVertical(block) {
	block.setPermutation(block.permutation
		.withState("mr:top", block.above()?.typeId === block.typeId)
		.withState("mr:bottom", block.below()?.typeId === block.typeId)
	);
}
export function updateVerticalNearestBlocks(block, id) {
	if (block.above()?.typeId === id) updateVertical(block.above());
	if (block.below()?.typeId === id) updateVertical(block.below());
}
export function updateHorizontal(block, direction, id) {
	block.setPermutation(block.permutation
		.withState("mr:right", getRightBlock(block, direction)?.hasTag(`${id}_${direction}`))
		.withState("mr:left", getLeftBlock(block, direction)?.hasTag(`${id}_${direction}`))
	);
}
export function updateHorizontalNearestBlocks(block, direction, id) {
	const rightBlock = getRightBlock(block, direction);
	const leftBlock = getLeftBlock(block, direction);
	if (rightBlock?.typeId === id) updateHorizontal(rightBlock, rightBlock?.permutation?.getState(directionState), id);
	if (leftBlock?.typeId === id) updateHorizontal(leftBlock, leftBlock?.permutation?.getState(directionState), id);
}
export class McfUpdateSideBlocks {
	constructor() {
		this.onPlace = this.onPlace.bind(this);
		this.onPlayerBreak = this.onPlayerBreak.bind(this);
	}
	onPlace(e, p) {
		const direction = e.block.permutation.getState(directionState);
		updateHorizontal(e.block, direction, p.params.id);
		updateHorizontalNearestBlocks(e.block, direction, p.params.id);
	}
	onPlayerBreak(e, p) {
		const direction = e.brokenBlockPermutation.getState(directionState);
		updateHorizontalNearestBlocks(e.block, direction, p.params.id);
	}
}
export class McfSit {
	constructor() {
		this.onPlayerInteract = this.onPlayerInteract.bind(this);
	}
	static spawnEntityRotated(id, block, direction, { y = 0, z = 0, old } = {}) {
		const center = block.center();
		const offsets = {
			north: { dx: 0, dz: z, rot: old ? 0 : 180 },
			south: { dx: 0, dz: -z, rot: old ? 180: 0 },
			west:  { dx: z, dz: 0, rot: old ? -90 : 90 },
			east:  { dx: -z, dz: 0, rot: old ? 90 : -90 },
		}[direction];
		const pos = { x: center.x + offsets.dx, y: center.y + y, z: center.z + offsets.dz };
		const entity = block.dimension.spawnEntity(id, pos);
		entity.setRotation({ x: 0, y: offsets.rot });
		return entity;
	}
	static sit(block, player, entityType, { y = 0, z = 0, old = false } = {}) {
		if (block.dimension.getEntitiesAtBlockLocation(block.location).some(e => e.typeId === entityType)) return;
		const dir = block.permutation.getState(directionState);
		this.spawnEntityRotated(entityType, block, dir, { y, z, old }).getComponent("rideable").addRider(player);
	}
	onPlayerInteract(e, p) {
		McfSit.sit(e.block, e.player, "mr:chair_entity", p.params.offset);
	}
}
export class McfUpdateOldCardinalBlocks {
	constructor() {
		this.onPlace = this.onPlace.bind(this);
		this.onPlayerBreak = this.onPlayerBreak.bind(this);
	}
	update(block, tag) {
		block.setPermutation(block.permutation
			.withState("mr:n", block.north()?.hasTag(tag) ? 1 : 0)
			.withState("mr:s", block.south()?.hasTag(tag) ? 1 : 0)
			.withState("mr:w", block.west()?.hasTag(tag) ? 1 : 0)
			.withState("mr:e", block.east()?.hasTag(tag) ? 1 : 0));
	}
	updateNearest(block, tag) {
		if (block.north().hasTag(tag)) this.update(block.north(), tag);
		if (block.south().hasTag(tag)) this.update(block.south(), tag);
		if (block.west().hasTag(tag)) this.update(block.west(), tag);
		if (block.east().hasTag(tag)) this.update(block.east(), tag);
	}
	onPlace(e, p) {
		this.update(e.block, p.params.id);
		this.updateNearest(e.block, p.params.id);
	}
	onPlayerBreak(e, p) {
		this.updateNearest(e.block, p.params.id);
	}
}
export class McfUpdateCardinalBlocks {
	constructor() {
		this.onPlace = this.onPlace.bind(this);
		this.onPlayerBreak = this.onPlayerBreak.bind(this);
	}
	update(block, tag) {
		block.setPermutation(block.permutation
			.withState("mr:n", block.north()?.hasTag(tag))
			.withState("mr:s", block.south()?.hasTag(tag))
			.withState("mr:w", block.west()?.hasTag(tag))
			.withState("mr:e", block.east()?.hasTag(tag)));
	}
	updateNearest(block, tag) {
		if (block.north().hasTag(tag)) this.update(block.north(), tag);
		if (block.south().hasTag(tag)) this.update(block.south(), tag);
		if (block.west().hasTag(tag)) this.update(block.west(), tag);
		if (block.east().hasTag(tag)) this.update(block.east(), tag);
	}
	onPlace(e, p) {
		this.update(e.block, p.params.id);
		this.updateNearest(e.block, p.params.id);
	}
	onPlayerBreak(e, p) {
		this.updateNearest(e.block, p.params.id);
	}
}
export class McfBounce {
	constructor() {
		this.onEntityFallOn = this.onEntityFallOn.bind(this);
	}
	bounce(e, sound) {
		const { entity, fallDistance, dimension } = e;
		try {
			if (!entity.isSneaking) {
				const maxFallDistance = 10;
				let maxKnockback = 1.5;
				const minKnockback = 0.5;
				if (entity.matches({ type: "minecraft:player" }) && entity.isJumping) maxKnockback = 1.7;
				dimension.playSound(sound, entity.location);
				const knockback = Math.min(minKnockback + ((fallDistance / maxFallDistance) * (maxKnockback - minKnockback)), maxKnockback);
				entity.applyKnockback({ x: 0, z: 0 }, knockback);
				const delay = Math.floor((knockback / 1) * 20);
				system.runTimeout(() => {
					try { entity.addEffect("resistance", 10, { amplifier: 4, showParticles: false }); } catch (error) { }
				}, delay);
			}
		} catch (error) {}
	}
	onEntityFallOn(e, p) {
		this.bounce(e, p.params.sound);
	}
}
export class McfUpdateOldSideBlocks {
	constructor() {
		this.onPlace = this.onPlace.bind(this);
		this.onPlayerBreak = this.onPlayerBreak.bind(this);
	}
	update(block, direction, tag) {
		block.setPermutation(block.permutation
			.withState("mr:right", getLeftBlock(block, direction)?.hasTag(`${tag}_${direction}`))
			.withState("mr:left", getRightBlock(block, direction)?.hasTag(`${tag}_${direction}`))
		);
	}
	updateNeighbor(block, direction, tag, getter) {
		const neighbor = getter(block, direction);
		if (neighbor?.hasTag(`${tag}_${direction}`)) this.update(neighbor, neighbor?.permutation?.getState(directionState), tag);
	}
	updateNearest(block, direction, tag) {
		this.updateNeighbor(block, direction, tag, getLeftBlock);
		this.updateNeighbor(block, direction, tag, getRightBlock);
	}
	onPlace(e, p) {
		const direction = e.block.permutation.getState(directionState);
		this.update(e.block, direction, p.params.tag);
		this.updateNearest(e.block, direction, p.params.tag);
	}
	onPlayerBreak(e, p) {
		const direction = e.brokenBlockPermutation.getState(directionState);
		this.updateNearest(e.block, direction, p.params.tag);
	}
}
export class McfPlaceBlockWall {
	beforeOnPlayerPlace(e) {
		const { face, permutationToPlace } = e;
		if (face !== Direction.Up) {
			e.permutationToPlace = permutationToPlace
			.withState("mr:is_wall", true)
			.withState(directionState, face.toLowerCase());
		}
	}
}
export class McfPlaceBlockFace {
	beforeOnPlayerPlace(e) {
		e.permutationToPlace = e.permutationToPlace.withState(directionState, e.face.toLowerCase());
	}
}
export class McfRotateBy45 {
	constructor() {
		this.beforeOnPlayerPlace = this.beforeOnPlayerPlace.bind(this);
	}
	beforeOnPlayerPlace(e) {
		const { player } = e;
		const yRot = player.getRotation().y;
		const { direction, rotate } = this.isDiagonal(yRot);
		e.permutationToPlace = e.permutationToPlace.withState(directionState, direction).withState("mcf:rotated", rotate);
	}
	isDiagonal(yRot) {
		const normalized = yRot + 180;
		const sector = Math.floor((normalized + 22.5) / 45) % 8;
		const directions = ["south", "south", "west", "west", "north", "north", "east", "east"];
		const rotates = [false, true, false, true, false, true, false, true];
		return { direction: directions[sector], rotate: rotates[sector] };
	}
}
export class McfCreateInterface {
	onPlace(e, p) {
		const { block, dimension } = e;
		const params = p.params;
		const nameTag = params.name_tag;
		const slots = params.slots;
		const container = dimension.spawnEntity(params.entity_id, block.bottomCenter());
		if (nameTag) container.nameTag = nameTag;
		if (slots && Array.isArray(slots)) {
			const containerInv = container.getComponent("inventory").container;
			for (const slot of slots) {
				containerInv.setItem(slot, new ItemStack("mr:timer", 1));
			}
		}
	}
}
export class McfAddItem {
	constructor() {
		this.onPlayerInteract = this.onPlayerInteract.bind(this);
	}
	onPlayerInteract(e, p) {
		const { player, block, dimension } = e;
		const item = player.getComponent("equippable")?.getEquipment(EquipmentSlot.Mainhand);
		const params = p.params;
		const itemList = params.item_list;
		const amount = block.permutation.getState("mr:item_amount");
		if (itemList.includes(item?.typeId) && amount < params.max_amount) {
			this.updateBlockState(block, amount + 1, "block.itemframe.add_item");
			decrementItemInHand(player);
			return;
		}
		if (!itemList.includes(item?.typeId) && amount > 0) {
			const giveItem = params.default;
			this.updateBlockState(block, amount - 1, "block.itemframe.remove_item")
			dimension.spawnItem(new ItemStack(giveItem, 1), player.location);
		}
	}
	updateBlockState(block, newAmount, sound) {
		block.setPermutation(block.permutation.withState("mr:item_amount", newAmount));
		block.dimension.playSound(sound, block.center());
	}
}
export class McfItemPlacer {
	onPlayerInteract(e, p) {
		const { player, block, dimension} = e;
		const item = player.getComponent("equippable")?.getEquipment(EquipmentSlot.Mainhand);
		const tags = p.params.allow.tags ?? [];
		const condition = item && tags.some(tag => item.hasTag(tag));
		const oldItemEntity = dimension.getEntitiesAtBlockLocation(block.location).find(en => en.matches({ type: "mr:item_placer" }));
		if (condition && !oldItemEntity) {
			const direction = block.permutation.getState(directionState);
			const itemEntity = spawnEntityRotatedByBlock("mr:item_placer", block, direction, { ...(p.params.offset ?? {}), init: block.bottomCenter() });
			equipEntityWithItem(player, itemEntity, item.typeId);
			return;
		}
		if (oldItemEntity) {
			const storedItem = oldItemEntity.getComponent("inventory").container.getItem(0);
			if (storedItem) dimension.spawnItem(storedItem, player.location);
			oldItemEntity.remove();
		}
	}
}