<?php
/**
 * Structured article block renderer service.
 *
 * @package SmartyPress_AI_Engine
 */

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

/**
 * Renders normalized AI article blocks as Gutenberg blocks or Classic Editor HTML.
 */
class SmartyPress_AI_Content_Generator_Article_Block_Renderer {

	/**
	 * Supported article block types.
	 *
	 * @var string[]
	 */
	const SUPPORTED_BLOCK_TYPES = array(
		'lead',
		'heading',
		'paragraph',
		'list',
		'callout',
		'table',
		'image_placeholder',
		'separator',
		'cta',
	);

	/**
	 * Supported image marker styles.
	 *
	 * @var string[]
	 */
	const IMAGE_PLACEHOLDER_STYLES = array(
		'realistic_photo',
		'editorial_illustration',
		'light_infographic',
		'product_shot',
		'concept_art',
	);

	/**
	 * Normalize structured article data.
	 *
	 * @param array $article Raw article data.
	 * @return array
	 */
	public function normalize_article( $article ) {
		$article = is_array( $article ) ? $article : array();
		$content = is_array( $article['content'] ?? null ) ? $article['content'] : array();
		$blocks  = is_array( $content['blocks'] ?? null ) ? $content['blocks'] : array();

		$normalized_blocks = array();

		if ( ! empty( $content['lead'] ) ) {
			$normalized_blocks[] = array(
				'type' => 'lead',
				'text' => $this->sanitize_rich_text( $content['lead'] ),
			);
		}

		foreach ( $blocks as $block ) {
			$normalized = $this->normalize_block( $block );

			if ( ! empty( $normalized ) ) {
				$normalized_blocks[] = $normalized;
			}
		}

		return array(
			'title'        => sanitize_text_field( $article['title'] ?? '' ),
			'excerpt'      => sanitize_textarea_field( $article['excerpt'] ?? '' ),
			'seo_keywords' => $this->sanitize_string_list( $article['seo_keywords'] ?? array() ),
			'content'      => array(
				'toc'    => ! empty( $content['toc'] ),
				'blocks' => $normalized_blocks,
			),
		);
	}

	/**
	 * Render structured article data to Gutenberg block markup.
	 *
	 * @param array $article Raw or normalized article data.
	 * @return string
	 */
	public function to_gutenberg_blocks( $article ) {
		$article = $this->normalize_article( $article );
		$blocks  = $article['content']['blocks'];
		$output  = array();

		if ( ! empty( $article['content']['toc'] ) ) {
			$toc = $this->build_toc_items( $blocks );

			if ( ! empty( $toc ) ) {
				$output[] = $this->render_toc_gutenberg( $toc );
			}
		}

		foreach ( $blocks as $block ) {
			$rendered = $this->render_block_gutenberg( $block );

			if ( '' !== $rendered ) {
				$output[] = $rendered;
			}
		}

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

	/**
	 * Render structured article data to Classic Editor HTML.
	 *
	 * @param array $article Raw or normalized article data.
	 * @param array $options Render options.
	 * @return string
	 */
	public function to_classic_html( $article, $options = array() ) {
		$article = $this->normalize_article( $article );
		$blocks  = $article['content']['blocks'];
		$output  = array();

		if ( ! empty( $article['content']['toc'] ) ) {
			$toc = $this->build_toc_items( $blocks );

			if ( ! empty( $toc ) ) {
				$output[] = $this->render_toc_html( $toc );
			}
		}

		foreach ( $blocks as $block ) {
			$rendered = $this->render_block_html( $block, $options );

			if ( '' !== $rendered ) {
				$output[] = $rendered;
			}
		}

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

	/**
	 * Normalize a single article block.
	 *
	 * @param mixed $block Raw block.
	 * @return array
	 */
	private function normalize_block( $block ) {
		if ( ! is_array( $block ) ) {
			return array();
		}

		$type = sanitize_key( $block['type'] ?? '' );

		if ( ! in_array( $type, self::SUPPORTED_BLOCK_TYPES, true ) ) {
			return array();
		}

		if ( 'heading' === $type ) {
			$level = min( 4, max( 2, absint( $block['level'] ?? 2 ) ) );
			$text  = $this->sanitize_rich_text( $block['text'] ?? '' );

			if ( '' === $text ) {
				return array();
			}

			return array(
				'type'   => 'heading',
				'level'  => $level,
				'text'   => $text,
				'anchor' => $this->normalize_anchor( $block['anchor'] ?? '', wp_strip_all_tags( $text ) ),
			);
		}

		if ( in_array( $type, array( 'lead', 'paragraph' ), true ) ) {
			$text = $this->sanitize_rich_text( $block['text'] ?? '' );

			return '' !== $text ? array( 'type' => $type, 'text' => $text ) : array();
		}

		if ( 'list' === $type ) {
			$items = $this->sanitize_rich_text_list( $block['items'] ?? array() );

			return ! empty( $items ) ? array(
				'type'    => 'list',
				'ordered' => ! empty( $block['ordered'] ),
				'items'   => $items,
			) : array();
		}

		if ( 'callout' === $type ) {
			$content = $this->sanitize_rich_text( $block['content'] ?? ( $block['text'] ?? '' ) );

			if ( '' === $content ) {
				return array();
			}

			$style = sanitize_key( $block['style'] ?? 'highlight' );
			if ( ! in_array( $style, array( 'highlight', 'info', 'warning' ), true ) ) {
				$style = 'highlight';
			}

			return array(
				'type'    => 'callout',
				'style'   => $style,
				'title'   => sanitize_text_field( $block['title'] ?? '' ),
				'content' => $content,
			);
		}

		if ( 'table' === $type ) {
			$headers = $this->sanitize_string_list( $block['headers'] ?? array() );
			$rows    = array();

			foreach ( is_array( $block['rows'] ?? null ) ? $block['rows'] : array() as $row ) {
				if ( ! is_array( $row ) ) {
					continue;
				}

				$clean_row = array();
				foreach ( $row as $cell ) {
					$clean_row[] = $this->sanitize_rich_text( $cell );
				}

				if ( ! empty( array_filter( $clean_row ) ) ) {
					$rows[] = $clean_row;
				}
			}

			return ! empty( $rows ) ? array(
				'type'    => 'table',
				'headers' => $headers,
				'rows'    => $rows,
			) : array();
		}

		if ( 'image_placeholder' === $type ) {
			$text = sanitize_textarea_field( $block['text'] ?? ( $block['context'] ?? ( $block['prompt'] ?? '' ) ) );

			return '' !== $text ? array(
				'type'  => 'image_placeholder',
				'text'  => $text,
				'style' => $this->normalize_image_placeholder_style( $block['style'] ?? '' ),
			) : array();
		}

		if ( 'cta' === $type ) {
			$text = $this->sanitize_rich_text( $block['text'] ?? '' );

			if ( '' === $text ) {
				return array();
			}

			return array(
				'type'        => 'cta',
				'text'        => $text,
				'button_text' => sanitize_text_field( $block['button_text'] ?? '' ),
				'url'         => esc_url_raw( $block['url'] ?? '' ),
			);
		}

		return array( 'type' => 'separator' );
	}

	/**
	 * Render a block as Gutenberg markup.
	 *
	 * @param array $block Normalized block.
	 * @return string
	 */
	private function render_block_gutenberg( $block ) {
		switch ( $block['type'] ) {
			case 'lead':
				return $this->create_gutenberg_block(
					'core/paragraph',
					array( 'className' => 'smartypress-lead' ),
					'<p class="smartypress-lead">' . $block['text'] . '</p>'
				);

			case 'paragraph':
				return $this->create_gutenberg_block( 'core/paragraph', array(), '<p>' . $block['text'] . '</p>' );

			case 'heading':
				$attributes = array( 'level' => $block['level'] );
				if ( ! empty( $block['anchor'] ) ) {
					$attributes['anchor'] = $block['anchor'];
				}
				return $this->create_gutenberg_block(
					'core/heading',
					$attributes,
					$this->render_heading_gutenberg_html( $block['level'], $block['text'], $block['anchor'] )
				);

			case 'list':
				return $this->render_list_gutenberg( $block );

			case 'callout':
				return $this->render_callout_gutenberg( $block );

			case 'table':
				return $this->render_table_gutenberg( $block );

			case 'image_placeholder':
				return $this->create_gutenberg_block(
					'smartypress-ai/image-marker',
					array(
						'context'      => wp_strip_all_tags( $block['text'] ),
						'prompt'       => wp_strip_all_tags( $block['text'] ),
						'style'        => $block['style'] ?? 'realistic_photo',
						'status'       => 'placeholder',
						'attachmentId' => 0,
					)
				);

			case 'separator':
				return $this->create_gutenberg_block( 'core/separator', array(), '<hr class="wp-block-separator has-alpha-channel-opacity"/>' );

			case 'cta':
				return $this->render_cta_gutenberg( $block );
		}

		return '';
	}

	/**
	 * Render a block as Classic Editor HTML.
	 *
	 * @param array $block Normalized block.
	 * @param array $options Render options.
	 * @return string
	 */
	private function render_block_html( $block, $options = array() ) {
		switch ( $block['type'] ) {
			case 'lead':
				if ( ! empty( $options['plain_lead'] ) ) {
					return '<p>' . $block['text'] . '</p>';
				}

				return '<p class="smartypress-lead">' . $block['text'] . '</p>';

			case 'paragraph':
				return '<p>' . $block['text'] . '</p>';

			case 'heading':
				return sprintf( '<h%1$d id="%2$s">%3$s</h%1$d>', $block['level'], esc_attr( $block['anchor'] ), $block['text'] );

			case 'list':
				$tag = ! empty( $block['ordered'] ) ? 'ol' : 'ul';
				return sprintf( '<%1$s>%2$s</%1$s>', $tag, $this->render_list_items_html( $block['items'] ) );

			case 'callout':
				return $this->render_callout_html( $block );

			case 'table':
				return $this->render_table_html( $block );

			case 'image_placeholder':
				return '';

			case 'separator':
				return '<hr>';

			case 'cta':
				return $this->render_cta_html( $block );
		}

		return '';
	}

	/**
	 * Normalize generated image marker style.
	 *
	 * @param mixed $style Raw style.
	 * @return string
	 */
	private function normalize_image_placeholder_style( $style ) {
		$style = sanitize_key( $style );

		return in_array( $style, self::IMAGE_PLACEHOLDER_STYLES, true ) ? $style : 'realistic_photo';
	}

	/**
	 * Render generated table of contents as Gutenberg.
	 *
	 * @param array $items TOC items.
	 * @return string
	 */
	private function render_toc_gutenberg( $items ) {
		$inner_content  = $this->create_gutenberg_block(
			'core/heading',
			array( 'level' => 4 ),
			$this->render_heading_gutenberg_html( 4, esc_html__( 'Contents', 'smartypress-ai-engine' ) )
		);
		$inner_content .= "\n\n" . $this->create_gutenberg_block(
			'core/list',
			array( 'ordered' => true ),
			'<ol class="wp-block-list">' . $this->render_toc_items_gutenberg( $items ) . '</ol>'
		);

		return $this->create_gutenberg_block(
			'core/group',
			array( 'className' => 'smartypress-toc' ),
			'<div class="wp-block-group smartypress-toc">' . $inner_content . '</div>'
		);
	}

	/**
	 * Render generated table of contents as HTML.
	 *
	 * @param array $items TOC items.
	 * @return string
	 */
	private function render_toc_html( $items ) {
		return '<nav class="smartypress-toc"><h4>' . esc_html__( 'Contents', 'smartypress-ai-engine' ) . '</h4><ol>' . $this->render_toc_items_html( $items ) . '</ol></nav>';
	}

	/**
	 * Build TOC items from H2 headings.
	 *
	 * @param array $blocks Normalized blocks.
	 * @return array
	 */
	private function build_toc_items( $blocks ) {
		$items = array();

		foreach ( $blocks as $block ) {
			if ( 'heading' === $block['type'] && 2 === absint( $block['level'] ) ) {
				$items[] = array(
					'anchor' => $block['anchor'],
					'text'   => wp_strip_all_tags( $block['text'] ),
				);
			}
		}

		return $items;
	}

	/**
	 * Render TOC list items.
	 *
	 * @param array $items TOC items.
	 * @return string
	 */
	private function render_toc_items_html( $items ) {
		$html = '';

		foreach ( $items as $item ) {
			$html .= sprintf(
				'<li><a href="#%1$s">%2$s</a></li>',
				esc_attr( $item['anchor'] ),
				esc_html( $item['text'] )
			);
		}

		return $html;
	}

	/**
	 * Render list as Gutenberg.
	 *
	 * @param array $block List block.
	 * @return string
	 */
	private function render_list_gutenberg( $block ) {
		$tag = ! empty( $block['ordered'] ) ? 'ol' : 'ul';

		return $this->create_gutenberg_block(
			'core/list',
			! empty( $block['ordered'] ) ? array( 'ordered' => true ) : array(),
			sprintf( '<%1$s class="wp-block-list">%2$s</%1$s>', $tag, $this->render_list_items_gutenberg( $block['items'] ) )
		);
	}

	/**
	 * Render heading HTML that matches Gutenberg's saved markup.
	 *
	 * @param int    $level Heading level.
	 * @param string $text Heading text.
	 * @param string $anchor Optional anchor.
	 * @return string
	 */
	private function render_heading_gutenberg_html( $level, $text, $anchor = '' ) {
		$level = min( 6, max( 1, absint( $level ) ) );
		$id    = '' !== $anchor ? ' id="' . esc_attr( $anchor ) . '"' : '';

		return sprintf( '<h%1$d class="wp-block-heading"%2$s>%3$s</h%1$d>', $level, $id, $text );
	}

	/**
	 * Render list items as Gutenberg child blocks.
	 *
	 * @param array $items List item HTML strings.
	 * @return string
	 */
	private function render_list_items_gutenberg( $items ) {
		$html = '';

		foreach ( $items as $item ) {
			$html .= $this->create_gutenberg_block(
				'core/list-item',
				array(),
				'<li>' . $item . '</li>'
			);
		}

		return $html;
	}

	/**
	 * Render table of contents items as Gutenberg child list item blocks.
	 *
	 * @param array $items TOC items.
	 * @return string
	 */
	private function render_toc_items_gutenberg( $items ) {
		$html = '';

		foreach ( $items as $item ) {
			$html .= $this->create_gutenberg_block(
				'core/list-item',
				array(),
				sprintf(
					'<li><a href="#%1$s">%2$s</a></li>',
					esc_attr( $item['anchor'] ),
					esc_html( $item['text'] )
				)
			);
		}

		return $html;
	}

	/**
	 * Render list items.
	 *
	 * @param array $items List items.
	 * @return string
	 */
	private function render_list_items_html( $items ) {
		$html = '';

		foreach ( $items as $item ) {
			$html .= '<li>' . $item . '</li>';
		}

		return $html;
	}

	/**
	 * Render callout as Gutenberg group.
	 *
	 * @param array $block Callout block.
	 * @return string
	 */
	private function render_callout_gutenberg( $block ) {
		$class = 'smartypress-callout smartypress-callout--' . sanitize_html_class( $block['style'] );
		$inner = '';

		if ( '' !== $block['title'] ) {
			$inner .= $this->create_gutenberg_block(
				'core/paragraph',
				array(),
				'<p><strong>' . esc_html( $block['title'] ) . '</strong></p>'
			);
			$inner .= "\n\n";
		}

		$inner .= $this->create_gutenberg_block(
			'core/paragraph',
			array(),
			'<p>' . $block['content'] . '</p>'
		);

		$html = '<div class="wp-block-group ' . esc_attr( $class ) . '">' . $inner . '</div>';

		return $this->create_gutenberg_block( 'core/group', array( 'className' => $class ), $html );
	}

	/**
	 * Render callout as HTML.
	 *
	 * @param array $block Callout block.
	 * @return string
	 */
	private function render_callout_html( $block ) {
		$class = 'smartypress-callout smartypress-callout--' . sanitize_html_class( $block['style'] );
		$html  = '<div class="' . esc_attr( $class ) . '">';
		$html .= '' !== $block['title'] ? '<p><strong>' . esc_html( $block['title'] ) . '</strong></p>' : '';
		$html .= '<p>' . $block['content'] . '</p></div>';

		return $html;
	}

	/**
	 * Render table as Gutenberg.
	 *
	 * @param array $block Table block.
	 * @return string
	 */
	private function render_table_gutenberg( $block ) {
		return $this->create_gutenberg_block( 'core/table', array(), '<figure class="wp-block-table">' . $this->render_table_html( $block ) . '</figure>' );
	}

	/**
	 * Render table as HTML.
	 *
	 * @param array $block Table block.
	 * @return string
	 */
	private function render_table_html( $block ) {
		$html = '<table>';

		if ( ! empty( $block['headers'] ) ) {
			$html .= '<thead><tr>';
			foreach ( $block['headers'] as $header ) {
				$html .= '<th>' . esc_html( $header ) . '</th>';
			}
			$html .= '</tr></thead>';
		}

		$html .= '<tbody>';
		foreach ( $block['rows'] as $row ) {
			$html .= '<tr>';
			foreach ( $row as $cell ) {
				$html .= '<td>' . $cell . '</td>';
			}
			$html .= '</tr>';
		}
		$html .= '</tbody></table>';

		return $html;
	}

	/**
	 * Render CTA as Gutenberg.
	 *
	 * @param array $block CTA block.
	 * @return string
	 */
	private function render_cta_gutenberg( $block ) {
		$inner = $this->create_gutenberg_block(
			'core/paragraph',
			array(),
			'<p>' . $block['text'] . '</p>'
		);

		if ( ! empty( $block['url'] ) && ! empty( $block['button_text'] ) ) {
			$button = $this->create_gutenberg_block(
				'core/button',
				array( 'url' => $block['url'] ),
				'<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="' . esc_url( $block['url'] ) . '">' . esc_html( $block['button_text'] ) . '</a></div>'
			);
			$inner .= "\n\n" . $this->create_gutenberg_block(
				'core/buttons',
				array(),
				'<div class="wp-block-buttons">' . $button . '</div>'
			);
		}

		$html = '<div class="wp-block-group smartypress-cta">' . $inner . '</div>';

		return $this->create_gutenberg_block( 'core/group', array( 'className' => 'smartypress-cta' ), $html );
	}

	/**
	 * Render CTA as HTML.
	 *
	 * @param array $block CTA block.
	 * @return string
	 */
	private function render_cta_html( $block ) {
		$html = '<div class="smartypress-cta"><p>' . $block['text'] . '</p>';

		if ( ! empty( $block['url'] ) && ! empty( $block['button_text'] ) ) {
			$html .= '<p><a class="button" href="' . esc_url( $block['url'] ) . '">' . esc_html( $block['button_text'] ) . '</a></p>';
		}

		return $html . '</div>';
	}

	/**
	 * Create Gutenberg block with comment delimiters.
	 *
	 * @param string $block_name Block name.
	 * @param array  $attributes Block attributes.
	 * @param string $content Block content.
	 * @return string
	 */
	private function create_gutenberg_block( $block_name, $attributes = array(), $content = '' ) {
		if ( function_exists( 'get_comment_delimited_block_content' ) ) {
			return get_comment_delimited_block_content( $block_name, $attributes, $content );
		}

		$serialized_block_name = str_replace( 'core/', '', $block_name );
		$serialized_attributes = '';

		if ( ! empty( $attributes ) ) {
			$serialized_attributes = wp_json_encode( $attributes, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
			$serialized_attributes = preg_replace( '/--/', '\\u002d\\u002d', $serialized_attributes );
			$serialized_attributes = preg_replace( '/</', '\\u003c', $serialized_attributes );
			$serialized_attributes = preg_replace( '/>/', '\\u003e', $serialized_attributes );
			$serialized_attributes = preg_replace( '/&/', '\\u0026', $serialized_attributes );
			$serialized_attributes .= ' ';
		}

		$content = trim( $content );

		if ( '' === $content ) {
			return sprintf( '<!-- wp:%s %s/-->', $serialized_block_name, $serialized_attributes );
		}

		return sprintf(
			'<!-- wp:%s %s-->%s<!-- /wp:%s -->',
			$serialized_block_name,
			$serialized_attributes,
			$content,
			$serialized_block_name
		);
	}

	/**
	 * Normalize anchor.
	 *
	 * @param string $anchor Suggested anchor.
	 * @param string $fallback Fallback text.
	 * @return string
	 */
	private function normalize_anchor( $anchor, $fallback ) {
		$anchor = sanitize_title( $anchor );

		return '' !== $anchor ? $anchor : sanitize_title( $fallback );
	}

	/**
	 * Sanitize rich text with a small safe inline HTML subset.
	 *
	 * @param mixed $text Raw text.
	 * @return string
	 */
	private function sanitize_rich_text( $text ) {
		if ( ! is_scalar( $text ) ) {
			return '';
		}

		return wp_kses(
			(string) $text,
			array(
				'a'      => array(
					'href'   => true,
					'title'  => true,
					'target' => true,
					'rel'    => true,
				),
				'strong' => array(),
				'em'     => array(),
				'code'   => array(),
				'br'     => array(),
			)
		);
	}

	/**
	 * Sanitize a list of rich text items.
	 *
	 * @param mixed $items Raw items.
	 * @return array
	 */
	private function sanitize_rich_text_list( $items ) {
		if ( is_string( $items ) ) {
			$items = preg_split( '/\r\n|\r|\n/', $items );
		}

		if ( ! is_array( $items ) ) {
			return array();
		}

		$clean = array();
		foreach ( $items as $item ) {
			$value = $this->sanitize_rich_text( $item );
			if ( '' !== trim( wp_strip_all_tags( $value ) ) ) {
				$clean[] = $value;
			}
		}

		return $clean;
	}

	/**
	 * Sanitize a plain string list.
	 *
	 * @param mixed $items Raw list.
	 * @return array
	 */
	private function sanitize_string_list( $items ) {
		if ( is_string( $items ) ) {
			$items = preg_split( '/\r\n|\r|\n|,/', $items );
		}

		if ( ! is_array( $items ) ) {
			return array();
		}

		$clean = array();
		foreach ( $items as $item ) {
			if ( is_scalar( $item ) ) {
				$value = sanitize_text_field( (string) $item );
				if ( '' !== $value ) {
					$clean[] = $value;
				}
			}
		}

		return array_values( array_unique( $clean ) );
	}
}
