package com.livingworld.regions;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * Stores module-specific data by module ID.
 */
public class RegionModuleData {

    private Map<String, Object> moduleData;

    /**
     * Creates a new empty RegionModuleData instance.
     */
    public RegionModuleData() {
        this.moduleData = new HashMap<>();
    }

    /**
     * Associates the given data with the specified module ID.
     *
     * @param moduleId the module identifier (must not be null or blank)
     * @param data     the data to store (must not be null)
     * @throws IllegalArgumentException if moduleId is null or blank
     * @throws IllegalArgumentException if data is null
     */
    public void put(String moduleId, Object data) {
        if (moduleId == null || moduleId.isBlank()) {
            throw new IllegalArgumentException("moduleId must not be null or blank");
        }
        if (data == null) {
            throw new IllegalArgumentException("Data for module '" + moduleId + "' must not be null");
        }
        this.moduleData.put(moduleId, data);
    }

    /**
     * Returns the data associated with the given module ID, cast to the requested type.
     *
     * <p>If the module ID is not present or the stored data does not match the
     * requested type, this method returns {@link Optional#empty()} instead of
     * throwing an exception.</p>
     *
     * @param moduleId the module identifier (must not be null or blank)
     * @param type     the expected type of the stored data
     * @param <T>      the target type
     * @return the data cast to {@code type}, or {@link Optional#empty()} if not found or type mismatch
     */
    @SuppressWarnings("unchecked")
    public <T> Optional<T> get(String moduleId, Class<T> type) {
        if (moduleId == null || moduleId.isBlank()) {
            throw new IllegalArgumentException("moduleId must not be null or blank");
        }
        Object value = this.moduleData.get(moduleId);
        if (value == null) {
            return Optional.empty();
        }
        if (!type.isInstance(value)) {
            return Optional.empty();
        }
        return Optional.of((T) value);
    }

    /**
     * Returns whether this instance contains data for the given module ID.
     *
     * @param moduleId the module identifier (must not be null or blank)
     * @return true if data exists for the given module ID
     * @throws IllegalArgumentException if moduleId is null or blank
     */
    public boolean contains(String moduleId) {
        if (moduleId == null || moduleId.isBlank()) {
            throw new IllegalArgumentException("moduleId must not be null or blank");
        }
        return this.moduleData.containsKey(moduleId);
    }

    /**
     * Returns all module IDs currently stored.
     *
     * @return an unmodifiable set of module identifiers
     */
    public Set<String> moduleIds() {
        return Collections.unmodifiableSet(new HashSet<>(this.moduleData.keySet()));
    }

    /**
     * Returns a shallow copy of this instance.
     *
     * <p>The returned {@link RegionModuleData} has its own map, but the values
     * inside are shared references.</p>
     *
     * @return a new {@code RegionModuleData} with the same entries
     */
    public RegionModuleData copyShallow() {
        RegionModuleData copy = new RegionModuleData();
        copy.moduleData.putAll(this.moduleData);
        return copy;
    }
}