<?php
/**
 * Abstract Module Base Class
 *
 * @package     WPUltimateSecurity\UltimateSecurity\Modules\Base
 * @since       2.0.0
 */

declare(strict_types=1);

namespace WPUltimateSecurity\UltimateSecurity\Modules\Base;

use WPUltimateSecurity\UltimateSecurity\Contracts\EventInterface;
use WPUltimateSecurity\UltimateSecurity\Contracts\IntrospectableModuleInterface;
use WPUltimateSecurity\UltimateSecurity\Events\ModuleRegisteredEvent;
use WPUltimateSecurity\UltimateSecurity\Infrastructure\Settings\SettingsService;
use WPUltimateSecurity\UltimateSecurity\Services\EventBus;

/**
 * Abstract base class for plugin modules.
 *
 * Provides default implementations for IntrospectableModuleInterface methods
 * and helper methods for configuration access and hook registration.
 *
 * Subclasses must implement:
 * - getId(): string - Unique module identifier
 * - getName(): string - Human-readable name
 * - getDomain(): string - Module domain (authentication, protection, etc.)
 *
 * @since 2.0.0
 */
abstract class AbstractModule implements IntrospectableModuleInterface
{
    /**
     * Module version. Override in subclass to track version.
     *
     * @var string
     */
    protected const VERSION = '1.0.0';

    /**
     * Track which modules have been registered.
     *
     * @var array<string, bool>
     */
    protected static array $registeredModules = [];

    /**
     * Register the module with WordPress.
     *
     * Handles idempotent registration, checks if enabled, and fires hooks.
     *
     * @return void
     */
    public static function register(): void
    {
        $id = static::getId();

        if (isset(self::$registeredModules[$id])) {
            return;
        }

        self::$registeredModules[$id] = true;

        if (!static::isEnabled()) {
            do_action('ultimate_security_module_skipped', $id);
            return;
        }

        static::boot();

        // Emit module registered event
        self::emit(new ModuleRegisteredEvent(
            static::getId(),
            static::class,
            static::getDomain()
        ));

        // Register module as event subscriber if it has subscriptions
        $events = static::getSubscribedEvents();
        if (!empty($events)) {
            // Register each event subscription
            $bus = EventBus::getInstance();
            foreach ($events as $eventName => $handler) {
                if (is_array($handler)) {
                    $bus->subscribe($eventName, [static::class, $handler[0]], $handler[1]);
                } else {
                    $bus->subscribe($eventName, [static::class, $handler]);
                }
            }
        }

        do_action('ultimate_security_module_registered', $id, static::class);
    }

    /**
     * Boot the module after registration.
     *
     * Override in subclass to add hooks, filters, and initialization logic.
     *
     * @return void
     */
    public static function boot(): void
    {
        // Override in subclass
    }

    /**
     * Clean up when module is deactivated.
     *
     * Override in subclass for cleanup tasks like removing scheduled hooks.
     *
     * @return void
     */
    public static function deactivate(): void
    {
        // Override in subclass
    }

    /**
     * Get the module version.
     *
     * @return string
     */
    public static function getVersion(): string
    {
        return static::VERSION;
    }

    /**
     * Get IDs of modules this module depends on.
     *
     * @return string[]
     */
    public static function getDependencies(): array
    {
        return [];
    }

    /**
     * Get system requirements.
     *
     * @return array<string, string>
     */
    public static function getRequirements(): array
    {
        return [];
    }

    /**
     * Get configuration schema with defaults and types.
     *
     * @return array<string, array{type: string, default: mixed, label?: string}>
     */
    public static function getConfigSchema(): array
    {
        return [];
    }

    /**
     * Get current resolved settings based on config schema.
     *
     * @return array<string, mixed>
     */
    public static function getSettings(): array
    {
        $schema = static::getConfigSchema();
        $settings = [];

        foreach ($schema as $key => $definition) {
            $settings[$key] = static::config($key, $definition['default'] ?? null);
        }

        return $settings;
    }

    /**
     * Get hooks this module fires.
     *
     * @return array<string, array{type: string, params: string[]}>
     */
    public static function getHooks(): array
    {
        return [];
    }

    /**
     * Check if this module is enabled.
     *
     * @return bool
     */
    public static function isEnabled(): bool
    {
        return (bool) static::config('enabled', true);
    }

    /**
     * Get unique module identifier.
     *
     * @deprecated 2.0.0 Use getId() instead.
     * @see self::getId()
     *
     * @return string
     */
    public static function getIdentifier(): string
    {
        return static::getId();
    }

    /**
     * Emit an event through the EventBus.
     *
     * @since 2.0.0
     *
     * @param EventInterface $event Event to dispatch.
     *
     * @return EventInterface The dispatched event.
     */
    protected static function emit(EventInterface $event): EventInterface
    {
        return EventBus::getInstance()->dispatch($event);
    }

    /**
     * Get events this module subscribes to.
     *
     * Override in subclasses to subscribe to events.
     * Returns array of event name => method name.
     *
     * @since 2.0.0
     *
     * @return array<string, string|array{0: string, 1: int}>
     */
    public static function getSubscribedEvents(): array
    {
        return [];
    }

    /**
     * Get a config value for this module.
     *
     * @param string $key     The config key within this module's namespace.
     * @param mixed  $default Default value if not found.
     *
     * @return mixed
     */
    protected static function config(string $key, mixed $default = null): mixed
    {
        $service = SettingsService::getInstance();
        return $service->get(static::getId() . '.' . $key, $default);
    }

    /**
     * Register an action callback.
     *
     * @param string   $hook         Hook name.
     * @param callable $callback     Callback function.
     * @param int      $priority     Priority (default 10).
     * @param int      $acceptedArgs Number of accepted arguments (default 1).
     */
    protected static function action(string $hook, callable $callback, int $priority = 10, int $acceptedArgs = 1): void
    {
        add_action($hook, $callback, $priority, $acceptedArgs);
    }

    /**
     * Register a filter callback.
     *
     * @param string   $hook         Hook name.
     * @param callable $callback     Callback function.
     * @param int      $priority     Priority (default 10).
     * @param int      $acceptedArgs Number of accepted arguments (default 1).
     */
    protected static function filter(string $hook, callable $callback, int $priority = 10, int $acceptedArgs = 1): void
    {
        add_filter($hook, $callback, $priority, $acceptedArgs);
    }

    /**
     * Fire an action.
     *
     * @param string $hook Hook name.
     * @param mixed  ...$args Arguments to pass.
     */
    protected static function doAction(string $hook, mixed ...$args): void
    {
        do_action($hook, ...$args);
    }

    /**
     * Apply filters.
     *
     * @param string $hook  Hook name.
     * @param mixed  $value Value to filter.
     * @param mixed  ...$args Additional arguments.
     *
     * @return mixed Filtered value.
     */
    protected static function applyFilters(string $hook, mixed $value, mixed ...$args): mixed
    {
        return apply_filters($hook, $value, ...$args);
    }

    /**
     * Check if module has been registered.
     *
     * @return bool
     */
    public static function isRegistered(): bool
    {
        return isset(self::$registeredModules[static::getId()]);
    }

    /**
     * Reset registration state (for testing).
     *
     * @internal
     */
    public static function resetRegistration(): void
    {
        unset(self::$registeredModules[static::getId()]);
    }
}
