package com.livingworld.core.services;

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

/**
 * A plain Java service registry that stores and retrieves services by their {@link ServiceKey}.
 *
 * <p>The registry supports registration, lookup, and a lock mechanism to prevent further
 * modifications once all services have been registered.</p>
 *
 * <p>This class is not thread-safe. If multiple threads access the registry concurrently,
 * external synchronization is required.</p>
 */
public final class ServiceRegistry {

    private Map<ServiceKey<?>, Object> services = new HashMap<>();
    private boolean locked = false;

    /**
     * Registers a service with the given key.
     *
     * @param key     the service key identifying the service (must not be null)
     * @param service the service instance to register (must not be null)
     * @throws IllegalArgumentException if {@code key} is null, {@code service} is null
     * @throws IllegalStateException    if this registry is already locked, or a service with the same key is already registered
     */
    @SuppressWarnings("unchecked")
    public <T> void register(ServiceKey<T> key, T service) {
        if (key == null) {
            throw new IllegalArgumentException("Service key must not be null");
        }
        if (service == null) {
            throw new IllegalArgumentException("Service instance must not be null for key: " + key);
        }
        if (locked) {
            throw new IllegalStateException("Cannot register services after the registry is locked");
        }
        // Use the key itself as the map key. Since ServiceKey overrides equals/hashCode,
        // duplicate registration of the same key will be detected.
        if (services.containsKey(key)) {
            throw new IllegalStateException("A service with key " + key + " is already registered");
        }
        services.put(key, service);
    }

    /**
     * Returns the service identified by the given key.
     *
     * @param <T> the type of the service
     * @param key the service key identifying the service (must not be null)
     * @return the registered service instance
     * @throws IllegalArgumentException if {@code key} is null
     * @throws IllegalStateException    if no service is registered for the given key
     */
    @SuppressWarnings("unchecked")
    public <T> T get(ServiceKey<T> key) {
        if (key == null) {
            throw new IllegalArgumentException("Service key must not be null");
        }
        Object service = services.get(key);
        if (service == null) {
            throw new IllegalStateException("No service registered for key: " + key);
        }
        return (T) service;
    }

    /**
     * Returns an {@link Optional} containing the service identified by the given key,
     * or empty if no service is registered.
     *
     * @param <T> the type of the service
     * @param key the service key identifying the service (must not be null)
     * @return an optional containing the service, or empty if not found
     * @throws IllegalArgumentException if {@code key} is null
     */
    @SuppressWarnings("unchecked")
    public <T> Optional<T> find(ServiceKey<T> key) {
        if (key == null) {
            throw new IllegalArgumentException("Service key must not be null");
        }
        return Optional.ofNullable((T) services.get(key));
    }

    /**
     * Returns whether a service is registered for the given key.
     *
     * @param key the service key to check (must not be null)
     * @return true if a service is registered for the key, false otherwise
     * @throws IllegalArgumentException if {@code key} is null
     */
    public boolean isRegistered(ServiceKey<?> key) {
        if (key == null) {
            throw new IllegalArgumentException("Service key must not be null");
        }
        return services.containsKey(key);
    }

    /**
     * Returns the number of services currently registered.
     *
     * @return the count of registered services
     */
    public int getServiceCount() {
        return services.size();
    }

    /**
     * Locks this registry, preventing any further registrations.
     *
     * <p>After locking, the internal service map is replaced with an unmodifiable
     * view to guarantee no external code can mutate registered services.</p>
     */
    public void lock() {
        locked = true;
        this.services = Collections.unmodifiableMap(new HashMap<>(services));
    }

    /**
     * Returns whether this registry is locked.
     *
     * @return true if the registry is locked, false otherwise
     */
    public boolean isLocked() {
        return locked;
    }
}