<?php
/**
 * Plugin — composition root.
 *
 * Wires up the core services, registers every module with the registry, then
 * boots the modules enabled in settings. Boots once per request on
 * `plugins_loaded`.
 *
 * SECURITY MODEL:
 *   The admin UI is gated by the standard `manage_options` capability, and
 *   individual modules apply their own finer-grained capability checks where
 *   appropriate. The plugin deliberately uses WordPress capabilities only —
 *   there is no hardcoded-owner or single-operator gate anywhere, which keeps
 *   it correct for any multi-user site.
 *
 * @package Platform\Helm
 */

declare( strict_types = 1 );

namespace Platform\Helm;

use Platform\Helm\Admin\AdminBar;
use Platform\Helm\Admin\DashboardPage;
use Platform\Helm\Admin\SettingsPage;
use Platform\Helm\Admin\SetupWizard;
use Platform\Helm\Admin\ToolsPage;
use Platform\Helm\Contracts\ModuleInterface;
use Platform\Helm\Core\Assets;
use Platform\Helm\Core\Logger;
use Platform\Helm\Core\ModuleRegistry;
use Platform\Helm\Core\Settings;
use Platform\Helm\Core\Ui;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

final class Plugin {

	private static ?self $instance = null;

	private Settings $settings;
	private Ui $ui;
	private ModuleRegistry $registry;
	private Assets $assets;
	private DashboardPage $dashboard_page;
	private SettingsPage $settings_page;
	private ToolsPage $tools_page;
	private SetupWizard $setup_wizard;
	private AdminBar $admin_bar;
	private bool $booted = false;

	public static function instance(): self {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	private function __construct() {
		$this->settings = new Settings();
		$this->registry = new ModuleRegistry();
		$this->assets   = new Assets();
	}

	private function __clone() {}

	public function __wakeup(): void {
		throw new \RuntimeException( 'Cannot unserialize Plugin singleton.' );
	}

	public function settings(): Settings {
		return $this->settings;
	}

	public function registry(): ModuleRegistry {
		return $this->registry;
	}

	public function assets(): Assets {
		return $this->assets;
	}

	/**
	 * Public entry point. Idempotent — safe to call more than once.
	 */
	public function boot(): void {
		if ( $this->booted ) {
			return;
		}
		$this->booted = true;

		// Load translations.
		add_action( 'init', [ $this, 'load_textdomain' ] );

		// Register modules with the registry.
		$this->register_modules();

		// Admin pages: Dashboard (overview) owns the top-level slug; Settings
		// (module toggles) attaches as a submenu.
		$this->dashboard_page = new DashboardPage( $this->settings, $this->registry );
		$this->dashboard_page->register_hooks();

		$this->ui = new Ui();
		$this->ui->register_hooks();

		$this->settings_page = new SettingsPage( $this->settings, $this->registry );
		$this->settings_page->register_hooks();

		$this->tools_page = new ToolsPage( $this->settings );
		$this->tools_page->register_hooks();

		$this->setup_wizard = new SetupWizard( $this->settings, $this->registry );
		$this->setup_wizard->register_hooks();

		$this->admin_bar = new AdminBar();
		$this->admin_bar->register_hooks();

		// Boot enabled modules on `plugins_loaded`, priority 20 — late enough
		// that other plugins (e.g. SEO plugins we integrate with) have already
		// registered their public APIs.
		add_action( 'plugins_loaded', [ $this, 'boot_modules' ], 20 );

		// "Settings" link on the Plugins screen.
		add_filter(
			'plugin_action_links_' . PLATFORM_HELM_BASENAME,
			[ $this, 'plugin_action_links' ]
		);

		// Lifecycle.
		register_activation_hook( PLATFORM_HELM_FILE, [ $this, 'on_activate' ] );
		register_deactivation_hook( PLATFORM_HELM_FILE, [ $this, 'on_deactivate' ] );
	}

	public function load_textdomain(): void {
		load_plugin_textdomain(
			'platform-helm',
			false,
			dirname( PLATFORM_HELM_BASENAME ) . '/languages'
		);
	}

	/**
	 * Instantiate and register every module.
	 *
	 * Phase 1 step 1 ships the framework with NO modules registered yet, so the
	 * skeleton can be verified in isolation. Modules are added here one at a
	 * time as each phase lands. The list is filterable so integrations and
	 * future Pro add-ons can extend or trim it without touching core.
	 */
	private function register_modules(): void {
		$modules = [
			// --- Phase 1: linking core ---
			new \Platform\Helm\Modules\Link_Suggester\LinkSuggesterModule(),
			new \Platform\Helm\Modules\Link_Architect\LinkArchitectModule( $this->assets ),

			// --- Phase 2: operations layer ---
			new \Platform\Helm\Modules\Link_Health\LinkHealthModule(),
			new \Platform\Helm\Modules\Persona_Dashboards\PersonaDashboardsModule(),

			// --- Phase 3: team & ops ---
			new \Platform\Helm\Modules\Role_Manager\RoleManagerModule(),
			new \Platform\Helm\Modules\Activity_Log\ActivityLogModule(),
			new \Platform\Helm\Modules\Audit_Trail\AuditTrailModule(),
			new \Platform\Helm\Modules\Activity_Intelligence\ActivityIntelligenceModule(),
			new \Platform\Helm\Modules\Member_Health\MemberHealthModule(),

			// --- Phase 4: SEO & content intelligence ---
			new \Platform\Helm\Modules\Ai\AiModule( $this->assets ),
			new \Platform\Helm\Modules\Aeo\AeoModule( $this->assets ),
			new \Platform\Helm\Modules\Meta_Gen\MetaGenModule( $this->assets ),
			new \Platform\Helm\Modules\Schema\SchemaModule( $this->assets ),
			new \Platform\Helm\Modules\Gtm\GtmModule( $this->assets ),

			// --- Phase 5: site tools ---
			new \Platform\Helm\Modules\LinkAssassin\LinkAssassinModule( $this->assets ),
			new \Platform\Helm\Modules\GhostPurge\GhostPurgeModule( $this->assets ),
			new \Platform\Helm\Modules\UploadsVault\UploadsVaultModule(),
			new \Platform\Helm\Modules\Login_Branding\LoginBrandingModule(),

			// --- Phase 6: search performance ---
			new \Platform\Helm\Modules\GSC\GscModule( $this->assets ),
			new \Platform\Helm\Modules\Content_Auditor\ContentAuditorModule( $this->assets ),
		];

		/**
		 * Filter the list of registered modules.
		 *
		 * @param ModuleInterface[] $modules
		 */
		$modules = apply_filters( 'platform_helm_modules', $modules );

		foreach ( $modules as $module ) {
			if ( $module instanceof ModuleInterface ) {
				$this->registry->register( $module );
			}
		}
	}

	/**
	 * Boot each enabled module inside its own try/catch, so one module failing
	 * never takes down the others or the site.
	 */
	public function boot_modules(): void {
		foreach ( $this->registry->all() as $module ) {
			// Pro gate: a locked (Pro, unlicensed) module never boots, so it
			// registers zero hooks regardless of any stored "enabled" value.
			if ( \Platform\Helm\Core\Licensing::is_locked( $module->id() ) ) {
				continue;
			}
			if ( ! $this->settings->is_module_enabled( $module->id(), $module->default_enabled() ) ) {
				continue;
			}
			try {
				$module->boot();
			} catch ( \Throwable $e ) {
				Logger::error( sprintf( 'Module %s failed to boot.', $module->id() ), $e );
			}
		}
	}

	/**
	 * @param array<int,string> $links
	 * @return array<int,string>
	 */
	public function plugin_action_links( array $links ): array {
		$dashboard_link = sprintf(
			'<a href="%s">%s</a>',
			esc_url( admin_url( 'admin.php?page=' . DashboardPage::PAGE_SLUG ) ),
			esc_html__( 'Dashboard', 'platform-helm' )
		);
		$modules_link = sprintf(
			'<a href="%s">%s</a>',
			esc_url( admin_url( 'admin.php?page=' . SettingsPage::PAGE_SLUG ) ),
			esc_html__( 'Modules', 'platform-helm' )
		);
		array_unshift( $links, $dashboard_link, $modules_link );
		return $links;
	}

	/**
	 * Activation. First-run module defaults are written lazily by
	 * Settings::is_module_enabled(), so activation stays cheap. Rewrite rules
	 * are flushed in case a module registered any (none do in Phase 1).
	 */
	public function on_activate(): void {
		// Mark a one-time "show the setup wizard" flag, unless the user has
		// already completed setup before (so re-activation during dev doesn't
		// nag). The wizard clears the redirect flag once shown.
		if ( false === get_option( 'platform_helm_setup_complete', false ) ) {
			update_option( 'platform_helm_do_setup_redirect', 1, false );
		}
		flush_rewrite_rules();
	}

	public function on_deactivate(): void {
		// Clean up scheduled cron events so we never orphan them.
		\Platform\Helm\Modules\Persona_Dashboards\CronWarmer::unschedule();
		\Platform\Helm\Modules\Link_Health\HealthAlerts::unschedule();
		\Platform\Helm\Modules\Activity_Log\ActivityMaintenance::unschedule();
		\Platform\Helm\Modules\Activity_Intelligence\ActivityIntelligenceModule::unschedule();
		// GSC daily sync cron.
		wp_clear_scheduled_hook( \Platform\Helm\Modules\GSC\GscModule::SYNC_CRON_HOOK );
		flush_rewrite_rules();
	}
}
