<?php
/**
 * Cache module — CDN URL Rewriter
 *
 * Rewrites the host part of static asset URLs to a CDN CNAME. Generic
 * (works with BunnyCDN, KeyCDN, Cloudflare, StackPath, …). Triggered on:
 *
 *   - style_loader_src / script_loader_src       (CSS/JS)
 *   - wp_get_attachment_url                      (uploaded media)
 *   - wp_calculate_image_srcset                  (responsive images)
 *   - script_loader_tag (fallback for inline-printed handles)
 *
 * Excludes:
 *   - admin URLs (always served from origin)
 *   - non-asset extensions (HTML, PHP, etc.)
 *   - user-defined exclusions (one substring per line)
 *
 * @package BlaminhorEssentials
 */

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

class Blaminhor_Essentials_Cache_Cdn_Rewriter {

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

	/**
	 * Cached site host (computed once per request).
	 *
	 * @var string|null
	 */
	private $site_host;

	/**
	 * Cached normalised CDN base (with scheme). Empty string when off.
	 *
	 * @var string
	 */
	private $cdn_base = '';

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

	public function register_hooks() {
		// Only wire up if enabled — saves filter overhead.
		if ( ! $this->parent->get_setting( 'cdn_enabled', false ) ) {
			return;
		}
		$cdn = trim( (string) $this->parent->get_setting( 'cdn_url', '' ) );
		if ( '' === $cdn ) {
			return;
		}

		$this->cdn_base  = trailingslashit( $this->normalise_cdn_url( $cdn ) );
		$this->site_host = wp_parse_url( home_url(), PHP_URL_HOST );

		add_filter( 'style_loader_src', array( $this, 'rewrite' ), 9999 );
		add_filter( 'script_loader_src', array( $this, 'rewrite' ), 9999 );
		add_filter( 'wp_get_attachment_url', array( $this, 'rewrite' ), 9999 );
		add_filter( 'wp_calculate_image_srcset', array( $this, 'rewrite_srcset' ), 9999 );
	}

	/**
	 * @param string $url
	 * @return string
	 */
	public function rewrite( $url ) {
		if ( ! is_string( $url ) || '' === $url ) {
			return $url;
		}
		if ( '' === $this->cdn_base ) {
			return $url;
		}
		// Bypass for admins / logged-in (when cache_logged_in is OFF) and the
		// non-cacheable contexts. Without this, a logged-in admin would receive
		// CDN-rewritten URLs for assets in the page they are debugging, which
		// hides "is the asset published correctly to my origin?" issues.
		// Replaces the previous is_admin()-only check.
		if ( $this->parent->should_skip_runtime_optimizations() ) {
			return $url;
		}
		// Only same-host URLs.
		$host = wp_parse_url( $url, PHP_URL_HOST );
		if ( ! $host || $host !== $this->site_host ) {
			return $url;
		}
		// Exclusions by extension or substring.
		if ( $this->is_excluded( $url ) ) {
			return $url;
		}
		// Strip current scheme + host, prepend CDN base.
		$path = wp_parse_url( $url, PHP_URL_PATH );
		if ( null === $path ) {
			return $url;
		}
		$qs   = wp_parse_url( $url, PHP_URL_QUERY );
		$path = ltrim( $path, '/' );
		return $this->cdn_base . $path . ( $qs ? '?' . $qs : '' );
	}

	/**
	 * Rewrite every URL inside an srcset array.
	 *
	 * @param array $sources
	 * @return array
	 */
	public function rewrite_srcset( $sources ) {
		if ( ! is_array( $sources ) ) {
			return $sources;
		}
		foreach ( $sources as $w => $entry ) {
			if ( isset( $entry['url'] ) ) {
				$sources[ $w ]['url'] = $this->rewrite( $entry['url'] );
			}
		}
		return $sources;
	}

	/**
	 * Whether a URL matches one of the user-defined exclusions or a non-asset
	 * extension.
	 *
	 * @param string $url
	 * @return bool
	 */
	private function is_excluded( $url ) {
		$ext = strtolower( pathinfo( wp_parse_url( $url, PHP_URL_PATH ), PATHINFO_EXTENSION ) );
		// Don't rewrite HTML, XML, JSON: those are page responses, not assets.
		if ( in_array( $ext, array( 'php', 'html', 'htm', 'xml', 'json' ), true ) ) {
			return true;
		}

		$raw = (string) $this->parent->get_setting( 'cdn_excluded_extensions', '' );
		foreach ( preg_split( '/[\r\n,]+/', $raw ) as $line ) {
			$line = trim( $line, ".\t " );
			if ( '' === $line ) {
				continue;
			}
			if ( $line === $ext ) {
				return true;
			}
			if ( false !== stripos( $url, $line ) ) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Normalise the user-supplied CDN URL: ensure scheme, lower-case host,
	 * remove trailing slash before our trailingslashit() wraps it.
	 *
	 * @param string $url
	 * @return string
	 */
	private function normalise_cdn_url( $url ) {
		$url = trim( $url );
		if ( false === strpos( $url, '://' ) ) {
			// Force HTTPS when scheme is missing (mixed content audit).
			$url = 'https://' . ltrim( $url, '/' );
		}
		return untrailingslashit( $url );
	}
}
