package me.earthme.luminol.entitycull;

import me.earthme.luminol.config.modules.optimizations.EntityCullConfig;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.AbstractMinecart;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.server.level.ServerPlayer;

/**
 * Decides whether an entity may be culled (packets suppressed) for a given viewer because it lies
 * outside the viewer's view cone. Pure read-only geometry; safe to call from the entity tracker.
 *
 * @see me.earthme.luminol.config.modules.optimizations.EntityCullConfig
 */
public final class EntityCullPolicy {

    private EntityCullPolicy() {}

    /**
     * @return {@code true} if {@code entity} should be hidden from {@code viewer} (do not pair),
     *         {@code false} to keep vanilla visibility.
     */
    public static boolean shouldCull(ServerPlayer viewer, Entity entity) {
        if (!EntityCullConfig.enabled) return false;
        if (entity == viewer) return false;

        // Category exemptions.
        if (entity instanceof Player && !EntityCullConfig.cullPlayers) return false;
        if ((entity instanceof AbstractMinecart || entity instanceof Boat) && !EntityCullConfig.cullVehicles) return false;
        // Never cull the entity the viewer is riding or that is riding the viewer.
        if (viewer.getVehicle() == entity || entity.getVehicle() == viewer) return false;

        final double dx = entity.getX() - viewer.getX();
        final double dz = entity.getZ() - viewer.getZ();
        final double dy = entity.getY() - viewer.getY();

        final double horizSq = dx * dx + dz * dz;
        final double nearSq = EntityCullConfig.nearRadius * EntityCullConfig.nearRadius;
        // Always send anything inside the near radius (point-blank pop-in guard).
        if (horizSq <= nearSq) return false;

        // Viewer facing direction (unit vector in XZ from yaw; full 3D if vertical culling on).
        final float yawRad = (float) Math.toRadians(viewer.getYRot());
        // Minecraft yaw: 0 = +Z (south), 90 = -X (west). facing = (-sin(yaw), 0, cos(yaw)).
        final double fx = -Math.sin(yawRad);
        final double fz = Math.cos(yawRad);

        final double cosHalfFov = Math.cos(Math.toRadians(EntityCullConfig.halfFovDegrees));

        if (EntityCullConfig.verticalCulling) {
            final float pitchRad = (float) Math.toRadians(viewer.getXRot());
            final double cosPitch = Math.cos(pitchRad);
            final double f3x = fx * cosPitch;
            final double f3z = fz * cosPitch;
            final double f3y = -Math.sin(pitchRad);
            final double len = Math.sqrt(horizSq + dy * dy);
            if (len < 1.0E-4) return false;
            final double dot = (dx * f3x + dy * f3y + dz * f3z) / len;
            return dot < cosHalfFov;
        }

        // Horizontal-only cone test.
        final double horizLen = Math.sqrt(horizSq);
        if (horizLen < 1.0E-4) return false;
        final double dot = (dx * fx + dz * fz) / horizLen;
        return dot < cosHalfFov;
    }
}
