<?php

/**
 * WebP/AVIF Delivery System
 *
 * Automatically serves modern image formats to supported browsers
 *
 * @package ProRank\SEO\Core\ImageOptimization
 * @since   1.0.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Core\ImageOptimization;

use ProRank\SEO\Plugin;

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


/**
 * WebPDelivery class
 */


class WebPDelivery {
	
	/**
	 * Plugin instance
	 *
	 * @var Plugin
	 */
	private Plugin $plugin;
	
	/**
	 * Constructor
	 */
	public function __construct() {
		$this->plugin = Plugin::get_instance();
		$this->init();
	}
	
	/**
	 * Initialize WebP delivery
	 */
	private function init(): void {
		if ( Plugin::is_elementor_runtime_request() ) {
			return;
		}

		$settings = $this->plugin->settings();
		$delivery_method = $settings->get( 'images_delivery_method', 'picture' );
		
		if ( $delivery_method === 'none' ) {
			return;
		}
		
		// Picture element method (recommended)
		if ( in_array( $delivery_method, [ 'picture', 'both' ], true ) ) {
			add_filter( 'the_content', [ $this, 'wrap_images_in_picture' ], 20 );
			add_filter( 'post_thumbnail_html', [ $this, 'wrap_featured_image_in_picture' ], 20 );
			add_filter( 'wp_get_attachment_image', [ $this, 'wrap_attachment_image_in_picture' ], 20, 5 );
		}
		
		// Rewrite method (server-side)
		if ( in_array( $delivery_method, [ 'rewrite', 'both' ], true ) ) {
			add_action( 'init', [ $this, 'add_rewrite_rules' ] );
			add_filter( 'mod_rewrite_rules', [ $this, 'add_htaccess_rules' ] );
		}
		
		// Add support for srcset
		add_filter( 'wp_calculate_image_srcset', [ $this, 'add_webp_to_srcset' ], 10, 5 );
		
		// Preload modern formats
		add_action( 'wp_head', function() {
			try {
				$this->preload_modern_formats();
			} catch ( \Throwable $e ) {
				if ( function_exists( 'prorank_log' ) ) {
					prorank_log( '[ProRank WebPDelivery] preload failed: ' . $e->getMessage() );
				}
			}
		}, 1 );
	}
	
	/**
	 * Wrap images in picture elements
	 *
	 * @param string $content Content.
	 * @return string
	 */
	public function wrap_images_in_picture( $content ): string {
		$content = is_string( $content ) ? $content : (string) $content;

		if ( ! $this->should_process() ) {
			return $content;
		}
		
		// Find all img tags
		$pattern = '/<img([^>]+)>/i';
		
		try {
			$result = preg_replace_callback( $pattern, [ $this, 'convert_img_to_picture' ], $content );
		} catch ( \Throwable $e ) {
			if ( function_exists( 'prorank_log' ) ) {
				prorank_log( '[ProRank WebPDelivery] Failed to wrap content images: ' . $e->getMessage() );
			}
			return $content;
		}

		return is_string( $result ) ? $result : $content;
	}
	
	/**
	 * Convert img tag to picture element
	 *
	 * @param array $matches Regex matches.
	 * @return string
	 */
	private function convert_img_to_picture( array $matches ): string {
		$img_tag = $matches[0];
		$attributes = $matches[1];
		
		// Extract src
		if ( ! preg_match( '/src=["\']([^"\']+)["\']/', $attributes, $src_match ) ) {
			return $img_tag;
		}
		
		$original_src = $src_match[1];
		$origin_src = $this->normalize_photon_url_to_origin( $original_src ) ?: $original_src;
		
		// Skip if already WebP/AVIF
		if ( preg_match( '/\.(webp|avif)$/i', $original_src ) ) {
			return $img_tag;
		}
		
		// Get attachment ID from URL
		$attachment_id = $this->get_attachment_id_from_attributes( $attributes );
		if ( ! $attachment_id ) {
			$attachment_id = $this->get_attachment_id_from_url( $origin_src );
		}
		if ( ! $attachment_id ) {
			return $img_tag;
		}
		
		// Check for modern format versions
		$webp_path = get_post_meta( $attachment_id, '_prorank_webp_path', true );
		$avif_path = get_post_meta( $attachment_id, '_prorank_avif_path', true );
		
		if ( ! $webp_path && ! $avif_path ) {
			return $img_tag;
		}
		
		// Build picture element
		$picture = '<picture>';
		
		// Add AVIF source if available
		if ( $avif_path && file_exists( $avif_path ) ) {
			$avif_url = $this->path_to_url( $avif_path );
			$picture .= sprintf(
				'<source srcset="%s" type="image/avif">',
				esc_url( $avif_url )
			);
		}
		
		// Add WebP source if available
		if ( $webp_path && file_exists( $webp_path ) ) {
			$webp_url = $this->path_to_url( $webp_path );
			$picture .= sprintf(
				'<source srcset="%s" type="image/webp">',
				esc_url( $webp_url )
			);
		}
		
		// Add original image as fallback
		$picture .= $this->normalize_img_tag_image_urls( $img_tag );
		$picture .= '</picture>';
		
		return $picture;
	}
	
	/**
	 * Wrap featured image in picture element
	 *
	 * @param string $html Featured image HTML.
	 * @return string
	 */
	public function wrap_featured_image_in_picture( $html ): string {
		$html = is_string( $html ) ? $html : (string) $html;
		return $this->wrap_images_in_picture( $html );
	}
	
	/**
	 * Wrap attachment image in picture element
	 *
	 * @param string $html          Image HTML.
	 * @param int    $attachment_id Attachment ID.
	 * @param string $size          Image size.
	 * @param bool   $icon          Whether it's an icon.
	 * @param array  $attr          Attributes.
	 * @return string
	 */
	public function wrap_attachment_image_in_picture( $html, $attachment_id, $size = 'thumbnail', $icon = false, $attr = [] ): string {
		$html = is_string( $html ) ? $html : (string) $html;
		$attachment_id = (int) $attachment_id;

		if ( ! $this->should_process() ) {
			return $html;
		}
		
		// Check for modern format versions
		$webp_path = get_post_meta( $attachment_id, '_prorank_webp_path', true );
		$avif_path = get_post_meta( $attachment_id, '_prorank_avif_path', true );
		
		if ( ! $webp_path && ! $avif_path ) {
			return $html;
		}
		
		// Build picture element
		$picture = '<picture>';
		
		// Add AVIF source if available
		if ( $avif_path && file_exists( $avif_path ) ) {
			$avif_url = $this->path_to_url( $avif_path );
			$picture .= sprintf(
				'<source srcset="%s" type="image/avif">',
				esc_url( $avif_url )
			);
		}
		
		// Add WebP source if available
		if ( $webp_path && file_exists( $webp_path ) ) {
			$webp_url = $this->path_to_url( $webp_path );
			$picture .= sprintf(
				'<source srcset="%s" type="image/webp">',
				esc_url( $webp_url )
			);
		}
		
		// Add original image
		$picture .= $this->normalize_img_tag_image_urls( $html );
		$picture .= '</picture>';
		
		return $picture;
	}
	
	/**
	 * Add WebP versions to srcset
	 *
	 * @param array  $sources       Sources array.
	 * @param array  $size_array    Size array.
	 * @param string $image_src     Image source.
	 * @param array  $image_meta    Image metadata.
	 * @param int    $attachment_id Attachment ID.
	 * @return array
	 */
	public function add_webp_to_srcset( $sources, $size_array, $image_src, $image_meta, $attachment_id ): array {
		if ( ! is_array( $sources ) || ! is_array( $size_array ) || ! is_array( $image_meta ) ) {
			return is_array( $sources ) ? $sources : [];
		}

		$attachment_id = (int) $attachment_id;

		// Check if browser supports WebP
		if ( ! $this->browser_supports_webp() ) {
			return $sources;
		}
		
		$webp_path = get_post_meta( $attachment_id, '_prorank_webp_path', true );
		if ( ! $webp_path || ! file_exists( $webp_path ) ) {
			return $sources;
		}
		
		// Add WebP version to sources
		$webp_url = $this->path_to_url( $webp_path );
		$sources['webp'] = [
			'url' => $webp_url,
			'descriptor' => 'w',
			'value' => $size_array[0],
		];
		
		return $sources;
	}
	
	/**
	 * Add rewrite rules for WebP
	 */
	public function add_rewrite_rules(): void {
		add_rewrite_rule(
			'^wp-content/uploads/(.+)\.(jpe?g|png)$',
			'index.php?prorank_webp_image=$matches[1].$matches[2]',
			'top'
		);
	}
	
	/**
	 * Add htaccess rules for WebP
	 *
	 * @param string $rules Current rules.
	 * @return string
	 */
	public function add_htaccess_rules( string $rules ): string {
        $webp_rules = "\n# BEGIN ProRank SEO WebP\n";
        $webp_rules .= "<IfModule mod_rewrite.c>\n";
        $webp_rules .= "RewriteEngine On\n\n";
        $webp_rules .= "# Check if browser supports WebP\n";
        $webp_rules .= "RewriteCond %{HTTP_ACCEPT} image/webp\n\n";
        $webp_rules .= "# Check if WebP version exists\n";
        $webp_rules .= "RewriteCond %{REQUEST_FILENAME}.webp -f\n\n";
        $webp_rules .= "# Serve WebP version\n";
        $webp_rules .= "RewriteRule ^(.+)\\.(jpe?g|png)$ $1.$2.webp [T=image/webp,E=accept:1]\n";
        $webp_rules .= "</IfModule>\n\n";
        $webp_rules .= "<IfModule mod_headers.c>\n";
        $webp_rules .= "Header append Vary Accept env=REDIRECT_accept\n";
        $webp_rules .= "</IfModule>\n\n";
        $webp_rules .= "<IfModule mod_mime.c>\n";
        $webp_rules .= "AddType image/webp .webp\n";
        $webp_rules .= "</IfModule>\n";
        $webp_rules .= "# END ProRank SEO WebP\n";

		return $webp_rules . $rules;
	}
	
	/**
	 * Preload modern format images
	 */
	public function preload_modern_formats(): void {
		if ( ! is_singular() ) {
			return;
		}
		
		$post_id = get_the_ID();
		$thumbnail_id = get_post_thumbnail_id( $post_id );
		
		if ( ! $thumbnail_id ) {
			return;
		}
		
		// Check for WebP version
		$webp_path = get_post_meta( $thumbnail_id, '_prorank_webp_path', true );
		if ( $webp_path && file_exists( $webp_path ) ) {
			$webp_url = $this->path_to_url( $webp_path );
			printf(
				'<link rel="preload" as="image" href="%s" type="image/webp">' . "\n",
				esc_url( $webp_url )
			);
		}
		
		// Check for AVIF version
		$avif_path = get_post_meta( $thumbnail_id, '_prorank_avif_path', true );
		if ( $avif_path && file_exists( $avif_path ) ) {
			$avif_url = $this->path_to_url( $avif_path );
			printf(
				'<link rel="preload" as="image" href="%s" type="image/avif">' . "\n",
				esc_url( $avif_url )
			);
		}
	}
	
	/**
	 * Check if processing should occur
	 *
	 * @return bool
	 */
	private function should_process(): bool {
		// Don't process in admin
		if ( is_admin() ) {
			return false;
		}
		
		// Don't process for REST API requests
		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return false;
		}
		
		// Check if modules are enabled
		$settings = $this->plugin->settings();
		return $settings->get( 'images_convert_to_webp', false ) || 
		       $settings->get( 'images_convert_to_avif', false );
	}
	
	/**
	 * Check if browser supports WebP
	 *
	 * @return bool
	 */
	private function browser_supports_webp(): bool {
		return isset( $_SERVER['HTTP_ACCEPT'] ) && 
		       strpos( $_SERVER['HTTP_ACCEPT'], 'image/webp' ) !== false;
	}
	
	/**
	 * Get attachment ID from URL
	 *
	 * @param string $url Image URL.
	 * @return int
	 */
	private function get_attachment_id_from_url( string $url ): int {
		global $wpdb;

		$url = html_entity_decode( $url, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
		$url = $this->normalize_photon_url_to_origin( $url ) ?: $url;
		$url = strtok( $url, '?#' );
		if ( ! is_string( $url ) || $url === '' ) {
			return 0;
		}
		
		$upload_dir = wp_upload_dir();
		$base_url = $upload_dir['baseurl'];
		
		// Remove base URL, allowing scheme/host normalization.
		$file_path = '';
		if ( strpos( $url, trailingslashit( $base_url ) ) === 0 ) {
			$file_path = str_replace( trailingslashit( $base_url ), '', $url );
		} else {
			$base_path = (string) wp_parse_url( $base_url, PHP_URL_PATH );
			$url_path = (string) wp_parse_url( $url, PHP_URL_PATH );
			$base_path = trim( $base_path, '/' );
			$url_path = trim( $url_path, '/' );
			if ( $base_path !== '' && strpos( $url_path, $base_path . '/' ) === 0 ) {
				$file_path = substr( $url_path, strlen( $base_path ) + 1 );
			}
		}

		if ( $file_path === '' ) {
			return 0;
		}
		
		// Remove size suffix
		$file_path = preg_replace( '/-\d+x\d+(\.[^.]+)$/i', '$1', $file_path );
		
		// Query database
		$attachment_id = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT ID FROM {$wpdb->posts} 
				JOIN {$wpdb->postmeta} ON {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id 
				WHERE {$wpdb->posts}.post_type = 'attachment' 
				AND {$wpdb->postmeta}.meta_key = '_wp_attached_file' 
				AND {$wpdb->postmeta}.meta_value = %s",
				$file_path
			)
		);
		
		return (int) $attachment_id;
	}

	/**
	 * Extract WordPress attachment ID from common image classes.
	 *
	 * @param string $attributes Raw img tag attributes.
	 * @return int
	 */
	private function get_attachment_id_from_attributes( string $attributes ): int {
		if ( preg_match( '/\bwp-image-(\d+)\b/i', $attributes, $matches ) ) {
			return (int) $matches[1];
		}

		return 0;
	}

	/**
	 * Convert Jetpack Photon image URLs back to the local origin URL.
	 *
	 * Photon emits URLs like https://i0.wp.com/example.com/wp-content/uploads/x.jpg.
	 * ProRank variants are generated on the origin filesystem, so attachment
	 * lookup and fallback delivery must work from the origin URL.
	 *
	 * @param string $url Image URL.
	 * @return string|null Origin URL when the input is a Photon URL.
	 */
	private function normalize_photon_url_to_origin( string $url ): ?string {
		$url = trim( html_entity_decode( $url, ENT_QUOTES | ENT_HTML5, 'UTF-8' ) );
		if ( $url === '' ) {
			return null;
		}

		$parts = wp_parse_url( $url );
		if ( ! is_array( $parts ) ) {
			return null;
		}

		$host = strtolower( (string) ( $parts['host'] ?? '' ) );
		if ( ! preg_match( '/^i\d?\.wp\.com$/', $host ) ) {
			return null;
		}

		$path = ltrim( (string) ( $parts['path'] ?? '' ), '/' );
		if ( $path === '' ) {
			return null;
		}

		$site_hosts = array_filter( array_unique( [
			strtolower( (string) wp_parse_url( home_url(), PHP_URL_HOST ) ),
			strtolower( (string) wp_parse_url( site_url(), PHP_URL_HOST ) ),
			strtolower( (string) wp_parse_url( (string) wp_upload_dir()['baseurl'], PHP_URL_HOST ) ),
		] ) );

		foreach ( $site_hosts as $site_host ) {
			$site_host = preg_replace( '/^www\./', '', $site_host );
			if ( ! is_string( $site_host ) || $site_host === '' ) {
				continue;
			}

			$path_host = strtolower( strtok( $path, '/' ) ?: '' );
			$normalized_path_host = preg_replace( '/^www\./', '', $path_host );
			if ( $normalized_path_host !== $site_host ) {
				continue;
			}

			$origin_path = substr( $path, strlen( $path_host ) );
			$origin_path = '/' . ltrim( (string) $origin_path, '/' );
			if ( $origin_path === '/' ) {
				return null;
			}

			return home_url( $origin_path );
		}

		return null;
	}

	/**
	 * Replace Photon src/srcset URLs in an img tag with origin URLs.
	 *
	 * @param string $img_tag Image tag HTML.
	 * @return string
	 */
	private function normalize_img_tag_image_urls( string $img_tag ): string {
		if ( stripos( $img_tag, '.wp.com/' ) === false ) {
			return $img_tag;
		}

		$updated = preg_replace_callback(
			'/(src|srcset)=([\"\'])(.*?)\2/i',
			function ( array $matches ): string {
				$attribute = (string) $matches[1];
				$quote = (string) $matches[2];
				$value = (string) $matches[3];

				if ( strtolower( $attribute ) === 'srcset' ) {
					$value = preg_replace_callback(
						'/(https?:\/\/i\d?\.wp\.com\/[^\s,]+)/i',
						function ( array $url_matches ): string {
							$origin = $this->normalize_photon_url_to_origin( (string) $url_matches[1] );
							return $origin ?: (string) $url_matches[1];
						},
						$value
					) ?: $value;
				} else {
					$value = $this->normalize_photon_url_to_origin( $value ) ?: $value;
				}

				$escaped_value = strtolower( $attribute ) === 'srcset' ? esc_attr( $value ) : esc_url( $value );

				return $attribute . '=' . $quote . $escaped_value . $quote;
			},
			$img_tag
		);

		return is_string( $updated ) ? $updated : $img_tag;
	}
	
	/**
	 * Convert file path to URL
	 *
	 * @param string $path File path.
	 * @return string
	 */
	private function path_to_url( string $path ): string {
		$upload_dir = wp_upload_dir();
		return str_replace( $upload_dir['basedir'], $upload_dir['baseurl'], $path );
	}
}
