package com.livingworld.modules;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * Central registry for simulation modules.
 *
 * <p>This class manages module registration, lookup, lifecycle coordination,
 * and filtering. It is intentionally simple for v1 — dependency sorting
 * and advanced lifecycle management are deferred to later milestones.</p>
 *
 * <p><b>Thread safety:</b> This class is not thread-safe. All operations must
 * be performed on the server main thread.</p>
 */
public final class ModuleRegistry {

    private final Map<String, SimulationModule> modules;

    /**
     * Creates a new empty module registry.
     */
    public ModuleRegistry() {
        this.modules = new LinkedHashMap<>();
    }

    // ------------------------------------------------------------------
    // Registration
    // ------------------------------------------------------------------

    /**
     * Registers a simulation module.
     *
     * <p>If a module with the same ID is already registered, an
     * {@link IllegalArgumentException} is thrown to prevent silent overwrites.</p>
     *
     * @param module the module to register (must not be null)
     * @throws IllegalArgumentException if module is null or a module with the same ID is already registered
     */
    public void register(SimulationModule module) {
        if (module == null) {
            throw new IllegalArgumentException("module must not be null");
        }

        ModuleMetadata metadata = module.getMetadata();
        if (metadata == null) {
            throw new IllegalArgumentException("module metadata must not be null");
        }
        String moduleId = module.getModuleId();
        if (moduleId == null || moduleId.isBlank()) {
            throw new IllegalArgumentException("module ID must not be null or blank");
        }
        if (!moduleId.equals(metadata.moduleId())) {
            throw new IllegalArgumentException(
                    "module ID '" + moduleId + "' does not match metadata ID '" + metadata.moduleId() + "'");
        }
        if (modules.containsKey(moduleId)) {
            throw new IllegalArgumentException(
                "A module with ID '" + moduleId + "' is already registered");
        }

        modules.put(moduleId, module);
    }

    // ------------------------------------------------------------------
    // Lookup
    // ------------------------------------------------------------------

    /**
     * Returns all registered modules in an unmodifiable list.
     *
     * @return an unmodifiable list of all registered modules (never null)
     */
    public List<SimulationModule> getAllModules() {
        return Collections.unmodifiableList(new ArrayList<>(modules.values()));
    }

    /**
     * Returns enabled modules — those where {@code metadata.defaultEnabled()} is true.
     *
     * @return an unmodifiable list of enabled modules (never null)
     */
    public List<SimulationModule> getEnabledModules() {
        List<SimulationModule> enabled = new ArrayList<>();
        for (SimulationModule module : modules.values()) {
            if (module.getMetadata().defaultEnabled()) {
                enabled.add(module);
            }
        }
        return Collections.unmodifiableList(enabled);
    }

    /**
     * Finds a module by its unique identifier.
     *
     * @param moduleId the module ID to look up (must not be null or blank)
     * @return an optional containing the module if found, otherwise empty
     * @throws IllegalArgumentException if moduleId is null or blank
     */
    public Optional<SimulationModule> find(String moduleId) {
        if (moduleId == null || moduleId.isBlank()) {
            throw new IllegalArgumentException("moduleId must not be null or blank");
        }
        return Optional.ofNullable(modules.get(moduleId));
    }

    // ------------------------------------------------------------------
    // Lifecycle coordination
    // ------------------------------------------------------------------

    /**
     * Initialises all registered modules.
     *
     * <p>Each module is initialised in registration order. If one module's
     * {@code initialize()} call throws an exception, the remaining modules
     * are still attempted so that partial initialisation diagnostics can be
     * collected.</p>
     *
     * <p><b>TODO (Milestone 9):</b> Implement dependency-aware topological sort
     * so that modules are initialised in the correct order based on their
     * {@code ModuleMetadata.dependencies()} list.</p>
     *
     * @param context the module context providing service access (must not be null)
     */
    public void initializeAll(ModuleContext context) {
        if (context == null) {
            throw new IllegalArgumentException("context must not be null");
        }
        for (SimulationModule module : modules.values()) {
            module.initialize(context);
        }
    }

    /**
     * Shuts down all registered modules.
     *
     * <p>Each module's {@code shutdown()} method is called in reverse registration
     * order. Exceptions from one module do not prevent other modules from
     * shutting down.</p>
     */
    public void shutdownAll() {
        // Iterate in reverse to shut down in opposite order of initialisation
        List<SimulationModule> reversed = new ArrayList<>(modules.values());
        Collections.reverse(reversed);

        for (SimulationModule module : reversed) {
            try {
                module.shutdown();
            } catch (Exception e) {
                com.livingworld.debug.LivingWorldLogger.error(
                        com.livingworld.debug.DiagnosticCategory.SIMULATION,
                        "Module shutdown failed: " + module.getModuleId(), e);
            }
        }
    }
}
