package pers.neige.neigeitems.libs.bot.inker.bukkit.nbt.neigeitems.utils;

import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Item;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.Nullable;
import pers.neige.neigeitems.NeigeItems;
import pers.neige.neigeitems.libs.bot.inker.bukkit.nbt.internal.annotation.CbVersion;
import pers.neige.neigeitems.ref.entity.RefCraftEntity;
import pers.neige.neigeitems.ref.entity.RefEntity;
import pers.neige.neigeitems.ref.entity.RefEntityItem;
import pers.neige.neigeitems.ref.nbt.RefCraftItemStack;
import pers.neige.neigeitems.ref.server.level.RefServerEntity;
import pers.neige.neigeitems.ref.server.level.RefTrackedEntity;
import pers.neige.neigeitems.ref.server.level.RefWorldServer;
import pers.neige.neigeitems.ref.world.RefAABB;
import pers.neige.neigeitems.ref.world.RefCraftWorld;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Predicate;

public class WorldUtils {
    /**
     * 1.17+ 版本起, World#dropItem 方法可以传入一个 Consumer, 用于在实体添加到世界中之前对实体进行一些操作.
     */
    private static final boolean DROP_ITEM_WITH_CONSUMER_SUPPORT = CbVersion.v1_17_R1.isSupport();
    /**
     * 1.13+ 版本起, CraftWorld#getNearbyEntities 方法中的 Predicate 参数由 Guava 实现变更为 Java 实现.
     */
    private static final boolean JAVA_PREDICATE_SUPPORT = CbVersion.v1_13_R1.isSupport();
    /**
     * 1.19.4+ 版本起, Chunk 类移除 bukkitChunk 字段, 需要自行构建 CraftChunk 实例.
     */
    private static final boolean REMOVE_BUKKIT_CHUNK_FIELD = CbVersion.v1_19_R3.isSupport();
    /**
     * 1.17+ 版本起, spawnEntity 方法添加 boolean 类型 randomizeData 参数, 用于随机生成实体数据.
     */
    private static final boolean RANDOMIZE_DATA_SUPPORT = CbVersion.v1_17_R1.isSupport();
    /**
     * 1.20.2+ 版本起, addEntity 方法的 function 参数由 org.bukkit.util.Consumer 变更为 java.util.function.Consumer.
     */
    private static final boolean JAVA_CONSUMER_SUPPORT = CbVersion.v1_20_R2.isSupport();
    /**
     * 1.14+ 版本起, WorldServer 类移除 tracker 字段.
     */
    private static final boolean REMOVED_TRACKER = CbVersion.v1_14_R1.isSupport();

    /**
     * 获取世界中掉落物实体的最大存活时长(tick).
     *
     * @param world 待检测世界.
     * @return 世界中掉落物实体的最大存活时长(tick).
     */
    public static int getDespawnRate(
        World world
    ) {
        return ((RefCraftWorld) (Object) world).getHandle().spigotConfig.itemDespawnRate;
    }

    /**
     * 在指定世界的指定坐标生成一个掉落物, 生成实体前对实体进行一些操作.
     *
     * @param world     待掉落世界.
     * @param location  待掉落坐标.
     * @param itemStack 待掉落物品.
     * @param function  掉落前对物品执行的操作.
     * @return 生成的掉落物.
     */
    public static @NonNull Item dropItem(
        @NonNull World world,
        @NonNull Location location,
        @NonNull ItemStack itemStack,
        @Nullable Consumer<Item> function
    ) {
        RefWorldServer worldServer = ((RefCraftWorld) (Object) world).getHandle();

        RefEntityItem entity = new RefEntityItem(
            worldServer,
            location.getX(),
            location.getY(),
            location.getZ(),
            RefCraftItemStack.asNMSCopy(itemStack)
        );
        Item itemEntity = (Item) entity.getBukkitEntity();
        entity.pickupDelay = 10;
        if (function != null) {
            function.accept(itemEntity);
        }

        worldServer.addEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM);
        return itemEntity;
    }

    /**
     * 在指定世界的指定坐标生成一个掉落物, 生成实体前对实体添加一个向量.
     *
     * @param world     待掉落世界.
     * @param location  待掉落坐标.
     * @param itemStack 待掉落物品.
     * @param velocity  待添加向量.
     * @return 生成的掉落物.
     */
    public static @NonNull Item dropItem(
        @NonNull World world,
        @NonNull Location location,
        @NonNull ItemStack itemStack,
        @NonNull Vector velocity
    ) {
        RefWorldServer worldServer = ((RefCraftWorld) (Object) world).getHandle();

        RefEntityItem entity = new RefEntityItem(
            worldServer,
            location.getX(),
            location.getY(),
            location.getZ(),
            RefCraftItemStack.asNMSCopy(itemStack)
        );
        Item itemEntity = (Item) entity.getBukkitEntity();
        entity.pickupDelay = 10;
        itemEntity.setVelocity(velocity);

        worldServer.addEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM);
        return itemEntity;
    }

    /**
     * 检索实体周围, 指定大小立方体区域内的实体.
     *
     * @param entity 待检索实体.
     * @param x      立方体 x 轴边长的一半.
     * @param y      立方体 y 轴边长的一半.
     * @param z      立方体 z 轴边长的一半.
     * @param filter 对应实体需要满足的条件.
     * @return 生成的掉落物.
     */
    public static @NonNull List<Entity> getNearbyEntities(
        @NonNull Entity entity,
        double x,
        double y,
        double z,
        @Nullable Predicate<? super Entity> filter
    ) {
        return getNearbyEntities(entity, x, y, z, Integer.MAX_VALUE, filter);
    }

    /**
     * 检索实体周围, 指定大小立方体区域内的实体.
     *
     * @param entity 待检索实体.
     * @param x      立方体 x 轴边长的一半.
     * @param y      立方体 y 轴边长的一半.
     * @param z      立方体 z 轴边长的一半.
     * @param limit  检索数量上限.
     * @return 生成的掉落物.
     */
    public static @NonNull List<Entity> getNearbyEntities(
        @NonNull Entity entity,
        double x,
        double y,
        double z,
        int limit
    ) {
        return getNearbyEntities(entity, x, y, z, limit, null);
    }

    /**
     * 检索实体周围, 指定大小立方体区域内的实体.
     *
     * @param entity 待检索实体.
     * @param x      立方体 x 轴边长的一半.
     * @param y      立方体 y 轴边长的一半.
     * @param z      立方体 z 轴边长的一半.
     * @param limit  检索数量上限.
     * @param filter 对应实体需要满足的条件.
     * @return 生成的掉落物.
     */
    public static @NonNull List<Entity> getNearbyEntities(
        @NonNull Entity entity,
        double x,
        double y,
        double z,
        int limit,
        @Nullable Predicate<? super Entity> filter
    ) {
        Location location = entity.getLocation();
        return getEntities(entity, entity.getWorld(), location.getX() - x, location.getY() - y, location.getZ() - z, location.getX() + x, location.getY() + y, location.getZ() + z, limit, filter);
    }

    /**
     * 检索指定世界的指定坐标周围, 指定大小立方体区域内的实体.
     *
     * @param world    待检索世界.
     * @param location 待检索坐标.
     * @param x        立方体 x 轴边长的一半.
     * @param y        立方体 y 轴边长的一半.
     * @param z        立方体 z 轴边长的一半.
     * @param filter   对应实体需要满足的条件.
     * @return 生成的掉落物.
     */
    public static @NonNull List<Entity> getNearbyEntities(
        @NonNull World world,
        @NonNull Location location,
        double x,
        double y,
        double z,
        @Nullable Predicate<? super Entity> filter
    ) {
        return getNearbyEntities(world, location, x, y, z, Integer.MAX_VALUE, filter);
    }

    /**
     * 检索指定世界的指定坐标周围, 指定大小立方体区域内的实体.
     *
     * @param world    待检索世界.
     * @param location 待检索坐标.
     * @param x        立方体 x 轴边长的一半.
     * @param y        立方体 y 轴边长的一半.
     * @param z        立方体 z 轴边长的一半.
     * @param limit    检索数量上限.
     * @return 生成的掉落物.
     */
    public static @NonNull List<Entity> getNearbyEntities(
        @NonNull World world,
        @NonNull Location location,
        double x,
        double y,
        double z,
        int limit
    ) {
        return getNearbyEntities(world, location, x, y, z, limit, null);
    }

    /**
     * 检索指定世界的指定坐标周围, 指定大小立方体区域内的实体.
     *
     * @param world    待检索世界.
     * @param location 待检索坐标.
     * @param x        立方体 x 轴边长的一半.
     * @param y        立方体 y 轴边长的一半.
     * @param z        立方体 z 轴边长的一半.
     * @param limit    检索数量上限.
     * @param filter   对应实体需要满足的条件.
     * @return 生成的掉落物.
     */
    public static @NonNull List<Entity> getNearbyEntities(
        @NonNull World world,
        @NonNull Location location,
        double x,
        double y,
        double z,
        int limit,
        @Nullable Predicate<? super Entity> filter
    ) {
        return getEntities(null, world, location.getX() - x, location.getY() - y, location.getZ() - z, location.getX() + x, location.getY() + y, location.getZ() + z, limit, filter);
    }

    /**
     * 检索指定世界指定大小立方体区域内的实体.
     *
     * @param world  待检索世界.
     * @param minX   立方体 x 轴最小值.
     * @param minY   立方体 y 轴最小值.
     * @param minZ   立方体 z 轴最小值.
     * @param maxX   立方体 x 轴最大值.
     * @param maxY   立方体 y 轴最大值.
     * @param maxZ   立方体 z 轴最大值.
     * @param filter 对应实体需要满足的条件.
     * @return 生成的掉落物.
     */
    public static @NonNull List<Entity> getEntities(
        @NonNull World world,
        double minX,
        double minY,
        double minZ,
        double maxX,
        double maxY,
        double maxZ,
        @Nullable Predicate<? super Entity> filter
    ) {
        return getEntities(null, world, minX, minY, minZ, maxX, maxY, maxZ, Integer.MAX_VALUE, filter);
    }

    /**
     * 检索指定世界指定大小立方体区域内的实体.
     *
     * @param world 待检索世界.
     * @param minX  立方体 x 轴最小值.
     * @param minY  立方体 y 轴最小值.
     * @param minZ  立方体 z 轴最小值.
     * @param maxX  立方体 x 轴最大值.
     * @param maxY  立方体 y 轴最大值.
     * @param maxZ  立方体 z 轴最大值.
     * @param limit 检索数量上限.
     * @return 生成的掉落物.
     */
    public static @NonNull List<Entity> getEntities(
        @NonNull World world,
        double minX,
        double minY,
        double minZ,
        double maxX,
        double maxY,
        double maxZ,
        int limit
    ) {
        return getEntities(null, world, minX, minY, minZ, maxX, maxY, maxZ, limit, null);
    }

    /**
     * 检索指定世界指定大小立方体区域内的实体.
     *
     * @param world  待检索世界.
     * @param minX   立方体 x 轴最小值.
     * @param minY   立方体 y 轴最小值.
     * @param minZ   立方体 z 轴最小值.
     * @param maxX   立方体 x 轴最大值.
     * @param maxY   立方体 y 轴最大值.
     * @param maxZ   立方体 z 轴最大值.
     * @param limit  检索数量上限.
     * @param filter 对应实体需要满足的条件.
     * @return 生成的掉落物.
     */
    public static @NonNull List<Entity> getEntities(
        @NonNull World world,
        double minX,
        double minY,
        double minZ,
        double maxX,
        double maxY,
        double maxZ,
        int limit,
        @Nullable Predicate<? super Entity> filter
    ) {
        return getEntities(null, world, minX, minY, minZ, maxX, maxY, maxZ, limit, filter);
    }

    /**
     * 检索指定世界指定大小立方体区域内的实体.
     *
     * @param except 排除在外的实体.
     * @param world  待检索世界.
     * @param minX   立方体 x 轴最小值.
     * @param minY   立方体 y 轴最小值.
     * @param minZ   立方体 z 轴最小值.
     * @param maxX   立方体 x 轴最大值.
     * @param maxY   立方体 y 轴最大值.
     * @param maxZ   立方体 z 轴最大值.
     * @param limit  检索数量上限.
     * @param filter 对应实体需要满足的条件.
     * @return 生成的掉落物.
     */
    public static @NonNull List<Entity> getEntities(
        @Nullable Entity except,
        @NonNull World world,
        double minX,
        double minY,
        double minZ,
        double maxX,
        double maxY,
        double maxZ,
        int limit,
        @Nullable Predicate<? super Entity> filter
    ) {
        RefEntity realExcept = null;
        if (except instanceof RefCraftEntity) {
            realExcept = ((RefCraftEntity) except).getHandle();
        }
        RefWorldServer worldServer = ((RefCraftWorld) (Object) world).getHandle();
        RefAABB aabb = new RefAABB(minX, minY, minZ, maxX, maxY, maxZ);
        final List<Entity> entities = new ArrayList<>();
        if (JAVA_PREDICATE_SUPPORT) {
            worldServer.getEntities(realExcept, aabb, (Predicate<RefEntity>) refEntity -> {
                if (entities.size() < limit) {
                    Entity entity = refEntity.getBukkitEntity();
                    if (filter == null || filter.test(entity)) {
                        entities.add(entity);
                        return true;
                    }
                }
                return false;
            });
        } else {
            worldServer.getEntities(realExcept, aabb, refEntity -> {
                if (entities.size() < limit) {
                    Entity entity = refEntity.getBukkitEntity();
                    if (filter == null || filter.test(entity)) {
                        entities.add(entity);
                        return true;
                    }
                }
                return false;
            });
        }
        return entities;
    }

    /**
     * 生成一个实体, 但不添加进世界里.
     *
     * @param location 待生成坐标.
     * @param type     实体类型.
     * @return 生成的实体.
     */
    public static @Nullable Entity createEntity(
        @NonNull Location location,
        @NonNull EntityType type
    ) {
        return createEntity(location, type, true);
    }

    /**
     * 生成一个实体, 但不添加进世界里.
     *
     * @param location      待生成坐标.
     * @param type          实体类型.
     * @param randomizeData 是否随机实体数据(仅在1.17+版本生效).
     * @return 生成的实体.
     */
    public static @Nullable Entity createEntity(
        @NonNull Location location,
        @NonNull EntityType type,
        boolean randomizeData
    ) {
        RefEntity entity = createNmsEntity(location, type, randomizeData);
        return entity == null ? null : entity.getBukkitEntity();
    }

    /**
     * 在指定世界的指定坐标生成一个实体, 生成实体前对实体进行一些操作.
     *
     * @param location 待生成坐标.
     * @param type     实体类型.
     * @param function 生成前对实体执行的操作.
     * @return 生成的实体.
     */
    public static @Nullable Entity spawnEntity(
        @NonNull Location location,
        @NonNull EntityType type,
        @Nullable Consumer<Entity> function
    ) {
        return spawnEntity(location, type, true, function, CreatureSpawnEvent.SpawnReason.CUSTOM);
    }

    /**
     * 在指定世界的指定坐标生成一个实体, 生成实体前对实体进行一些操作.
     *
     * @param location      待生成坐标.
     * @param type          实体类型.
     * @param randomizeData 是否随机实体数据(仅在1.17+版本生效).
     * @param function      生成前对实体执行的操作.
     * @return 生成的实体.
     */
    public static @Nullable Entity spawnEntity(
        @NonNull Location location,
        @NonNull EntityType type,
        boolean randomizeData,
        @Nullable Consumer<Entity> function
    ) {
        return spawnEntity(location, type, randomizeData, function, CreatureSpawnEvent.SpawnReason.CUSTOM);
    }

    /**
     * 在指定世界的指定坐标生成一个实体, 生成实体前对实体进行一些操作.
     *
     * @param location      待生成坐标.
     * @param type          实体类型.
     * @param randomizeData 是否随机实体数据(仅在1.17+版本生效).
     * @param function      生成前对实体执行的操作.
     * @param spawnReason   生成原因.
     * @return 生成的实体.
     */
    public static @Nullable Entity spawnEntity(
        @NonNull Location location,
        @NonNull EntityType type,
        boolean randomizeData,
        @Nullable Consumer<Entity> function,
        @Nullable CreatureSpawnEvent.SpawnReason spawnReason
    ) {
        World world = location.getWorld();
        if (world == null) return null;
        RefCraftWorld craftWorld = (RefCraftWorld) (Object) world;

        RefEntity entity;
        if (RANDOMIZE_DATA_SUPPORT) {
            entity = craftWorld.createEntity(location, type.getEntityClass(), randomizeData);
        } else {
            entity = craftWorld.createEntity(location, type.getEntityClass());
        }
        Entity bukkitEntity = entity.getBukkitEntity();

        if (RANDOMIZE_DATA_SUPPORT) {
            if (JAVA_CONSUMER_SUPPORT) {
                craftWorld.addEntity(entity, spawnReason, function, randomizeData);
            } else {
                if (function != null) {
                    craftWorld.addEntity(entity, spawnReason, (org.bukkit.util.Consumer<Entity>) function::accept, randomizeData);
                } else {
                    craftWorld.addEntity(entity, spawnReason, (org.bukkit.util.Consumer<Entity>) null, randomizeData);
                }
            }
        } else {
            if (function != null) {
                craftWorld.addEntity(entity, spawnReason, function::accept);
            } else {
                craftWorld.addEntity(entity, spawnReason, null);
            }
        }
        return bukkitEntity;
    }

    /**
     * 通过实体ID获取对应的实体.
     *
     * @param world    实体所在世界.
     * @param entityId 实体ID.
     * @return 对应的实体.
     */
    public static @Nullable Entity getEntityFromID(
        @NonNull World world,
        int entityId
    ) {
        if ((Object) world instanceof RefCraftWorld) {
            RefEntity entity = ((RefCraftWorld) (Object) world).getHandle().getEntity(entityId);
            if (entity != null) {
                return entity.getBukkitEntity();
            }
        }
        return null;
    }

    /**
     * 通过实体ID获取对应的实体(允许异步操作).
     *
     * @param world    实体所在世界.
     * @param entityId 实体ID.
     * @return 对应的实体.
     */
    public static @NonNull CompletableFuture<Entity> getEntityFromIDAsync(
        @NonNull World world,
        int entityId
    ) {
        if (Bukkit.isPrimaryThread()) {
            return CompletableFuture.completedFuture(getEntityFromID(world, entityId));
        } else {
            CompletableFuture<Entity> result = new CompletableFuture<>();
            Bukkit.getScheduler().runTask(NeigeItems.getInstance(), () -> {
                result.complete(getEntityFromID(world, entityId));
            });
            return result;
        }
    }

    /**
     * 通过实体ID获取对应的实体.
     *
     * @param world    实体所在世界.
     * @param entityId 实体ID.
     * @return 对应的实体.
     */
    public static @Nullable Entity getEntityFromID1(
        @NonNull World world,
        int entityId
    ) {
        if ((Object) world instanceof RefCraftWorld) {
            RefEntity entity = getEntityFromIDByNms1(((RefCraftWorld) (Object) world).getHandle(), entityId);
            if (entity != null) {
                return entity.getBukkitEntity();
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    protected static @Nullable RefEntity getEntityFromIDByNms1(
        @NonNull RefWorldServer world,
        int entityId
    ) {
        RefServerEntity serverEntity = null;
        if (REMOVED_TRACKER) {
            RefTrackedEntity trackedEntity = ((Map<Integer, RefTrackedEntity>) world.getChunkSource().chunkMap.entityMap).get(entityId);
            if (trackedEntity != null) {
                return trackedEntity.entity;
            }
        } else {
            serverEntity = world.tracker.trackedEntities.get(entityId);
        }
        if (serverEntity != null) {
            return serverEntity.entity;
        }
        return null;
    }

    /**
     * 生成一个实体, 但不添加进世界里.
     *
     * @param location 待生成坐标.
     * @param type     实体类型.
     * @return 生成的实体.
     */
    protected static @Nullable RefEntity createNmsEntity(
        @NonNull Location location,
        @NonNull EntityType type
    ) {
        return createNmsEntity(location, type, true);
    }

    /**
     * 生成一个实体, 但不添加进世界里.
     *
     * @param location      待生成坐标.
     * @param type          实体类型.
     * @param randomizeData 是否随机实体数据(仅在1.17+版本生效).
     * @return 生成的实体.
     */
    protected static @Nullable RefEntity createNmsEntity(
        @NonNull Location location,
        @NonNull EntityType type,
        boolean randomizeData
    ) {
        World world = location.getWorld();
        if (world == null) return null;
        RefCraftWorld craftWorld = (RefCraftWorld) (Object) world;

        RefEntity entity;
        if (RANDOMIZE_DATA_SUPPORT) {
            entity = craftWorld.createEntity(location, type.getEntityClass(), randomizeData);
        } else {
            entity = craftWorld.createEntity(location, type.getEntityClass());
        }
        return entity;
    }

    public static Entity getEntityFromServerEntity(
        @NonNull Object serverEntity
    ) {
        if (serverEntity instanceof RefServerEntity) {
            return ((RefServerEntity) serverEntity).entity.getBukkitEntity();
        }
        return null;
    }
}
