<?php
/**
 * Container Bridge
 *
 * Provides a bridge for Pro plugin to access and extend Free's container.
 * This enables Pro to register its services without duplicating the DI infrastructure.
 *
 * @package     WPUltimateSecurity\UltimateSecurity\Infrastructure\Container
 * @since       2.0.0
 * @author      Ultimate Security Team
 * @license     GPL-2.0-or-later
 */

declare(strict_types=1);

namespace WPUltimateSecurity\UltimateSecurity\Infrastructure\Container;

/**
 * Bridge class for external plugins to interact with the container.
 *
 * Pro plugin uses this to:
 * - Access Free's container instance
 * - Register Pro-specific services
 * - Extend or decorate Free's services
 *
 * Example usage in Pro:
 * ```php
 * use WPUltimateSecurity\UltimateSecurity\Infrastructure\Container\ContainerBridge;
 *
 * // Get the container
 * $container = ContainerBridge::getContainer();
 *
 * // Register Pro services
 * ContainerBridge::registerProServices(function($container) {
 *     $container->singleton(SMSProvider::class, function() {
 *         return new SMSProvider();
 *     });
 *
 *     $container->singleton(EventLogger::class, function() {
 *         return new EventLogger();
 *     });
 * });
 * ```
 *
 * @since 2.0.0
 */
final class ContainerBridge
{
    /**
     * Registered Pro service registrars.
     *
     * @var array<callable>
     */
    private static array $proRegistrars = [];

    /**
     * Whether Pro services have been registered.
     *
     * @var bool
     */
    private static bool $proServicesRegistered = false;

    /**
     * Get the main container instance.
     *
     * @since 2.0.0
     *
     * @return Container|null The container instance or null if not initialized.
     */
    public static function getContainer(): ?Container
    {
        return Container::getInstance();
    }

    /**
     * Check if the container is available.
     *
     * @since 2.0.0
     *
     * @return bool True if container is initialized.
     */
    public static function isContainerReady(): bool
    {
        return Container::getInstance() !== null;
    }

    /**
     * Register Pro services callback.
     *
     * Pro plugin calls this to register its services with the container.
     * The callback receives the container instance.
     *
     * @since 2.0.0
     *
     * @param callable $registrar Callback that receives Container instance.
     *
     * @return void
     */
    public static function registerProServices(callable $registrar): void
    {
        self::$proRegistrars[] = $registrar;

        // If container is already ready, execute immediately
        if (self::isContainerReady() && !self::$proServicesRegistered) {
            self::executeProRegistrars();
        }
    }

    /**
     * Execute all Pro service registrars.
     *
     * Called by the container after initialization to register Pro services.
     *
     * @since 2.0.0
     *
     * @return void
     */
    public static function executeProRegistrars(): void
    {
        if (self::$proServicesRegistered) {
            return;
        }

        $container = self::getContainer();
        if ($container === null) {
            return;
        }

        foreach (self::$proRegistrars as $registrar) {
            $registrar($container);
        }

        self::$proServicesRegistered = true;

        /**
         * Fires after Pro services have been registered.
         *
         * @since 2.0.0
         *
         * @param Container $container The container instance.
         */
        do_action('ultimate_security_pro_services_registered', $container);
    }

    /**
     * Get a service from the container.
     *
     * Convenience method for external plugins.
     *
     * @since 2.0.0
     *
     * @param string $id The service identifier.
     *
     * @return mixed The service instance.
     *
     * @throws ServiceNotFoundException If service not found.
     */
    public static function get(string $id): mixed
    {
        $container = self::getContainer();
        if ($container === null) {
            throw new ContainerException('Container not initialized');
        }

        return $container->get($id);
    }

    /**
     * Check if a service exists in the container.
     *
     * @since 2.0.0
     *
     * @param string $id The service identifier.
     *
     * @return bool True if service exists.
     */
    public static function has(string $id): bool
    {
        $container = self::getContainer();
        return $container !== null && $container->has($id);
    }

    /**
     * Register a singleton service.
     *
     * Convenience method for Pro to register singletons.
     *
     * @since 2.0.0
     *
     * @param string   $id       Service identifier.
     * @param callable $resolver Service factory.
     *
     * @return void
     */
    public static function singleton(string $id, callable $resolver): void
    {
        $container = self::getContainer();
        if ($container === null) {
            // Queue for later
            self::registerProServices(function ($c) use ($id, $resolver) {
                $c->singleton($id, $resolver);
            });
            return;
        }

        $container->singleton($id, $resolver);
    }

    /**
     * Register a transient service.
     *
     * @since 2.0.0
     *
     * @param string   $id       Service identifier.
     * @param callable $resolver Service factory.
     *
     * @return void
     */
    public static function bind(string $id, callable $resolver): void
    {
        $container = self::getContainer();
        if ($container === null) {
            self::registerProServices(function ($c) use ($id, $resolver) {
                $c->bind($id, $resolver);
            });
            return;
        }

        $container->bind($id, $resolver);
    }

    /**
     * Extend an existing service with a decorator.
     *
     * @since 2.0.0
     *
     * @param string   $id        The service to extend.
     * @param callable $decorator Function receiving the original service.
     *
     * @return void
     */
    public static function extend(string $id, callable $decorator): void
    {
        $container = self::getContainer();
        if ($container === null) {
            self::registerProServices(function ($c) use ($id, $decorator) {
                if ($c->has($id)) {
                    $original = $c->get($id);
                    $c->singleton($id, function () use ($decorator, $original) {
                        return $decorator($original);
                    });
                }
            });
            return;
        }

        if ($container->has($id)) {
            $original = $container->get($id);
            $container->singleton($id, function () use ($decorator, $original) {
                return $decorator($original);
            });
        }
    }

    /**
     * Reset bridge state (for testing).
     *
     * @since 2.0.0
     * @internal
     *
     * @return void
     */
    public static function reset(): void
    {
        self::$proRegistrars = [];
        self::$proServicesRegistered = false;
    }

    /**
     * Private constructor to prevent instantiation.
     */
    private function __construct()
    {
    }
}
