<?php
/**
 * Preload / resource hint optimizer.
 *
 * Automatically adds preconnect and dns-prefetch hints for common
 * external services detected in the site's output.
 *
 * @package MonitorCat\Modules\Speed\Optimizer
 */

declare(strict_types=1);

namespace MonitorCat\Modules\Speed\Optimizer;

defined( 'ABSPATH' ) || exit;

use MonitorCat\Settings;

/**
 * Adds resource hints (preconnect, dns-prefetch) for known external origins.
 */
final class PreloadOptimizer {

	/**
	 * Plugin settings.
	 *
	 * @var Settings
	 */
	private Settings $settings;

	/**
	 * Known external origins worth preconnecting to.
	 *
	 * @var array<string, string> Domain => friendly label.
	 */
	private const KNOWN_ORIGINS = array(
		'fonts.googleapis.com'     => 'Google Fonts CSS',
		'fonts.gstatic.com'        => 'Google Fonts Files',
		'www.google-analytics.com' => 'Google Analytics',
		'www.googletagmanager.com' => 'Google Tag Manager',
		'cdn.jsdelivr.net'         => 'jsDelivr CDN',
		'cdnjs.cloudflare.com'     => 'Cloudflare CDN',
		'unpkg.com'                => 'unpkg CDN',
	);

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

	/**
	 * Register hooks if the optimization is enabled.
	 */
	public function boot(): void {
		if ( ! $this->settings->get( 'speed.optimize_preload', true ) ) {
			return;
		}

		if ( is_admin() ) {
			return;
		}

		add_filter( 'wp_resource_hints', array( $this, 'add_resource_hints' ), 10, 2 );
	}

	/**
	 * Add preconnect hints for known external origins that are enqueued.
	 *
	 * @param mixed[] $urls          Current hint URLs (strings or arrays with 'href' key).
	 * @param string  $relation_type The relation type ('dns-prefetch', 'preconnect', etc.).
	 * @return mixed[] Modified URLs.
	 */
	public function add_resource_hints( array $urls, string $relation_type ): array {
		if ( 'preconnect' !== $relation_type ) {
			return $urls;
		}

		$enqueued_origins = $this->detect_external_origins();

		// Collect existing origins to avoid duplicates.
		$existing_origins = array();
		foreach ( $urls as $url ) {
			$href               = is_array( $url ) ? (string) ( $url['href'] ?? '' ) : (string) $url;
			$existing_origins[] = $href;
		}

		foreach ( $enqueued_origins as $origin ) {
			$already_present = false;
			foreach ( $existing_origins as $href ) {
				if ( false !== strpos( $href, $origin ) ) {
					$already_present = true;
					break;
				}
			}

			if ( ! $already_present ) {
				$urls[] = array(
					'href'        => 'https://' . $origin,
					'crossorigin' => 'anonymous',
				);
			}
		}

		return $urls;
	}

	/**
	 * Detect which known external origins are actually being used by enqueued assets.
	 *
	 * @return string[] List of origin domains to preconnect to.
	 */
	private function detect_external_origins(): array {
		$found = array();

		// Check enqueued styles.
		$wp_styles = wp_styles();
		foreach ( $wp_styles->registered as $style ) {
			if ( ! empty( $style->src ) ) {
				$this->check_url_against_origins( (string) $style->src, $found );
			}
		}

		// Check enqueued scripts.
		$wp_scripts = wp_scripts();
		foreach ( $wp_scripts->registered as $script ) {
			if ( ! empty( $script->src ) ) {
				$this->check_url_against_origins( (string) $script->src, $found );
			}
		}

		return $found;
	}

	/**
	 * Check if a URL matches any known external origin.
	 *
	 * @param string   $url   The URL to check.
	 * @param string[] $found Array of found origins (by reference).
	 */
	private function check_url_against_origins( string $url, array &$found ): void {
		foreach ( self::KNOWN_ORIGINS as $domain => $label ) {
			if ( false !== strpos( $url, $domain ) && ! in_array( $domain, $found, true ) ) {
				$found[] = $domain;
			}
		}
	}
}
