<?php
/**
 * Module Registry Service
 *
 * Provides discovery and introspection of registered modules.
 *
 * @package     WPUltimateSecurity\UltimateSecurity\Services
 * @since       2.0.0
 */

declare(strict_types=1);

namespace WPUltimateSecurity\UltimateSecurity\Services;

use WPUltimateSecurity\UltimateSecurity\Contracts\IntrospectableModuleInterface;

/**
 * Registry for module discovery and metadata collection.
 *
 * Provides:
 * - Module registration and tracking
 * - Dependency-based initialization ordering
 * - Metadata introspection for tooling
 *
 * @since 2.0.0
 */
class ModuleRegistry
{
    /**
     * Registered module classes.
     *
     * @var array<string, class-string<IntrospectableModuleInterface>>
     */
    private static array $modules = [];

    /**
     * Register a module class.
     *
     * @param class-string<IntrospectableModuleInterface> $moduleClass The module class to register.
     *
     * @return void
     */
    public static function register(string $moduleClass): void
    {
        if (!is_subclass_of($moduleClass, IntrospectableModuleInterface::class)) {
            return;
        }

        $id = $moduleClass::getId();
        self::$modules[$id] = $moduleClass;
    }

    /**
     * Get all registered module classes.
     *
     * @return array<string, class-string<IntrospectableModuleInterface>>
     */
    public static function getModules(): array
    {
        return self::$modules;
    }

    /**
     * Get a module class by ID.
     *
     * @param string $id The module ID.
     *
     * @return class-string<IntrospectableModuleInterface>|null
     */
    public static function get(string $id): ?string
    {
        return self::$modules[$id] ?? null;
    }

    /**
     * Get modules by domain.
     *
     * @param string $domain The domain to filter by.
     *
     * @return array<string, class-string<IntrospectableModuleInterface>>
     */
    public static function getByDomain(string $domain): array
    {
        return array_filter(
            self::$modules,
            fn($class) => $class::getDomain() === $domain
        );
    }

    /**
     * Get all module metadata for introspection.
     *
     * @return array<string, array{
     *     id: string,
     *     name: string,
     *     domain: string,
     *     version: string,
     *     enabled: bool,
     *     class: string
     * }>
     */
    public static function getAllMetadata(): array
    {
        $metadata = [];

        foreach (self::$modules as $id => $class) {
            $metadata[$id] = [
                'id' => $id,
                'name' => $class::getName(),
                'domain' => $class::getDomain(),
                'version' => $class::getVersion(),
                'enabled' => $class::isEnabled(),
                'class' => $class,
            ];
        }

        return $metadata;
    }

    /**
     * Initialize all registered modules.
     *
     * Sorts modules by dependencies before calling register().
     *
     * @return void
     */
    public static function init(): void
    {
        $sorted = self::sortByDependencies();

        foreach ($sorted as $class) {
            $class::register();
        }
    }

    /**
     * Boot all enabled registered modules.
     *
     * @return void
     */
    public static function bootAll(): void
    {
        foreach (self::$modules as $class) {
            if ($class::isEnabled()) {
                $class::boot();
            }
        }
    }

    /**
     * Sort modules by dependencies using topological sort.
     *
     * @return array<class-string<IntrospectableModuleInterface>>
     */
    private static function sortByDependencies(): array
    {
        $sorted = [];
        $visited = [];

        foreach (self::$modules as $id => $class) {
            self::visit($id, $class, $visited, $sorted);
        }

        return $sorted;
    }

    /**
     * Visit a module for topological sort.
     *
     * @param string                        $id      Module ID.
     * @param class-string<IntrospectableModuleInterface> $class   Module class.
     * @param array<string, bool>           $visited Visited tracking array.
     * @param array<class-string<IntrospectableModuleInterface>> $sorted  Sorted result array.
     *
     * @return void
     */
    private static function visit(string $id, string $class, array &$visited, array &$sorted): void
    {
        if (isset($visited[$id])) {
            return;
        }

        $visited[$id] = true;

        foreach ($class::getDependencies() as $depId) {
            if (isset(self::$modules[$depId])) {
                self::visit($depId, self::$modules[$depId], $visited, $sorted);
            }
        }

        $sorted[] = $class;
    }

    /**
     * Reset registry (for testing).
     *
     * @internal
     *
     * @return void
     */
    public static function reset(): void
    {
        self::$modules = [];
    }
}
