<?php

declare(strict_types=1);

namespace Australcode\Rewards;

defined( 'ABSPATH' ) || exit;

use Australcode\Rewards\Admin\AdminMenu;
use Australcode\Rewards\Admin\AssetLoader;
use Australcode\Rewards\Admin\CampaignsPage;
use Australcode\Rewards\Admin\CustomerAdminPro;
use Australcode\Rewards\Admin\CustomersPage;
use Australcode\Rewards\Admin\EarningRulesPage;
use Australcode\Rewards\Admin\ImportPage;
use Australcode\Rewards\Admin\RedemptionRulesPage;
use Australcode\Rewards\Admin\ProductEarningAdmin;
use Australcode\Rewards\Admin\ReferralsPage;
use Australcode\Rewards\Admin\SettingsPage;
use Australcode\Rewards\Admin\TiersPage;
use Australcode\Rewards\Admin\UpgradePage;
use Australcode\Rewards\Frontend\Blocks;
use Australcode\Rewards\Frontend\CartPointsNotice;
use Australcode\Rewards\Frontend\FloatingWidget;
use Australcode\Rewards\Frontend\FrontendAssets;
use Australcode\Rewards\Frontend\MyAccountRewards;
use Australcode\Rewards\Frontend\OrderEmailPoints;
use Australcode\Rewards\Frontend\ProductPointsHook;
use Australcode\Rewards\Frontend\ProShortcodes;
use Australcode\Rewards\Frontend\RedemptionOptions;
use Australcode\Rewards\Frontend\Shortcodes;
use Australcode\Rewards\Frontend\TierProgressRenderer;
use Australcode\Rewards\Hooks\BirthdayHook;
use Australcode\Rewards\Hooks\CartThresholdHook;
use Australcode\Rewards\Hooks\DailyLoginHook;
use Australcode\Rewards\Hooks\FirstOrderBonusHook;
use Australcode\Rewards\Hooks\GuestOrderLinkHook;
use Australcode\Rewards\Hooks\LifetimeSpendHook;
use Australcode\Rewards\Hooks\OrderEarningHook;
use Australcode\Rewards\Hooks\OrdersMilestoneHook;
use Australcode\Rewards\Hooks\ProfileBonusHook;
use Australcode\Rewards\Hooks\RedemptionCartHook;
use Australcode\Rewards\Hooks\ReferralHook;
use Australcode\Rewards\Hooks\ReviewRewardHook;
use Australcode\Rewards\Hooks\SignupBonusHook;
use Australcode\Rewards\Cli\ImportCommand;
use Australcode\Rewards\Infrastructure\Database\Migrator;
use Australcode\Rewards\Rest\CampaignsController;
use Australcode\Rewards\Rest\EarningRulesController;
use Australcode\Rewards\Rest\RedemptionController;
use Australcode\Rewards\Rest\RedemptionRulesController;
use Australcode\Rewards\Rest\TiersController;
use Australcode\Rewards\Rest\WalletController;
use Australcode\Rewards\Service\CampaignService;
use Australcode\Rewards\Service\EarningRuleEngine;
use Australcode\Rewards\Service\PointsExpiryService;
use Australcode\Rewards\Service\RewardEmails;
use Australcode\Rewards\Service\TierService;

/**
 * Bootstrap orchestrator.
 *
 * The only singleton in the codebase — and it holds no business logic. It wires
 * the DI container, runs pending migrations, and registers the services that
 * hook into WordPress. Everything else is resolved through the container with
 * explicit constructor injection.
 */
final class Plugin {

	private static ?Plugin $instance = null;

	private Container $container;

	private function __construct() {
		$this->container = new Container();
	}

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

	public function container(): Container {
		return $this->container;
	}

	/**
	 * Entry point invoked on `plugins_loaded` (priority 20).
	 */
	public function boot(): void {
		if ( ! $this->is_woocommerce_active() ) {
			add_action( 'admin_notices', array( $this, 'render_woocommerce_missing_notice' ) );
			return;
		}

		$this->maybe_run_pending_migrations();

		// Core services (always present in both the free and premium builds).
		$this->container->get( AdminMenu::class )->register();
		$this->container->get( AssetLoader::class )->register();
		$this->container->get( UpgradePage::class )->register();
		$this->container->get( SettingsPage::class )->register();
		$this->container->get( CustomersPage::class )->register();
		$this->container->get( ImportPage::class )->register();
		$this->container->get( ProductEarningAdmin::class )->register();
		$this->container->get( OrderEarningHook::class )->register();
		$this->container->get( GuestOrderLinkHook::class )->register();
		$this->container->get( RedemptionCartHook::class )->register();
		$this->container->get( FrontendAssets::class )->register();
		$this->container->get( Shortcodes::class )->register();
		$this->container->get( Blocks::class )->register();
		$this->container->get( ProductPointsHook::class )->register();
		$this->container->get( CartPointsNotice::class )->register();
		$this->container->get( MyAccountRewards::class )->register();
		$this->container->get( FloatingWidget::class )->register();
		$this->container->get( OrderEmailPoints::class )->register();
		$this->container->get( WalletController::class )->register();
		$this->container->get( RedemptionController::class )->register();

		// Pro services (@fs_premium_only): registered only when their class shipped
		// — i.e. the premium build. They are stripped from the free WP.org build, so
		// register_if_exists() skips them there instead of fataling. The tier and
		// campaign services self-register their multipliers onto the
		// acrwd_earning_multiplier filter; the pages self-register their submenu
		// under the Australcode Rewards menu (after AdminMenu builds the parent).
		$this->register_if_exists( TierService::class );
		$this->register_if_exists( CampaignService::class );
		$this->register_if_exists( TiersPage::class );
		$this->register_if_exists( CampaignsPage::class );
		$this->register_if_exists( ReferralsPage::class );
		$this->register_if_exists( CustomerAdminPro::class );
		$this->register_if_exists( EarningRuleEngine::class );
		$this->register_if_exists( EarningRulesPage::class );
		$this->register_if_exists( RedemptionOptions::class );
		$this->register_if_exists( RedemptionRulesPage::class );
		$this->register_if_exists( SignupBonusHook::class );
		$this->register_if_exists( DailyLoginHook::class );
		$this->register_if_exists( FirstOrderBonusHook::class );
		$this->register_if_exists( ReviewRewardHook::class );
		$this->register_if_exists( BirthdayHook::class );
		$this->register_if_exists( ProfileBonusHook::class );
		$this->register_if_exists( OrdersMilestoneHook::class );
		$this->register_if_exists( CartThresholdHook::class );
		$this->register_if_exists( LifetimeSpendHook::class );
		$this->register_if_exists( ReferralHook::class );
		$this->register_if_exists( TierProgressRenderer::class );
		$this->register_if_exists( ProShortcodes::class );
		$this->register_if_exists( PointsExpiryService::class );
		$this->register_if_exists( RewardEmails::class );
		$this->register_if_exists( TiersController::class );
		$this->register_if_exists( CampaignsController::class );
		$this->register_if_exists( EarningRulesController::class );
		$this->register_if_exists( RedemptionRulesController::class );

		if ( defined( 'WP_CLI' ) && \WP_CLI ) {
			\WP_CLI::add_command( 'acrwd import', $this->container->get( ImportCommand::class ) );
		}
	}

	/**
	 * Resolve a service from the container and call its register() — but only if
	 * the class shipped. Pro classes are @fs_premium_only and stripped from the free
	 * WP.org build; this guard skips them there instead of fataling on a missing
	 * class. The is_object/method_exists pair keeps it type-safe (the container
	 * returns mixed for a runtime class-string).
	 *
	 * @param class-string $service_class Fully-qualified service class name.
	 */
	private function register_if_exists( string $service_class ): void {
		if ( ! class_exists( $service_class ) ) {
			return;
		}
		$service = $this->container->get( $service_class );
		if ( is_object( $service ) && method_exists( $service, 'register' ) ) {
			$service->register();
		}
	}

	/**
	 * Idempotent migration runner. Compares the installed version option with
	 * the current plugin version and runs the Migrator when they differ — so
	 * updates apply without requiring a manual deactivate/reactivate.
	 */
	private function maybe_run_pending_migrations(): void {
		$installed = get_option( 'acrwd_installed_version', '' );
		if ( ACRWD_VERSION === $installed ) {
			return;
		}
		$this->container->get( Migrator::class )->migrate();
		update_option( 'acrwd_installed_version', ACRWD_VERSION, false );
	}

	private function is_woocommerce_active(): bool {
		return class_exists( \WooCommerce::class );
	}

	public function render_woocommerce_missing_notice(): void {
		echo '<div class="notice notice-error"><p>';
		echo esc_html__( 'Australcode Points and Rewards requires WooCommerce to be installed and active.', 'australcode-points-rewards' );
		echo '</p></div>';
	}
}
