<?php

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}
/**
 * Main plugin bootstrap and lifecycle hooks.
 *
 * @package RewardsWP
 */

use Awesomemotive\Rewardswp\Controllers\TaskSchedulerController;
use Awesomemotive\Rewardswp\DependencyInjection\DIContainerBuilder;
use Awesomemotive\Rewardswp\Controllers\Admin\MainAdminController;
use Awesomemotive\Rewardswp\Controllers\InstallController;
use Awesomemotive\Rewardswp\Controllers\MainController;
use Awesomemotive\Rewardswp\Controllers\UninstallController;
use Awesomemotive\Rewardswp\Pro\Controllers\ProUninstallController;
use function Awesomemotive\Rewardswp\Helpers\is_rewardswp_pro_plan;

/**
 * Main RewardsWP class.
 */
class AwesomeMotiveRewardsWP {
	/**
	 * Single instance of the class.
	 *
	 * @since 1.0.0
	 *
	 * @var AwesomeMotiveRewardsWP|null
	 */
	private static ?AwesomeMotiveRewardsWP $instance = null;

	/**
	 * DI container object.
	 *
	 * @since 1.0.0
	 * @var DI\Container
	 */
	private DI\Container $container;

	/**
	 * Constructor.
	 *
	 * @throws Exception If autoloader fails.
	 */
	private function __construct() {
		if ( ! file_exists( AM_REWARDSWP_PLUGIN_DIR . '/vendor' ) ) {
			throw new Exception(
				esc_html(
					'Unable to find vendor/ directory, did you run composer install?'
				)
			);
		}

		// Autoload classes from the src/ folder.
		$this->autoload();

		// Set the dependency injection (DI) container.
		$this->container = ( new DIContainerBuilder() )->get_container();

		$this->hooks();
	}

	/**
	 * Get the single instance of the class.
	 *
	 * @return AwesomeMotiveRewardsWP
	 */
	public static function get_instance(): ?AwesomeMotiveRewardsWP {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Retrieve the DI container.
	 *
	 * @since 1.0.0
	 * @return DI\Container
	 */
	public function get_container(): DI\Container {
		return $this->container;
	}

	/**
	 * PSR 4 Autoloader.
	 *
	 * @return void
	 * @throws Exception If the autoloader file can't be found.
	 * @since 1.0.0
	 */
	private function autoload(): void {
		$autoloader_dir = AM_REWARDSWP_PLUGIN_DIR . 'vendor/autoload.php';

		if ( ! file_exists( $autoloader_dir ) ) {
			throw new Exception( "Can't find autoloader. Did you run `composer install`?" );
		}

		// Workaround for Jetpack Autoloader conflict.
		// WooCommerce and Google for WooCommerce use the Jetpack Autoloader which scans
		// all plugins' vendor directories (including inactive plugins) and marks Composer
		// file-hashes as "loaded" in $GLOBALS without actually including the files.
		// Clear our hashes so Composer's autoloader loads them correctly.
		$autoload_files = AM_REWARDSWP_PLUGIN_DIR . 'vendor/composer/autoload_files.php';

		if ( isset( $GLOBALS['__composer_autoload_files'] ) && file_exists( $autoload_files ) ) {
			$hashes = array_keys( require $autoload_files );

			foreach ( $hashes as $hash ) {
				unset( $GLOBALS['__composer_autoload_files'][ $hash ] );
			}
		}

		require $autoloader_dir;

		// Ensure Action Scheduler is always loaded.
		$action_scheduler_dir = AM_REWARDSWP_PLUGIN_DIR . 'vendor/woocommerce/action-scheduler/action-scheduler.php';

		if ( file_exists( $action_scheduler_dir ) ) {
			require_once $action_scheduler_dir;
		}
	}

	/**
	 * Activate plugin hook.
	 *
	 * @since 1.0.0
	 *
	 * @throws Exception If activation routines fail.
	 */
	public function activate_plugin() {
		$this->get_container()->get( InstallController::class )->init();
	}

	/**
	 * Deactivate plugin hook.
	 *
	 * @since 1.0.0
	 *
	 * @throws Exception If deactivation routines fail.
	 */
	public function deactivate_plugin() {
		$this->get_container()->get( UninstallController::class )->init();

		// If Pro version, also cleanup Pro-specific resources.
		if ( class_exists( ProUninstallController::class ) ) {
			$this->get_container()->get( ProUninstallController::class )->init();
		}
	}

	/**
	 * Initialize hooks.
	 *
	 * @since 1.0.0
	 * @sicne 1.0.2 Changed controllers to load on init to avoid load_textdomain warnings introduced in WordPress 6.7.
	 *
	 * @return void
	 * */
	private function hooks() {
		// Register the main activation hook.
		register_activation_hook( AM_REWARDSWP_PLUGIN_FILE, [ $this, 'activate_plugin' ] );

		// Register uninstall hook.
		register_deactivation_hook( AM_REWARDSWP_PLUGIN_FILE, [ $this, 'deactivate_plugin' ] );

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

		/*
		 * The storefront widget's content is fetched over admin-ajax.php, where
		 * is_admin() is true and determine_locale() would otherwise return the
		 * logged-in visitor's profile locale instead of the site language. Force
		 * the site language for requests marked by Helpers\frontend_ajax_url()
		 * so the widget stays consistent with the rest of the storefront.
		 */
		add_filter( 'determine_locale', [ $this, 'use_site_locale_for_frontend_ajax' ] );

		// Controllers can't be loaded before init, otherwise it may trigger load_textdomain warnings.
		add_action( 'init', [ $this, 'load_controllers' ] );

		// Action Scheduler tasks should run on init hook.
		add_action( 'init', [ $this, 'process_tasks' ] );
	}

	/**
	 * Load the plugin text domain for translation.
	 *
	 * Loads translation files from the languages/ directory.
	 *
	 * @since 1.1.0
	 *
	 * @return void
	 */
	public function load_textdomain(): void {
		if ( is_textdomain_loaded( 'rewardswp' ) ) {
			return;
		}

		$locale = determine_locale();

		/**
		 * Default location used by WordPress.org language packs.
		 *
		 * @since 1.0.6
		 *
		 * @param string $languages_path Absolute path to the languages directory.
		 */
		$languages_path = apply_filters(
			'rewardswp_languages_path',
			trailingslashit( WP_LANG_DIR ) . 'plugins/'
		);

		$mofile = $languages_path . 'rewardswp-' . $locale . '.mo';

		if ( file_exists( $mofile ) && load_textdomain( 'rewardswp', $mofile ) ) {
			return;
		}

		$plugin_mofile = AM_REWARDSWP_PLUGIN_DIR . 'languages/rewardswp-' . $locale . '.mo';

		if ( file_exists( $plugin_mofile ) ) {
			load_textdomain( 'rewardswp', $plugin_mofile );
		}
	}

	/**
	 * Force the site locale for the storefront widget's AJAX requests.
	 *
	 * The widget renders its initial markup on a normal front-end page request
	 * (site locale), but loads tab content such as the History list over
	 * admin-ajax.php. Because admin-ajax.php makes is_admin() true, WordPress's
	 * determine_locale() returns the logged-in visitor's profile locale for those
	 * requests, so AJAX-loaded strings appear in the visitor's language while the
	 * rest of the widget is in the site language - a partially translated UI.
	 *
	 * Storefront requests are recognized by the `rewardswp_locale=site` query
	 * marker that Helpers\frontend_ajax_url() appends to the AJAX URL handed to
	 * all front-end scripts. Being part of the URL (not the POST body), the
	 * marker is readable from the very first determine_locale() call, before
	 * init. Admin-screen AJAX uses plain, unmarked admin-ajax URLs and keeps
	 * honoring each admin's own profile language.
	 *
	 * The marker is client-controlled, which is safe here: the only thing it
	 * selects is the language of the caller's own response.
	 *
	 * @since 1.4.4
	 *
	 * @param string $locale Locale determined by WordPress.
	 *
	 * @return string Site locale for marked storefront AJAX requests, otherwise unchanged.
	 */
	public function use_site_locale_for_frontend_ajax( string $locale ): string {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Locale routing only; no state change.
		$marker = isset( $_GET['rewardswp_locale'] ) ? sanitize_key( wp_unslash( $_GET['rewardswp_locale'] ) ) : '';

		if ( wp_doing_ajax() && 'site' === $marker ) {
			return get_locale();
		}

		return $locale;
	}

	/**
	 * Initialize the plugin.
	 *
	 * The main controllers call all the other controllers.
	 *
	 * @since 1.0.0
	 *
	 * @throws Exception If a controller fails to initialize.
	 */
	public function load_controllers() {

		// Admin-only controllers.
		if ( is_admin() ) {
			$this->container->get( MainAdminController::class )->init();
		}

		// Run global controllers.
		$this->container->get( MainController::class )->init();
	}

	/**
	 * Process action scheduler tasks.
	 *
	 * @since 1.0.0
	 *
	 * @throws Exception If the task scheduler fails to initialize.
	 */
	public function process_tasks(): void {
		$this->container->get( TaskSchedulerController::class )->init();
	}

	/**
	 * Retrieves whether it's running the Pro edition.
	 *
	 * @since 1.0.0
	 */
	public function is_pro(): string {
		return is_rewardswp_pro_plan();
	}
}
