<?php
/**
 * Core plugin orchestrator.
 *
 * @package Donatico
 */

declare(strict_types=1);

namespace Donatico;

/**
 * The main plugin class. Bootstraps all subsystems, wires them together
 * via the Loader, and exposes a single run() entry point called from
 * the plugins_loaded hook in the bootstrap file.
 */
final class Plugin {

	/** @var Plugin|null */
	private static ?self $instance = null;

	/** @var Loader */
	private Loader $loader;

	/**
	 * Internationalization handler.
	 *
	 * @var \Donatico\I18n|null
	 */
	private ?I18n $i18n = null;

	/** @var string */
	private string $version;

	/**
	 * Return the single shared instance.
	 *
	 * @return static
	 */
	public static function getInstance(): self {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/** Sealed — use getInstance(). */
	private function __construct() {
		$this->version = DONATICO_VERSION;
		$this->loader  = new Loader();

		$this->loadDependencies();
		$this->setLocale();
		$this->defineAdminHooks();
		$this->defineFrontendHooks();
		$this->defineStripeHooks();
		$this->defineEmailHooks();
		$this->loadPremium();
	}

	/** @return void */
	private function __clone() {}

	/**
	 * @return void
	 * @throws \Exception Always.
	 */
	public function __wakeup(): void {
		throw new \Exception( 'Plugin singleton cannot be unserialized.' );
	}

	/**
	 * Instantiate all subsystem classes.
	 * They are stored as properties so the Loader can reference them.
	 *
	 * @return void
	 */
	private function loadDependencies(): void {
		$this->i18n = new I18n();
		// Admin and Frontend are bootstrapped in their respective define*Hooks methods.
		// Stripe, Database, Email subsystems are injected where needed.
	}

	/**
	 * Register the i18n text-domain loader.
	 *
	 * @return void
	 */
	private function setLocale(): void {
		$this->loader->addAction( 'init', $this->i18n, 'loadPluginTextDomain' );
	}

	/**
	 * Register all admin-side hooks via the Loader.
	 *
	 * @return void
	 */
	private function defineAdminHooks(): void {
		// Admin bootstrap is deferred to is_admin() context to avoid loading
		// unnecessary classes on every frontend request.
		if ( is_admin() ) {
			$admin = new Admin\Admin( $this->loader );
			$admin->registerHooks();
		}
	}

	/**
	 * Register all frontend hooks via the Loader.
	 *
	 * @return void
	 */
	private function defineFrontendHooks(): void {
		$frontend = new Frontend\Frontend( $this->loader );
		$frontend->registerHooks();

		// Thank-you page — only on regular frontend requests.
		if ( ! \is_admin() && ! \wp_doing_ajax() ) {
			$repository   = new Database\DonationRepository();
			$thank_you    = new Frontend\ThankYouPage( $repository );
			$this->loader->addAction( 'init', $thank_you, 'handleReturn' );
		}
	}

	/**
	 * Register Stripe subsystem hooks (webhook REST route).
	 * REST API requests are not admin and not AJAX, so this fires for all
	 * request types — the route itself gates access via Stripe signature.
	 *
	 * @return void
	 */
	private function defineStripeHooks(): void {
		$repository      = new Database\DonationRepository();
		$webhook_handler = new Stripe\WebhookHandler( $repository );
		$this->loader->addAction( 'rest_api_init', $webhook_handler, 'registerRoute' );
	}

	/**
	 * Register email notification hooks.
	 *
	 * Each email class gets its own TemplateRenderer instance so that tag
	 * state from one email cannot bleed into another when two actions fire
	 * on the same hook (e.g. both AdminNotification and DonorThankYou hook
	 * to donatico_donation_completed).
	 *
	 * @return void
	 */
	private function defineEmailHooks(): void {
		$email_service = new Emails\EmailService();

		$admin_notification = new Emails\AdminNotification( $email_service, new Emails\TemplateRenderer() );
		$donor_thank_you    = new Emails\DonorThankYou( $email_service, new Emails\TemplateRenderer() );
		$payment_failed     = new Emails\PaymentFailedAlert( $email_service, new Emails\TemplateRenderer() );

		$this->loader->addAction( 'donatico_donation_completed', $admin_notification, 'onDonationCompleted' );
		$this->loader->addAction( 'donatico_donation_completed', $donor_thank_you, 'onDonationCompleted' );
		$this->loader->addAction( 'donatico_donation_failed', $payment_failed, 'onDonationFailed' );
	}

	/**
	 * Load premium features when licensed.
	 *
	 * The PremiumLoader class is @fs_premium_only and does not exist in the
	 * free build, so guard with class_exists so the free build never fatals
	 * on the missing class.
	 *
	 * @return void
	 */
	private function loadPremium(): void {
		if ( donatico_can_use_premium() && class_exists( '\Donatico\Premium\PremiumLoader' ) ) {
			\Donatico\Premium\PremiumLoader::instance()->init();
		}
	}

	/**
	 * Dispatch all queued hooks to WordPress and start the plugin.
	 *
	 * @return void
	 */
	public function run(): void {
		$this->loader->run();
	}

	/**
	 * The plugin version string.
	 *
	 * @return string
	 */
	public function getVersion(): string {
		return $this->version;
	}
}
