package com.tiedup.remake.v2.furniture;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;

/**
 * Thread-safe registry for data-driven furniture definitions.
 *
 * <p>Server-authoritative: definitions are loaded from {@code data/<namespace>/tiedup_furniture/}
 * JSON files by the server reload listener, then synced to clients via
 * {@code PacketSyncFurnitureDefinitions}. Unlike {@link
 * com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry}, there is no {@code mergeAll()}
 * because furniture definitions have a single source of truth (server data pack).</p>
 *
 * <p>Uses volatile atomic swap to ensure the render thread and network threads
 * always see a consistent snapshot of definitions.</p>
 *
 * @see FurnitureDefinition
 */
public final class FurnitureRegistry {

    /** NBT key storing the furniture definition ID on furniture entities. */
    public static final String NBT_FURNITURE_ID = "tiedup_furniture_id";

    /**
     * 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,
        FurnitureDefinition
    > DEFINITIONS = Map.of();

    /**
     * Monotonically increasing revision counter, incremented on every
     * {@link #reload}. Client-side caches (e.g.,
     * {@code DataDrivenIconOverrides} in {@code FURNITURE_PLACER} mode)
     * observe this to invalidate themselves when the registry changes.
     */
    private static volatile int revision = 0;

    private FurnitureRegistry() {}

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

    /**
     * Current registry revision — incremented on every {@link #reload}.
     * Client-side caches compare their stored revision against this to detect
     * staleness across {@code /reload} cycles or sync-packet updates.
     */
    public static int getRevision() {
        return revision;
    }

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

    /**
     * Lookup a definition by string ID (from SyncedEntityData or NBT).
     *
     * @param furnitureIdStr the string form of the ResourceLocation, or null/empty
     * @return the definition, or null if the string is blank, unparseable, or unknown
     */
    @Nullable
    public static FurnitureDefinition get(String furnitureIdStr) {
        if (furnitureIdStr == null || furnitureIdStr.isEmpty()) return null;
        ResourceLocation id = ResourceLocation.tryParse(furnitureIdStr);
        return id != null ? DEFINITIONS.get(id) : null;
    }

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

    /**
     * Full map snapshot for packet serialization.
     *
     * <p>The returned map is the same unmodifiable reference held internally,
     * so it is safe to iterate during packet encoding without copying.</p>
     *
     * @return unmodifiable map of all definitions keyed by ID
     */
    public static Map<ResourceLocation, FurnitureDefinition> getAllMap() {
        return DEFINITIONS;
    }

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