<?php

declare( strict_types=1 );

namespace StrykeWP\Modules;

use StrykeWP\Core\Logger;
use StrykeWP\Core\OptionManager;

/**
 * Central registry for all STRYKE WP modules.
 *
 * Responsibilities:
 * - Holds the collection of registered ModuleInterface instances.
 * - Reads each module's enabled state from OptionManager.
 * - Boots enabled modules by calling their boot() method.
 * - Handles enable/disable toggle via AJAX (called from Dashboard).
 *
 * @package StrykeWP\Modules
 */
class ModuleRegistry {

	/** @var array<string, ModuleInterface> Keyed by module ID. */
	private array $modules = [];

	public function __construct(
		private readonly OptionManager $options,
		private readonly Logger        $logger,
	) {}

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

	/**
	 * Registers a module with the registry.
	 *
	 * @param ModuleInterface $module Module to register.
	 */
	public function register( ModuleInterface $module ): void {
		$this->modules[ $module->get_id() ] = $module;
	}

	// ---------------------------------------------------------------------------
	// Boot
	// ---------------------------------------------------------------------------

	/**
	 * Boots all modules that are currently enabled.
	 *
	 * Called once from Plugin::boot() on `plugins_loaded`.
	 */
	public function boot_enabled_modules(): void {
		foreach ( $this->modules as $id => $module ) {
			if ( $this->options->is_module_enabled( $id ) ) {
				$module->boot( $this->logger, $this->options );
				$this->logger->debug( "Module booted: {$id}" );
			}
		}
	}

	// ---------------------------------------------------------------------------
	// Enable / Disable
	// ---------------------------------------------------------------------------

	/**
	 * Enables a module and fires its on_enable() lifecycle hook.
	 *
	 * Calls init() on the module before on_enable() so that $this->logger and
	 * $this->options are always available inside lifecycle callbacks — even when
	 * the module was disabled at boot time and therefore never had boot() called.
	 *
	 * @param string $module_id Module identifier.
	 * @return bool True on success, false if module not found.
	 */
	public function enable( string $module_id, bool $check_capability = true ): bool {
		if ( $check_capability && ! current_user_can( 'manage_options' ) ) {
			return false;
		}
		if ( ! isset( $this->modules[ $module_id ] ) ) {
			return false;
		}
		$module = $this->modules[ $module_id ];
		$module->init( $this->logger, $this->options );
		try {
			$module->on_enable();
		} catch ( \Throwable $e ) {
			$this->logger->error( "Module enable failed: {$module_id} — " . $e->getMessage() );
			return false;
		}
		$this->options->set_module_enabled( $module_id, true );
		$this->logger->info( "Module enabled: {$module_id}" );
		return true;
	}

	/**
	 * Disables a module and fires its on_disable() lifecycle hook.
	 *
	 * Calls init() on the module before on_disable() for the same reason as
	 * enable() — the module may not have been booted (was disabled at boot time).
	 * Calling init() again on an already-booted module is harmless.
	 *
	 * @param string $module_id Module identifier.
	 * @return bool True on success, false if module not found.
	 */
	public function disable( string $module_id, bool $check_capability = true ): bool {
		if ( $check_capability && ! current_user_can( 'manage_options' ) ) {
			return false;
		}
		if ( ! isset( $this->modules[ $module_id ] ) ) {
			return false;
		}
		$module = $this->modules[ $module_id ];
		$module->init( $this->logger, $this->options );
		try {
			$module->on_disable();
		} catch ( \Throwable $e ) {
			$this->logger->error( "Module disable failed: {$module_id} — " . $e->getMessage() );
			return false;
		}
		$this->options->set_module_enabled( $module_id, false );
		$this->logger->info( "Module disabled: {$module_id}" );
		return true;
	}

	// ---------------------------------------------------------------------------
	// Queries
	// ---------------------------------------------------------------------------

	/**
	 * Returns all registered modules.
	 *
	 * @return array<string, ModuleInterface>
	 */
	public function all(): array {
		return $this->modules;
	}

	/**
	 * Returns a single module by ID, or null if not registered.
	 *
	 * @param string $module_id Module identifier.
	 */
	public function get( string $module_id ): ?ModuleInterface {
		return $this->modules[ $module_id ] ?? null;
	}

	/**
	 * Checks if a given module is currently enabled.
	 *
	 * @param string $module_id Module identifier.
	 * @return bool
	 */
	public function is_enabled( string $module_id ): bool {
		return $this->options->is_module_enabled( $module_id );
	}
}
