<?php
/**
 * Asset analyzer.
 *
 * Audits enqueued CSS and JavaScript files on the front end.
 * Reflexive: aware of Monitor.Cat optimizations and credits them.
 *
 * @package MonitorCat\Modules\Speed\Analyzer
 */

declare(strict_types=1);

namespace MonitorCat\Modules\Speed\Analyzer;

defined( 'ABSPATH' ) || exit;

use MonitorCat\Settings;

/**
 * Analyzes CSS and JS assets loaded on the site front end.
 */
final class AssetAnalyzer {

	/**
	 * Good threshold for total asset files.
	 *
	 * @var int
	 */
	private const FILES_GOOD_THRESHOLD = 10;

	/**
	 * Warning threshold for total asset files.
	 *
	 * @var int
	 */
	private const FILES_WARNING_THRESHOLD = 20;

	/**
	 * Plugin settings for reflexive analysis.
	 *
	 * @var Settings
	 */
	private Settings $settings;

	/**
	 * Constructor.
	 *
	 * @param Settings $settings Plugin settings.
	 */
	public function __construct( Settings $settings ) {
		$this->settings = $settings;
	}

	/**
	 * Run the asset analysis.
	 *
	 * @param string|null $prefetched_html Optional pre-fetched home page HTML to avoid duplicate requests.
	 * @return array{score: int, status: string, label: string, checks: array<int, array{label: string, status: string, message: string, value: string|null}>}
	 */
	public function analyze( ?string $prefetched_html = null ): array {
		$checks = array();
		$html   = $prefetched_html ?? $this->fetch_home_page();

		$checks[] = $this->check_css_files( $html );
		$checks[] = $this->check_js_files( $html );
		$checks[] = $this->check_render_blocking( $html );
		$checks[] = $this->check_jquery( $html );
		$checks[] = $this->check_total_assets( $html );

		$score  = $this->calculate_score( $checks );
		$status = $this->determine_status( $score );

		return array(
			'score'  => $score,
			'status' => $status,
			'label'  => __( 'Asset Loading', 'monitor-cat' ),
			'checks' => $checks,
		);
	}

	/**
	 * Fetch the home page HTML via wp_remote_get.
	 *
	 * @return string The response body, or empty string on failure.
	 */
	private function fetch_home_page(): string {
		$response = wp_remote_get(
			home_url( '/' ),
			array(
				'timeout'   => 5,
				'sslverify' => false,
			)
		);

		if ( is_wp_error( $response ) ) {
			return '';
		}

		return wp_remote_retrieve_body( $response );
	}

	/**
	 * Count CSS stylesheet files.
	 *
	 * @param string $html The page HTML.
	 * @return array{label: string, status: string, message: string, value: string|null}
	 */
	private function check_css_files( string $html ): array {
		if ( empty( $html ) ) {
			return array(
				'label'   => __( 'CSS Files', 'monitor-cat' ),
				'status'  => 'warning',
				'message' => __( 'Could not fetch the page to count CSS files.', 'monitor-cat' ),
				'value'   => null,
			);
		}

		$count = preg_match_all( '/<link[^>]*\brel=["\']stylesheet["\'][^>]*>/i', $html );

		if ( $count <= 5 ) {
			$status  = 'good';
			$message = sprintf(
				/* translators: %d: number of CSS files */
				__( 'Found %d CSS file(s). Well optimized.', 'monitor-cat' ),
				$count
			);
		} elseif ( $count <= 12 ) {
			$status  = 'warning';
			$message = sprintf(
				/* translators: %d: number of CSS files */
				__( 'Found %d CSS files. Consider combining or reducing stylesheets.', 'monitor-cat' ),
				$count
			);
		} else {
			$status  = 'critical';
			$message = sprintf(
				/* translators: %d: number of CSS files */
				__( 'Found %d CSS files. Too many stylesheets will slow page rendering.', 'monitor-cat' ),
				$count
			);
		}

		return array(
			'label'   => __( 'CSS Files', 'monitor-cat' ),
			'status'  => $status,
			'message' => $message,
			'value'   => (string) $count,
		);
	}

	/**
	 * Count external JavaScript files.
	 *
	 * @param string $html The page HTML.
	 * @return array{label: string, status: string, message: string, value: string|null}
	 */
	private function check_js_files( string $html ): array {
		if ( empty( $html ) ) {
			return array(
				'label'   => __( 'JS Files', 'monitor-cat' ),
				'status'  => 'warning',
				'message' => __( 'Could not fetch the page to count JS files.', 'monitor-cat' ),
				'value'   => null,
			);
		}

		$count = preg_match_all( '/<script[^>]*\bsrc=["\'][^"\']+["\'][^>]*>/i', $html );

		if ( $count <= 5 ) {
			$status  = 'good';
			$message = sprintf(
				/* translators: %d: number of JS files */
				__( 'Found %d JavaScript file(s). Well optimized.', 'monitor-cat' ),
				$count
			);
		} elseif ( $count <= 10 ) {
			$status  = 'warning';
			$message = sprintf(
				/* translators: %d: number of JS files */
				__( 'Found %d JavaScript files. Consider combining or deferring scripts.', 'monitor-cat' ),
				$count
			);
		} else {
			$status  = 'critical';
			$message = sprintf(
				/* translators: %d: number of JS files */
				__( 'Found %d JavaScript files. Too many scripts will slow page loading.', 'monitor-cat' ),
				$count
			);
		}

		return array(
			'label'   => __( 'JS Files', 'monitor-cat' ),
			'status'  => $status,
			'message' => $message,
			'value'   => (string) $count,
		);
	}

	/**
	 * Check for render-blocking scripts in the document head.
	 * Reflexive: credits Monitor.Cat Script Defer when active.
	 *
	 * @param string $html The page HTML.
	 * @return array{label: string, status: string, message: string, value: string|null}
	 */
	private function check_render_blocking( string $html ): array {
		if ( empty( $html ) ) {
			return array(
				'label'   => __( 'Render-Blocking Scripts', 'monitor-cat' ),
				'status'  => 'warning',
				'message' => __( 'Could not analyze render-blocking scripts.', 'monitor-cat' ),
				'value'   => null,
			);
		}

		$head = '';
		if ( preg_match( '/<head[^>]*>(.*?)<\/head>/is', $html, $matches ) ) {
			$head = $matches[1];
		}

		$blocking_count = 0;
		if ( preg_match_all( '/<script[^>]*\bsrc=["\'][^"\']+["\'][^>]*>/i', $head, $script_matches ) ) {
			foreach ( $script_matches[0] as $script_tag ) {
				if ( ! preg_match( '/\b(async|defer)\b/i', $script_tag ) ) {
					++$blocking_count;
				}
			}
		}

		$script_defer_active = (bool) $this->settings->get( 'speed.optimize_scripts', true );

		if ( 0 === $blocking_count ) {
			$message = __( 'No render-blocking scripts found in the document head.', 'monitor-cat' );
			if ( $script_defer_active ) {
				$message = __( 'No render-blocking scripts — protected by Monitor.Cat Script Defer.', 'monitor-cat' );
			}

			return array(
				'label'   => __( 'Render-Blocking Scripts', 'monitor-cat' ),
				'status'  => 'good',
				'message' => $message,
				'value'   => '0',
			);
		}

		if ( $blocking_count <= 3 ) {
			return array(
				'label'   => __( 'Render-Blocking Scripts', 'monitor-cat' ),
				'status'  => 'warning',
				/* translators: %d: number of render-blocking scripts */
				'message' => sprintf( __( 'Found %d render-blocking script(s) in the head. Add async or defer attributes.', 'monitor-cat' ), $blocking_count ),
				'value'   => (string) $blocking_count,
			);
		}

		return array(
			'label'   => __( 'Render-Blocking Scripts', 'monitor-cat' ),
			'status'  => 'critical',
			/* translators: %d: number of render-blocking scripts */
			'message' => sprintf( __( 'Found %d render-blocking scripts in the head. This significantly delays page rendering.', 'monitor-cat' ), $blocking_count ),
			'value'   => (string) $blocking_count,
		);
	}

	/**
	 * Check for jQuery presence.
	 * Reflexive: acknowledges jQuery Migrate removal when active.
	 *
	 * @param string $html The page HTML.
	 * @return array{label: string, status: string, message: string, value: string|null}
	 */
	private function check_jquery( string $html ): array {
		if ( empty( $html ) ) {
			return array(
				'label'   => __( 'jQuery', 'monitor-cat' ),
				'status'  => 'warning',
				'message' => __( 'Could not check for jQuery.', 'monitor-cat' ),
				'value'   => null,
			);
		}

		$has_jquery  = (bool) preg_match( '/jquery(?:\.min)?\.js/i', $html );
		$has_migrate = (bool) preg_match( '/jquery-migrate/i', $html );

		if ( ! $has_jquery ) {
			return array(
				'label'   => __( 'jQuery', 'monitor-cat' ),
				'status'  => 'good',
				'message' => __( 'jQuery is not loaded on the front end. Modern approach.', 'monitor-cat' ),
				'value'   => __( 'Not loaded', 'monitor-cat' ),
			);
		}

		$migrate_removed = (bool) $this->settings->get( 'speed.remove_jquery_migrate', true );

		if ( ! $has_migrate && $migrate_removed ) {
			return array(
				'label'   => __( 'jQuery', 'monitor-cat' ),
				'status'  => 'warning',
				'message' => __( 'jQuery is loaded but jQuery Migrate removed by Monitor.Cat. Plugins requiring jQuery still work.', 'monitor-cat' ),
				'value'   => __( 'Core only', 'monitor-cat' ),
			);
		}

		return array(
			'label'   => __( 'jQuery', 'monitor-cat' ),
			'status'  => 'warning',
			'message' => __( 'jQuery is loaded on the front end. Consider whether it is needed or can be replaced with vanilla JS.', 'monitor-cat' ),
			'value'   => __( 'Loaded', 'monitor-cat' ),
		);
	}

	/**
	 * Check total number of asset files (CSS + JS combined).
	 *
	 * @param string $html The page HTML.
	 * @return array{label: string, status: string, message: string, value: string|null}
	 */
	private function check_total_assets( string $html ): array {
		if ( empty( $html ) ) {
			return array(
				'label'   => __( 'Total Assets', 'monitor-cat' ),
				'status'  => 'warning',
				'message' => __( 'Could not count total assets.', 'monitor-cat' ),
				'value'   => null,
			);
		}

		$css_count = preg_match_all( '/<link[^>]*\brel=["\']stylesheet["\'][^>]*>/i', $html );
		$js_count  = preg_match_all( '/<script[^>]*\bsrc=["\'][^"\']+["\'][^>]*>/i', $html );
		$total     = $css_count + $js_count;

		if ( $total < self::FILES_GOOD_THRESHOLD ) {
			return array(
				'label'   => __( 'Total Assets', 'monitor-cat' ),
				'status'  => 'good',
				/* translators: %d: total number of asset files */
				'message' => sprintf( __( '%d total asset files loaded. Well optimized.', 'monitor-cat' ), $total ),
				'value'   => (string) $total,
			);
		}

		if ( $total <= self::FILES_WARNING_THRESHOLD ) {
			return array(
				'label'   => __( 'Total Assets', 'monitor-cat' ),
				'status'  => 'warning',
				/* translators: %d: total number of asset files */
				'message' => sprintf( __( '%d total asset files loaded. Consider reducing the number of enqueued resources.', 'monitor-cat' ), $total ),
				'value'   => (string) $total,
			);
		}

		return array(
			'label'   => __( 'Total Assets', 'monitor-cat' ),
			'status'  => 'critical',
			/* translators: %d: total number of asset files */
			'message' => sprintf( __( '%d total asset files loaded. This is excessive and will significantly impact performance.', 'monitor-cat' ), $total ),
			'value'   => (string) $total,
		);
	}

	/**
	 * Calculate the overall score from checks.
	 *
	 * @param array<int, array{label: string, status: string, message: string, value: string|null}> $checks The checks array.
	 * @return int Score from 0 to 100.
	 */
	private function calculate_score( array $checks ): int {
		if ( empty( $checks ) ) {
			return 0;
		}

		$total_weight = 0;
		$earned       = 0;
		$weights      = array( 20, 20, 25, 10, 25 );

		foreach ( $checks as $index => $check ) {
			$weight        = $weights[ $index ] ?? 20;
			$total_weight += $weight;

			if ( 'good' === $check['status'] ) {
				$earned += $weight;
			} elseif ( 'warning' === $check['status'] ) {
				$earned += (int) round( $weight * 0.5 );
			}
		}

		return $total_weight > 0 ? (int) round( ( $earned / $total_weight ) * 100 ) : 0;
	}

	/**
	 * Determine overall status from score.
	 *
	 * @param int $score The calculated score.
	 * @return string 'good', 'warning', or 'critical'.
	 */
	private function determine_status( int $score ): string {
		if ( $score >= 80 ) {
			return 'good';
		}

		if ( $score >= 50 ) {
			return 'warning';
		}

		return 'critical';
	}
}
