package net.mcreator.phayriosisreborn.meteor;

import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.levelgen.Heightmap;
import net.mcreator.phayriosisreborn.config.PhayriosisWorldSettings;
import net.mcreator.phayriosisreborn.difficulty.PhayriosisDifficultySystem;
import net.mcreator.phayriosisreborn.network.PhayriosisrebornModVariables;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

/** Converts a Phase 4 pending roll into one real, synchronized falling meteor entity. */
@Mod.EventBusSubscriber(modid = "phayriosisreborn", bus = Mod.EventBusSubscriber.Bus.FORGE)
public final class PhayriosisMeteorLaunchSystem {
    private static final double FAILED_LAUNCH_RETRY_TICKS = 20.0D;
    private static final double FLIGHT_SPEED = 1.8D;
    private static final double HORIZONTAL_APPROACH = 92.0D;
    private static final double VERTICAL_APPROACH = 240.0D;

    private PhayriosisMeteorLaunchSystem() {}

    @SubscribeEvent
    public static void onLevelTick(TickEvent.LevelTickEvent event) {
        if (event.phase != TickEvent.Phase.END) return;
        if (!(event.level instanceof ServerLevel level)) return;

        MinecraftServer server = level.m_7654_();
        if (server == null) return;
        ServerLevel overworld = server.m_129880_(Level.f_46428_);
        if (overworld == null || level != overworld) return;

        tryLaunch(level);
    }

    public static void tryLaunch(ServerLevel level) {
        PhayriosisMeteorEventData state = PhayriosisMeteorEventData.get(level);
        if (!state.pendingMeteor || state.activeMeteor) return;

        double now = (double) level.m_46467_();
        if (now < state.nextLaunchAttemptGameTime) return;
        state.launchAttempts += 1.0D;

        ServerPlayer player = level.m_8890_();
        if (player == null || PhayriosisMeteorEntityRegistry.METEOR == null) {
            state.nextLaunchAttemptGameTime = now + FAILED_LAUNCH_RETRY_TICKS;
            state.m_77762_();
            return;
        }

        PhayriosisWorldSettings settings = PhayriosisWorldSettings.get(level);
        // Phase-5.5 cinematic distance migration. The Phase-2 backend originally seeded
        // 80-120 blocks before the live meteor had been visually validated. Phase 5.7 now
        // migrates the immediately-previous 240-320 overextended range back into the reliable cinematic band.
        if (!settings.worldCreationConfigured
                && Math.abs(settings.meteorMinDistance - 165.0D) < 1.0E-6D
                && Math.abs(settings.meteorMaxDistance - 185.0D) < 1.0E-6D) {
            settings.meteorMinDistance = 210.0D;
            settings.meteorMaxDistance = 250.0D;
            settings.m_77762_();
        }

        double minDistance = sanitizeDistance(settings.meteorMinDistance, 210.0D);
        double maxDistance = sanitizeDistance(settings.meteorMaxDistance, 250.0D);
        if (maxDistance < minDistance) {
            double swap = minDistance;
            minDistance = maxDistance;
            maxDistance = swap;
        }

        double angle = Math.random() * Math.PI * 2.0D;
        double distance = minDistance + Math.random() * (maxDistance - minDistance);
        double dirX = Math.cos(angle);
        double dirZ = Math.sin(angle);

        double targetX = player.m_20185_() + dirX * distance;
        double targetZ = player.m_20189_() + dirZ * distance;
        int blockX = floorToInt(targetX);
        int blockZ = floorToInt(targetZ);
        double targetY = (double) level.m_6924_(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, blockX, blockZ) + 1.0D;

        double spawnX = targetX - dirX * HORIZONTAL_APPROACH;
        double spawnZ = targetZ - dirZ * HORIZONTAL_APPROACH;
        double desiredY = Math.max(targetY + VERTICAL_APPROACH, player.m_20186_() + 200.0D);
        double spawnY = Math.min(desiredY, (double) level.m_151558_() - 10.0D);
        if (spawnY <= targetY + 24.0D) spawnY = targetY + 24.0D;

        PhayriosisMeteorEntity meteor = new PhayriosisMeteorEntity(PhayriosisMeteorEntityRegistry.METEOR, level);
        meteor.m_6034_(spawnX, spawnY, spawnZ);
        meteor.initializePath(targetX, targetY, targetZ, FLIGHT_SPEED);

        // 0.0.17.9: the 0.0.17.8 runtime class accidentally lost this call even though
        // the chunk-loader class remained packaged. Force a fully-ticking corridor before
        // addFreshEntity so the meteor keeps moving outside normal player simulation range.
        PhayriosisMeteorChunkLoader.forceFlight(level, meteor, spawnX, spawnZ, targetX, targetZ);
        if (!level.m_7967_(meteor)) {
            // Never leak Forge tickets when an entity launch fails.
            PhayriosisMeteorChunkLoader.releaseFlight(level, meteor, spawnX, spawnZ, targetX, targetZ);
            state.nextLaunchAttemptGameTime = now + FAILED_LAUNCH_RETRY_TICKS;
            state.m_77762_();
            return;
        }

        // The live infection meteor is the transition into the catastrophic infection stage.
        // Make the phase change immediate and authoritative as soon as the entity successfully
        // enters the world, rather than waiting for impact or a later progression tick.
        forcePhaseFour(level);

        // Only consume the Phase 4 pending event after addFreshEntity succeeds.
        state.activeMeteor = true;
        state.activeSinceGameTime = now;
        state.successfulLaunches += 1.0D;
        state.lastLaunchGameTime = now;
        state.lastTargetX = targetX;
        state.lastTargetY = targetY;
        state.lastTargetZ = targetZ;
        state.nextLaunchAttemptGameTime = 0.0D;
        state.schemaVersion = PhayriosisMeteorEventData.CURRENT_SCHEMA;
        state.m_77762_();
        PhayriosisMeteorScheduler.markPendingMeteorLaunched(level);
        PhayriosisMeteorOneShotGate.retireTickWorkers();
        System.out.println("[Phayriosis Meteor] launch success: target=(" + targetX + ", " + targetY + ", " + targetZ
                + ") spawn=(" + spawnX + ", " + spawnY + ", " + spawnZ + ")");
    }

    private static void forcePhaseFour(ServerLevel level) {
        PhayriosisrebornModVariables.WorldVariables vars = PhayriosisrebornModVariables.WorldVariables.get(level);
        double phaseFourFloor = PhayriosisDifficultySystem.requiredPoints(level, 10000.0D);
        if (vars != null && (vars.phayriosis_points < phaseFourFloor || vars.phayriosis_phase < 4.0D)) {
            vars.phayriosis_points = Math.max(vars.phayriosis_points, phaseFourFloor);
            vars.phayriosis_phase = 4.0D;
            vars.syncData(level);
            System.out.println("[Phayriosis Meteor] meteor launch forced progression to Phase 4");
        }
    }

    /** Phase 5 impact acknowledgement. Phase 6 will call terrain generation from here. */
    public static void onMeteorImpact(ServerLevel level, double targetX, double targetY, double targetZ) {
        PhayriosisMeteorEventData state = PhayriosisMeteorEventData.get(level);
        state.activeMeteor = false;
        state.activeSinceGameTime = -1.0D;
        state.impacts += 1.0D;
        state.lastImpactGameTime = (double) level.m_46467_();
        state.lastTargetX = targetX;
        state.lastTargetY = targetY;
        state.lastTargetZ = targetZ;
        state.schemaVersion = PhayriosisMeteorEventData.CURRENT_SCHEMA;
        state.m_77762_();
        System.out.println("[Phayriosis Meteor] impact acknowledged: target=(" + targetX + ", " + targetY + ", " + targetZ + ")");
    }

    private static double sanitizeDistance(double value, double fallback) {
        if (Double.isNaN(value) || Double.isInfinite(value)) return fallback;
        return Math.max(16.0D, Math.min(value, 512.0D));
    }

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


// 0.0.17.9 source/runtime are now synchronized: forceFlight is a real call above, not only a note.
