package com.livingworld.modules.worldeffects;

import com.livingworld.data.serialization.PersistenceReader;
import com.livingworld.data.serialization.PersistenceWriter;
import com.livingworld.events.LivingWorldEvent;
import com.livingworld.modules.ModuleContext;
import com.livingworld.modules.ModuleMetadata;
import com.livingworld.modules.ModuleUpdateResult;
import com.livingworld.modules.RegionUpdateContext;
import com.livingworld.modules.ServerContext;
import com.livingworld.modules.SimulationModule;
import com.livingworld.modules.atmosphere.AtmosphereModule;
import com.livingworld.modules.atmosphere.AtmosphereRegionData;
import com.livingworld.modules.recovery.RecoveryRegionData;
import com.livingworld.modules.recovery.RecoveryModule;
import com.livingworld.modules.recovery.SuccessionStage;
import com.livingworld.modules.resources.ResourceDepletionModule;
import com.livingworld.modules.resources.ResourceRegionData;
import com.livingworld.modules.water.WaterModule;
import com.livingworld.modules.water.WaterRegionData;
import com.livingworld.regions.Region;
import com.livingworld.regions.RegionMetrics;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

/**
 * Translates ecosystem simulation state into visible world change requests.
 *
 * <p>This module runs <em>last</em> in the pipeline. It reads the fully-updated
 * state of all other modules and emits {@link WorldEffectRequest}s to registered
 * {@link WorldEffectConsumer}s. The consumers (typically the NeoForge platform
 * adapter) apply the requests as actual block changes on loaded chunks.
 *
 * <p>This module contains no Minecraft imports. The platform boundary lives
 * entirely within the consumer implementations.
 *
 * <h3>Visible effects generated</h3>
 * <ol>
 *   <li>{@link WorldEffectType#GRASS_DEGRADES_TO_DIRT} — when pollutionScore &gt; 60
 *       and soilQuality &lt; 30.
 *   <li>{@link WorldEffectType#VEGETATION_SPREADS} — when vegetationPressure &gt; 60
 *       and soilQuality &gt; 50.
 *   <li>{@link WorldEffectType#SAPLING_GROWTH_SLOWED} — when logging depletion
 *       exceeds 50 % (read from {@link ResourceRegionData}).
 *   <li>{@link WorldEffectType#SAPLING_GROWTH_BOOSTED} — when the succession stage
 *       is {@link SuccessionStage#YOUNG_WOODLAND} or higher.
 *   <li>{@link WorldEffectType#POLLUTION_VISUAL_INDICATOR} — when pollutionScore &gt; 70.
 * </ol>
 *
 * <h3>Registering consumers</h3>
 * Call {@link #registerConsumer(WorldEffectConsumer)} before the simulation starts.
 * Multiple consumers may be registered. If no consumer is registered the module
 * operates silently as a no-op.
 */
public final class WorldEffectsModule implements SimulationModule {

    public static final String MODULE_ID = "worldeffects";

    // Thresholds that trigger effect requests.
    // Grass degradation: pollution score > 15 AND soil quality below the gate.
    // Soil defaults to 60 so the gate is set at 75 to avoid blocking early effects.
    private static final double GRASS_DEGRADE_POLLUTION_MIN  = 15.0;
    private static final double GRASS_DEGRADE_SOIL_MAX       = 75.0;
    // Vegetation die-off: mirrors VegetationModule's bad-conditions thresholds.
    private static final double DIEOFF_SOIL_MAX              = 20.0;
    private static final double DIEOFF_POLLUTION_MIN         = 30.0;
    private static final double VEG_SPREAD_VEG_MIN           = 60.0;
    private static final double VEG_SPREAD_SOIL_MIN          = 50.0;
    private static final double SAPLING_SLOW_LOGGING_MIN     = 50.0;
    // Smoke particles appear as soon as any meaningful pollution exists.
    private static final double POLLUTION_INDICATOR_MIN      = 10.0;
    // Wildfire: lightning storm over a drought-stressed region.
    private static final double WILDFIRE_DROUGHT_MIN         = 70.0;
    private static final double WILDFIRE_THUNDER_MIN         = 0.5;
    private static final double WILDFIRE_CHANCE_PER_CYCLE   = 0.01; // 1 % per sim cycle

    private static final ModuleMetadata METADATA = new ModuleMetadata(
            MODULE_ID,
            "World Effects",
            "1.0.0",
            "Translates ecosystem state into visible block-change requests for the platform layer.",
            "1",
            List.of("pollution", "soil", "vegetation", "resources", "recovery"),
            List.of(),
            true,
            true,
            false);

    private final List<WorldEffectConsumer> consumers = new ArrayList<>();
    private final Random wildfireRandom = new Random();

    /**
     * Registers a consumer that will receive effect requests each simulation tick.
     *
     * @param consumer the consumer to register (must not be null)
     */
    public void registerConsumer(WorldEffectConsumer consumer) {
        if (consumer == null) throw new IllegalArgumentException("consumer must not be null");
        consumers.add(consumer);
    }

    /** Returns an unmodifiable view of all registered consumers. */
    public List<WorldEffectConsumer> getConsumers() {
        return Collections.unmodifiableList(consumers);
    }

    /**
     * Directly emits an effect request to all registered consumers from outside the
     * normal module update cycle (e.g. from the bootstrap post-sim hooks).
     */
    public void queueEffect(WorldEffectRequest request) {
        emit(request);
    }

    @Override
    public String getModuleId() { return MODULE_ID; }

    @Override
    public ModuleMetadata getMetadata() { return METADATA; }

    @Override
    public void initialize(ModuleContext context) {
        if (context == null) throw new IllegalArgumentException("context must not be null");
    }

    @Override
    public void onServerStarted(ServerContext context) {}

    @Override
    public void createDefaultRegionData(Region region) {
        // No per-region data; this module only reads state, it does not persist any.
    }

    @Override
    public ModuleUpdateResult updateRegion(RegionUpdateContext context) {
        if (context == null) throw new IllegalArgumentException("context must not be null");
        if (consumers.isEmpty()) return ModuleUpdateResult.noChange();

        Region region    = context.getRegion();
        RegionMetrics m  = region.getMetrics();
        boolean emitted  = false;

        // --- Effect 1: grass degrades when pollution is high and soil is poor ---
        if (m.getPollutionScore() > GRASS_DEGRADE_POLLUTION_MIN
                && m.getSoilQuality() < GRASS_DEGRADE_SOIL_MAX) {
            double intensity = computeIntensity(
                    m.getPollutionScore() - GRASS_DEGRADE_POLLUTION_MIN, 40.0)
                    * computeIntensity(GRASS_DEGRADE_SOIL_MAX - m.getSoilQuality(), 30.0);
            emit(new WorldEffectRequest(
                    WorldEffectType.GRASS_DEGRADES_TO_DIRT, region.getCoordinate(), intensity));
            emitted = true;
        }

        // --- Effect 2: vegetation spreads when soil and vegetation pressure are healthy ---
        if (m.getVegetationPressure() > VEG_SPREAD_VEG_MIN
                && m.getSoilQuality() > VEG_SPREAD_SOIL_MIN) {
            double intensity = computeIntensity(
                    m.getVegetationPressure() - VEG_SPREAD_VEG_MIN, 40.0);
            emit(new WorldEffectRequest(
                    WorldEffectType.VEGETATION_SPREADS, region.getCoordinate(), intensity));
            emitted = true;
        }

        // --- Effect 3: logging depletion slows sapling growth ---
        ResourceRegionData resources = region.getModuleData()
                .get(ResourceDepletionModule.MODULE_ID, ResourceRegionData.class)
                .orElse(null);
        if (resources != null && resources.getLoggingDepletion() > SAPLING_SLOW_LOGGING_MIN) {
            double intensity = computeIntensity(
                    resources.getLoggingDepletion() - SAPLING_SLOW_LOGGING_MIN, 50.0);
            emit(new WorldEffectRequest(
                    WorldEffectType.SAPLING_GROWTH_SLOWED, region.getCoordinate(), intensity));
            emitted = true;
        }

        // --- Effect 4: advanced succession boosts sapling growth ---
        RecoveryRegionData recovery = region.getModuleData()
                .get(RecoveryModule.MODULE_ID, RecoveryRegionData.class)
                .orElse(null);
        if (recovery != null
                && recovery.getSuccessionStage().ordinal()
                   >= SuccessionStage.YOUNG_WOODLAND.ordinal()) {
            double intensity = computeIntensity(
                    recovery.getSuccessionStage().ordinal()
                    - SuccessionStage.YOUNG_WOODLAND.ordinal() + 1.0, 2.0);
            emit(new WorldEffectRequest(
                    WorldEffectType.SAPLING_GROWTH_BOOSTED, region.getCoordinate(), intensity));
            emitted = true;
        }

        // --- Effect 5: pollution visual indicator ---
        if (m.getPollutionScore() > POLLUTION_INDICATOR_MIN) {
            double intensity = computeIntensity(
                    m.getPollutionScore() - POLLUTION_INDICATOR_MIN, 30.0);
            emit(new WorldEffectRequest(
                    WorldEffectType.POLLUTION_VISUAL_INDICATOR, region.getCoordinate(), intensity));
            emitted = true;
        }

        // --- Effect 6: wildfire — drought + storm ignites surface vegetation ---
        AtmosphereRegionData atm = region.getModuleData()
                .get(AtmosphereModule.MODULE_ID, AtmosphereRegionData.class)
                .orElse(null);
        WaterRegionData water = region.getModuleData()
                .get(WaterModule.MODULE_ID, WaterRegionData.class)
                .orElse(null);
        if (atm != null && water != null
                && water.getDroughtRisk() > WILDFIRE_DROUGHT_MIN
                && atm.getThunderLevel() > WILDFIRE_THUNDER_MIN
                && wildfireRandom.nextDouble() < WILDFIRE_CHANCE_PER_CYCLE) {
            double intensity = computeIntensity(atm.getThunderLevel() - WILDFIRE_THUNDER_MIN, 0.5)
                    * computeIntensity(water.getDroughtRisk() - WILDFIRE_DROUGHT_MIN, 30.0);
            emit(new WorldEffectRequest(
                    WorldEffectType.WILDFIRE, region.getCoordinate(), Math.max(0.1, intensity)));
            emitted = true;
        }

        // --- Effect 7: vegetation dies under bad soil or heavy pollution ---
        // Mirrors the bad-conditions gate in VegetationModule so block changes
        // stay in sync with the data-layer die-off.
        boolean badSoil       = m.getSoilQuality()    < DIEOFF_SOIL_MAX;
        boolean badPollution  = m.getPollutionScore() > DIEOFF_POLLUTION_MIN;
        if (badSoil || badPollution) {
            double intensitySoil  = badSoil
                    ? computeIntensity(DIEOFF_SOIL_MAX - m.getSoilQuality(), DIEOFF_SOIL_MAX)
                    : 0.0;
            double intensityPoll  = badPollution
                    ? computeIntensity(m.getPollutionScore() - DIEOFF_POLLUTION_MIN, 70.0)
                    : 0.0;
            double dieoffIntensity = Math.max(intensitySoil, intensityPoll);
            emit(new WorldEffectRequest(
                    WorldEffectType.VEGETATION_DIES, region.getCoordinate(),
                    Math.max(0.1, dieoffIntensity)));
            emitted = true;
        }

        // --- Effect 8: rain pools in arid low-succession terrain ---
        // Heavy regional rain on barren/sparse-grass land cannot drain into vegetation;
        // water collects in depressions, forming puddles visible as actual water blocks.
        if (recovery != null && atm != null
                && recovery.getSuccessionStage().ordinal() <= SuccessionStage.SPARSE_GRASS.ordinal()
                && atm.getRainLevel() > 0.3) {
            double intensity = computeIntensity(atm.getRainLevel() - 0.3, 0.5);
            emit(new WorldEffectRequest(
                    WorldEffectType.WATER_POOL_FORMS, region.getCoordinate(), Math.max(0.1, intensity)));
            emitted = true;
        }

        return emitted ? ModuleUpdateResult.changed() : ModuleUpdateResult.noChange();
    }

    @Override
    public void onLivingWorldEvent(LivingWorldEvent event) {}

    @Override
    public void saveModuleData(PersistenceWriter writer) {}

    @Override
    public void loadModuleData(PersistenceReader reader) {}

    @Override
    public void shutdown() { consumers.clear(); }

    // ------------------------------------------------------------------
    // helpers
    // ------------------------------------------------------------------

    /** Computes effect intensity as a fraction of the excess over a range, clamped to [0, 1]. */
    private static double computeIntensity(double excess, double range) {
        return Math.min(1.0, Math.max(0.0, excess / range));
    }

    private void emit(WorldEffectRequest request) {
        for (WorldEffectConsumer consumer : consumers) {
            consumer.consume(request);
        }
    }
}
