<?php
/**
 * Email template renderer.
 *
 * @package MailDoctor
 */

namespace MailDoctor;

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

/**
 * Handles rendering custom email templates.
 */
class WPMD_Email_Renderer {

	/**
	 * Render email HTML using provided template data and tokens.
	 *
	 * @param array<string, string> $template Template data.
	 * @param array<string, string> $tokens   Token map.
	 * @return string
	 */
	public static function render( array $template, array $tokens ): string {
		$html = isset( $template['html'] ) ? (string) $template['html'] : '';
		$css  = isset( $template['css'] ) ? (string) $template['css'] : '';

		if ( '' === $html && '' === $css ) {
			return '';
		}

		$replacements = self::format_tokens_for_replacement( $tokens );

		$html = self::replace_tokens( $html, $replacements );
		$css  = self::replace_tokens( $css, $replacements );

		$css  = self::sanitize_css( $css );
		$html = self::sanitize_html( $html );

		if ( '' !== $css ) {
			$html = '<style>' . $css . '</style>' . $html;
		}

		Mailer::trace_maildbg(
			'template.rendered',
			array(
				'html_bytes' => strlen( $html ),
				'css_bytes'  => strlen( $css ),
			)
		);

		return $html;
	}

	/**
	 * Replace {{token}} placeholders with values.
	 *
	 * @param string                $content     Raw content.
	 * @param array<string, string> $replacements Replacement map.
	 * @return string
	 */
	public static function replace_tokens( string $content, array $replacements ): string {
		if ( '' === $content || empty( $replacements ) ) {
			return $content;
		}

		return preg_replace_callback(
			'/\{\{\s*([a-zA-Z0-9_\.\-]+)\s*\}\}/',
			static function ( $matches ) use ( $replacements ) {
				$raw_key     = (string) $matches[1];
				$normalized  = self::normalize_token_key( $raw_key );
				$replacement = $replacements[ $normalized ] ?? $replacements[ $raw_key ] ?? '';

				return $replacement;
			},
			$content
		);
	}

	/**
	 * Prepare token replacements by ensuring scalar values.
	 *
	 * @param array<string, mixed> $tokens Token map.
	 * @return array<string, string>
	 */
	protected static function format_tokens_for_replacement( array $tokens ): array {
		$replacements = array();

		foreach ( $tokens as $token => $value ) {
			if ( ! is_scalar( $value ) ) {
				continue;
			}

			$key = self::normalize_token_key( (string) $token );

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

			$replacements[ $key ] = (string) $value;
		}

		return $replacements;
	}

	/**
	 * Sanitize template HTML allowing safe tags.
	 *
	 * @param string $html Raw HTML.
	 * @return string
	 */
	protected static function sanitize_html( string $html ): string {
		if ( '' === $html ) {
			return '';
		}

		$allowed                         = wp_kses_allowed_html( 'post' );
		$allowed['table']['cellpadding'] = true;
		$allowed['table']['cellspacing'] = true;
		$allowed['table']['role']        = true;
		$allowed['th']['scope']          = true;
		$allowed['td']['align']          = true;
		$allowed['tr']['align']          = true;
		// Ensure common <img> attributes (including src) are allowed for templates.
		$allowed['img']['style']   = true;
		$allowed['img']['src']     = true;
		$allowed['img']['alt']     = true;
		$allowed['img']['width']   = true;
		$allowed['img']['height']  = true;
		$allowed['td']['style']    = true;
		$allowed['th']['style']    = true;
		$allowed['tr']['style']    = true;
		$allowed['table']['style'] = true;
		$allowed['div']['style']   = true;
		$allowed['span']['style']  = true;
		$allowed['p']['style']     = true;
		$allowed['a']['style']     = true;

		return wp_kses( $html, $allowed );
	}

	/**
	 * Sanitize inline CSS.
	 *
	 * @param string $css Raw CSS string.
	 * @return string
	 */
	protected static function sanitize_css( string $css ): string {
		if ( '' === $css ) {
			return '';
		}

		$css = str_replace( array( '</style>', '<?', '?>' ), '', $css );

		/**
		 * Filter sanitized CSS prior to inclusion.
		 *
		 * @param string $css CSS string.
		 */
		return apply_filters( 'mail_doctor_email_css', trim( $css ) );
	}

	/**
	 * Normalise a token key for lookups.
	 *
	 * @param string $token Raw token identifier.
	 * @return string
	 */
	protected static function normalize_token_key( string $token ): string {
		$token = strtolower( trim( $token ) );

		if ( '' === $token ) {
			return '';
		}

		if ( ! preg_match( '/^[a-z0-9\._\-]+$/', $token ) ) {
			return '';
		}

		return $token;
	}
}
