package net.mcreator.phayriosisreborn.meteor;

import java.lang.reflect.*;
import java.util.*;

/**
 * Optional, reflection-only visual/audio bridge inspired by Ballistix' nuclear blast.
 * It has no hard Ballistix dependency. When Ballistix is present its registered nuclear
 * sound and particle option classes are used; otherwise vanilla/Phayriosis fallbacks
 * run with the same 5-block-per-tick wave timing supplied by the impact system.
 */
public final class PhayriosisMeteorBallistixVisualBridge {
    private static final double VISUAL_MAX_RANGE_SQ = 512.0D * 512.0D;
    private static final double SOUND_MAX_RANGE_SQ = 1024.0D * 1024.0D;
    private static volatile Method sendParticleMethod;
    private static volatile Method heightMethod;
    private static volatile Object heightType;

    private PhayriosisMeteorBallistixVisualBridge() {}

    public static void playImpactSound(Object level, double x, double y, double z) {
        if (level == null) return;
        try {
            int variant = 1 + (int)(Math.random() * 3.0D);
            Object directSound = registryValue("SOUND_EVENTS", "explosionoverhaul", "explode_close_power_7_" + variant);
            Object distantSound = registryValue("SOUND_EVENTS", "explosionoverhaul", "explode_far_power_7_" + variant);
            if (directSound == null) directSound = ballistixNuclearSound();
            if (directSound == null) directSound = registryValue("SOUND_EVENTS", "phayriosisreborn", "largeexplosion");
            if (directSound == null) directSound = registryValue("SOUND_EVENTS", "minecraft", "entity.generic.explode");
            if (directSound == null) return;
            if (distantSound == null) distantSound = directSound;
            final Object sound = directSound;
            final Object farSound = distantSound;

            Class<?> soundSourceClass = Class.forName("net.minecraft.sounds.SoundSource");
            @SuppressWarnings({"rawtypes", "unchecked"})
            Object blocks = Enum.valueOf((Class<? extends Enum>) soundSourceClass, "BLOCKS");
            @SuppressWarnings({"rawtypes", "unchecked"})
            Object playersSource = Enum.valueOf((Class<? extends Enum>) soundSourceClass, "PLAYERS");

            // Ballistix first emits the nuclear sound at the actual blast location at high volume.
            Object blockPos = containingBlockPos(x, y, z);
            Method blockPlay = findMethod(level.getClass(), "m_5594_", 6);
            if (blockPlay != null && blockPos != null) {
                blockPlay.invoke(level, null, blockPos, sound, blocks, 25.0F, 0.96F);
            }

            // Guarantee that distant witnesses receive an audible directional report.
            // Rather than pretending the sound originates inside the player, place this
            // helper source 28 blocks toward the crater so Sound Physics keeps direction.
            Object listObj = invokeNoArg(level, "m_6907_");
            if (listObj instanceof Iterable<?> players) {
                Method xyzPlay = findMethod(level.getClass(), "m_6263_", 8);
                if (xyzPlay != null) {
                    for (Object player : players) {
                        double px = coord(player, "m_20185_");
                        double py = coord(player, "m_20186_");
                        double pz = coord(player, "m_20189_");
                        double dx = x - px, dy = y - py, dz = z - pz;
                        double d2 = dx*dx + dy*dy + dz*dz;
                        if (d2 > SOUND_MAX_RANGE_SQ) continue;
                        double d = Math.sqrt(Math.max(1.0E-6D, d2));
                        if (d > 55.0D) {
                            double proxy = Math.min(28.0D, d * 0.35D);
                            double sx = px + dx / d * proxy;
                            double sy = py + dy / d * proxy;
                            double sz = pz + dz / d * proxy;
                            xyzPlay.invoke(level, null, sx, sy, sz, farSound, playersSource, 18.0F, 0.96F);
                        }
                    }
                }
            }
        } catch (Throwable t) {
            System.err.println("[Phayriosis Meteor] impact sound bridge fallback failed: " + t);
        }
    }

    public static void emitImpactFlash(Object level, double x, double y, double z, double scale) {
        if (level == null) return;
        boolean usedBallistix = false;
        try {
            // 0.0.17.10: amplify the accepted Ballistix-style bloom into a much brighter
            // world-event flash: a substantially larger white-hot core plus denser hot smoke.
            Object white = newBallistixBlastSmoke(1.0F, 1.0F, 1.0F,
                    (float)(22.0D * scale), -0.045F, 170, true, true, 24, 0.985D);
            Object hot = newBallistixBlastSmoke(1.0F, 0.72F, 0.18F,
                    (float)(13.5D * scale), 0.035F, 105, true, false, 0, 0.988D);
            if (white != null) {
                sendToNearby(level, white, x, y + 3.0D, z,
                        Math.max(36, (int)Math.round(170.0D * scale)),
                        13.0D * scale, 10.0D * scale, 13.0D * scale, 0.52D);
                usedBallistix = true;
            }
            if (hot != null) {
                sendToNearby(level, hot, x, y + 2.0D, z,
                        Math.max(24, (int)Math.round(90.0D * scale)),
                        15.0D * scale, 11.0D * scale, 15.0D * scale, 0.36D);
                usedBallistix = true;
            }
        } catch (Throwable ignored) {}

        // Always add a shader-friendly white/yellow/red core. This is deliberately much
        // larger than the old single FLASH particle and remains visible without Ballistix.
        try {
            Object flash = registryValue("PARTICLE_TYPES", "minecraft", "flash");
            Object emitter = registryValue("PARTICLE_TYPES", "minecraft", "explosion_emitter");
            Object flame = registryValue("PARTICLE_TYPES", "minecraft", "flame");
            Object lava = registryValue("PARTICLE_TYPES", "minecraft", "lava");
            Object endRod = registryValue("PARTICLE_TYPES", "minecraft", "end_rod");
            Object glow = registryValue("PARTICLE_TYPES", "minecraft", "glow");
            if (flash != null) {
                // A compact sphere of flash origins emulates Ballistix' persistent star core.
                for (int i = 0; i < 42; i++) {
                    double a = (Math.PI * 2.0D * i) / 42.0D;
                    double rr = 2.0D + (i % 5) * 2.4D * scale;
                    sendToNearby(level, flash, x + Math.cos(a)*rr, y + 3.0D + (i%7)*1.35D,
                            z + Math.sin(a)*rr, 1, 0, 0, 0, 0);
                }
            }
            if (emitter != null) sendToNearby(level, emitter, x, y + 2.0D, z,
                    Math.max(8, (int)Math.round(18.0D * scale)), 17.0D*scale, 11.0D*scale, 17.0D*scale, 0.0D);
            if (endRod != null) sendToNearby(level, endRod, x, y + 4.0D, z,
                    Math.max(64, (int)Math.round(310.0D * scale)), 24.0D*scale, 16.0D*scale, 24.0D*scale, 0.26D);
            if (glow != null) sendToNearby(level, glow, x, y + 5.0D, z,
                    Math.max(48, (int)Math.round(220.0D * scale)), 22.0D*scale, 15.0D*scale, 22.0D*scale, 0.18D);
            if (flame != null) sendToNearby(level, flame, x, y + 2.0D, z,
                    Math.max(80, (int)Math.round(390.0D * scale)), 26.0D*scale, 14.0D*scale, 26.0D*scale, 0.34D);
            if (lava != null) sendToNearby(level, lava, x, y + 1.0D, z,
                    Math.max(48, (int)Math.round(220.0D * scale)), 20.0D*scale, 12.0D*scale, 20.0D*scale, 0.24D);
        } catch (Throwable t) {
            if (!usedBallistix) System.err.println("[Phayriosis Meteor] impact flash bridge failed: " + t);
        }
    }

    public static void emitShockwaveRing(Object level, double x, double y, double z, double radius) {
        if (level == null || radius <= 0.0D) return;
        try {
            // 0.0.17.9: keep Ballistix' 5-block/tick propagation speed, but make the
            // pressure front read as a thick rolling mist rather than a one-particle line.
            // Only build detailed ring segments near actual players so a 512-block world-event
            // wave does not sample/load terrain all around the entire circumference.
            double alpha = Math.max(0.12D, 1.0D - radius / 512.0D);
            Object ballistixWave = newBallistixShockwave(1.0F, 1.0F, 1.0F,
                    (float)alpha, 1.8F, 22, false, 0.992D);
            Object cloud = registryValue("PARTICLE_TYPES", "minecraft", "cloud");
            Object poof = registryValue("PARTICLE_TYPES", "minecraft", "poof");
            Object smoke = registryValue("PARTICLE_TYPES", "minecraft", "large_smoke");

            int points = Math.max(72, Math.min(160, (int)Math.round(radius * 0.46D)));
            double[] bands = new double[] {-4.5D, 0.0D, 4.5D};
            for (double band : bands) {
                double bandRadius = Math.max(1.0D, radius + band);
                for (int i = 0; i < points; i++) {
                    double a = ((double)i / (double)points) * Math.PI * 2.0D;
                    double px = x + Math.cos(a) * bandRadius;
                    double pz = z + Math.sin(a) * bandRadius;
                    // Do not query a heightmap for remote, unseen ring segments. This keeps
                    // the super-long-range effect visual-only outside loaded observer areas.
                    if (!isNearAnyPlayer(level, px, y, pz, 150.0D)) continue;
                    double py = groundY(level, px, pz, y);
                    if (ballistixWave != null) {
                        sendToNearby(level, ballistixWave, px, py + 0.65D, pz, 3,
                                0.72D, 0.32D, 0.72D, 0.012D);
                        // A soft vanilla haze layered around Ballistix' particle makes the
                        // front look like a mist wall while remaining visible with shaders.
                        if (cloud != null) sendToNearby(level, cloud, px, py + 0.80D, pz, 2,
                                1.25D, 0.42D, 1.25D, 0.035D);
                        if (smoke != null && (i % 4)==0) sendToNearby(level, smoke, px, py + 1.05D, pz, 1,
                                0.85D, 0.38D, 0.85D, 0.018D);
                    } else {
                        if (cloud != null) sendToNearby(level, cloud, px, py + 0.78D, pz, 3,
                                1.45D, 0.48D, 1.45D, 0.045D);
                        if (poof != null && (i % 2)==0) sendToNearby(level, poof, px, py + 1.0D, pz, 2,
                                0.95D, 0.32D, 0.95D, 0.025D);
                        if (smoke != null && (i % 3)==0) sendToNearby(level, smoke, px, py + 1.18D, pz, 1,
                                0.75D, 0.35D, 0.75D, 0.015D);
                    }
                }
            }
        } catch (Throwable t) {
            System.err.println("[Phayriosis Meteor] shockwave visual bridge failed: " + t);
        }
    }

    private static boolean isNearAnyPlayer(Object level, double x, double y, double z, double range) {
        try {
            Object listObj = invokeNoArg(level, "m_6907_");
            if (!(listObj instanceof Iterable<?> players)) return false;
            double r2 = range * range;
            for (Object player : players) {
                double dx = coord(player, "m_20185_") - x;
                double dy = coord(player, "m_20186_") - y;
                double dz = coord(player, "m_20189_") - z;
                if (dx*dx + dy*dy + dz*dz <= r2) return true;
            }
        } catch (Throwable ignored) {}
        return false;
    }

    private static Object newBallistixShockwave(float r,float g,float b,float a,float scale,int life,boolean physics,double friction) {
        try {
            Class<?> c = Class.forName("ballistix.client.particle.ParticleOptionsShockwave");
            Object o = c.getConstructor().newInstance();
            for (Method m : c.getMethods()) {
                if (m.getName().equals("setParameters") && m.getParameterCount()==8) {
                    return m.invoke(o, r,g,b,a,scale,life,physics,friction);
                }
            }
        } catch (Throwable ignored) {}
        return null;
    }

    private static Object newBallistixBlastSmoke(float r,float g,float b,float scale,float gravity,int life,
                                                   boolean physics,boolean extra,int extraLife,double friction) {
        try {
            Class<?> c = Class.forName("ballistix.client.particle.ParticleOptionsBlastSmoke");
            Object o = c.getConstructor().newInstance();
            for (Method m : c.getMethods()) {
                if (m.getName().equals("setParameters") && m.getParameterCount()==10) {
                    return m.invoke(o, r,g,b,scale,gravity,life,physics,extra,extraLife,friction);
                }
            }
        } catch (Throwable ignored) {}
        return null;
    }

    private static Object ballistixNuclearSound() {
        try {
            Class<?> c = Class.forName("ballistix.registers.BallistixSounds");
            Object ro = c.getField("SOUND_NUCLEAREXPLOSION").get(null);
            Method get = ro.getClass().getMethod("get");
            return get.invoke(ro);
        } catch (Throwable ignored) {
            return null;
        }
    }

    private static Object registryValue(String field, String namespace, String path) {
        try {
            Class<?> regs = Class.forName("net.minecraftforge.registries.ForgeRegistries");
            Object reg = regs.getField(field).get(null);
            Class<?> rl = Class.forName("net.minecraft.resources.ResourceLocation");
            Object key = rl.getConstructor(String.class, String.class).newInstance(namespace, path);
            Method getValue = findMethod(reg.getClass(), "getValue", 1);
            return getValue == null ? null : getValue.invoke(reg, key);
        } catch (Throwable ignored) {
            return null;
        }
    }

    private static Object containingBlockPos(double x,double y,double z) {
        try {
            Class<?> bp = Class.forName("net.minecraft.core.BlockPos");
            Method m = findMethod(bp, "m_274561_", 3);
            return m == null ? null : m.invoke(null, x,y,z);
        } catch (Throwable ignored) { return null; }
    }

    private static double groundY(Object level, double x, double z, double fallback) {
        try {
            if (heightMethod == null) {
                heightMethod = findMethod(level.getClass(), "m_6924_", 3);
                Class<?> types = Class.forName("net.minecraft.world.level.levelgen.Heightmap$Types");
                @SuppressWarnings({"rawtypes","unchecked"})
                Object t = Enum.valueOf((Class<? extends Enum>)types, "MOTION_BLOCKING_NO_LEAVES");
                heightType = t;
            }
            if (heightMethod != null && heightType != null) {
                Object v = heightMethod.invoke(level, heightType, floor(x), floor(z));
                if (v instanceof Number n) return n.doubleValue();
            }
        } catch (Throwable ignored) {}
        return fallback;
    }

    private static void sendToNearby(Object level, Object particle, double x,double y,double z,int count,
                                     double sx,double sy,double sz,double speed) {
        if (particle == null) return;
        try {
            Object listObj = invokeNoArg(level, "m_6907_");
            if (!(listObj instanceof Iterable<?> players)) return;
            if (sendParticleMethod == null) sendParticleMethod = findMethod(level.getClass(), "m_8624_", 11);
            if (sendParticleMethod == null) return;
            for (Object player : players) {
                double dx = coord(player,"m_20185_") - x;
                double dy = coord(player,"m_20186_") - y;
                double dz = coord(player,"m_20189_") - z;
                if (dx*dx + dy*dy + dz*dz <= VISUAL_MAX_RANGE_SQ) {
                    sendParticleMethod.invoke(level, player, particle, true, x,y,z,count,sx,sy,sz,speed);
                }
            }
        } catch (Throwable ignored) {}
    }

    private static Object invokeNoArg(Object target, String name) throws Exception {
        Method m = findMethod(target.getClass(), name, 0);
        return m == null ? null : m.invoke(target);
    }

    private static double coord(Object target, String method) {
        try {
            Object v = invokeNoArg(target, method);
            return v instanceof Number n ? n.doubleValue() : 0.0D;
        } catch (Throwable ignored) { return 0.0D; }
    }

    private static Method findMethod(Class<?> c, String name, int count) {
        for (Class<?> k = c; k != null; k = k.getSuperclass()) {
            for (Method m : k.getDeclaredMethods()) {
                if (m.getName().equals(name) && m.getParameterCount()==count) {
                    try { m.setAccessible(true); } catch (Throwable ignored) {}
                    return m;
                }
            }
        }
        return null;
    }

    private static int floor(double v) {
        int i=(int)v;
        return v < (double)i ? i-1 : i;
    }
}
