package com.livingworld.commands;

import java.util.Arrays;
import java.util.function.Supplier;

import com.livingworld.core.LivingWorldConstants;
import com.livingworld.modules.atmosphere.AtmosphereModule;
import com.livingworld.modules.atmosphere.AtmosphereRegionData;
import com.livingworld.modules.pollution.PollutionModule;
import com.livingworld.modules.pollution.PollutionRegionData;
import com.livingworld.modules.recovery.RecoveryModule;
import com.livingworld.modules.recovery.RecoveryRegionData;
import com.livingworld.modules.recovery.SuccessionStage;
import com.livingworld.modules.soil.SoilModule;
import com.livingworld.modules.soil.SoilRegionData;
import com.livingworld.modules.water.WaterModule;
import com.livingworld.modules.water.WaterRegionData;
import com.livingworld.regions.Region;
import com.livingworld.regions.RegionCoordinate;
import com.livingworld.regions.RegionManager;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.SuggestionProvider;

import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;

/**
 * Debug command branch: {@code /lw set <property> <value>}.
 *
 * <p>Directly mutates simulation data in the player's current region so that
 * ecosystem transformation systems (succession, water runoff, pool formation)
 * can be tested without waiting for natural accumulation.
 *
 * <h3>Sub-commands</h3>
 * <pre>
 *   /lw set soil fertility &lt;0-100&gt;
 *   /lw set soil moisture  &lt;0-100&gt;
 *   /lw set water availability &lt;0-100&gt;
 *   /lw set water drought      &lt;0-100&gt;
 *   /lw set pollution &lt;0-100&gt;   (sets all three pollution layers proportionally)
 *   /lw set stage &lt;STAGE_NAME&gt;  (forces succession stage; resets progress)
 *   /lw set cap   &lt;STAGE_NAME&gt;  (forces succession ceiling)
 *   /lw set rain  &lt;0.0-1.0&gt;    (overrides regional rain level for one sim cycle)
 * </pre>
 */
public final class RegionSetCommand {

    private static final SuggestionProvider<CommandSourceStack> STAGE_SUGGESTIONS =
            (ctx, builder) -> SharedSuggestionProvider.suggest(
                    Arrays.stream(SuccessionStage.values()).map(Enum::name), builder);

    private RegionSetCommand() {}

    public static LiteralArgumentBuilder<CommandSourceStack> build(Supplier<RegionManager> rm) {
        return Commands.literal("set")
                // --- soil ---
                .then(Commands.literal("soil")
                        .then(Commands.literal("fertility")
                                .then(Commands.argument("value", DoubleArgumentType.doubleArg(0, 100))
                                        .executes(ctx -> setSoilFertility(ctx.getSource(), rm.get(),
                                                DoubleArgumentType.getDouble(ctx, "value")))))
                        .then(Commands.literal("moisture")
                                .then(Commands.argument("value", DoubleArgumentType.doubleArg(0, 100))
                                        .executes(ctx -> setSoilMoisture(ctx.getSource(), rm.get(),
                                                DoubleArgumentType.getDouble(ctx, "value"))))))
                // --- water ---
                .then(Commands.literal("water")
                        .then(Commands.literal("availability")
                                .then(Commands.argument("value", DoubleArgumentType.doubleArg(0, 100))
                                        .executes(ctx -> setWaterAvailability(ctx.getSource(), rm.get(),
                                                DoubleArgumentType.getDouble(ctx, "value")))))
                        .then(Commands.literal("drought")
                                .then(Commands.argument("value", DoubleArgumentType.doubleArg(0, 100))
                                        .executes(ctx -> setWaterDrought(ctx.getSource(), rm.get(),
                                                DoubleArgumentType.getDouble(ctx, "value"))))))
                // --- pollution ---
                .then(Commands.literal("pollution")
                        .then(Commands.argument("value", DoubleArgumentType.doubleArg(0, 100))
                                .executes(ctx -> setPollution(ctx.getSource(), rm.get(),
                                        DoubleArgumentType.getDouble(ctx, "value")))))
                // --- succession stage ---
                .then(Commands.literal("stage")
                        .then(Commands.argument("name", StringArgumentType.word())
                                .suggests(STAGE_SUGGESTIONS)
                                .executes(ctx -> setStage(ctx.getSource(), rm.get(),
                                        StringArgumentType.getString(ctx, "name")))))
                // --- succession cap ---
                .then(Commands.literal("cap")
                        .then(Commands.argument("name", StringArgumentType.word())
                                .suggests(STAGE_SUGGESTIONS)
                                .executes(ctx -> setCap(ctx.getSource(), rm.get(),
                                        StringArgumentType.getString(ctx, "name")))))
                // --- regional rain level ---
                .then(Commands.literal("rain")
                        .then(Commands.argument("value", DoubleArgumentType.doubleArg(0, 1))
                                .executes(ctx -> setRain(ctx.getSource(), rm.get(),
                                        DoubleArgumentType.getDouble(ctx, "value")))));
    }

    // ------------------------------------------------------------------
    // Executors
    // ------------------------------------------------------------------

    private static int setSoilFertility(CommandSourceStack src, RegionManager rm, double value) {
        return modifyRegion(src, rm, "soil.fertility=" + value, region -> {
            SoilRegionData soil = getOrDefault(region, SoilModule.MODULE_ID, SoilRegionData.class,
                    SoilRegionData.defaults());
            soil.setFertility(value);
            region.getModuleData().put(SoilModule.MODULE_ID, soil);
        });
    }

    private static int setSoilMoisture(CommandSourceStack src, RegionManager rm, double value) {
        return modifyRegion(src, rm, "soil.moisture=" + value, region -> {
            SoilRegionData soil = getOrDefault(region, SoilModule.MODULE_ID, SoilRegionData.class,
                    SoilRegionData.defaults());
            soil.setMoisture(value);
            region.getModuleData().put(SoilModule.MODULE_ID, soil);
        });
    }

    private static int setWaterAvailability(CommandSourceStack src, RegionManager rm, double value) {
        return modifyRegion(src, rm, "water.availability=" + value, region -> {
            WaterRegionData water = getOrDefault(region, WaterModule.MODULE_ID, WaterRegionData.class,
                    WaterRegionData.defaults());
            water.setWaterAvailability(value);
            region.getModuleData().put(WaterModule.MODULE_ID, water);
        });
    }

    private static int setWaterDrought(CommandSourceStack src, RegionManager rm, double value) {
        return modifyRegion(src, rm, "water.drought=" + value, region -> {
            WaterRegionData water = getOrDefault(region, WaterModule.MODULE_ID, WaterRegionData.class,
                    WaterRegionData.defaults());
            water.setDroughtRisk(value);
            region.getModuleData().put(WaterModule.MODULE_ID, water);
        });
    }

    private static int setPollution(CommandSourceStack src, RegionManager rm, double value) {
        return modifyRegion(src, rm, "pollution=" + value, region -> {
            PollutionRegionData existing = getOrDefault(region, PollutionModule.MODULE_ID,
                    PollutionRegionData.class, PollutionRegionData.defaults());
            region.getModuleData().put(PollutionModule.MODULE_ID,
                    new PollutionRegionData(value, value * 0.5, value * 0.3,
                            existing.getDecayResistance()));
        });
    }

    private static int setStage(CommandSourceStack src, RegionManager rm, String stageName) {
        SuccessionStage stage = parseStage(src, stageName);
        if (stage == null) return 0;
        return modifyRegion(src, rm, "stage=" + stage.name(), region -> {
            RecoveryRegionData rec = getOrDefault(region, RecoveryModule.MODULE_ID,
                    RecoveryRegionData.class, RecoveryRegionData.defaults());
            // Construct a fresh data object at the desired stage, preserving cap.
            SuccessionStage cap = rec.getMaxSuccessionStage();
            if (stage.ordinal() > cap.ordinal()) {
                // Auto-raise cap if forcing to a higher stage.
                cap = stage;
            }
            region.getModuleData().put(RecoveryModule.MODULE_ID,
                    new RecoveryRegionData(stage, 0.0, 0.0, cap));
        });
    }

    private static int setCap(CommandSourceStack src, RegionManager rm, String stageName) {
        SuccessionStage stage = parseStage(src, stageName);
        if (stage == null) return 0;
        return modifyRegion(src, rm, "cap=" + stage.name(), region -> {
            RecoveryRegionData rec = getOrDefault(region, RecoveryModule.MODULE_ID,
                    RecoveryRegionData.class, RecoveryRegionData.defaults());
            rec.setMaxSuccessionStage(stage);
            region.getModuleData().put(RecoveryModule.MODULE_ID, rec);
        });
    }

    private static int setRain(CommandSourceStack src, RegionManager rm, double value) {
        return modifyRegion(src, rm, "rain=" + value, region -> {
            AtmosphereRegionData atm = getOrDefault(region, AtmosphereModule.MODULE_ID,
                    AtmosphereRegionData.class, AtmosphereRegionData.defaults());
            atm.setRainLevel(value);
            region.getModuleData().put(AtmosphereModule.MODULE_ID, atm);
        });
    }

    // ------------------------------------------------------------------
    // Shared helpers
    // ------------------------------------------------------------------

    /** Resolves the player's region, applies {@code mutation}, marks dirty, replies. */
    private static int modifyRegion(CommandSourceStack src, RegionManager rm,
                                    String change, RegionMutation mutation) {
        if (rm == null) {
            src.sendFailure(Component.literal("Region manager not ready."));
            return 0;
        }
        ServerPlayer player;
        try {
            player = src.getPlayerOrException();
        } catch (CommandSyntaxException e) {
            src.sendFailure(Component.literal("/lw set must be run by a player."));
            return 0;
        }

        String dimId = player.level().dimension().location().toString();
        int rx = (int) Math.floor(player.getX() / (LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS * 16.0));
        int rz = (int) Math.floor(player.getZ() / (LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS * 16.0));
        RegionCoordinate coord = new RegionCoordinate(dimId, rx, rz);

        Region region = rm.getOrCreateRegion(coord);
        mutation.apply(region);
        rm.markDirty(region);

        src.sendSuccess(() -> Component.literal(
                String.format("[LW] Set %s in region (%d,%d)", change, rx, rz)), false);
        return 1;
    }

    private static <T> T getOrDefault(Region region, String moduleId, Class<T> type, T fallback) {
        return region.getModuleData().get(moduleId, type).orElse(fallback);
    }

    private static SuccessionStage parseStage(CommandSourceStack src, String name) {
        try {
            return SuccessionStage.valueOf(name.toUpperCase());
        } catch (IllegalArgumentException e) {
            src.sendFailure(Component.literal(
                    "Unknown stage '" + name + "'. Valid: BARREN, SPARSE_GRASS, GRASSLAND,"
                            + " SCRUBLAND, YOUNG_WOODLAND, MATURE_FOREST"));
            return null;
        }
    }

    @FunctionalInterface
    private interface RegionMutation {
        void apply(Region region);
    }
}
