<?php
/**
 * Cache module — Auto Resource Hints
 *
 * Scans the buffered HTML for third-party domains referenced via <link>,
 * <script>, <img>, srcset and inline url() declarations, and injects
 * <link rel="dns-prefetch"> + <link rel="preconnect"> hints into <head>
 * for the most-used ones. Saves DNS lookup + TCP handshake before the
 * browser actually needs the resource.
 *
 * The site's own domain is excluded automatically, as are domains that
 * already have a matching hint upstream so we never duplicate.
 *
 * @package BlaminhorEssentials
 */

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

class Blaminhor_Essentials_Cache_Auto_Resource_Hints {

	const TOP_DOMAINS = 5;

	/** @var Blaminhor_Essentials_Module_Cache */
	private $parent;

	public function __construct( Blaminhor_Essentials_Module_Cache $parent ) {
		$this->parent = $parent;
	}

	public function register_hooks() {
		add_filter( 'blaminhor_essentials_cache_html_buffer', array( $this, 'optimize_buffer' ), 13, 1 );
	}

	/**
	 * @param string $html
	 * @return string
	 */
	public function optimize_buffer( $html ) {
		if ( ! is_string( $html ) || '' === $html ) {
			return $html;
		}
		// Defence-in-depth bypass for skip-optimisation requests.
		if ( $this->parent->should_skip_runtime_optimizations() ) {
			return $html;
		}
		if ( ! $this->parent->get_setting( 'auto_resource_hints_enabled', true ) ) {
			return $html;
		}
		// Only inject in pages that have a <head>.
		$head_pos = stripos( $html, '<head>' );
		if ( false === $head_pos ) {
			return $html;
		}
		$head_end = stripos( $html, '</head>', $head_pos );
		if ( false === $head_end ) {
			return $html;
		}

		$site_host = strtolower( (string) wp_parse_url( home_url(), PHP_URL_HOST ) );
		$head_html = substr( $html, $head_pos, $head_end - $head_pos );

		// Already hinted: collect existing dns-prefetch + preconnect targets.
		$already = $this->collect_existing_hints( $head_html );

		// Count third-party domain occurrences across the whole document.
		$counts = $this->count_third_party_domains( $html, $site_host );
		if ( empty( $counts ) ) {
			return $html;
		}

		// Pick the top-N most referenced ones, ignoring those already hinted.
		arsort( $counts, SORT_NUMERIC );
		$picked = array();
		foreach ( $counts as $host => $n ) {
			if ( count( $picked ) >= self::TOP_DOMAINS ) {
				break;
			}
			if ( in_array( $host, $already, true ) ) {
				continue;
			}
			$picked[] = $host;
		}
		if ( empty( $picked ) ) {
			return $html;
		}

		$snippet = '';
		foreach ( $picked as $host ) {
			$snippet .= '<link rel="dns-prefetch" href="//' . esc_attr( $host ) . '">'
				. '<link rel="preconnect" href="https://' . esc_attr( $host ) . '" crossorigin>';
		}

		// Insert right after <head>.
		return substr( $html, 0, $head_pos + 6 ) . $snippet . substr( $html, $head_pos + 6 );
	}

	/**
	 * @param string $head_html
	 * @return string[]
	 */
	private function collect_existing_hints( $head_html ) {
		$hosts = array();
		if ( preg_match_all( '#<link[^>]+rel=("|\')(?:dns-prefetch|preconnect)\1[^>]*>#i', $head_html, $links ) ) {
			foreach ( $links[0] as $link ) {
				if ( preg_match( '#href=("|\')([^"\']+)\1#i', $link, $hm ) ) {
					$h = strtolower( (string) wp_parse_url( $hm[2], PHP_URL_HOST ) );
					if ( '' === $h ) {
						// Tolerate href="//host"
						$bare = ltrim( $hm[2], '/' );
						$h = strtolower( strtok( $bare, '/' ) );
					}
					if ( '' !== $h ) {
						$hosts[] = $h;
					}
				}
			}
		}
		return array_values( array_unique( $hosts ) );
	}

	/**
	 * @param string $html
	 * @param string $site_host
	 * @return array<string,int>
	 */
	private function count_third_party_domains( $html, $site_host ) {
		$counts = array();
		// Extract every absolute URL referenced via the common attributes.
		if ( ! preg_match_all( '#\b(?:href|src|srcset|action|data-src)\s*=\s*("|\')([^"\']+)\1#i', $html, $m ) ) {
			return $counts;
		}
		foreach ( $m[2] as $value ) {
			// srcset can contain multiple URLs separated by commas, with descriptors.
			$urls = preg_split( '#,\s*#', $value );
			foreach ( $urls as $piece ) {
				$piece = trim( (string) $piece );
				if ( '' === $piece ) {
					continue;
				}
				// Strip srcset width/density descriptors.
				$piece = (string) strtok( $piece, ' ' );
				if ( 0 !== strpos( $piece, 'http://' ) && 0 !== strpos( $piece, 'https://' ) && 0 !== strpos( $piece, '//' ) ) {
					continue;
				}
				$host = strtolower( (string) wp_parse_url( $piece, PHP_URL_HOST ) );
				if ( '' === $host || $host === $site_host ) {
					continue;
				}
				// Skip data: / blob: just in case parse_url ever lets them through.
				if ( preg_match( '/^(data|blob|javascript|mailto|tel)/i', $piece ) ) {
					continue;
				}
				if ( ! isset( $counts[ $host ] ) ) {
					$counts[ $host ] = 0;
				}
				$counts[ $host ]++;
			}
		}
		return $counts;
	}
}
