package com.tiedup.remake.worldgen;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;

/**
 * Thread-safe registry for data-driven room theme definitions.
 *
 * <p>Server-authoritative: definitions are loaded from {@code data/<namespace>/tiedup_room_themes/}
 * JSON files by the server reload listener. Unlike furniture, there is no client sync
 * because room themes are only used during server-side worldgen.</p>
 *
 * <p>Uses volatile atomic swap to ensure threads always see a consistent snapshot.</p>
 *
 * @see RoomThemeDefinition
 */
public final class RoomThemeRegistry {

    /**
     * Volatile reference to an unmodifiable map. {@link #reload} builds a new map
     * and swaps atomically; consumer threads always see a consistent snapshot.
     */
    private static volatile Map<
        ResourceLocation,
        RoomThemeDefinition
    > DEFINITIONS = Map.of();

    /** Hardcoded fallback for when the registry is empty (worldgen safety). */
    private static final RoomThemeDefinition FALLBACK = createFallback();

    private RoomThemeRegistry() {}

    /**
     * Atomically replace all definitions with a new set.
     * Called by the reload listener after parsing all JSON files.
     *
     * @param newDefs the new definitions map (will be defensively copied)
     */
    public static void reload(Map<ResourceLocation, RoomThemeDefinition> newDefs) {
        DEFINITIONS = Collections.unmodifiableMap(new HashMap<>(newDefs));
    }

    /**
     * Get a definition by its unique ID.
     *
     * @param id the definition ID (e.g. "tiedup:oubliette")
     * @return the definition, or null if not found
     */
    @Nullable
    public static RoomThemeDefinition get(ResourceLocation id) {
        return DEFINITIONS.get(id);
    }

    /**
     * Get all registered definitions.
     *
     * @return unmodifiable collection of all definitions
     */
    public static Collection<RoomThemeDefinition> getAll() {
        return DEFINITIONS.values();
    }

    /**
     * Pick a random theme proportionally by weight.
     *
     * @param random the random source
     * @return a randomly selected theme, or null if registry is empty
     */
    @Nullable
    public static RoomThemeDefinition pickRandom(RandomSource random) {
        Collection<RoomThemeDefinition> all = DEFINITIONS.values();
        if (all.isEmpty()) return null;

        int totalWeight = 0;
        for (RoomThemeDefinition def : all) {
            totalWeight += def.weight();
        }

        if (totalWeight <= 0) return null;

        int roll = random.nextInt(totalWeight);
        int cumulative = 0;
        for (RoomThemeDefinition def : all) {
            cumulative += def.weight();
            if (roll < cumulative) return def;
        }

        // Fallback (should not happen)
        return all.iterator().next();
    }

    /**
     * Pick a random theme, or return a hardcoded stone bricks fallback if the
     * registry is empty. Guarantees non-null for worldgen safety.
     *
     * @param random the random source
     * @return a theme definition (never null)
     */
    public static RoomThemeDefinition pickRandomOrFallback(RandomSource random) {
        RoomThemeDefinition picked = pickRandom(random);
        return picked != null ? picked : FALLBACK;
    }

    /**
     * Clear all definitions. Called on world unload or for testing.
     */
    public static void clear() {
        DEFINITIONS = Map.of();
    }

    /**
     * Create a minimal stone bricks fallback theme for when no JSON themes are loaded.
     */
    private static RoomThemeDefinition createFallback() {
        BlockState stone = Blocks.STONE_BRICKS.defaultBlockState();
        BlockPalette single = new BlockPalette(
            Map.of("default", List.of(new BlockPalette.WeightedEntry(stone, 1.0f)))
        );
        return new RoomThemeDefinition(
            ResourceLocation.fromNamespaceAndPath("tiedup", "fallback"),
            1,
            single,   // wall
            single,   // floor
            single,   // ceiling
            stone,    // wallShell
            stone,    // wallAccent
            single,   // pillar
            null,     // no scatter
            new DecorationConfig(
                List.of(), List.of(), null, List.of(), false, false
            )
        );
    }
}
