<?php

namespace SugarCalendarBookings\Support\Tasks;

use ActionScheduler_DataController;
use ActionScheduler_DBStore;
use SugarCalendarBookings\Support\Bootable;

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

/**
 * Class TaskRegistry.
 *
 * @since 1.0.0
 */
class TaskRegistry implements Bootable {

	/**
	 * Actions group.
	 *
	 * @since 1.0.0
	 */
	const GROUP = 'sugar_calendar_bookings';

	/**
	 * Pending or in-progress actions.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	private static $active_actions = null;

	/**
	 * Available task classes.
	 *
	 * @since 1.0.0
	 *
	 * @var array<class-string<AbstractTask>>
	 */
	private $task_classes = [];

	/**
	 * Register hooks.
	 *
	 * @since 1.0.0
	 */
	public function hooks() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks

		// Hide the Action Scheduler admin menu item.
		add_action( 'admin_menu', [ $this, 'admin_hide_as_menu' ], PHP_INT_MAX );

		// Skip tasks registration if Action Scheduler is not usable yet.
		if ( ! self::is_usable() ) {
			return;
		}

		add_action( 'init', [ $this, 'init_tasks' ] );

		// Cancel tasks on plugin deactivation.
		register_deactivation_hook( SCBOOKINGS_PLUGIN_FILE, [ $this, 'cancel_all' ] );
	}

	/**
	 * Register tasks.
	 *
	 * @since 1.0.0
	 *
	 * @param array<class-string<AbstractTask>> $task_classes List of task classes.
	 */
	public function register_tasks( $task_classes ) {

		$this->task_classes = array_merge( $this->task_classes, $task_classes );
	}

	/**
	 * Initialize tasks.
	 *
	 * @since 1.0.0
	 */
	public function init_tasks() {

		foreach ( $this->task_classes as $task_class ) {
			if ( ! is_subclass_of( $task_class, AbstractTask::class ) ) {
				continue;
			}

			$task = new $task_class();

			if ( method_exists( $task, 'init' ) ) {
				$task->init();
			}
		}
	}

	/**
	 * Hide Action Scheduler admin area when not in debug mode.
	 *
	 * @since 1.0.0
	 */
	public function admin_hide_as_menu() {

		$plugin_exceptions = [
			'woocommerce/woocommerce.php',
			'action-scheduler/action-scheduler.php',
			'wp-crontrol/wp-crontrol.php',
		];

		/**
		 * Filters the list of plugins for which
		 * the Action Scheduler Tools ->Scheduled Actions menu item
		 * should remain visible.
		 *
		 * @since 1.0.0
		 *
		 * @param array $plugin_exceptions List of plugins exceptions.
		 */
		$plugin_exceptions = apply_filters(
			'sugar_calendar_bookings_support_tasks_task_registry_action_scheduler_tools_plugin_exceptions',
			$plugin_exceptions
		);

		$hide_as_menu = empty( array_filter( $plugin_exceptions, 'is_plugin_active' ) );

		/**
		 * Filter whether to hide Tools > Action Scheduler menu item.
		 *
		 * @since 1.0.0
		 *
		 * @param bool $hide_as_menu Whether to hide Action Scheduler menu item.
		 */
		$hide_as_menu = apply_filters(
			'sugar_calendar_bookings_support_tasks_task_registry_admin_hide_as_menu',
			$hide_as_menu
		);

		if ( $hide_as_menu ) {
			remove_submenu_page( 'tools.php', 'action-scheduler' );
		}
	}

	/**
	 * Cancel all the AS actions for a group.
	 *
	 * @since 1.0.0
	 *
	 * @param string $group Group to cancel all actions for.
	 */
	public function cancel_all( $group = '' ) {

		if ( empty( $group ) ) {
			$group = self::GROUP;
		} else {
			$group = sanitize_key( $group );
		}

		if ( class_exists( 'ActionScheduler_DBStore' ) ) {
			ActionScheduler_DBStore::instance()->cancel_actions_by_group( $group );
		}
	}

	/**
	 * Whether ActionScheduler thinks that it has migrated or not.
	 *
	 * @since 1.0.0
	 *
	 * @return bool
	 */
	public static function is_usable() {

		// No tasks if ActionScheduler wasn't loaded.
		if ( ! class_exists( 'ActionScheduler_DataController' ) ) {
			return false;
		}

		return ActionScheduler_DataController::is_migration_complete();
	}

	/**
	 * Whether task has been scheduled and is pending.
	 *
	 * @since 1.0.0
	 *
	 * @param string $hook Hook to check for.
	 *
	 * @return bool|null
	 */
	public static function is_scheduled( $hook ) {

		// If ActionScheduler wasn't loaded, then no tasks are scheduled.
		if ( ! function_exists( 'as_next_scheduled_action' ) ) {
			return null;
		}

		if ( is_null( self::$active_actions ) ) {
			self::$active_actions = self::get_active_actions();
		}

		if ( in_array( $hook, self::$active_actions, true ) ) {
			return true;
		}

		// Action is not in the array, so it is not scheduled or belongs to another group.
		if ( function_exists( 'as_has_scheduled_action' ) ) {
			// This function more performant than `as_next_scheduled_action`, but it is available only since AS 3.3.0.
			return as_has_scheduled_action( $hook );
		} else {
			return as_next_scheduled_action( $hook ) !== false;
		}
	}

	/**
	 * Get all Easy WP SMTP pending or in-progress actions.
	 *
	 * @since 1.0.0
	 */
	private static function get_active_actions() {

		global $wpdb;

		$group = self::GROUP;
		$sql   = "SELECT a.hook FROM {$wpdb->prefix}actionscheduler_actions a
					JOIN {$wpdb->prefix}actionscheduler_groups g ON g.group_id = a.group_id
					WHERE g.slug = '$group' AND a.status IN ('in-progress', 'pending')";

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery
		$results = $wpdb->get_results( $sql, 'ARRAY_N' );

		return $results ? array_merge( ...$results ) : [];
	}
}
