<?php
/**
 * Email Inliner for WAWP.
 *
 * @package automation-web-platform
 */

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

/**
 * Class WAWP_Email_Inliner
 *
 * @package automation-web-platform
 */
class WAWP_Email_Inliner {

	/**
	 * Inlines CSS into HTML style attributes.
	 *
	 * @param string $html The full HTML document.
	 * @return string The HTML with inlined styles.
	 */
	public static function inline( $html ) {
		if ( empty( $html ) ) {
			return $html;
		}

		// Save and restore error handling to avoid DOMDocument warnings on invalid HTML.
		$internal_errors = libxml_use_internal_errors( true );
		$dom             = new DOMDocument();

		// Load HTML with UTF-8 support.
		$dom->loadHTML( '<?xml encoding="utf-8" ?>' . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
		$xpath = new DOMXPath( $dom );

		$styles    = $dom->getElementsByTagName( 'style' );
		$css_rules = array();

		// Extract CSS from <style> tags.
		foreach ( $styles as $style ) {
			$css       = $style->{'nodeValue'};
			$css_rules = array_merge( $css_rules, self::parse_css( $css ) );
		}

		// Apply rules to elements.
		foreach ( $css_rules as $selector => $properties ) {
			try {
				// Convert CSS selector to XPath (very basic support).
				$xpath_selector = self::selector_to_xpath( $selector );
				if ( ! $xpath_selector ) {
					continue;
				}

				$elements = $xpath->query( $xpath_selector );
				if ( $elements ) {
					foreach ( $elements as $element ) {
						$existing_style = $element->getAttribute( 'style' );
						$new_style      = self::merge_styles( $existing_style, $properties );
						$element->setAttribute( 'style', $new_style );
					}
				}
			} catch ( Exception $e ) {
				// Skip invalid selectors.
				continue;
			}
		}

		// Optionally remove <style> tags (though some clients like media queries)
		// We'll keep them for responsiveness but the elements now have inline styles as well.

		$output = $dom->saveHTML();

		// Remove the XML encoding tag we added.
		$output = str_replace( '<?xml encoding="utf-8" ?>', '', $output );
		libxml_use_internal_errors( $internal_errors );

		return $output;
	}

	/**
	 * Parse CSS into an array of rules.
	 *
	 * @param string $css CSS string.
	 * @return array
	 */
	private static function parse_css( $css ) {
		$rules = array();

		// Remove comments.
		$css = preg_replace( '/\/\*.*?\*\//s', '', $css );

		// Match rules: selector { properties }.
		preg_match_all( '/([^{]+)\s*\{\s*([^}]+)\s*\}/', $css, $matches, PREG_SET_ORDER );
		foreach ( $matches as $match ) {
			$selectors = explode( ',', $match[1] );
			foreach ( $selectors as $selector ) {
				$selector = trim( $selector );
				if ( empty( $selector ) || 0 === strpos( $selector, '@' ) ) {
					// Skip media queries and empty selectors.
					continue;
				}
				$rules[ $selector ] = trim( $match[2] );
			}
		}

		return $rules;
	}

	/**
	 * Convert CSS selector to XPath.
	 *
	 * @param string $selector CSS selector.
	 * @return string|null
	 */
	private static function selector_to_xpath( $selector ) {
		$selector = trim( $selector );
		if ( empty( $selector ) ) {
			return null;
		}

		// Handle comma separated selectors.
		if ( strpos( $selector, ',' ) !== false ) {
			$parts  = explode( ',', $selector );
			$xpaths = array();
			foreach ( $parts as $p ) {
				$xp = self::selector_to_xpath( $p );
				if ( $xp ) {
					$xpaths[] = ltrim( $xp, '/' );
				}
			}
			return empty( $xpaths ) ? null : '//' . implode( ' | //', $xpaths );
		}

		// Handle space separated (descendant) selectors.
		if ( strpos( $selector, ' ' ) !== false ) {
			$parts = preg_split( '/\s+/', $selector );
			$xpath = '';
			foreach ( $parts as $p ) {
				$xp = self::selector_to_xpath( $p );
				if ( ! $xp ) {
					return null;
				}
				$xpath .= ( $xpath ? '/' : '' ) . ltrim( $xp, '/' );
			}
			return '//' . $xpath;
		}

		// Handle compound selectors (e.g. div.class#id or .class1.class2).
		// Match: Tag (optional) followed by any number of .class or #id.
		if ( preg_match( '/^([a-zA-Z0-9*]*)((\.[a-zA-Z0-9_-]+|#[a-zA-Z0-9_-]+)*)$/', $selector, $m ) ) {
			$tag     = ! empty( $m[1] ) ? $m[1] : '*';
			$filters = array();
			if ( ! empty( $m[2] ) ) {
				preg_match_all( '/(\.[a-zA-Z0-9_-]+|#[a-zA-Z0-9_-]+)/', $m[2], $matches );
				foreach ( $matches[0] as $match ) {
					if ( '.' === $match[0] ) {
						$class     = substr( $match, 1 );
						$filters[] = "contains(concat(' ', normalize-space(@class), ' '), ' $class ')";
					} elseif ( '#' === $match[0] ) {
						$id        = substr( $match, 1 );
						$filters[] = "@id='$id'";
					}
				}
			}

			$xpath = "//$tag";
			if ( ! empty( $filters ) ) {
				$xpath .= '[' . implode( ' and ', $filters ) . ']';
			}

			return $xpath;
		}

		// Handle Attribute selectors (e.g. [width="100%"] or table[cellpadding="0"]).
		if ( preg_match( '/^([a-zA-Z0-9*]*)\[([a-zA-Z0-9_-]+)=[\'"]?([^\'"]+)[\'"]?\]$/', $selector, $m ) ) {
			$tag = ! empty( $m[1] ) ? $m[1] : '*';
			return "//{$tag}[@{$m[2]}='{$m[3]}']";
		}

		return null;
	}

	/**
	 * Merge two style strings.
	 *
	 * @param string $existing Existing style.
	 * @param string $new_styles New style.
	 * @return string
	 */
	private static function merge_styles( $existing, $new_styles ) {
		$existing   = trim( $existing );
		$new_styles = trim( $new_styles );

		if ( empty( $new_styles ) ) {
			return $existing;
		}
		if ( empty( $existing ) ) {
			return $new_styles;
		}

		// Ensure new ends with semicolon.
		if ( ';' !== substr( $new_styles, -1 ) ) {
			$new_styles .= ';';
		}

		// New (CSS) comes first, Existing (Inline) comes last so it wins.
		return $new_styles . ' ' . $existing;
	}
}
