<?php

// Cannot `declare( strict_types=1 );` to avoid fatal if prior to PHP 7.0.0, since this is called from main plugin file and handles the PHP version checking.

namespace DocketConnector;

use DocketConnector\Core;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Abort if this file is called directly.
}

if ( ! class_exists( Bootstrap::class ) ) {
	/**
	 * The file that gets things going.
	 */
	class Bootstrap {
		/**
		 * The list of required (and/or recommended) plugins, as passed to TGM Plugin Activation.
		 *
		 * These links may contain affiliate links to paid products, which may financially benefit the author of this
		 * framework or this plugin but do not add to the cost of the paid products. You are not required to use
		 * these links to obtain the listed products.
		 *
		 * @link http://tgmpluginactivation.com/
		 *
		 * @see  \DocketConnector\GravityForms\Setup::$gravity_forms_affiliate_link
		 * @see  \DocketConnector\GravityForms\Setup::$gravity_forms_version_min
		 */
		private $required_plugins = [
			[
				'name'         => 'Gravity Forms',
				'slug'         => 'gravityforms',
				'source'       => 'external',
				'required'     => true,
				'external_url' => 'https://rocketgenius.pxf.io/aVVqM',
				'version'      => '2.4.18', // @todo Keep up with the most recent version of the @since tags from the GFPaymentAddOn class.
			],
		];

		/**
		 * Begins execution of the plugin.
		 *
		 * Since everything within the plugin is registered via hooks, then kicking off the plugin from this point in the file
		 * does not affect the page life cycle.
		 *
		 * Also returns copy of the app object so 3rd party developers can interact with the plugin's hooks contained within.
		 *
		 * @return Bootstrap|null
		 */
		public function init(): ?self {
			$plugin = new self();

			if ( $plugin->is_ready() ) {
				$core = new Core\Init();
				$core->run();

				return $plugin;
			} else {
				return null;
			}
		}

		/**
		 * Register the required plugins.
		 *
		 * @link https://github.com/TGMPA/TGM-Plugin-Activation/blob/master/example.php How/What to put in here.
		 *
		 * The variables passed to the `tgmpa()` function should be:
		 * - an array of plugin arrays;
		 * - optionally a configuration array.
		 * If you are not changing anything in the configuration array, you can remove the array and remove the
		 * variable from the function call: `tgmpa( $plugins );`.
		 * In that case, the TGMPA default settings will be used.
		 *
		 * This function is hooked into `tgmpa_register`, which is fired on the WP `init` action on priority 10.
		 */
		public function tgmpa_register_required_plugins(): void {
			/*
			 * Array of configuration settings. Amend each line as needed.
			 */
			$config = [
				'id'           => PluginData::plugin_text_domain(),      // Unique ID for hashing notices for multiple instances of TGMPA.
				'parent_slug'  => 'plugins.php',           // Parent menu slug.
				'capability'   => 'activate_plugins',      // Capability needed to view plugin install page, should be a capability associated with the parent menu used.
				'has_notices'  => true,                    // Show admin notices or not.
				'dismissable'  => false,                   // If false, a user cannot dismiss the nag message.
				'dismiss_msg'  => '',                      // If 'dismissable' is false, this message will be output at top of nag.
				'is_automatic' => false,                   // Automatically activate plugins after installation or not.
				'message'      => '',                      // Message to output right before the plugins table.
				'strings'      => [
					'notice_can_install_required'    => _n_noop(
					// translators: 1: plugin name(s).
						PluginData::get_plugin_display_name() . ' requires the following plugin: %1$s.',
						PluginData::get_plugin_display_name() . ' requires the following plugins: %1$s.',
						'docket-connector'
					),
					'notice_can_install_recommended' => _n_noop(
					// translators: 1: plugin name(s).
						PluginData::get_plugin_display_name() . ' recommends the following plugin: %1$s.',
						PluginData::get_plugin_display_name() . ' recommends the following plugins: %1$s.',
						'docket-connector'
					),
					'notice_ask_to_update'           => _n_noop(
					// translators: 1: plugin name(s).
						'The following plugin needs to be updated to its latest version to ensure maximum compatibility with ' . PluginData::get_plugin_display_name() . ': %1$s.',
						'The following plugins need to be updated to their latest version to ensure maximum compatibility with ' . PluginData::get_plugin_display_name() . ': %1$s.',
						'docket-connector'
					),
					// translators: 1: plugin name
					'plugin_needs_higher_version'    => __(
						'Plugin not activated. A higher version of %s is needed for this plugin. Please update the plugin.',
						'docket-connector'
					),
					'nag_type'                       => 'error', // Determines admin notice type - can only be one of the typical WP notice classes, such as 'updated', 'update-nag', 'notice-warning', 'notice-info' or 'error'. Some of which may not work as expected in older WP versions.
				],
			];

			tgmpa( $this->required_plugins, $config );
		}

		/**
		 * Output a message about unsatisfactory version of PHP.
		 */
		public function notice_old_php_version(): void {
			$help_link = sprintf( '<a href="%1$s" target="_blank">%1$s</a>', 'https://wordpress.org/about/requirements/' );

			$message = sprintf(
			// translators: 1: plugin display name, 2: required minimum PHP version, 3: current PHP version, help link
				__( '%1$s requires at least PHP version %2$s in order to work. You have version %3$s. Please see %4$s for more information.', 'docket-connector' ),
				'<strong>' . PluginData::get_plugin_display_name() . '</strong>',
				'<strong>' . PluginData::required_min_php_version() . '</strong>',
				'<strong>' . PHP_VERSION . '</strong>',
				$help_link
			);

			$this->do_admin_notice( $message );
		}

		/**
		 * Output a wp-admin notice.
		 *
		 * @param string $message
		 * @param string $type
		 */
		public function do_admin_notice( string $message, string $type = 'error' ): void {
			$class = sprintf( '%s %s', $type, sanitize_html_class( PluginData::plugin_text_domain() ) );

			printf( '<div class="%s"><p>%s</p></div>', $class, $message );
		}

		/**
		 * Check if we have everything that is required.
		 *
		 * @return bool
		 */
		public function is_ready(): bool {
			$success = true;

			if ( version_compare( PHP_VERSION, PluginData::required_min_php_version(), '<' ) ) {
				add_action( 'admin_notices', [ $this, 'notice_old_php_version' ] );
				$success = false;
			}

			if ( $success ) {
				$success = $this->required_plugins_are_active();
			}

			add_action( 'tgmpa_register', [ $this, 'tgmpa_register_required_plugins' ] );

			return $success;
		}

		public function is_plugin_active_and_sufficient_version(
			string $plugin_slug,
			string $min_version = '',
			string $max_version = ''
		): bool {
			// The file in which is_plugin_active() is located.
			require_once( ABSPATH . 'wp-admin/includes/plugin.php' );

			// Check if active.
			$basename = $this->get_plugin_basename_from_slug( $plugin_slug );

			if ( ! is_plugin_active( $basename ) ) {
				return false;
			}

			// Is active so check sufficient version.

			// No version constraints so all is good.
			if (
				empty( $min_version )
				&& empty( $max_version )
			) {
				return true;
			}

			$plugin_data = get_plugin_data( PluginData::all_plugins_dir() . $basename );

			if ( empty( $plugin_data['Version'] ) ) {
				return false;
			}

			if (
				! empty( $min_version )
				&& version_compare( $plugin_data['Version'], $min_version, '<' )
			) {
				return false;
			}

			if (
				! empty( $max_version )
				&& version_compare( $plugin_data['Version'], $max_version, '>' )
			) {
				return false;
			}

			return true;
		}

		/**
		 * Checks if all the required plugins are active.
		 *
		 * @link https://github.com/TGMPA/TGM-Plugin-Activation/issues/760 This method won't be required if this gets added.
		 *
		 * @return bool Whether the required plugins are active.
		 */
		private function required_plugins_are_active(): bool {
			foreach ( $this->required_plugins as $required_plugin ) {
				// Only check plugins that are *required*, not the ones that are just *recommended*.
				if ( empty( $required_plugin['required'] ) ) {
					continue;
				}

				if ( ! $this->is_plugin_active_and_sufficient_version( $required_plugin['slug'], $required_plugin['version'] ) ) {
					return false;
				}
			}

			return true;
		}

		/**
		 * Get the file path of the plugin file from the plugin slug, if the plugin is installed.
		 *
		 * @see TGM_Plugin_Activation::_get_plugin_basename_from_slug()
		 *
		 * @param string $slug Plugin slug (typically folder name) as provided by the developer.
		 *
		 * @return string Either file path for plugin directory, or just the plugin file slug.
		 */
		private function get_plugin_basename_from_slug( string $slug ): string {
			$keys = array_keys( get_plugins() );

			foreach ( $keys as $key ) {
				if ( preg_match( '|^' . $slug . '/|', $key ) ) {
					return $key;
				}
			}

			return $slug;
		}

	}
}
