/*
 * SPDX-FileCopyrightText: 2026 KOKA99CAB
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

package net.koka99cab.portals.world;

import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.koka99cab.portals.PortalsMod;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.RandomSource;
import net.minecraft.world.Difficulty;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.phys.AABB;

public final class SkeletonWasteSpawner {
	private static final int TICK_INTERVAL = 80;
	private static final int LOCAL_RADIUS = 64;
	private static final int LOCAL_LIMIT = 24;

	private SkeletonWasteSpawner() {
	}

	public static void register() {
		ServerTickEvents.END_LEVEL_TICK.register(SkeletonWasteSpawner::tick);
		PortalsMod.LOGGER.debug("Registered skeleton waste spawn support");
	}

	private static void tick(ServerLevel level) {
		if (!level.dimension().equals(ModDimensions.SKELETON_WASTE) || level.getDifficulty() == Difficulty.PEACEFUL || level.players().isEmpty()) {
			return;
		}

		if (level.getGameTime() % TICK_INTERVAL != 0) {
			return;
		}

		RandomSource random = level.getRandom();

		for (ServerPlayer player : level.players()) {
			if (countSkeletonFamily(level, player.blockPosition()) >= LOCAL_LIMIT) {
				continue;
			}

			for (int attempt = 0; attempt < 4; attempt++) {
				if (trySpawnNear(level, player, random)) {
					break;
				}
			}
		}
	}

	private static boolean trySpawnNear(ServerLevel level, ServerPlayer player, RandomSource random) {
		double angle = random.nextDouble() * Math.PI * 2.0D;
		int distance = 24 + random.nextInt(33);
		int x = player.blockPosition().getX() + (int) Math.round(Math.cos(angle) * distance);
		int z = player.blockPosition().getZ() + (int) Math.round(Math.sin(angle) * distance);
		int y = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z);
		BlockPos pos = new BlockPos(x, y, z);
		EntityType<? extends Mob> type = randomSkeletonType(random);

		if (player.distanceToSqr(x + 0.5D, y, z + 0.5D) < 400.0D || !canSpawnAt(level, pos, type)) {
			return false;
		}

		return type.spawn(level, pos, EntitySpawnReason.NATURAL) != null;
	}

	private static EntityType<? extends Mob> randomSkeletonType(RandomSource random) {
		int roll = random.nextInt(100);

		if (roll < 45) {
			return EntityType.SKELETON;
		}

		if (roll < 75) {
			return EntityType.STRAY;
		}

		if (roll < 90) {
			return EntityType.PARCHED;
		}

		return EntityType.BOGGED;
	}

	private static boolean canSpawnAt(ServerLevel level, BlockPos pos, EntityType<?> type) {
		if (pos.getY() <= level.getMinY() + 1 || pos.getY() >= level.getMaxY() - 2) {
			return false;
		}

		if (!level.isEmptyBlock(pos) || !level.isEmptyBlock(pos.above())) {
			return false;
		}

		if (!level.getFluidState(pos).isEmpty() || !level.getFluidState(pos.above()).isEmpty()) {
			return false;
		}

		if (level.getBlockState(pos.below()).isAir() || level.getBlockState(pos.below()).liquid()) {
			return false;
		}

		return level.noCollision(type.getSpawnAABB(pos.getX() + 0.5D, pos.getY(), pos.getZ() + 0.5D));
	}

	private static int countSkeletonFamily(ServerLevel level, BlockPos center) {
		AABB bounds = new AABB(center).inflate(LOCAL_RADIUS);

		return level.getEntities((Entity) null, bounds, SkeletonWasteSpawner::isSkeletonFamily).size();
	}

	private static boolean isSkeletonFamily(Entity entity) {
		EntityType<?> type = entity.getType();
		return entity.isAlive()
				&& (type == EntityType.SKELETON
				|| type == EntityType.STRAY
				|| type == EntityType.BOGGED
				|| type == EntityType.PARCHED);
	}
}
