<?php

declare(strict_types=1);

namespace KitgenixImageOptimizer\Core;

defined( 'ABSPATH' ) || exit;

/**
 * Serves WebP/AVIF sidecar files to supporting browsers by wrapping <img>
 * elements in <picture> with <source type="image/avif"> and/or
 * <source type="image/webp"> – AVIF listed first since browsers pick the
 * first source they support, and AVIF is usually the smaller of the two.
 *
 * Sidecars are named <original-file>.<ext> (e.g. photo.jpg.webp,
 * photo.jpg.avif) and are generated by Optimizer::optimize_file() when the
 * corresponding conversion setting is on AND the server can actually encode
 * that format.
 */
final class Webp_Delivery {

	public static function init(): void {
		if ( ! Settings::convert_to_webp() && ! Settings::convert_to_avif() ) {
			return;
		}

		// Wrap <img> tags in post content and featured images.
		add_filter( 'the_content', [ self::class, 'wrap_content_images' ], 99 );
		add_filter( 'post_thumbnail_html', [ self::class, 'wrap_content_images' ], 99 );

		// Wrap wp_get_attachment_image() output.
		add_filter( 'wp_get_attachment_image', [ self::class, 'wrap_single_image' ], 99 );
	}

	/**
	 * Scan an HTML block for <img> tags and wrap eligible ones with <picture>.
	 */
	public static function wrap_content_images( string $html ): string {
		return (string) preg_replace_callback(
			'/<img\b[^>]*>/i',
			static function ( array $match ): string {
				return self::img_to_picture( $match[0] );
			},
			$html
		);
	}

	/**
	 * Wrap a single <img> string returned by wp_get_attachment_image().
	 */
	public static function wrap_single_image( string $html ): string {
		return self::img_to_picture( $html );
	}

	/**
	 * Attempt to wrap a single <img> tag with a <picture> element.
	 * Returns the original tag unchanged when no sidecar is found.
	 */
	private static function img_to_picture( string $img_html ): string {
		if ( ! preg_match( '/\bsrc=["\']([^"\']+)["\']/', $img_html, $src_m ) ) {
			return $img_html;
		}

		$src_url = $src_m[1];

		// Only process JPEG and PNG source images.
		if ( ! preg_match( '/\.(jpe?g|png)(\?[^"\']*)?$/i', $src_url ) ) {
			return $img_html;
		}

		$upload_dir = wp_upload_dir();
		$base_url   = trailingslashit( $upload_dir['baseurl'] );
		$base_dir   = trailingslashit( $upload_dir['basedir'] );

		// Normalise both URLs to the same scheme so http/https mismatches don't break matching.
		$src_normalised  = set_url_scheme( $src_url );
		$base_normalised = set_url_scheme( $base_url );

		// Only process images hosted in the uploads directory.
		if ( ! str_starts_with( $src_normalised, $base_normalised ) ) {
			return $img_html;
		}

		$src_rel = (string) strtok( substr( $src_normalised, strlen( $base_normalised ) ), '?' );

		$sizes_attr = '';
		if ( preg_match( '/\bsizes=["\']([^"\']+)["\']/', $img_html, $sz_m ) ) {
			$sizes_attr = ' sizes="' . esc_attr( $sz_m[1] ) . '"';
		}

		$srcset_attr = '';
		if ( preg_match( '/\bsrcset=["\']([^"\']+)["\']/', $img_html, $ss_m ) ) {
			$srcset_attr = $ss_m[1];
		}

		$sources = '';

		// AVIF first – browsers use the first <source> they support.
		if ( Settings::convert_to_avif() ) {
			$sources .= self::build_source_tag( 'avif', 'image/avif', $src_rel, $srcset_attr, $sizes_attr, $base_url, $base_dir );
		}
		if ( Settings::convert_to_webp() ) {
			$sources .= self::build_source_tag( 'webp', 'image/webp', $src_rel, $srcset_attr, $sizes_attr, $base_url, $base_dir );
		}

		if ( '' === $sources ) {
			return $img_html;
		}

		return '<picture>' . $sources . $img_html . '</picture>';
	}

	/**
	 * Build a single <source> tag for one sidecar format, or an empty string
	 * if no sidecar exists for the main image (and, when a srcset is present,
	 * for every entry in it).
	 */
	private static function build_source_tag( string $ext, string $mime, string $src_rel, string $srcset_attr, string $sizes_attr, string $base_url, string $base_dir ): string {
		$sidecar_abs = $base_dir . $src_rel . '.' . $ext;
		if ( ! file_exists( $sidecar_abs ) ) {
			return '';
		}

		$converted_srcset = '';
		if ( '' !== $srcset_attr ) {
			$converted_srcset = self::convert_srcset( $srcset_attr, $ext, $base_url, $base_dir );
		}

		if ( '' === $converted_srcset ) {
			$converted_srcset = esc_attr( $base_url . $src_rel . '.' . $ext );
		}

		return '<source type="' . esc_attr( $mime ) . '" srcset="' . $converted_srcset . '"' . $sizes_attr . '>';
	}

	/**
	 * Convert a JPEG/PNG srcset string to its sidecar-format equivalent.
	 * Returns an empty string if any entry is missing a sidecar (caller falls
	 * back to the single-src version for that format).
	 */
	private static function convert_srcset( string $srcset, string $ext, string $base_url, string $base_dir ): string {
		$parts            = array_filter( array_map( 'trim', explode( ',', $srcset ) ) );
		$converted_parts  = [];
		$base_url_norm    = set_url_scheme( $base_url );
		$base_url_norm_ln = strlen( $base_url_norm );

		foreach ( $parts as $part ) {
			$segs = (array) preg_split( '/\s+/', trim( $part ), 2 );
			$url  = (string) ( $segs[0] ?? '' );

			if ( '' === $url ) {
				continue;
			}

			$desc     = isset( $segs[1] ) ? ' ' . $segs[1] : '';
			$url_norm = set_url_scheme( $url );

			// Non-local URL – bail and let caller use the single-src fallback.
			if ( ! str_starts_with( $url_norm, $base_url_norm ) ) {
				return '';
			}

			$rel     = (string) strtok( substr( $url_norm, $base_url_norm_ln ), '?' );
			$sidecar = $base_dir . $rel . '.' . $ext;

			if ( ! file_exists( $sidecar ) ) {
				return '';
			}

			$converted_parts[] = esc_attr( $base_url . $rel . '.' . $ext ) . $desc;
		}

		return implode( ', ', $converted_parts );
	}
}
