package net.mcreator.phayriosisreborn.config;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.mcreator.phayriosisreborn.network.PhayriosisrebornModVariables;
import net.minecraftforge.event.server.ServerStartedEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.loading.FMLPaths;

/**
 * Global config bridge for modpacks and dedicated servers.
 * The file is a set of defaults by default; optional flags can re-apply it to existing worlds.
 */
@Mod.EventBusSubscriber(modid = "phayriosisreborn", bus = Mod.EventBusSubscriber.Bus.FORGE)
public final class PhayriosisFileConfig {
    public static final String FILE_NAME = "phayriosisreborn-common.toml";

    // General behavior.
    public static boolean useConfigDefaultsForNewWorlds = true;
    public static boolean overrideExistingWorldSettings = false;
    public static boolean applyStartingStateToExistingWorlds = false;

    // Meteor.
    public static boolean meteorEnabled = false;
    public static double meteorMinimumDay = 7.0D;
    public static double meteorMinimumPhase = 1.0D;
    public static double meteorCheckIntervalTicks = 3600.0D;
    public static double meteorChance = 0.5D;
    public static double meteorMinDistance = 210.0D;
    public static double meteorMaxDistance = 250.0D;
    public static boolean meteorInfectionEnabled = true;

    // Difficulty.
    public static int difficultyMode = 1;
    public static double blockSpreadSpeed = 3.0D;
    public static double maxInfectableHardness = 2.9D;

    // Infection configuration.
    public static boolean blockSpreadEnabled = true;
    public static boolean conversionsEnabled = true;
    public static boolean playerConversionEnabled = true;
    public static boolean parasiteSpawningEnabled = false;
    public static boolean dimensionalSpawningEnabled = true;

    // Advanced.
    public static boolean phaseFreezeEnabled = false;

    // Admin-command-equivalent starting state.
    public static int startingPhase = 1;
    public static double startingPoints = -1.0D; // -1 = derive from startingPhase.
    public static boolean startAtMaxPower = false;

    private static boolean loaded;

    static { ensureLoaded(); }

    private PhayriosisFileConfig() {}

    public static synchronized void ensureLoaded() {
        if (loaded) return;
        Path path = configPath();
        try {
            Files.createDirectories(path.getParent());
            if (!Files.exists(path)) {
                Files.writeString(path, defaultToml(), StandardCharsets.UTF_8);
            }
            read(path);
        } catch (Throwable t) {
            System.err.println("[Phayriosis Config] Could not load " + path + ": " + t);
        }
        sanitize();
        loaded = true;
    }

    public static synchronized void reload() {
        loaded = false;
        ensureLoaded();
    }

    private static Path configPath() {
        return FMLPaths.CONFIGDIR.get().resolve(FILE_NAME).toAbsolutePath().normalize();
    }

    private static void read(Path path) throws IOException {
        Map<String, String> values = parse(Files.readAllLines(path, StandardCharsets.UTF_8));

        useConfigDefaultsForNewWorlds = bool(values, "general.use_config_defaults_for_new_worlds", useConfigDefaultsForNewWorlds);
        overrideExistingWorldSettings = bool(values, "general.override_existing_world_settings", overrideExistingWorldSettings);
        applyStartingStateToExistingWorlds = bool(values, "general.apply_starting_state_to_existing_worlds", applyStartingStateToExistingWorlds);

        meteorEnabled = bool(values, "meteor.enabled", meteorEnabled);
        meteorMinimumDay = number(values, "meteor.minimum_day", meteorMinimumDay);
        meteorMinimumPhase = number(values, "meteor.minimum_phase", meteorMinimumPhase);
        meteorCheckIntervalTicks = number(values, "meteor.check_interval_ticks", meteorCheckIntervalTicks);
        meteorChance = number(values, "meteor.chance", meteorChance);
        meteorMinDistance = number(values, "meteor.min_distance", meteorMinDistance);
        meteorMaxDistance = number(values, "meteor.max_distance", meteorMaxDistance);
        meteorInfectionEnabled = bool(values, "meteor.impact_infection_enabled", meteorInfectionEnabled);

        difficultyMode = integer(values, "infection_difficulty.mode", difficultyMode);
        blockSpreadSpeed = number(values, "infection_difficulty.block_spread_speed", blockSpreadSpeed);
        maxInfectableHardness = number(values, "infection_difficulty.max_infectable_hardness", maxInfectableHardness);

        blockSpreadEnabled = bool(values, "infection.block_spread_enabled", blockSpreadEnabled);
        conversionsEnabled = bool(values, "infection.mob_conversions_enabled", conversionsEnabled);
        playerConversionEnabled = bool(values, "infection.player_conversion_enabled", playerConversionEnabled);
        parasiteSpawningEnabled = bool(values, "infection.natural_infected_spawns_enabled", parasiteSpawningEnabled);
        dimensionalSpawningEnabled = bool(values, "infection.dimensional_spawning_enabled", dimensionalSpawningEnabled);

        phaseFreezeEnabled = bool(values, "advanced.phase_progression_frozen", phaseFreezeEnabled);

        startingPhase = integer(values, "starting_state.starting_phase", startingPhase);
        startingPoints = number(values, "starting_state.starting_points", startingPoints);
        startAtMaxPower = bool(values, "starting_state.start_at_max_power", startAtMaxPower);
    }

    private static Map<String, String> parse(List<String> lines) {
        Map<String, String> out = new HashMap<>();
        String section = "";
        for (String raw : lines) {
            String line = stripComment(raw).trim();
            if (line.isEmpty()) continue;
            if (line.startsWith("[") && line.endsWith("]")) {
                section = line.substring(1, line.length() - 1).trim().toLowerCase(Locale.ROOT);
                continue;
            }
            int eq = line.indexOf('=');
            if (eq <= 0) continue;
            String key = line.substring(0, eq).trim().toLowerCase(Locale.ROOT);
            String value = line.substring(eq + 1).trim();
            if (value.length() >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
                value = value.substring(1, value.length() - 1);
            }
            out.put(section.isEmpty() ? key : section + "." + key, value);
        }
        return out;
    }

    private static String stripComment(String line) {
        boolean quoted = false;
        for (int i = 0; i < line.length(); i++) {
            char c = line.charAt(i);
            if (c == '"') quoted = !quoted;
            if (c == '#' && !quoted) return line.substring(0, i);
        }
        return line;
    }

    private static boolean bool(Map<String, String> map, String key, boolean def) {
        String v = map.get(key);
        return v == null ? def : Boolean.parseBoolean(v.trim());
    }

    private static double number(Map<String, String> map, String key, double def) {
        String v = map.get(key);
        if (v == null) return def;
        try { return Double.parseDouble(v.trim()); }
        catch (NumberFormatException ignored) { return def; }
    }

    private static int integer(Map<String, String> map, String key, int def) {
        String v = map.get(key);
        if (v == null) return def;
        try { return Integer.parseInt(v.trim()); }
        catch (NumberFormatException ignored) {
            try { return (int)Math.round(Double.parseDouble(v.trim())); }
            catch (NumberFormatException ignoredAgain) { return def; }
        }
    }

    private static void sanitize() {
        difficultyMode = clampInt(difficultyMode, 0, 4);
        blockSpreadSpeed = clamp(blockSpreadSpeed, 1.0D, 5.0D);
        maxInfectableHardness = Math.max(0.0D, maxInfectableHardness);

        meteorMinimumDay = Math.max(0.0D, meteorMinimumDay);
        meteorMinimumPhase = clamp(meteorMinimumPhase, 1.0D, 8.0D);
        meteorCheckIntervalTicks = Math.max(20.0D, meteorCheckIntervalTicks);
        meteorChance = clamp(meteorChance, 0.0D, 1.0D);
        meteorMinDistance = Math.max(0.0D, meteorMinDistance);
        meteorMaxDistance = Math.max(meteorMinDistance, meteorMaxDistance);

        startingPhase = clampInt(startingPhase, 1, 8);
        if (startingPoints < 0.0D) startingPoints = -1.0D;
    }

    private static double clamp(double value, double min, double max) {
        return Math.max(min, Math.min(max, value));
    }

    private static int clampInt(int value, int min, int max) {
        return Math.max(min, Math.min(max, value));
    }

    @SubscribeEvent
    public static void onServerStarted(ServerStartedEvent event) {
        ensureLoaded();
        MinecraftServer server = event.getServer();
        if (server == null) return;
        ServerLevel level = server.m_129880_(Level.f_46428_);
        if (level == null) return;

        // Capture freshness before the Create World draft marks the world initialized.
        PhayriosisWorldSettings before = PhayriosisWorldSettings.get(level);
        boolean fresh = level.m_46467_() <= 1200L && !before.initialized && !before.worldCreationConfigured;

        // The Create World screen, when explicitly used, wins over file defaults.
        boolean uiApplied = PhayriosisPendingWorldSettings.consumeInto(level);
        PhayriosisWorldSettings settings = PhayriosisWorldSettings.get(level);

        if (overrideExistingWorldSettings || (!uiApplied && fresh && useConfigDefaultsForNewWorlds)) {
            applySettings(level, settings);
        }

        if ((fresh && useConfigDefaultsForNewWorlds) || applyStartingStateToExistingWorlds) {
            applyStartingState(level);
        }
    }

    private static void applySettings(LevelAccessor world, PhayriosisWorldSettings settings) {
        settings.meteorEnabled = meteorEnabled;
        settings.meteorMinimumDay = meteorMinimumDay;
        settings.meteorMinimumPhase = meteorMinimumPhase;
        settings.meteorCheckIntervalTicks = meteorCheckIntervalTicks;
        settings.meteorChance = meteorChance;
        settings.meteorMinDistance = meteorMinDistance;
        settings.meteorMaxDistance = meteorMaxDistance;
        settings.meteorInfectionEnabled = meteorInfectionEnabled;

        settings.infectionDifficultyMode = difficultyMode;
        settings.nightmareModeEnabled = difficultyMode == 4;
        settings.blockSpreadSpeed = blockSpreadSpeed;
        settings.maxInfectableHardness = maxInfectableHardness;
        settings.blockSpreadEnabled = blockSpreadEnabled;
        settings.conversionsEnabled = conversionsEnabled;
        settings.playerConversionEnabled = playerConversionEnabled;
        settings.parasiteSpawningEnabled = parasiteSpawningEnabled;
        settings.dimensionalSpawningEnabled = dimensionalSpawningEnabled;
        settings.phaseFreezeEnabled = phaseFreezeEnabled;
        settings.schemaVersion = PhayriosisWorldSettings.CURRENT_SCHEMA;
        settings.initialized = true;
        settings.worldCreationConfigured = true;
        settings.m_77762_();

        PhayriosisrebornModVariables.MapVariables legacy = PhayriosisrebornModVariables.MapVariables.get(world);
        if (legacy != null) {
            legacy.blockspread = blockSpreadEnabled;
            legacy.conversions = conversionsEnabled;
            legacy.playerconversion = playerConversionEnabled;
            legacy.doparasitesspawn = parasiteSpawningEnabled;
            legacy.doparasitesspawndimension = dimensionalSpawningEnabled;
            legacy.nightmaremode = difficultyMode == 4;
            legacy.blockspreadspeed = blockSpreadSpeed;
            legacy.maxhardness = maxInfectableHardness;
            legacy.phasefrozen = phaseFreezeEnabled;
            legacy.syncData(world);
        }
        System.out.println("[Phayriosis Config] applied file settings to world");
    }

    private static void applyStartingState(LevelAccessor world) {
        PhayriosisrebornModVariables.WorldVariables state = PhayriosisrebornModVariables.WorldVariables.get(world);
        if (state == null) return;

        if (startAtMaxPower) {
            state.phayriosis_points = Math.max(state.phayriosis_points, 2500000.0D);
            state.tumourspresent = Math.max(state.tumourspresent, 20.0D);
            state.nucleipresent = Math.max(state.nucleipresent, 10.0D);
            state.nestmindspresent = Math.max(state.nestmindspresent, 10.0D);
            state.hivemindspresent = Math.max(state.hivemindspresent, 10.0D);
            state.dreadmindspresent = Math.max(state.dreadmindspresent, 2.0D);

            PhayriosisrebornModVariables.MapVariables legacy = PhayriosisrebornModVariables.MapVariables.get(world);
            if (legacy != null) {
                legacy.maxhardness = Math.max(legacy.maxhardness, 50.0D);
                legacy.perennialdeaths = Math.max(legacy.perennialdeaths, 100.0D);
                legacy.infectedblocks = Math.max(legacy.infectedblocks, 1000000.0D);
                legacy.adaptationfall = true;
                legacy.adaptationfire = true;
                legacy.adaptationradiation = true;
                legacy.adaptationwater = true;
                legacy.adaptationsound = true;
                legacy.blockspread = true;
                legacy.playerconversion = true;
                legacy.conversions = true;
                legacy.syncData(world);
            }
        } else {
            double points = startingPoints >= 0.0D ? startingPoints : pointsForPhase(startingPhase);
            state.phayriosis_points = points;
        }
        state.syncData(world);
    }

    private static double pointsForPhase(int phase) {
        switch (phase) {
            case 8: return 2500000.0D;
            case 7: return 1000000.0D;
            case 6: return 500000.0D;
            case 5: return 200000.0D;
            case 4: return 10000.0D;
            case 3: return 3000.0D;
            case 2: return 200.0D;
            default: return 0.0D;
        }
    }

    private static String defaultToml() {
        return """
# Phayriosis Reborn Continued - common configuration
# This file is intended for modpacks and dedicated servers.
# By default it supplies NEW-WORLD defaults only. Existing worlds keep their saved values.

[general]
# Use this file as the defaults for worlds that have never been initialized.
use_config_defaults_for_new_worlds = true
# When true, the settings sections below are re-applied to existing worlds when they load.
# This does NOT continuously override commands while the server is running.
override_existing_world_settings = false
# Dangerous by design: when true, [starting_state] is also applied to existing worlds on load.
# Leave false unless you intentionally want to reset/force infection progress after restarts.
apply_starting_state_to_existing_worlds = false

[meteor]
# Same controls as Create World -> Meteor.
enabled = false
minimum_day = 7.0
minimum_phase = 1.0
check_interval_ticks = 3600.0
chance = 0.5
min_distance = 210.0
max_distance = 250.0
impact_infection_enabled = true

[infection_difficulty]
# 0=Easy, 1=Normal, 2=Advanced, 3=Hard, 4=Nightmare.
mode = 1
# Same range used by the in-game setting/command: 1..5.
block_spread_speed = 3.0
max_infectable_hardness = 2.9

[infection]
block_spread_enabled = true
mob_conversions_enabled = true
player_conversion_enabled = true
natural_infected_spawns_enabled = false
dimensional_spawning_enabled = true

[advanced]
phase_progression_frozen = false

[starting_state]
# Mirrors the admin phase/points controls for new worlds. Valid phase: 1..8.
starting_phase = 1
# -1 means use starting_phase. Any value >= 0 directly sets Phayriosis points instead.
starting_points = -1.0
# Mirrors /phayriosis_set_to_max_power for world startup. This also enables the same
# adaptation/conversion state and raises the same progression counters as that command.
start_at_max_power = false
""";
    }
}
