package com.livingworld.modules.vegetation;

import com.livingworld.config.EcosystemTuning;
import com.livingworld.core.services.CoreServices;
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.regions.Region;
import com.livingworld.regions.RegionMetrics;
import java.util.List;

/**
 * Simulates vegetation succession and die-off, writing
 * {@link RegionMetrics#getVegetationPressure()}.
 *
 * <p>This module runs after {@link com.livingworld.modules.soil.SoilModule} and
 * {@link com.livingworld.modules.pollution.PollutionModule} so it reads
 * current-tick soil quality and pollution.
 *
 * <h3>Succession model</h3>
 * When soil quality is adequate and pollution is low, vegetation follows a
 * succession path: grass → flowers → shrubs → trees. Each tier can only grow
 * once the tier below it reaches a critical threshold.
 *
 * <h3>Die-off model</h3>
 * High pollution or very low soil quality kills vegetation in the reverse order
 * (trees → shrubs → grass) and increases dead organic material.
 *
 * <h3>Dead vegetation</h3>
 * Dead matter decomposes slowly each tick, eventually recycling into soil
 * nutrients (modelled implicitly via the soil module's contamination dynamics).
 */
public final class VegetationModule implements SimulationModule {

    public static final String MODULE_ID = "vegetation";

    // --- growth thresholds (not tunable — structural to the succession model) ---
    private static final double GROWTH_SOIL_THRESHOLD      = 35.0;
    private static final double GROWTH_POLLUTION_LIMIT     = 40.0;
    private static final double SHRUB_UNLOCK_GRASS         = 50.0;
    private static final double TREE_UNLOCK_SHRUB          = 40.0;
    private static final double DIEOFF_SOIL_THRESHOLD      = 20.0;
    private static final double DIEOFF_POLLUTION_THRESHOLD = 30.0;
    private static final double DEAD_ACCUMULATION_RATE     = 0.50;
    private static final double TREE_SEVERE_POLLUTION      = 60.0;
    private static final double TREE_SEVERE_SOIL           = 10.0;
    private static final double DEAD_DECOMPOSITION_RATE    = 0.01;
    private static final double CHANGE_THRESHOLD           = 0.01;

    private EcosystemTuning tuning = new EcosystemTuning();

    private static final ModuleMetadata METADATA = new ModuleMetadata(
            MODULE_ID,
            "Vegetation",
            "1.0.0",
            "Simulates vegetation succession, growth, and die-off.",
            "1",
            List.of("pollution", "soil"),
            List.of(),
            true,
            true,
            false);

    @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");
        if (context.hasService(CoreServices.TUNING)) this.tuning = context.getService(CoreServices.TUNING);
    }

    @Override
    public void onServerStarted(ServerContext context) {}

    @Override
    public void createDefaultRegionData(Region region) {
        if (region == null) throw new IllegalArgumentException("region must not be null");
        if (!region.getModuleData().contains(MODULE_ID)) {
            region.getModuleData().put(MODULE_ID, VegetationRegionData.defaults());
        }
    }

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

        Region region = context.getRegion();
        VegetationRegionData data = region.getModuleData()
                .get(MODULE_ID, VegetationRegionData.class)
                .orElseGet(VegetationRegionData::defaults);
        RegionMetrics metrics = region.getMetrics();

        double prevVegetationPressure = metrics.getVegetationPressure();

        boolean goodConditions = metrics.getSoilQuality()    > GROWTH_SOIL_THRESHOLD
                              && metrics.getPollutionScore() < GROWTH_POLLUTION_LIMIT;

        boolean badConditions  = metrics.getSoilQuality()    < DIEOFF_SOIL_THRESHOLD
                              || metrics.getPollutionScore() > DIEOFF_POLLUTION_THRESHOLD;

        if (goodConditions) {
            double soilBonus = metrics.getSoilQuality() - GROWTH_SOIL_THRESHOLD;

            data.setGrassPressure(data.getGrassPressure()   + soilBonus * tuning.getGrassGrowthRate());
            data.setFlowerPressure(data.getFlowerPressure() + soilBonus * tuning.getFlowerGrowthRate());

            // Shrubs grow only once grass is established.
            if (data.getGrassPressure() > SHRUB_UNLOCK_GRASS) {
                data.setShrubPressure(data.getShrubPressure() + soilBonus * tuning.getShrubGrowthRate());
            }
            // Trees grow only once shrubs are established.
            if (data.getShrubPressure() > TREE_UNLOCK_SHRUB) {
                data.setTreePressure(data.getTreePressure() + soilBonus * tuning.getTreeGrowthRate());
            }
        }

        if (badConditions) {
            data.setGrassPressure(data.getGrassPressure()   - tuning.getGrassDieoffRate());
            data.setFlowerPressure(data.getFlowerPressure() - tuning.getFlowerDieoffRate());
            data.setShrubPressure(data.getShrubPressure()   - tuning.getShrubDieoffRate());
            // Trees die only under severe stress to reflect their resilience.
            if (metrics.getPollutionScore() > TREE_SEVERE_POLLUTION
                    || metrics.getSoilQuality() < TREE_SEVERE_SOIL) {
                data.setTreePressure(data.getTreePressure() - tuning.getTreeDieoffRate());
            }
            data.setDeadVegetation(data.getDeadVegetation() + DEAD_ACCUMULATION_RATE);
        }

        // Dead vegetation decomposes slowly each tick.
        data.setDeadVegetation(data.getDeadVegetation() * (1.0 - DEAD_DECOMPOSITION_RATE));

        // Vegetation pressure summary: living tiers weighted by ecological significance,
        // penalised by dead material burden.
        double vegetationPressure = Math.max(0.0, Math.min(100.0,
                data.getGrassPressure()  * 0.35
              + data.getFlowerPressure() * 0.10
              + data.getShrubPressure()  * 0.25
              + data.getTreePressure()   * 0.25
              - data.getDeadVegetation() * 0.20));
        metrics.setVegetationPressure(vegetationPressure);

        region.getModuleData().put(MODULE_ID, data);

        boolean changed = Math.abs(metrics.getVegetationPressure() - prevVegetationPressure)
                          > CHANGE_THRESHOLD;
        return changed ? 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() {}
}
