<?php
/**
 * EmailBlockRenderer — converts block JSON into email-safe HTML.
 *
 * @package CartCue
 */

namespace CartCue;

defined( 'ABSPATH' ) || exit;

final class EmailBlockRenderer {

	/**
	 * Convert a block JSON string/array into inner email HTML.
	 *
	 * @param string|array<mixed> $blocks Block JSON string or decoded array.
	 */
	/**
	 * @param string|array<mixed> $raw Block JSON string, block list, or { blocks, styles } document.
	 * @return array{blocks: array<int, array<string, mixed>>, styles: array<string, string>}
	 */
	public static function parse_document( $raw ): array {
		if ( is_string( $raw ) ) {
			$raw = json_decode( $raw, true );
		}
		if ( ! is_array( $raw ) ) {
			return [ 'blocks' => [], 'styles' => [] ];
		}
		if ( isset( $raw['blocks'] ) && is_array( $raw['blocks'] ) ) {
			return [
				'blocks' => $raw['blocks'],
				'styles' => is_array( $raw['styles'] ?? null ) ? $raw['styles'] : [],
			];
		}
		return [ 'blocks' => $raw, 'styles' => [] ];
	}

	public static function to_html( $blocks ): string {
		$doc    = self::parse_document( $blocks );
		$blocks = $doc['blocks'];
		$styles = self::normalize_styles( $doc['styles'] );

		if ( empty( $blocks ) ) {
			return '';
		}

		$parts = [];
		foreach ( $blocks as $block ) {
			$type = sanitize_key( $block['type'] ?? '' );
			$html = '';
			switch ( $type ) {
				case 'text':
					$html = self::render_text( $block, $styles );
					break;
				case 'button':
					$html = self::render_button( $block, $styles );
					break;
				case 'logo':
					$html = self::render_logo( $block );
					break;
				case 'image':
					$html = self::render_image( $block );
					break;
				case 'columns':
					$html = self::render_columns( $block );
					break;
				case 'divider':
					$html = self::render_divider( $block );
					break;
				case 'spacer':
					$html = self::render_spacer( $block );
					break;
				case 'billing_shipping':
					$html = self::render_billing_shipping( $block );
					break;
			}
			if ( '' !== $html ) {
				$parts[] = $html;
			}
		}

		return implode( "\n", $parts );
	}

	public static function is_builder_content( string $body ): bool {
		$trimmed = ltrim( $body );
		if ( '' === $trimmed ) {
			return false;
		}
		$first = $trimmed[0];
		if ( '[' !== $first && '{' !== $first ) {
			return false;
		}
		$decoded = json_decode( $trimmed, true );
		if ( ! is_array( $decoded ) ) {
			return false;
		}
		if ( isset( $decoded['blocks'] ) && is_array( $decoded['blocks'] ) ) {
			$blocks = $decoded['blocks'];
			if ( empty( $blocks ) ) {
				return false;
			}
			$first_block = $blocks[0] ?? null;
			return is_array( $first_block ) && isset( $first_block['type'] );
		}
		if ( empty( $decoded ) ) {
			return false;
		}
		$first_block = $decoded[0] ?? null;
		return is_array( $first_block ) && isset( $first_block['type'] );
	}

	/**
	 * Sanitize email body on save — builder JSON must not pass through wp_kses_post.
	 *
	 * @param string $raw       Raw POST value.
	 * @param string $body_mode Action setting body_mode (builder|classic).
	 */
	public static function sanitize_body_setting( string $raw, string $body_mode = '' ): string {
		if ( 'builder' === $body_mode || self::is_builder_content( $raw ) ) {
			$doc = self::parse_document( $raw );
			$sanitized = [
				'v'      => 1,
				'blocks' => self::sanitize_blocks( $doc['blocks'] ),
				'styles' => self::sanitize_styles( $doc['styles'] ),
			];
			$encoded = wp_json_encode( $sanitized );
			return is_string( $encoded ) ? $encoded : wp_json_encode( [ 'v' => 1, 'blocks' => [], 'styles' => [] ] );
		}

		return wp_kses_post( $raw );
	}

	/**
	 * @param array<int, array<string, mixed>> $blocks
	 * @return array<int, array<string, mixed>>
	 */
	private static function sanitize_blocks( array $blocks ): array {
		$out = [];
		foreach ( $blocks as $block ) {
			if ( ! is_array( $block ) ) {
				continue;
			}
			$out[] = self::sanitize_single_block( $block );
		}
		return $out;
	}

	/**
	 * @param array<string, mixed> $block
	 * @return array<string, mixed>
	 */
	private static function sanitize_single_block( array $block ): array {
		$type  = sanitize_key( (string) ( $block['type'] ?? '' ) );
		$clean = [ 'type' => $type ];
		foreach ( $block as $key => $value ) {
			if ( 'type' === $key ) {
				continue;
			}
			$key = sanitize_key( $key );
			if ( is_array( $value ) ) {
				$clean[ $key ] = array_map(
					static function ( $v ) {
						if ( is_array( $v ) ) {
							return self::sanitize_single_block( $v );
						}
						return is_string( $v ) ? wp_kses_post( $v ) : $v;
					},
					$value
				);
			} elseif ( is_string( $value ) ) {
				$clean[ $key ] = in_array( $key, [ 'content', 'text' ], true )
					? wp_kses_post( $value )
					: sanitize_text_field( $value );
			} else {
				$clean[ $key ] = $value;
			}
		}
		return $clean;
	}

	/**
	 * @param array<string, string> $styles
	 * @return array<string, string>
	 */
	private static function normalize_styles( array $styles ): array {
		$defaults = [
			'branding_color' => '#534AB7',
			'text_color'     => '#333333',
			'text_font'      => 'Arial, Helvetica, sans-serif',
			'text_size'      => '15px',
			'content_bg'     => '#ffffff',
			'outer_bg'       => '#f5f5f5',
		];
		$out = $defaults;
		foreach ( $styles as $key => $value ) {
			if ( ! is_string( $value ) || ! isset( $defaults[ $key ] ) ) {
				continue;
			}
			$out[ $key ] = $value;
		}
		$out['branding_color'] = self::safe_color( $out['branding_color'], $defaults['branding_color'] );
		$out['text_color']     = self::safe_color( $out['text_color'], $defaults['text_color'] );
		$out['content_bg']     = self::safe_color( $out['content_bg'], $defaults['content_bg'] );
		$out['outer_bg']       = self::safe_color( $out['outer_bg'], $defaults['outer_bg'] );
		$out['text_font']      = sanitize_text_field( $out['text_font'] );
		$out['text_size']      = preg_match( '/^\d{1,2}px$/', $out['text_size'] ) ? $out['text_size'] : $defaults['text_size'];
		return $out;
	}

	/**
	 * @param array<string, mixed> $styles
	 * @return array<string, string>
	 */
	private static function sanitize_styles( array $styles ): array {
		return self::normalize_styles( array_map( 'strval', $styles ) );
	}

	private static function render_text( array $block, array $styles ): string {
		$content = wp_kses_post( $block['content'] ?? '' );
		$align   = in_array( $block['align'] ?? 'left', [ 'left', 'center', 'right' ], true )
			? $block['align']
			: 'left';
		$font    = $styles['text_font'];
		$size    = $styles['text_size'];
		$color   = $styles['text_color'];

		return sprintf(
			'<div style="font-family:%s;font-size:%s;line-height:1.7;color:%s;text-align:%s;margin:0 0 16px 0;">%s</div>',
			esc_attr( $font ),
			esc_attr( $size ),
			esc_attr( $color ),
			esc_attr( $align ),
			$content
		);
	}

	private static function render_button( array $block, array $styles ): string {
		$text  = esc_html( $block['text'] ?? 'Click here' );
		$url   = self::esc_href( $block['url'] ?? '#' );
		$bg    = self::safe_color( $block['bg_color'] ?? $styles['branding_color'] );
		$fg    = self::safe_color( $block['text_color'] ?? '#ffffff' );
		$align = in_array( $block['align'] ?? 'center', [ 'left', 'center', 'right' ], true )
			? $block['align']
			: 'center';
		$font  = $styles['text_font'];

		return sprintf(
			'<table role="presentation" width="100%%" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 20px 0;">
				<tr>
					<td align="%s">
						<a href="%s" style="display:inline-block;background:%s;color:%s;font-family:%s;font-size:15px;font-weight:600;text-decoration:none;padding:13px 28px;border-radius:6px;line-height:1;">%s</a>
					</td>
				</tr>
			</table>',
			esc_attr( $align ),
			$url,
			esc_attr( $bg ),
			esc_attr( $fg ),
			esc_attr( $font ),
			$text
		);
	}

	private static function render_logo( array $block ): string {
		$logo_url = Variables::get_store_logo_url();
		if ( ! $logo_url ) {
			return '';
		}

		$width = max( 60, min( 300, (int) ( $block['width'] ?? 140 ) ) );
		$align = in_array( $block['align'] ?? 'left', [ 'left', 'center', 'right' ], true )
			? $block['align']
			: 'left';

		return sprintf(
			'<table role="presentation" width="100%%" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 16px 0;">'
			. '<tr><td align="%s">'
			. '<img src="%s" alt="%s" width="%d" style="display:block;max-width:100%%;height:auto;border:0;">'
			. '</td></tr></table>',
			esc_attr( $align ),
			esc_url( $logo_url ),
			esc_attr( get_bloginfo( 'name' ) ),
			$width
		);
	}

	private static function render_image( array $block ): string {
		$src = self::esc_href( $block['src'] ?? '', '' );
		if ( ! $src ) {
			return '';
		}

		$alt   = esc_attr( $block['alt'] ?? '' );
		$width = max( 50, min( 600, (int) ( $block['width'] ?? 300 ) ) );
		$align = in_array( $block['align'] ?? 'center', [ 'left', 'center', 'right' ], true )
			? $block['align']
			: 'center';
		$link  = self::esc_href( $block['link'] ?? '', '' );

		$image = sprintf(
			'<img src="%s" alt="%s" width="%d" style="display:block;max-width:100%%;height:auto;border:0;">',
			$src,
			$alt,
			$width
		);

		if ( $link ) {
			$image = '<a href="' . $link . '" style="display:block;">' . $image . '</a>';
		}

		return sprintf(
			'<table role="presentation" width="100%%" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 20px 0;">
				<tr><td align="%s">%s</td></tr>
			</table>',
			esc_attr( $align ),
			$image
		);
	}

	private static function render_columns( array $block ): string {
		$cols = (array) ( $block['cols'] ?? [] );
		if ( empty( $cols ) ) {
			return '';
		}

		$base_style = 'font-family:Arial,Helvetica,sans-serif;font-size:15px;line-height:1.7;color:#333333;vertical-align:top;';
		$left       = wp_kses_post( $cols[0]['content'] ?? '' );
		$right      = wp_kses_post( $cols[1]['content'] ?? '' );

		return sprintf(
			'<table role="presentation" width="100%%" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 20px 0;">
				<tr>
					<td width="48%%" style="%s padding-right:16px;">%s</td>
					<td width="4%%"></td>
					<td width="48%%" style="%s">%s</td>
				</tr>
			</table>',
			$base_style,
			$left,
			$base_style,
			$right
		);
	}

	private static function render_divider( array $block ): string {
		$color = self::safe_color( $block['color'] ?? '#e5e4f0' );

		return sprintf(
			'<table role="presentation" width="100%%" cellpadding="0" cellspacing="0" border="0" style="margin:8px 0 24px 0;">
				<tr><td style="border-top:1px solid %s;font-size:0;line-height:0;">&nbsp;</td></tr>
			</table>',
			esc_attr( $color )
		);
	}

	private static function render_spacer( array $block ): string {
		$height = max( 4, min( 120, (int) ( $block['height'] ?? 24 ) ) );
		return sprintf( '<div style="height:%dpx;font-size:0;line-height:0;">&nbsp;</div>', $height );
	}

	private static function render_billing_shipping( array $block ): string {
		$billing  = wp_kses_post( $block['billing'] ?? '{{order.billing_address}}' );
		$shipping = wp_kses_post( $block['shipping'] ?? '{{order.shipping_address}}' );

		return sprintf(
			'<table role="presentation" width="100%%" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 20px 0;">
				<tr>
					<td width="48%%" style="font-family:Arial,Helvetica,sans-serif;font-size:14px;vertical-align:top;padding-right:16px;">
						<strong>%s</strong><br>%s
					</td>
					<td width="4%%"></td>
					<td width="48%%" style="font-family:Arial,Helvetica,sans-serif;font-size:14px;vertical-align:top;">
						<strong>%s</strong><br>%s
					</td>
				</tr>
			</table>',
			esc_html__( 'Billing', 'cartcue-sales-automation' ),
			$billing,
			esc_html__( 'Shipping', 'cartcue-sales-automation' ),
			$shipping
		);
	}

	private static function safe_color( string $color, string $default = '#534AB7' ): string {
		$sanitized = sanitize_hex_color( $color );
		return $sanitized ? $sanitized : $default;
	}

	/**
	 * Escape a URL for href/src, preserving {{variable}} tokens.
	 */
	private static function esc_href( string $url, string $empty_fallback = '#' ): string {
		$url = trim( $url );
		if ( '' === $url ) {
			return esc_url( $empty_fallback );
		}
		if ( false !== strpos( $url, '{{' ) ) {
			return esc_attr( $url );
		}
		$escaped = esc_url( $url );
		return $escaped ? $escaped : esc_url( $empty_fallback );
	}
}
