<?php
/**
 * WordPress / PHP compatibility checks and admin messaging.
 *
 * @package JOCBM
 */

namespace JOCBM;

defined( 'ABSPATH' ) || exit;

/**
 * Class Compatibility
 */
final class Compatibility {

	/**
	 * Minimum WordPress version (Requires at least).
	 */
	public const MIN_WP = '6.4';

	/**
	 * Minimum PHP version (Requires PHP).
	 */
	public const MIN_PHP = '8.0';

	/**
	 * Highest WordPress version fully tested with this release.
	 * Keep in sync with readme.txt "Tested up to".
	 * When a newer major/minor WP ships and is not yet verified, leave this
	 * unchanged so wordpress.org shows the standard “untested” label.
	 */
	public const TESTED_UP_TO = '7.0';

	/**
	 * Whether WP + PHP meet hard requirements.
	 */
	public static function is_compatible(): bool {
		return self::wp_ok() && self::php_ok();
	}

	/**
	 * Current WordPress meets the minimum.
	 */
	public static function wp_ok(): bool {
		global $wp_version;

		$current = is_string( $wp_version ) ? $wp_version : get_bloginfo( 'version' );

		return version_compare( self::normalize_version( $current ), self::MIN_WP, '>=' );
	}

	/**
	 * Current PHP meets the minimum.
	 */
	public static function php_ok(): bool {
		return version_compare( PHP_VERSION, self::MIN_PHP, '>=' );
	}

	/**
	 * Site is running a newer WP major.minor than last tested.
	 * Soft warning — plugin still boots.
	 */
	public static function is_untested_wp(): bool {
		global $wp_version;

		$current = is_string( $wp_version ) ? $wp_version : get_bloginfo( 'version' );

		return version_compare(
			self::normalize_version( $current ),
			self::normalize_version( self::TESTED_UP_TO ),
			'>'
		);
	}

	/**
	 * Register notices when the environment is incompatible (plugin does not boot).
	 */
	public static function register_incompatible(): void {
		add_action( 'admin_notices', array( __CLASS__, 'render_incompatible_notice' ) );
		add_action( 'network_admin_notices', array( __CLASS__, 'render_incompatible_notice' ) );
		add_action( 'after_plugin_row_' . JOCBM_BASENAME, array( __CLASS__, 'render_plugin_row_incompatible' ), 10, 0 );
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_notice_styles' ) );
		add_filter( 'plugin_action_links_' . JOCBM_BASENAME, array( __CLASS__, 'filter_action_links_incompatible' ) );
	}

	/**
	 * Register soft “not yet tested” messaging when WP is newer than Tested up to.
	 */
	public static function register_untested(): void {
		if ( ! self::is_untested_wp() ) {
			return;
		}

		add_action( 'admin_notices', array( __CLASS__, 'render_untested_notice' ) );
		add_action( 'after_plugin_row_' . JOCBM_BASENAME, array( __CLASS__, 'render_plugin_row_untested' ), 10, 0 );
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_notice_styles' ) );
	}

	/**
	 * Activation guard — prevent install setup on incompatible hosts.
	 *
	 * @return true|\WP_Error
	 */
	public static function assert_can_activate() {
		if ( self::is_compatible() ) {
			return true;
		}

		deactivate_plugins( JOCBM_BASENAME );

		return new \WP_Error(
			'jocbm_incompatible',
			self::plain_incompatible_message()
		);
	}

	/**
	 * Admin banner when WP or PHP is too old.
	 */
	public static function render_incompatible_notice(): void {
		if ( ! current_user_can( 'activate_plugins' ) ) {
			return;
		}

		if ( ! self::should_show_admin_banner() ) {
			return;
		}

		$actions = array(
			array(
				'label' => __( 'View plugins', 'joc-blockora-morph' ),
				'url'   => admin_url( 'plugins.php' ),
			),
		);
		if ( ! self::wp_ok() ) {
			array_unshift(
				$actions,
				array(
					'label' => __( 'Update WordPress', 'joc-blockora-morph' ),
					'url'   => admin_url( 'update-core.php' ),
				)
			);
		}

		echo wp_kses_post(
			self::notice_html(
				'error',
				__( 'JOC Blockora Morph can’t run on this site', 'joc-blockora-morph' ),
				self::incompatible_body_html(),
				$actions
			)
		);
	}

	/**
	 * Soft notice: newer WP major/minor than last tested release.
	 */
	public static function render_untested_notice(): void {
		if ( ! current_user_can( 'activate_plugins' ) ) {
			return;
		}

		if ( get_transient( 'jocbm_dismiss_untested' ) ) {
			return;
		}

		if ( ! self::should_show_admin_banner() ) {
			return;
		}

		$dismiss_url = wp_nonce_url(
			add_query_arg( 'jocbm_dismiss_untested', '1' ),
			'jocbm_dismiss_untested'
		);

		$body  = '<p>' . esc_html(
			sprintf(
				/* translators: 1: current WordPress version, 2: last tested WordPress version */
				__( 'You’re running WordPress %1$s. JOC Blockora Morph was last fully tested with WordPress %2$s. A newer major release may work, but it hasn’t been verified yet — use a staging site if you can, and watch for a plugin update.', 'joc-blockora-morph' ),
				self::current_wp_label(),
				self::TESTED_UP_TO
			)
		) . '</p>';
		$body .= '<p class="bm-compat-notice__meta">' . esc_html__( 'WordPress.org may also label this plugin as “untested” until we confirm compatibility and update “Tested up to”.', 'joc-blockora-morph' ) . '</p>';

		echo wp_kses_post(
			self::notice_html(
				'warning',
				__( 'Not yet tested with this WordPress version', 'joc-blockora-morph' ),
				$body,
				array(
					array(
						'label' => __( 'Dismiss for 30 days', 'joc-blockora-morph' ),
						'url'   => $dismiss_url,
					),
				)
			)
		);
	}

	/**
	 * Dashboard / plugins / Blockora Morph screens only.
	 */
	private static function should_show_admin_banner(): bool {
		$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
		if ( ! $screen ) {
			return true;
		}

		if ( in_array( $screen->base, array( 'plugins', 'dashboard' ), true ) ) {
			return true;
		}

		if ( isset( $screen->id ) && false !== strpos( (string) $screen->id, 'joc-blockora-morph' ) ) {
			return true;
		}

		$page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		return 0 === strpos( $page, 'joc-blockora-morph' );
	}

	/**
	 * Handle dismiss of untested notice.
	 */
	public static function maybe_dismiss_untested(): void {
		if ( ! isset( $_GET['jocbm_dismiss_untested'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			return;
		}
		if ( ! current_user_can( 'activate_plugins' ) ) {
			return;
		}
		check_admin_referer( 'jocbm_dismiss_untested' );
		set_transient( 'jocbm_dismiss_untested', 1, MONTH_IN_SECONDS );
		wp_safe_redirect( remove_query_arg( array( 'jocbm_dismiss_untested', '_wpnonce' ) ) );
		exit;
	}

	/**
	 * Inline row under the plugin on Plugins screen (incompatible).
	 */
	public static function render_plugin_row_incompatible(): void {
		if ( ! current_user_can( 'activate_plugins' ) ) {
			return;
		}

		$colspan = function_exists( 'wp_is_auto_update_enabled_for_type' ) && wp_is_auto_update_enabled_for_type( 'plugin' ) ? 4 : 3;
		printf(
			'<tr class="plugin-update-tr active bm-compat-row bm-compat-row--error" id="joc-blockora-morph-incompatible"><td colspan="%d" class="plugin-update colspanchange"><div class="bm-compat-row__inner update-message notice inline notice-error notice-alt">%s</div></td></tr>',
			(int) $colspan,
			wp_kses_post(
				self::notice_inner_html(
					__( 'Incompatible with this WordPress / PHP setup', 'joc-blockora-morph' ),
					self::incompatible_body_html()
				)
			)
		);
	}

	/**
	 * Inline row under the plugin on Plugins screen (untested).
	 */
	public static function render_plugin_row_untested(): void {
		if ( ! current_user_can( 'activate_plugins' ) ) {
			return;
		}

		$colspan = function_exists( 'wp_is_auto_update_enabled_for_type' ) && wp_is_auto_update_enabled_for_type( 'plugin' ) ? 4 : 3;
		$body    = '<p>' . esc_html(
			sprintf(
				/* translators: 1: current WordPress version, 2: last tested version */
				__( 'Running WordPress %1$s — last tested with %2$s. The plugin directory may show “Untested with your version of WordPress” until we verify this release.', 'joc-blockora-morph' ),
				self::current_wp_label(),
				self::TESTED_UP_TO
			)
		) . '</p>';

		printf(
			'<tr class="plugin-update-tr active bm-compat-row bm-compat-row--warning" id="joc-blockora-morph-untested"><td colspan="%d" class="plugin-update colspanchange"><div class="bm-compat-row__inner update-message notice inline notice-warning notice-alt">%s</div></td></tr>',
			(int) $colspan,
			wp_kses_post(
				self::notice_inner_html(
					__( 'Not yet tested with this WordPress version', 'joc-blockora-morph' ),
					$body
				)
			)
		);
	}

	/**
	 * Soften action links when incompatible (keep Deactivate visible).
	 *
	 * @param array $links Action links.
	 * @return array
	 */
	public static function filter_action_links_incompatible( array $links ): array {
		$links['bm_compat'] = '<span class="bm-compat-badge">' . esc_html__( 'Requirements not met', 'joc-blockora-morph' ) . '</span>';
		return $links;
	}

	/**
	 * Shared CSS for branded notices via wp_enqueue / wp_add_inline_style.
	 */
	public static function enqueue_notice_styles(): void {
		if ( wp_style_is( 'joc-blockora-morph-compat', 'enqueued' ) ) {
			return;
		}

		wp_register_style( 'joc-blockora-morph-compat', false, array(), JOCBM_VERSION );
		wp_enqueue_style( 'joc-blockora-morph-compat' );
		wp_add_inline_style( 'joc-blockora-morph-compat', \JOCBM\Services\CssSanitizer::sanitize_inline( self::notice_styles_css() ) );
	}

	/**
	 * @return string Compat notice CSS (no style element wrapper).
	 */
	private static function notice_styles_css(): string {
		return <<<'CSS'
.bm-compat-notice {
	--bm-c-primary: #3525cd;
	--bm-c-surface: #f2f3ff;
	--bm-c-ink: #131b2e;
	--bm-c-muted: #464555;
	position: relative;
	margin: 16px 0;
	padding: 0;
	border: 1px solid color-mix(in srgb, var(--bm-c-primary) 22%, #c7c4d8);
	border-left-width: 4px;
	border-radius: 12px;
	background:
		radial-gradient(120% 80% at 100% 0%, color-mix(in srgb, var(--bm-c-primary) 12%, transparent), transparent 55%),
		linear-gradient(180deg, #ffffff 0%, var(--bm-c-surface) 100%);
	box-shadow: 0 8px 24px color-mix(in srgb, var(--bm-c-primary) 10%, transparent);
	overflow: hidden;
}
.bm-compat-notice.notice { padding: 0; }
.bm-compat-notice--error { border-left-color: #b32d2e; }
.bm-compat-notice--warning { border-left-color: #dba617; }
.bm-compat-notice__inner {
	display: flex;
	gap: 16px;
	align-items: flex-start;
	padding: 16px 18px 18px;
}
.bm-compat-notice__icon {
	flex: 0 0 auto;
	display: grid;
	place-items: center;
	width: 44px;
	height: 44px;
	border-radius: 12px;
	background: color-mix(in srgb, var(--bm-c-primary) 14%, #fff);
	color: var(--bm-c-primary);
	font-size: 22px;
	line-height: 1;
}
.bm-compat-notice--error .bm-compat-notice__icon {
	background: #fcf0f1;
	color: #b32d2e;
}
.bm-compat-notice--warning .bm-compat-notice__icon {
	background: #fcf9e8;
	color: #9a6700;
}
.bm-compat-notice__body { min-width: 0; flex: 1; }
.bm-compat-notice__eyebrow {
	margin: 0 0 4px;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--bm-c-primary);
}
.bm-compat-notice--error .bm-compat-notice__eyebrow { color: #b32d2e; }
.bm-compat-notice--warning .bm-compat-notice__eyebrow { color: #9a6700; }
.bm-compat-notice__title {
	margin: 0 0 8px;
	font-size: 16px;
	font-weight: 700;
	line-height: 1.35;
	color: var(--bm-c-ink);
}
.bm-compat-notice__body p {
	margin: 0 0 8px;
	font-size: 13px;
	line-height: 1.55;
	color: var(--bm-c-muted);
}
.bm-compat-notice__body p:last-child { margin-bottom: 0; }
.bm-compat-notice__meta { opacity: 0.92; font-size: 12px !important; }
.bm-compat-notice__reqs {
	display: grid;
	gap: 8px;
	margin: 12px 0 0;
	padding: 0;
	list-style: none;
}
.bm-compat-notice__req {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	padding: 10px 12px;
	border-radius: 10px;
	background: rgba(255, 255, 255, 0.82);
	border: 1px solid color-mix(in srgb, #c7c4d8 70%, transparent);
	font-size: 13px;
	color: var(--bm-c-ink);
}
.bm-compat-notice__pill {
	display: inline-flex;
	align-items: center;
	padding: 3px 9px;
	border-radius: 999px;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.02em;
}
.bm-compat-notice__pill.is-ok { background: #edfaef; color: #007017; }
.bm-compat-notice__pill.is-bad { background: #fcf0f1; color: #b32d2e; }
.bm-compat-notice__actions {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin-top: 14px;
}
.bm-compat-notice__actions a.button {
	border-radius: 8px !important;
}
.bm-compat-notice__actions a.button-primary {
	background: var(--bm-c-primary) !important;
	border-color: var(--bm-c-primary) !important;
	box-shadow: 0 4px 12px color-mix(in srgb, var(--bm-c-primary) 28%, transparent);
}
.bm-compat-badge {
	display: inline-block;
	padding: 2px 8px;
	border-radius: 999px;
	background: #fcf0f1;
	color: #b32d2e;
	font-size: 11px;
	font-weight: 700;
}
.bm-compat-row__inner.bm-compat-notice,
.bm-compat-row .bm-compat-notice {
	margin: 8px 12px 12px 40px;
}
@media (max-width: 782px) {
	.bm-compat-notice__inner { flex-direction: column; }
	.bm-compat-row__inner.bm-compat-notice { margin-left: 12px; }
}
CSS;
	}

	/**
	 * @return string Escaped HTML for requirement list + copy.
	 */
	private static function incompatible_body_html(): string {
		global $wp_version;

		$wp_current  = self::normalize_version( is_string( $wp_version ) ? $wp_version : get_bloginfo( 'version' ) );
		$php_current = self::normalize_version( PHP_VERSION );

		$html  = '<p>' . esc_html__( 'This site does not meet the minimum requirements, so JOC Blockora Morph stays inactive to avoid errors. Update the items marked below, then activate the plugin again.', 'joc-blockora-morph' ) . '</p>';
		$html .= '<ul class="bm-compat-notice__reqs">';
		$html .= self::requirement_row(
			__( 'WordPress', 'joc-blockora-morph' ),
			sprintf(
				/* translators: 1: current version, 2: required version */
				__( 'You have %1$s — needs %2$s+', 'joc-blockora-morph' ),
				$wp_current,
				self::MIN_WP
			),
			self::wp_ok()
		);
		$html .= self::requirement_row(
			__( 'PHP', 'joc-blockora-morph' ),
			sprintf(
				/* translators: 1: current version, 2: required version */
				__( 'You have %1$s — needs %2$s+', 'joc-blockora-morph' ),
				$php_current,
				self::MIN_PHP
			),
			self::php_ok()
		);
		$html .= '</ul>';

		return $html;
	}

	/**
	 * @param string $label Label.
	 * @param string $detail Detail text.
	 * @param bool   $ok Whether met.
	 */
	private static function requirement_row( string $label, string $detail, bool $ok ): string {
		$pill = $ok
			? '<span class="bm-compat-notice__pill is-ok">' . esc_html__( 'OK', 'joc-blockora-morph' ) . '</span>'
			: '<span class="bm-compat-notice__pill is-bad">' . esc_html__( 'Update needed', 'joc-blockora-morph' ) . '</span>';

		return '<li class="bm-compat-notice__req"><span><strong>' . esc_html( $label ) . '</strong> — ' . esc_html( $detail ) . '</span>' . $pill . '</li>';
	}

	/**
	 * @param string $variant error|warning.
	 * @param string $title Title.
	 * @param string $body_html Already-escaped body HTML.
	 * @param array  $actions List of [label, url].
	 */
	private static function notice_html( string $variant, string $title, string $body_html, array $actions = array() ): string {
		$icon = 'error' === $variant ? '⚠' : '✦';

		$html  = '<div class="notice bm-compat-notice bm-compat-notice--' . esc_attr( $variant ) . '">';
		$html .= '<div class="bm-compat-notice__inner">';
		$html .= '<div class="bm-compat-notice__icon" aria-hidden="true">' . esc_html( $icon ) . '</div>';
		$html .= '<div class="bm-compat-notice__body">';
		$html .= '<p class="bm-compat-notice__eyebrow">' . esc_html__( 'JOC Blockora Morph', 'joc-blockora-morph' ) . '</p>';
		$html .= '<h2 class="bm-compat-notice__title">' . esc_html( $title ) . '</h2>';
		$html .= $body_html;

		$links = '';
		foreach ( $actions as $i => $action ) {
			$url = isset( $action['url'] ) ? (string) $action['url'] : '';
			if ( '' === $url ) {
				continue;
			}
			$class = 0 === $i ? 'button button-primary' : 'button button-secondary';
			$links .= '<a class="' . esc_attr( $class ) . '" href="' . esc_url( $url ) . '">' . esc_html( (string) $action['label'] ) . '</a>';
		}
		if ( $links ) {
			$html .= '<div class="bm-compat-notice__actions">' . $links . '</div>';
		}

		$html .= '</div></div></div>';

		return $html;
	}

	/**
	 * Compact inner markup for plugin-row notices.
	 *
	 * @param string $title Title.
	 * @param string $body_html Escaped body.
	 */
	private static function notice_inner_html( string $title, string $body_html ): string {
		$html  = '<div class="bm-compat-notice__body" style="padding:4px 0">';
		$html .= '<p class="bm-compat-notice__eyebrow">' . esc_html__( 'JOC Blockora Morph', 'joc-blockora-morph' ) . '</p>';
		$html .= '<strong class="bm-compat-notice__title" style="display:block;font-size:14px;margin:0 0 6px">' . esc_html( $title ) . '</strong>';
		$html .= $body_html;
		$html .= '</div>';
		return $html;
	}

	/**
	 * Plain-text message for activation die / WP_Error.
	 */
	public static function plain_incompatible_message(): string {
		$parts = array(
			__( 'JOC Blockora Morph could not be activated because this site does not meet the minimum requirements.', 'joc-blockora-morph' ),
		);

		if ( ! self::wp_ok() ) {
			$parts[] = sprintf(
				/* translators: 1: required WP, 2: current WP */
				__( 'WordPress %1$s or newer is required (you have %2$s).', 'joc-blockora-morph' ),
				self::MIN_WP,
				self::current_wp_label()
			);
		}

		if ( ! self::php_ok() ) {
			$parts[] = sprintf(
				/* translators: 1: required PHP, 2: current PHP */
				__( 'PHP %1$s or newer is required (you have %2$s).', 'joc-blockora-morph' ),
				self::MIN_PHP,
				self::normalize_version( PHP_VERSION )
			);
		}

		return implode( ' ', $parts );
	}

	/**
	 * Strip to major.minor for comparisons / labels.
	 *
	 * @param string $version Raw version.
	 */
	public static function normalize_version( string $version ): string {
		$version = preg_replace( '/[^0-9.].*$/', '', $version );
		$parts   = array_map( 'intval', explode( '.', (string) $version ) );
		$major   = $parts[0] ?? 0;
		$minor   = $parts[1] ?? 0;

		return $major . '.' . $minor;
	}

	/**
	 * Current WP label (major.minor).
	 */
	private static function current_wp_label(): string {
		global $wp_version;
		$current = is_string( $wp_version ) ? $wp_version : get_bloginfo( 'version' );
		return self::normalize_version( $current );
	}
}
