<?php
/**
 * File containing the class Notices_Conditions_Checker.
 *
 * @package wp-job-manager
 */

namespace WP_Job_Manager\Admin;

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

/**
 * Notices_Conditions_Checker class.
 *
 * @since 1.40.0
 * @internal
 */
class Notices_Conditions_Checker {

	const ALL_WPJM_SCREENS_PLACEHOLDER = 'wpjm*';

	const ALL_WPJM_SCREEN_IDS = [
		'edit-job_listing',
		'job_listing_page_job-manager-settings',
	];

	/**
	 * Check notice conditions.
	 *
	 * @param array $conditions The notice conditions.
	 *
	 * @return bool
	 */
	public function check( $conditions ) {
		$has_screen_condition = false;
		$can_see_notice       = true;

		foreach ( $conditions as $condition ) {
			if ( ! isset( $condition['type'] ) ) {
				continue;
			}

			switch ( $condition['type'] ) {
				case 'min_php':
					if ( ! isset( $condition['version'] ) ) {
						break;
					}

					if ( ! $this->condition_check_min_php( $condition['version'] ) ) {
						$can_see_notice = false;
						break 2;
					}

					break;
				case 'min_wp':
					if ( ! isset( $condition['version'] ) ) {
						break;
					}

					if ( ! $this->condition_check_min_wp( $condition['version'] ) ) {
						$can_see_notice = false;
						break 2;
					}

					break;
				case 'user_cap':
					if ( ! isset( $condition['capabilities'] ) || ! is_array( $condition['capabilities'] ) ) {
						break;
					}

					if ( ! $this->condition_check_capabilities( $condition['capabilities'] ) ) {
						$can_see_notice = false;
						break 2;
					}

					break;
				case 'screens':
					if ( ! isset( $condition['screens'] ) || ! is_array( $condition['screens'] ) ) {
						break;
					}
					$has_screen_condition = true;
					if ( ! $this->condition_check_screen( $condition['screens'] ) ) {
						$can_see_notice = false;
						break 2;
					}

					break;
				case 'plugins':
					if ( ! isset( $condition['plugins'] ) || ! is_array( $condition['plugins'] ) ) {
						break;
					}

					if ( ! $this->condition_check_plugin( $condition['plugins'] ) ) {
						$can_see_notice = false;
						break 2;
					}
					break;
				case 'date_range':
					if ( ! isset( $condition['start_date'] ) && ! isset( $condition['end_date'] ) ) {
						break;
					}

					if ( ! $this->condition_check_date_range( $condition['start_date'] ?? null, $condition['end_date'] ?? null ) ) {
						$can_see_notice = false;
						break 2;
					}

					break;
			}
		}

		// If no screens condition was set, only show this message on WPJM screens.
		if ( $can_see_notice && ! $has_screen_condition && ! $this->condition_check_screen( [ self::ALL_WPJM_SCREENS_PLACEHOLDER ] ) ) {
			$can_see_notice = false;
		}

		return $can_see_notice;
	}

	/**
	 * Check a screen condition.
	 *
	 * @param array $allowed_screens Array of allowed screen IDs. `wpjm*` is a special screen ID for any WPJM screen.
	 *
	 * @return bool
	 */
	private function condition_check_screen( array $allowed_screens ): bool {
		$condition_pass = true;

		if ( in_array( self::ALL_WPJM_SCREENS_PLACEHOLDER, $allowed_screens, true ) ) {
			$allowed_screens = array_merge( $allowed_screens, self::ALL_WPJM_SCREEN_IDS );
		}

		$screen_id = $this->get_screen_id();

		if ( ! $screen_id || ! in_array( $screen_id, $allowed_screens, true ) ) {
			$condition_pass = false;
		}

		return $condition_pass;
	}

	/**
	 * Get the screen ID.
	 *
	 * @return string|null
	 */
	private function get_screen_id() {
		// Not available before the `admin_init` hook.
		if ( ! function_exists( 'get_current_screen' ) ) {
			return null;
		}

		$screen = get_current_screen();

		return $screen ? $screen->id : null;
	}

	/**
	 * Check a PHP version condition.
	 *
	 * @param string $min_version Minimum PHP version.
	 * @return bool
	 */
	private function condition_check_min_php( string $min_version ): bool {
		return version_compare( phpversion(), $min_version, '>=' );
	}

	/**
	 * Check a WP version condition.
	 *
	 * @param string $min_version Minimum WP version.
	 * @return bool
	 */
	private function condition_check_min_wp( string $min_version ): bool {
		return version_compare( get_bloginfo( 'version' ), $min_version, '>=' );
	}


	/**
	 * Check a capability condition.
	 *
	 * @param array $allowed_caps Array of capabilities that the user must have.
	 * @return bool
	 */
	private function condition_check_capabilities( array $allowed_caps ): bool {
		$condition_pass = true;

		foreach ( $allowed_caps as $cap ) {
			if ( ! current_user_can( $cap ) ) {
				$condition_pass = false;
				break;
			}
		}

		return $condition_pass;
	}


	/**
	 * Check a plugin condition.
	 *
	 * @param array $allowed_plugins Array of the plugins to check for.
	 *
	 * @return bool
	 */
	private function condition_check_plugin( array $allowed_plugins ): bool {
		$condition_pass = true;
		$active_plugins = $this->get_active_plugins();

		foreach ( $allowed_plugins as $plugin_basename => $plugin_condition ) {
			$plugin_active  = isset( $active_plugins[ $plugin_basename ] );
			$plugin_version = isset( $active_plugins[ $plugin_basename ]['Version'] ) ? $active_plugins[ $plugin_basename ]['Version'] : false;

			if ( false === $plugin_condition ) {
				// The plugin should not be active.
				if ( $plugin_active ) {
					$condition_pass = false;
					break;
				}
			} elseif ( true === $plugin_condition ) {
				// The plugin just needs to be active.
				if ( ! $plugin_active ) {
					$condition_pass = false;
					break;
				}
			} elseif ( isset( $plugin_condition['min'] ) || isset( $plugin_condition['max'] ) ) {
				// There is a plugin version condition, but we expect the plugin to be activated.
				if ( ! $plugin_active ) {
					$condition_pass = false;
					break;
				}

				if ( isset( $plugin_condition['min'] ) && version_compare( $plugin_version, $plugin_condition['min'], '<' ) ) {
					// If the activated plugin version is older than the minimum required, do not show the notice.
					$condition_pass = false;
					break;
				}

				if ( isset( $plugin_condition['max'] ) && version_compare( $plugin_version, $plugin_condition['max'], '>' ) ) {
					// If the activated plugin version is newer than the maximum required, do not show the notice.
					$condition_pass = false;
					break;
				}
			}
		}

		return $condition_pass;
	}

	/**
	 * Partial wrapper for `get_plugins()` function. Filters out non-active plugins.
	 *
	 * @return array Key is basename of active plugins and value is version.
	 */
	protected function get_active_plugins() {
		if ( ! function_exists( 'get_plugins' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		$plugins = get_plugins();
		foreach ( $plugins as $plugin_basename => $plugin_data ) {
			if ( ! is_plugin_active( $plugin_basename ) ) {
				unset( $plugins[ $plugin_basename ] );
			}
		}

		return $plugins;
	}

	/**
	 * Check a date range condition.
	 *
	 * @param ?string $start_date_str Start date.
	 * @param ?string $end_date_str   End date.
	 *
	 * @return bool
	 */
	private function condition_check_date_range( ?string $start_date_str, ?string $end_date_str ): bool {
		$now = new \DateTime();

		// Defaults to WP timezone, but can be overridden by passing string that includes timezone.
		$start_date = $start_date_str ? date_create( $start_date_str, wp_timezone() ) : null;
		$end_date   = $end_date_str ? date_create( $end_date_str, wp_timezone() ) : null;

		// If the passed date strings are invalid, don't show the notice.
		if ( false === $start_date || false === $end_date ) {
			return false;
		}

		if ( $start_date && $now < $start_date ) {
			return false;
		}

		if ( $end_date && $now > $end_date ) {
			return false;
		}

		return true;
	}
}
