package net.mcreator.phayriosisreborn.meteor;

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

/**
 * Server-side meteor scheduler.
 *
 * Clock domains are intentionally split:
 * - Level#getDayTime (m_46468_) controls calendar/day eligibility and follows /time set.
 * - Level#getGameTime (m_46467_) controls retry/check intervals and stays monotonic.
 */
@Mod.EventBusSubscriber(modid = "phayriosisreborn", bus = Mod.EventBusSubscriber.Bus.FORGE)
public final class PhayriosisMeteorScheduler {
    private static final double TICKS_PER_DAY = 24000.0D;
    private static final double INELIGIBLE_RECHECK_TICKS = 200.0D;
    private static final double FIRST_ELIGIBLE_CHECK_DELAY = 20.0D;
    private static final double MIN_ROLL_INTERVAL_TICKS = 20.0D;

    private PhayriosisMeteorScheduler() {}

    @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;

        tickOverworld(level);
    }

    public static void tickOverworld(ServerLevel level) {
        final double now = (double) level.m_46467_();
        final PhayriosisWorldSettings settings = PhayriosisWorldSettings.get(level);
        final PhayriosisMeteorEventData state = PhayriosisMeteorEventData.get(level);

        // Phase 5.1 migration: Phase 4/5 mistakenly stored the minimum-day timestamp in
        // the game-time clock domain. Clear only stale scheduler timing, preserving any
        // already pending/active live event.
        if (state.schemaVersion < PhayriosisMeteorEventData.CURRENT_SCHEMA) {
            state.schemaVersion = PhayriosisMeteorEventData.CURRENT_SCHEMA;
            if (!state.pendingMeteor && !state.activeMeteor) {
                state.initialized = false;
                state.nextCheckGameTime = 0.0D;
            }
            state.m_77762_();
        }

        if (!state.initialized) {
            state.initialized = true;
            state.schemaVersion = PhayriosisMeteorEventData.CURRENT_SCHEMA;
            state.nextCheckGameTime = initialCheckTime(level, now, settings);
            state.m_77762_();
            return;
        }

        if (state.pendingMeteor) return;
        if (now < state.nextCheckGameTime) return;

        state.lastCheckGameTime = now;
        state.eligibilityChecks += 1.0D;

        if (!settings.meteorEnabled) {
            state.nextCheckGameTime = now + INELIGIBLE_RECHECK_TICKS;
            state.m_77762_();
            return;
        }

        if (!hasReachedMinimumDay(level, settings.meteorMinimumDay)) {
            state.nextCheckGameTime = now + INELIGIBLE_RECHECK_TICKS;
            state.m_77762_();
            return;
        }

        PhayriosisrebornModVariables.WorldVariables progression = PhayriosisrebornModVariables.WorldVariables.get(level);
        if (progression == null || progression.phayriosis_phase < settings.meteorMinimumPhase) {
            state.nextCheckGameTime = now + INELIGIBLE_RECHECK_TICKS;
            state.m_77762_();
            return;
        }

        state.lastEligibleGameTime = now;
        state.rollAttempts += 1.0D;

        double chance = clamp01(settings.meteorChance);
        double roll = Math.random();
        boolean success = chance > 0.0D && roll < chance;
        System.out.println("[Phayriosis Meteor] eligible roll: chance=" + chance
                + " roll=" + roll + " success=" + success
                + " dayTime=" + level.m_46468_() + " gameTime=" + level.m_46467_());
        if (success) {
            state.pendingMeteor = true;
            state.pendingSinceGameTime = now;
            state.successfulRolls += 1.0D;
        } else {
            state.nextCheckGameTime = now + sanitizedInterval(settings.meteorCheckIntervalTicks);
        }
        state.m_77762_();
    }

    public static boolean hasPendingMeteor(ServerLevel level) {
        return PhayriosisMeteorEventData.get(level).pendingMeteor;
    }

    public static void markPendingMeteorLaunched(ServerLevel level) {
        PhayriosisMeteorEventData state = PhayriosisMeteorEventData.get(level);
        if (!state.pendingMeteor) return;
        state.pendingMeteor = false;
        state.pendingSinceGameTime = -1.0D;
        PhayriosisWorldSettings settings = PhayriosisWorldSettings.get(level);
        state.nextCheckGameTime = (double) level.m_46467_() + sanitizedInterval(settings.meteorCheckIntervalTicks);
        state.m_77762_();
    }

    public static boolean isCurrentlyEligible(ServerLevel level) {
        PhayriosisWorldSettings settings = PhayriosisWorldSettings.get(level);
        if (!settings.meteorEnabled) return false;
        if (!hasReachedMinimumDay(level, settings.meteorMinimumDay)) return false;
        PhayriosisrebornModVariables.WorldVariables progression = PhayriosisrebornModVariables.WorldVariables.get(level);
        return progression != null && progression.phayriosis_phase >= settings.meteorMinimumPhase;
    }

    private static double initialCheckTime(ServerLevel level, double now, PhayriosisWorldSettings settings) {
        if (!settings.meteorEnabled) return now + INELIGIBLE_RECHECK_TICKS;
        if (!hasReachedMinimumDay(level, settings.meteorMinimumDay)) return now + INELIGIBLE_RECHECK_TICKS;
        return now + FIRST_ELIGIBLE_CHECK_DELAY;
    }

    private static boolean hasReachedMinimumDay(ServerLevel level, double minimumDay) {
        return (double) level.m_46468_() >= minimumDayTick(minimumDay);
    }

    private static double minimumDayTick(double minimumDay) {
        if (Double.isNaN(minimumDay) || minimumDay <= 0.0D) return 0.0D;
        if (Double.isInfinite(minimumDay)) return Double.MAX_VALUE;
        return Math.ceil(minimumDay * TICKS_PER_DAY);
    }

    private static double sanitizedInterval(double configured) {
        if (Double.isNaN(configured) || Double.isInfinite(configured)) return 3600.0D;
        return Math.max(MIN_ROLL_INTERVAL_TICKS, configured);
    }

    private static double clamp01(double value) {
        if (Double.isNaN(value)) return 0.0D;
        if (value <= 0.0D) return 0.0D;
        if (value >= 1.0D) return 1.0D;
        return value;
    }
}
