<?php
namespace WPDRMS\ASP\Core;

use WPDRMS\ASP\Api\Rest0\Rest;
use WPDRMS\ASP\Asset;
use WPDRMS\ASP\Cache\ResultsCacheService;
use WPDRMS\ASP\Hooks\ActionsManager;
use WPDRMS\ASP\Hooks\AjaxManager;
use WPDRMS\ASP\Hooks\FiltersManager;
use WPDRMS\ASP\Modal\Factories\ModalFactory;
use WPDRMS\Utils\FileManager;
use WPDRMS\PluginCore\Traits\SingletonTrait;
use WPDRMS\ASP\Modal\Services\TimedModalService;
use WPDRMS\ASP\Statistics\StatisticsService;
use WPDRMS\ASP\Updates\Remote as UpdatesRemote;

if ( !defined('ABSPATH') ) {
	die('-1');
}


class Manager {
	use SingletonTrait;

	/**
	 * Context of the current WP environment
	 *
	 * Is used to include the correct and only necessary files for each context to save performance
	 *
	 * Possible values:
	 *  ajax - an ajax call triggered by the search
	 *  frontend - simple front-end call, or an ajax request not triggered by ASP
	 *  backend - on any of the plugin back-end pages
	 *  global_backend - on any other back-end page
	 *  special - special cases
	 *
	 * @since 1.0
	 * @var string
	 */
	private $context = 'frontend';

	/**
	 * Initialize and run the plugin-in
	 */
	private function __construct() {
		do_action('wd_asp_before_load');

		$this->preLoad();
		$this->loadInstances();
		$this->initUploadGlobals();
		$this->initCacheGlobals();
		/**
		 * Available after this point:
		 *      (WD_ASP_Init) wd_asp()->instances, (global) $wd_asp->instances
		 */

		FileManager::instance()->setAllowedDirectories(
			array(
				wd_asp()->upload_path,
				wd_asp()->bfi_path,
				wd_asp()->cache_path,
			)
		);

		register_activation_hook(ASP_FILE, array( $this, 'activationHook' ));
		/**
		 * Available after this point:
		 *      (array) wd_asp()->options, (global) $wd_asp->options
		 *      (WD_ASP_Init) wd_asp()->init, (global) $wd_asp->init
		 *      (WD_ASP_DBMan) wd_asp()->db, (global) $wd_asp->db
		 */
		add_action( 'init', array( $this, 'init' ), 0 );
	}

	public function init() {
		// Check if the plugin needs to be stopped on certain conditions
		// ..this needs to be here, otherwise filter not accesible from functions.php
		if ( $this->stopLoading() ) {
			return;
		}
		do_action('wd_asp_loading_start');

		// After 6.7 this must be executed in the "init" hook
		load_plugin_textdomain( 'ajax-search-pro', false, ASP_DIR . '/languages' );

		$this->getContext();
		/**
		 * Available after this point:
		 *      $this->context
		 */
		$this->loadIncludes();
		$this->loadShortcodes();
		$this->loadMenu();
		$this->loadHooks();

		\WPDRMS\ASP\NavMenu\Controller::getInstance();

		wd_asp()->init->safety_check();

		add_action('admin_notices', array( $this, 'loadNotices' ));

		do_action('wd_asp_loaded');
	}


	private function stopLoading() {
		$ret = false;

		if ( isset($_GET, $_GET['action']) ) {
			if ( $_GET['action'] == 'ere_property_search_ajax' ) {
				$ret = true;
			}
		}

		// Allow filtering this condition
		return apply_filters('asp_stop_loading', $ret);
	}


	/**
	 *  Preloading: for functions and other stuff needed
	 */
	private function preLoad() {
		require_once ASP_PATH . '/backend/settings/default_options.php';
		require_once ASP_FUNCTIONS_PATH . 'functions.php';

		// We need to initialize the init here to get the init->table() function
		wd_asp()->init = Init::getInstance();

		// This needs to be registered here, the 'init' is too late to register
		add_filter( 'cron_schedules', array( $this, 'cronExtraIntervals' ) );
	}

	/**
	 * Adds additional intervals to cron jobs
	 *
	 * @param $schedules
	 * @return mixed
	 */
	function cronExtraIntervals( $schedules ) {
		$schedules['asp_cr_two_minutes']     = array(
			'interval' => 120,
			'display'  => __( 'Every 2 Minutes', 'ajax-search-pro' ),
		);
		$schedules['asp_cr_three_minutes']   = array(
			'interval' => 180,
			'display'  => __( 'Every 3 Minutes', 'ajax-search-pro' ),
		);
		$schedules['asp_cr_five_minutes']    = array(
			'interval' => 300,
			'display'  => __( 'Every 5 Minutes', 'ajax-search-pro' ),
		);
		$schedules['asp_cr_fifteen_minutes'] = array(
			'interval' => 900,
			'display'  => __( 'Every 15 Minutes', 'ajax-search-pro' ),
		);
		$schedules['asp_cr_thirty_minutes']  = array(
			'interval' => 1800,
			'display'  => __( 'Every 30 Minutes', 'ajax-search-pro' ),
		);
		return $schedules;
	}


	/**
	 * Gets the upload path with back-slash
	 */
	public function initUploadGlobals() {
		if ( is_multisite() ) {
			/**
			 * On multisite, the wp_upload_dir() returns the current blog URLs,
			 * even if the switch_to_blog(1) is initiated (WordPress core bug?)
			 *
			 * Bypass solution: Save the upload dir option as a site option, when the main blog is visited,
			 *                  then get this value on each site, with a fallback.
			 * WARNING: DO NOT USE get_site_option() and update_site_option() - as those slow down the multisite loading by a LOT
			 *          use get_blog_option() and update_blog_option() instead
			 */
			$upload_dir = wp_upload_dir();
			if ( get_current_blog_id() == get_network()->site_id ) {
				update_blog_option(get_network()->site_id, '_asp_upload_dir', $upload_dir);
			} else {
				$upload_dir = get_blog_option(get_network()->site_id, '_asp_upload_dir', $upload_dir);
			}
		} else {
			$upload_dir = wp_upload_dir();
		}

		$upload_dir = apply_filters('asp_glob_upload_dir', $upload_dir);

		wd_asp()->upload_path = $upload_dir['basedir'] . '/' . wd_asp()->upload_dir . '/';
		wd_asp()->upload_url  = $upload_dir['baseurl'] . '/' . wd_asp()->upload_dir . '/';
		// Let us make sure, that the URL is using the correct protocol
		if ( defined('ASP_URL') ) {
			// Site is https, but URL is http
			if ( strpos(ASP_URL, 'https://') === 0 && strpos(wd_asp()->upload_url, 'https://') === false ) {
				wd_asp()->upload_url = str_replace('http://', 'https://', wd_asp()->upload_url);
				// Site is http, but URL is https
			} elseif ( strpos(ASP_URL, 'http://') === 0 && strpos(wd_asp()->upload_url, 'http://') === false ) {
				wd_asp()->upload_url = str_replace('https://', 'http://', wd_asp()->upload_url);
			}
		}

		if ( defined( 'BFITHUMB_UPLOAD_DIR' ) ) {
			wd_asp()->bfi_path = $upload_dir['basedir'] . '/' . BFITHUMB_UPLOAD_DIR . '/';
		} else {
			wd_asp()->bfi_path = $upload_dir['basedir'] . '/' . wd_asp()->bfi_dir . '/';
		}

		// Allow globals modification for developers
		wd_asp()->upload_path = apply_filters('asp_glob_upload_path', wd_asp()->upload_path);
		wd_asp()->upload_url  = apply_filters('asp_glob_upload_url', wd_asp()->upload_url);
		wd_asp()->bfi_path    = apply_filters('asp_glob_bfi_path', wd_asp()->bfi_path);
	}

	public function initCacheGlobals() {
		$base_url = WP_CONTENT_URL;
		if ( defined('ASP_URL') ) {
			if ( strpos(ASP_URL, 'https://') === 0 && strpos($base_url, 'https://') === false ) {
				$base_url = str_replace('http://', 'https://', $base_url);
			}
		}
		wd_asp()->global_cache_path = apply_filters('asp_glob_global_cache_path', WP_CONTENT_DIR . '/cache/');
		wd_asp()->cache_path        = apply_filters('asp_glob_cache_path', WP_CONTENT_DIR . '/cache/asp/');
		wd_asp()->cache_url         = apply_filters('asp_glob_cache_url', $base_url . '/cache/asp/');
	}

	/**
	 * Returns true for backend pages that are pure React UI and do not need
	 * the legacy option types (jQuery UI, wpdreams-* scripts/styles).
	 */
	private function isReactPage(): bool {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$page = isset($_GET['page']) ? sanitize_key($_GET['page']) : '';
		return in_array($page, array( 'asp_cache', 'asp_statistics', 'asp_analytics', 'asp_index_table', 'asp_synonyms', 'asp_maintenance', 'asp_updates_help', 'asp_compatibility_settings', 'asp_priorities', 'asp_ai_features' ), true);
	}

	/**
	 * Gets the call context for further use
	 */
	public function getContext() {

		$backend_pages = Menu::getMenuPages();

		if ( !empty($_POST['action']) ) {
			if ( in_array($_POST['action'], AjaxManager::getAll()) ) {
				$this->context = 'ajax';
			}
			if ( isset($_POST['wd_required']) ) {
				$this->context = 'special';
			}
			// If it is not part of the plugin ajax actions, the context stays "frontend"
		} elseif ( !empty($_GET['page']) && in_array($_GET['page'], $backend_pages) ) {
			$this->context = 'backend';
		} elseif ( is_admin() ) {
			$this->context = 'global_backend';
		} else {
			$this->context = 'frontend';
		}

		return $this->context;
	}

	/**
	 * Loads the instance data into the global scope
	 */
	private function loadInstances() {

		wd_asp()->instances = Instances::getInstance();
	}

	/**
	 * Loads the required files based on the context
	 */
	private function loadIncludes() {

		// This must be here!! If it's in a conditional statement, it will fail..
		require_once ASP_PATH . '/backend/vc/vc.extend.php';

		switch ( $this->context ) {
			case 'special':
				require_once ASP_PATH . '/backend/settings/types.inc.php';
				break;
			case 'ajax':
				break;
			case 'frontend':
				break;
			case 'backend':
				if ( !$this->isReactPage() ) {
					require_once ASP_PATH . '/backend/settings/types.inc.php';
				}
				break;
			case 'global_backend':
				break;
			default:
				break;
		}

		// Special case
		if ( wpdreams_on_backend_post_editor() ) {
			require_once ASP_PATH . '/backend/tinymce/buttons.php';
			require_once ASP_PATH . '/backend/metaboxes/default.php';
		}

		wd_asp()->css_manager = Asset\Css\Manager::getInstance();
		wd_asp()->updates     = UpdatesRemote::getInstance();

		// Lifting some weight off from ajax requests
		if ( $this->context != 'ajax' ) {
			if ( wd_asp()->o['asp_compatibility']['rest_api_enabled'] ) {
				wd_asp()->rest_api = Rest::getInstance();
			}
		}
	}

	/**
	 * Use the Shorcodes loader to assign the shortcodes to handler classes
	 */
	private function loadShortcodes() {
		Shortcodes::registerAll();
	}

	/**
	 * This is hooked to the admin_notices action
	 */
	public function loadNotices() {
		// -------------------- Handle requests here ----------------------
		// Update related notes
		if ( isset($_GET['asp_notice_clear_ru']) ) {
			update_option('asp_recently_updated', 0);
		}
		if ( isset($_GET['asp_notice_clear_ri']) ) {
			update_option('asp_recreate_index', 0);
		}

		// -------------------- Handle notices here ----------------------
		// Index table re-creation note
		if ( $this->context == 'backend' && get_option('asp_recreate_index', 0) == 1 ) {
			?>
			<div class="notice notice-error asp-notice-nohide asp-notice-ri">
				<p>
					<?php echo __('<b>Ajax Search Pro notice: </b> The Index Table options have been modified, please re-create the index table!', 'ajax-search-pro'); ?>
					<a class="button button-primary" href="<?php echo get_admin_url() . 'admin.php?page=asp_index_table'; ?>"><?php echo __('Let\'s do it!', 'ajax-search-pro'); ?></a>
					<a class="button button-secondary" href="<?php echo esc_url(add_query_arg(array( 'asp_notice_clear_ri' => '1' ))); ?>"><?php echo __('Hide this message', 'ajax-search-pro'); ?></a>
				</p>
			</div>
			<?php
		}
	}

	/**
	 * Generates the menu
	 */
	private function loadMenu() {

		add_action('admin_menu', array( '\\WPDRMS\\ASP\\Core\\Menu', 'register' ));
	}

	/**
	 * Fires up the plugin hooks
	 */
	private function loadHooks(): void {
		$factory = Factory::instance();
		// Register handlers only if the context is ajax indeed
		if ( $this->context === 'ajax' ) {
			AjaxManager::registerAll();
		}

		if ( $this->context !== 'ajax' ) {
			foreach ( $factory->get('Asset') as $asset ) {
				$asset->register();
			}

			// Editor blocks
			foreach ( $factory->get( 'Block' ) as $block ) {
				$block->register();
			}

			if ( $this->context === 'backend' ) {
				ActionsManager::register('admin_init', 'Compatibility');
				TimedModalService::instance(new ModalFactory())->init();
			}

			ActionsManager::registerAll();

			if ( wd_asp()->o['asp_compatibility']['rest_api_enabled'] ) {
				wd_asp()->rest_api->init();
			}

			add_action(
				'rest_api_init',
				function () {
					foreach ( Factory::instance()->get('Rest') as $rest ) {
						$rest->registerRoutes( ASP_DIR);
					}
				}
			);
		}

		foreach ( $factory->get('Integration') as $integration ) {
			$integration->register();
		}

		foreach ( $factory->get('Hook') as $hook ) {
			$hook->register();
		}

		StatisticsService::instance()->loadHooks();
		ResultsCacheService::instance()->loadHooks();
		FiltersManager::registerAll();
	}

	/**
	 * Run at the plugin activation
	 */
	public function activationHook(): void {
		// Run the activation tasks
		wd_asp()->init->activate();
	}
}