<?php

namespace EasyCriticalCSS;

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

class Critical_CSS_Status {
	public static function init() {
		add_action( 'admin_bar_menu', [ __CLASS__, 'add_critical_css_status_admin_bar' ], 100 );
		add_action( 'wp_enqueue_scripts', [ __CLASS__, 'enqueue_scripts' ] );
	}

	public static function update_critical_css_status( $identifier, $status, $last_error = '' ) {
		// Make sure we're dealing with a valid status.
		$valid_statuses = [ 'completed', 'expired', 'failed', 'pending', 'unprocessed' ];
		if ( ! in_array( $status, $valid_statuses, true ) ) {
			return;
		}

		// Make sure we have a hash (all entries should have a hash).
		$generated = Critical_CSS::get_generated_css( $identifier );
		if ( empty( $generated['url_hash'] ) ) {
			return;
		}

		// Add an error if we have one. Do this as a single DB call.
		if ( ! empty( $last_error ) ) {
			Database::save_last_error( $identifier, $last_error, $status );

			// Record failure if status is failed or expired.
			if (
				in_array( $status, [ 'failed', 'expired' ], true )
				&& Failure_Patterns::should_track_failure()
			) {
				$failure_class = Failure_Patterns::classify_failure( $last_error );
				Failure_Patterns::record_failure( $identifier, $failure_class );
			}

			return;
		}

		$data = [
			'url_hash'          => $generated['url_hash'],
			'processing_status' => $status,
		];

		Database::upsert_row( $data );

		// We need to reset get_generated_css cache.
		Critical_CSS::clear_cache( $identifier );
		Critical_CSS::clear_cache( $generated['url_hash'] );
		Critical_CSS::get_generated_css( $identifier );
	}

	public static function is_pending_generation_expired( $identifier ) {
		$generated = Critical_CSS::get_generated_css( $identifier );

		// No requested time or hash exists (unlikely). No requested time means we cannot determine actual expires time.
		if ( empty( $generated['requested_time'] ) || empty( $generated['url_hash'] ) ) {
			return false;
		}

		// We still have time.
		if ( Helpers::get_remaining_time( $generated['requested_time'] ) > 0 ) {
			return false;
		}

		// We're expired, so save the status as expired.
		self::update_critical_css_status(
			$identifier,
			'expired',
			__( 'Critical CSS API did not return CSS within 30 minutes.', 'easy-critical-css' )
		);

		return true;
	}

	public static function get_status( $identifier ) {
		$generated = Critical_CSS::get_generated_css( $identifier );

		// Is this a non-published post status?
		if ( ! empty( $generated['post_id'] ) && get_post_status( $generated['post_id'] ) !== 'publish' ) {
			return 'unprocessed';
		}

		// Are we out of pending purgatory?
		if ( $generated['status'] === 'pending' && self::is_pending_generation_expired( $identifier ) ) {
			return 'expired';
		}

		// Did the API return an invalid API response?
		if ( $generated['status'] === 'failed' && strpos( $generated['last_error'], 'API' ) !== false ) {
			return 'invalid';
		}

		/**
		 * Filters the Critical CSS status of an individual post/page.
		 *
		 * @since 1.3.0
		 *
		 * @param string $status Critical CSS status of an individual post/page.
		 * @param string|int $identifier URL, Post ID, or URL hash.
		 */
		$status = apply_filters( 'easy_cc_critical_status', $generated['status'], $identifier );

		return $status;
	}

	public static function get_critical_css_status_label( $status ) {
		return self::get_admin_bar_status_label( $status );
	}

	public static function get_unreachable_status_label( $reason ) {
		$reason_labels = [
			'api_expired'         => __( 'API Key Expired', 'easy-critical-css' ),
			'no_api_key'          => __( 'No API Key', 'easy-critical-css' ),
			'site_private'        => __( 'Site Not Public', 'easy-critical-css' ),
			'rest_unreachable'    => __( 'Site Unreachable', 'easy-critical-css' ),
			'unknown_unreachable' => __( 'Site REST Unreachable', 'easy-critical-css' ),
		];

		if ( isset( $reason_labels[ $reason ] ) ) {
			return $reason_labels[ $reason ];
		}

		return __( 'Site Unreachable', 'easy-critical-css' );
	}

	public static function get_admin_bar_status_label( $status, $reason = '' ) {
		$statuses = [
			'completed'   => __( 'Active', 'easy-critical-css' ),
			'expired'     => __( 'Needs Update', 'easy-critical-css' ),
			'excluded'    => __( 'Excluded', 'easy-critical-css' ),
			'failed'      => __( 'Generation Failed', 'easy-critical-css' ),
			'invalid'     => __( 'API Issue', 'easy-critical-css' ),
			'paused'      => __( 'Paused', 'easy-critical-css' ),
			'pending'     => __( 'Generating', 'easy-critical-css' ),
			'unprocessed' => __( 'Not Generated', 'easy-critical-css' ),
			'unreachable' => __( 'Site Unreachable', 'easy-critical-css' ),
		];

		if ( 'unreachable' === $status ) {
			return self::get_unreachable_status_label( $reason );
		}

		return isset( $statuses[ $status ] ) ? $statuses[ $status ] : __( 'Unknown', 'easy-critical-css' );
	}

	public static function get_admin_bar_status_href( $status, $reason = '' ) {
		if ( 'unreachable' !== $status ) {
			return '';
		}

		$linkable_reasons = [
			'api_expired',
			'no_api_key',
			'site_private',
			'rest_unreachable',
			'unknown_unreachable',
		];

		if ( ! in_array( $reason, $linkable_reasons, true ) ) {
			return '';
		}

		return admin_url( 'options-general.php?page=easy-critical-css-settings' );
	}

	private static function get_current_auto_mode_reason() {
		$auto_mode_status = Helpers::get_auto_mode_status();
		if ( ! empty( $auto_mode_status['reason'] ) ) {
			return $auto_mode_status['reason'];
		}

		return Helpers::get_auto_mode_status_reason( $auto_mode_status );
	}

	public static function add_critical_css_status_admin_bar( $wp_admin_bar ) {
		// Return early if we don't have an identifier.
		$identifier = Critical_CSS::get_page_identifier();
		if ( is_null( $identifier ) ) {
			return;
		}

		// Also if the identifier matches a wp-admin url.
		if ( strpos( $identifier, '/wp-admin/' ) !== false ) {
			return;
		}

		// Only display if we are on global auto mode.
		if ( Settings::get_global_mode() !== 'auto' ) {
			return;
		}

		$critical_css = Critical_CSS::get_generated_css( $identifier );

		// Has Critical in the DB?
		$has_db_critical = ! empty( $critical_css['critical_css'] );

		// What about files?
		$serve_from_files  = Settings::get_global_serve_css_from_files();
		$upload_base       = trailingslashit( wp_upload_dir()['basedir'] ) . 'easy-critical-css/critical/';
		$hash              = sanitize_file_name( $critical_css['url_hash'] );
		$has_file_critical = $serve_from_files && is_readable( "{$upload_base}{$hash}.css" );

		// Critical exists in either DB or as a file.
		$has_critical = $has_db_critical || $has_file_critical;

		$status       = self::get_status( $identifier );
		$reason       = 'unreachable' === $status ? self::get_current_auto_mode_reason() : '';
		$status_label = self::get_admin_bar_status_label( $status, $reason );
		$status_href  = self::get_admin_bar_status_href( $status, $reason );

		$node = [
			'id'     => 'critical_css_status',
			'title'  => "Critical CSS: <span class='critical-css-status $status'>$status_label</span>",
			'parent' => 'top-secondary',
		];

		if ( ! empty( $status_href ) ) {
			$node['href'] = $status_href;
		}

		$wp_admin_bar->add_node( $node );

		// If there’s savings data AND we display secondary, show it in the admin bar.
		if (
			Settings::get_individual_secondary_behavior( $identifier )
			&& ! empty( $critical_css['size_savings'] )
		) {
			$savings = json_decode( wp_unslash( $critical_css['size_savings'] ), true );

			if ( is_array( $savings ) && isset( $savings['bytes'], $savings['percent'] ) ) {
				$bytes   = size_format( $savings['bytes'] );
				$percent = number_format_i18n( $savings['percent'], 0 ) . '%';

				$wp_admin_bar->add_node(
					[
						'id'     => 'critical_css_savings',
						'title'  => sprintf(
							/* translators: 1: human-readable bytes, 2: percent saved */
							__( 'Savings: %1$s (%2$s)', 'easy-critical-css' ),
							$bytes,
							$percent
						),
						'parent' => 'critical_css_status',
					]
				);
			}
		}

		// Do we need to show the Generate Critical CSS link?
		if ( in_array( $status, [ 'completed', 'expired', 'invalid', 'unprocessed' ], true ) ) {
			$wp_admin_bar->add_node(
				[
					'id'     => 'critical_css_regenerate',
					'title'  => $has_critical ? __( 'Regenerate Critical CSS', 'easy-critical-css' ) : __( 'Generate Critical CSS', 'easy-critical-css' ),
					'parent' => 'critical_css_status',
					'href'   => '#',
					'meta'   => [
						'class' => 'critical-css-regenerate-link',
					],
				]
			);
		}

		// Do we need Delete link
		if ( $has_critical && current_user_can( 'manage_options' ) ) {
			$wp_admin_bar->add_node(
				[
					'id'     => 'critical_css_delete',
					'title'  => __( 'Delete Generated Critical CSS', 'easy-critical-css' ),
					'parent' => 'critical_css_status',
					'href'   => '#',
					'meta'   => [ 'class' => 'critical-css-delete-link' ],
				]
			);
		}
	}

	public static function enqueue_scripts() {
		if ( ! is_admin_bar_showing() ) {
			return;
		}

		$identifier = Critical_CSS::get_page_identifier();
		if ( empty( $identifier ) ) {
			return;
		}

		wp_enqueue_script( 'wp-api-fetch' );

		$api_settings = [
			'root'  => esc_url_raw( rest_url() ),
			'nonce' => wp_create_nonce( 'wp_rest' ),
		];

		// Ensure apiFetch requests include the REST root and nonce on the front end.
		wp_add_inline_script(
			'wp-api-fetch',
			sprintf(
				'window.wpApiSettings = Object.assign({}, window.wpApiSettings || {}, %s);',
				wp_json_encode( $api_settings )
			),
			'before'
		);
		?>
		<script>
			document.addEventListener('DOMContentLoaded', function () {
				if (window.wp && window.wp.apiFetch && !window.easyCriticalCssApiFetchConfigured) {
					const { apiFetch } = window.wp
					const root = window.wpApiSettings && window.wpApiSettings.root
					const nonce = window.wpApiSettings && window.wpApiSettings.nonce

					if (typeof apiFetch.createRootURLMiddleware === 'function' && root) {
						apiFetch.use(apiFetch.createRootURLMiddleware(root))
					}

					if (typeof apiFetch.createNonceMiddleware === 'function' && nonce) {
						apiFetch.use(apiFetch.createNonceMiddleware(nonce))
					}

					window.easyCriticalCssApiFetchConfigured = true
				}

				const eccIdentifier = '<?php echo esc_js( $identifier ); ?>'
				const apiRoot = (window.wpApiSettings && window.wpApiSettings.root) || '/wp-json/'
				const buildUrl = (route) => {
					try {
						return new URL(route.replace(/^\//, ''), apiRoot).toString()
					} catch (err) {
						return apiRoot.replace(/\/?$/, '/') + route.replace(/^\//, '')
					}
				}
				const buildHeaders = () => {
					const headers = { 'Content-Type': 'application/json' }
					if (window.wpApiSettings && window.wpApiSettings.nonce) {
						headers['X-WP-Nonce'] = window.wpApiSettings.nonce
					}
					return headers
				}
				document.querySelectorAll('.critical-css-regenerate-link a').forEach(link => {
					link.addEventListener('click', function (event) {
						event.preventDefault()
						if (!eccIdentifier) return
		
						if (!confirm('<?php echo esc_js( __( 'Are you sure you want to regenerate Critical CSS?', 'easy-critical-css' ) ); ?>')) {
							return
						}
		
						wp.apiFetch({
							url: buildUrl('/easy-critical-css/v1/generate'),
							method: 'POST',
							headers: buildHeaders(),
							body: JSON.stringify({ identifier: eccIdentifier })
						})
						.then(() => alert('<?php echo esc_js( __( 'Critical CSS generation started successfully.', 'easy-critical-css' ) ); ?>'))
						.catch(err => alert('<?php echo esc_js( __( 'Error: ', 'easy-critical-css' ) ); ?>' + err.message))
					})
				})
				document.querySelectorAll('.critical-css-delete-link a').forEach(link => {
					link.addEventListener('click', function (event) {
						event.preventDefault()
						if (!eccIdentifier) return
		
						if (!confirm('<?php echo esc_js( __( 'Are you sure you want to delete Critical CSS?', 'easy-critical-css' ) ); ?>')) {
							return
						}
		
						wp.apiFetch({
							url: buildUrl('/easy-critical-css/v1/delete'),
							method: 'POST',
							headers: buildHeaders(),
							body: JSON.stringify({ identifier: eccIdentifier })
						})
						.then(() => alert('<?php echo esc_js( __( 'Critical CSS styles deleted successfully.', 'easy-critical-css' ) ); ?>'))
						.catch(err => alert('<?php echo esc_js( __( 'Error: ', 'easy-critical-css' ) ); ?>' + err.message))
					})
				})
			})
		</script>
		<?php

		wp_enqueue_style(
			'easy-critical-css-admin',
			plugins_url( 'assets/admin.css', Plugin::get_plugin_file() ),
			null,
			Plugin::get_plugin_version()
		);
	}
}
