package me.earthme.luminol.explosion;

import me.earthme.luminol.config.modules.optimizations.ExplosionOptimizerConfig;
import net.minecraft.core.BlockPos;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.phys.Vec3;

import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Luminol built-in explosion optimizer.
 * Ported from LagFixer ExplosionOptimizerModule (GPL-3.0).
 */
public final class LuminolExplosionOptimizer {

    private static final ConcurrentHashMap<Long, Long> RECENT_EXPLOSIONS = new ConcurrentHashMap<>();

    private LuminolExplosionOptimizer() {}

    /**
     * Called before ServerLevel processes an explosion.
     * @return true if the explosion should be cancelled (effects will be simulated)
     */
    public static boolean onExplosion(net.minecraft.server.level.ServerLevel level,
                                       Entity source, float yield,
                                       double x, double y, double z) {
        if (!ExplosionOptimizerConfig.enabled) return false;

        // Yield cap
        float effectiveYield = yield;
        if (ExplosionOptimizerConfig.yieldLimitEnabled) {
            float limit = getYieldLimit(source);
            if (yield > limit) effectiveYield = limit;
        }

        // Anti-chain detection
        if (ExplosionOptimizerConfig.antiChainEnabled) {
            long cellKey = BlockPos.asLong(
                (int)(x / ExplosionOptimizerConfig.antiChainRadius),
                (int)(y / ExplosionOptimizerConfig.antiChainRadius),
                (int)(z / ExplosionOptimizerConfig.antiChainRadius)
            );
            long now = level.getGameTime();
            Long last = RECENT_EXPLOSIONS.get(cellKey);
            if (last != null && now - last < ExplosionOptimizerConfig.antiChainCooldownTicks) return true;
            RECENT_EXPLOSIONS.put(cellKey, now);
            if (RECENT_EXPLOSIONS.size() > 10000) RECENT_EXPLOSIONS.clear();
        }

        // Management mode - cancel big explosions and simulate effects
        if (ExplosionOptimizerConfig.managementEnabled && effectiveYield > ExplosionOptimizerConfig.managementThreshold) {
            simulateEffects(level, source, effectiveYield, x, y, z);
            return true;
        }

        return false;
    }

    private static float getYieldLimit(Entity source) {
        if (source != null) {
            String typeName = source.getType().toString().toUpperCase();
            for (var entry : ExplosionOptimizerConfig.yieldLimitPerType.entrySet()) {
                if (typeName.contains(entry.getKey())) return entry.getValue();
            }
        }
        return ExplosionOptimizerConfig.yieldLimitDefault;
    }

    private static void simulateEffects(net.minecraft.server.level.ServerLevel level,
                                         Entity source, float yield,
                                         double x, double y, double z) {
        // Sound
        if (ExplosionOptimizerConfig.simulateSound) {
            level.playSound(null, x, y, z, SoundEvents.GENERIC_EXPLODE, SoundSource.BLOCKS,
                4.0f, (1.0f + (level.random.nextFloat() - level.random.nextFloat()) * 0.2f) * 0.7f);
        }

        // Damage and knockback
        if (ExplosionOptimizerConfig.simulateDamage || ExplosionOptimizerConfig.simulateKnockback) {
            double radius = yield * 2.0;
            List<Entity> nearby = level.getEntities(source,
                new net.minecraft.world.phys.AABB(x - radius, y - radius, z - radius,
                                                   x + radius, y + radius, z + radius));
            for (Entity entity : nearby) {
                double dx = entity.getX() - x;
                double dy = entity.getY() - y;
                double dz = entity.getZ() - z;
                double dist = Math.sqrt(dx*dx + dy*dy + dz*dz);
                if (dist > radius || dist < 0.001) continue;

                double factor = (1.0 - dist / radius);

                if (ExplosionOptimizerConfig.simulateDamage && entity instanceof LivingEntity living) {
                    float damage = (float)(factor * factor * yield * 7.0f);
                    if (damage > 0) {
                        DamageSource dmgSrc = level.damageSources().explosion(source, source);
                        living.hurt(dmgSrc, damage);
                    }
                }

                if (ExplosionOptimizerConfig.simulateKnockback) {
                    double invSqrt = fastInverseSqrt(dx*dx + dy*dy + dz*dz);
                    double strength = factor;
                    entity.setDeltaMovement(
                        entity.getDeltaMovement().add(
                            dx * invSqrt * strength,
                            dy * invSqrt * strength,
                            dz * invSqrt * strength
                        )
                    );
                }
            }
        }
    }

    private static double fastInverseSqrt(double x) {
        if (x == 0) return 0;
        double half = 0.5 * x;
        long i = Double.doubleToLongBits(x);
        i = 0x5fe6eb50c7b537a9L - (i >> 1);
        x = Double.longBitsToDouble(i);
        x *= 1.5 - half * x * x;
        return x;
    }
}
