<?php
/**
 * Page weight analyzer.
 *
 * Analyzes page size and resource count for the site home page.
 *
 * @package MonitorCat\Modules\Speed\Analyzer
 */

declare(strict_types=1);

namespace MonitorCat\Modules\Speed\Analyzer;

defined( 'ABSPATH' ) || exit;

use MonitorCat\Settings;

/**
 * Analyzes page weight including HTML size, inline scripts, styles, and resource hints.
 * Reflexive: aware of Monitor.Cat optimizations and credits them.
 */
final class PageWeightAnalyzer {

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

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

	/**
	 * Good HTML size threshold in bytes (100 KB).
	 *
	 * @var int
	 */
	private const HTML_GOOD_THRESHOLD = 102400;

	/**
	 * Warning HTML size threshold in bytes (500 KB).
	 *
	 * @var int
	 */
	private const HTML_WARNING_THRESHOLD = 512000;

	/**
	 * Run the page weight 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_html_size( $html );
		$checks[] = $this->check_inline_scripts( $html );
		$checks[] = $this->check_inline_styles( $html );
		$checks[] = $this->check_resource_hints( $html );

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

		return array(
			'score'  => $score,
			'status' => $status,
			'label'  => __( 'Page Weight', '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 );
	}

	/**
	 * Check the total HTML size.
	 *
	 * @param string $html The page HTML.
	 * @return array{label: string, status: string, message: string, value: string|null}
	 */
	private function check_html_size( string $html ): array {
		$size = strlen( $html );

		if ( 0 === $size ) {
			return array(
				'label'   => __( 'HTML Size', 'monitor-cat' ),
				'status'  => 'critical',
				'message' => __( 'Could not fetch the home page to measure HTML size.', 'monitor-cat' ),
				'value'   => null,
			);
		}

		if ( $size < self::HTML_GOOD_THRESHOLD ) {
			return array(
				'label'   => __( 'HTML Size', 'monitor-cat' ),
				'status'  => 'good',
				/* translators: %s: human-readable file size */
				'message' => sprintf( __( 'HTML document size is %s, which is well optimized.', 'monitor-cat' ), size_format( $size ) ),
				'value'   => size_format( $size ),
			);
		}

		if ( $size < self::HTML_WARNING_THRESHOLD ) {
			return array(
				'label'   => __( 'HTML Size', 'monitor-cat' ),
				'status'  => 'warning',
				/* translators: %s: human-readable file size */
				'message' => sprintf( __( 'HTML document size is %s. Consider reducing inline content.', 'monitor-cat' ), size_format( $size ) ),
				'value'   => size_format( $size ),
			);
		}

		return array(
			'label'   => __( 'HTML Size', 'monitor-cat' ),
			'status'  => 'critical',
			/* translators: %s: human-readable file size */
			'message' => sprintf( __( 'HTML document size is %s. This is very large and will slow page loading.', 'monitor-cat' ), size_format( $size ) ),
			'value'   => size_format( $size ),
		);
	}

	/**
	 * Count and check inline scripts.
	 *
	 * @param string $html The page HTML.
	 * @return array{label: string, status: string, message: string, value: string|null}
	 */
	private function check_inline_scripts( string $html ): array {
		if ( empty( $html ) ) {
			return array(
				'label'   => __( 'Inline Scripts', 'monitor-cat' ),
				'status'  => 'warning',
				'message' => __( 'Could not analyze inline scripts.', 'monitor-cat' ),
				'value'   => null,
			);
		}

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

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

		if ( $count <= 3 ) {
			$message = sprintf(
				/* translators: %d: number of inline scripts */
				__( 'Found %d inline script(s). This is acceptable.', 'monitor-cat' ),
				$count
			);
			if ( $head_cleanup_active ) {
				$message = sprintf(
					/* translators: %d: number of inline scripts */
					__( 'Found %d inline script(s). Emoji detection script removed by Monitor.Cat Head Cleanup.', 'monitor-cat' ),
					$count
				);
			}

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

		if ( $count <= 10 ) {
			return array(
				'label'   => __( 'Inline Scripts', 'monitor-cat' ),
				'status'  => 'warning',
				/* translators: %d: number of inline scripts */
				'message' => sprintf( __( 'Found %d inline scripts. Consider consolidating or externalizing them.', 'monitor-cat' ), $count ),
				'value'   => (string) $count,
			);
		}

		return array(
			'label'   => __( 'Inline Scripts', 'monitor-cat' ),
			'status'  => 'critical',
			/* translators: %d: number of inline scripts */
			'message' => sprintf( __( 'Found %d inline scripts. Too many inline scripts increase HTML size and hurt caching.', 'monitor-cat' ), $count ),
			'value'   => (string) $count,
		);
	}

	/**
	 * Count and check inline styles.
	 *
	 * @param string $html The page HTML.
	 * @return array{label: string, status: string, message: string, value: string|null}
	 */
	private function check_inline_styles( string $html ): array {
		if ( empty( $html ) ) {
			return array(
				'label'   => __( 'Inline Styles', 'monitor-cat' ),
				'status'  => 'warning',
				'message' => __( 'Could not analyze inline styles.', 'monitor-cat' ),
				'value'   => null,
			);
		}

		$count = preg_match_all( '/<style[^>]*>/i', $html );

		if ( $count <= 2 ) {
			return array(
				'label'   => __( 'Inline Styles', 'monitor-cat' ),
				'status'  => 'good',
				/* translators: %d: number of inline style blocks */
				'message' => sprintf( __( 'Found %d inline style block(s). This is acceptable.', 'monitor-cat' ), $count ),
				'value'   => (string) $count,
			);
		}

		if ( $count <= 6 ) {
			return array(
				'label'   => __( 'Inline Styles', 'monitor-cat' ),
				'status'  => 'warning',
				/* translators: %d: number of inline style blocks */
				'message' => sprintf( __( 'Found %d inline style blocks. Consider combining or externalizing them.', 'monitor-cat' ), $count ),
				'value'   => (string) $count,
			);
		}

		return array(
			'label'   => __( 'Inline Styles', 'monitor-cat' ),
			'status'  => 'critical',
			/* translators: %d: number of inline style blocks */
			'message' => sprintf( __( 'Found %d inline style blocks. Excessive inline CSS hurts performance.', 'monitor-cat' ), $count ),
			'value'   => (string) $count,
		);
	}

	/**
	 * Count resource hints (preload, prefetch) in the document head.
	 *
	 * @param string $html The page HTML.
	 * @return array{label: string, status: string, message: string, value: string|null}
	 */
	private function check_resource_hints( string $html ): array {
		if ( empty( $html ) ) {
			return array(
				'label'   => __( 'Resource Hints', 'monitor-cat' ),
				'status'  => 'warning',
				'message' => __( 'Could not analyze resource hints.', 'monitor-cat' ),
				'value'   => null,
			);
		}

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

		$count = preg_match_all( '/<link[^>]*\brel=["\'](?:preload|prefetch|preconnect|dns-prefetch)["\'][^>]*>/i', $head );

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

		if ( $count > 0 ) {
			$message = sprintf(
				/* translators: %d: number of resource hints */
				__( 'Found %d resource hint(s) (preload/prefetch/preconnect). Good for performance.', 'monitor-cat' ),
				$count
			);
			if ( $preload_active ) {
				$message = sprintf(
					/* translators: %d: number of resource hints */
					__( 'Found %d resource hint(s) — including preconnects added by Monitor.Cat.', 'monitor-cat' ),
					$count
				);
			}

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

		if ( $preload_active ) {
			return array(
				'label'   => __( 'Resource Hints', 'monitor-cat' ),
				'status'  => 'good',
				'message' => __( 'Monitor.Cat Resource Hints is active. No external origins detected to preconnect to.', 'monitor-cat' ),
				'value'   => __( 'Protected', 'monitor-cat' ),
			);
		}

		return array(
			'label'   => __( 'Resource Hints', 'monitor-cat' ),
			'status'  => 'warning',
			'message' => __( 'No resource hints found. Enable Monitor.Cat Resource Hints for automatic preconnect.', 'monitor-cat' ),
			'value'   => '0',
		);
	}

	/**
	 * 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( 40, 20, 20, 20 );

		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';
	}
}
