<?php
namespace Prodigy\Includes\Helpers;

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

/**
 * Render the configured Elementor template for Prodigy error pages.
 */
class Prodigy_Error_Elementor_Template {
	private const DEFAULT_TEMPLATE_TITLE = 'Error 404';

	/**
	 * @param string $error_type Error type from Prodigy settings.
	 *
	 * @return bool
	 */
	public static function render( string $error_type = 'page_not_found' ): bool {
		$template_id = self::get_template_id( $error_type );
		if ( ! $template_id || ! class_exists( '\Elementor\Plugin' ) || ! did_action( 'elementor/loaded' ) ) {
			return false;
		}

		try {
			$content = \Elementor\Plugin::instance()->frontend->get_builder_content_for_display( $template_id, true );
		} catch ( \Throwable $error ) {
			unset( $error );
			return false;
		}

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

		$settings    = Prodigy_Platform_Settings::get_error_settings( $error_type );
		$content     = self::apply_error_settings_to_content( $content, $settings );
		$status_code = (string) ( $settings['error_number'] ?? '404' );
		?>
		<div class="prodigy-elementor-error-template prodigy-elementor-error-template--<?php echo esc_attr( $status_code ); ?>">
			<?php echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
		</div>
		<?php

		return true;
	}

	/**
	 * @param string $error_type Error type from Prodigy settings.
	 *
	 * @return int
	 */
	private static function get_template_id( string $error_type ): int {
		$template_id = (int) apply_filters(
			'prodigy_error_elementor_template_id',
			(int) get_option( 'prodigy_error_elementor_template_id' ),
			$error_type
		);

		if ( self::is_elementor_template( $template_id ) ) {
			return $template_id;
		}

		foreach ( self::get_template_titles( $error_type ) as $template_title ) {
			$template_id = self::get_template_id_by_title( $template_title );
			if ( $template_id ) {
				return $template_id;
			}
		}

		return 0;
	}

	/**
	 * @param string $error_type Error type from Prodigy settings.
	 *
	 * @return array
	 */
	private static function get_template_titles( string $error_type ): array {
		$titles = apply_filters(
			'prodigy_error_elementor_template_titles',
			array(
				self::DEFAULT_TEMPLATE_TITLE,
				'Elementor Error 404',
			),
			$error_type
		);

		if ( is_string( $titles ) ) {
			$titles = array( $titles );
		}

		return array_filter( array_map( 'strval', (array) $titles ) );
	}

	/**
	 * @param string $template_title Elementor template title.
	 *
	 * @return int
	 */
	private static function get_template_id_by_title( string $template_title ): int {
		$template = self::query_template(
			array(
				'title' => $template_title,
			)
		);

		if ( $template ) {
			return (int) $template->ID;
		}

		$template = self::query_template(
			array(
				's' => $template_title,
			)
		);

		return $template ? (int) $template->ID : 0;
	}

	/**
	 * @param array $query_args Query args that identify the Elementor template.
	 *
	 * @return \WP_Post|null
	 */
	private static function query_template( array $query_args ): ?\WP_Post {
		$query = new \WP_Query(
			array_merge(
				array(
					'post_type'              => 'elementor_library',
					'post_status'            => array( 'publish', 'private' ),
					'posts_per_page'         => 1,
					'no_found_rows'          => true,
					'ignore_sticky_posts'    => true,
					'update_post_meta_cache' => false,
					'update_post_term_cache' => false,
				),
				$query_args
			)
		);

		if ( ! empty( $query->post ) && self::is_elementor_template( (int) $query->post->ID ) ) {
			return $query->post;
		}

		return null;
	}

	/**
	 * @param int $template_id Elementor template post ID.
	 *
	 * @return bool
	 */
	private static function is_elementor_template( int $template_id ): bool {
		if ( $template_id <= 0 ) {
			return false;
		}

		if ( get_post_type( $template_id ) !== 'elementor_library' ) {
			return false;
		}

		return get_post_meta( $template_id, '_elementor_edit_mode', true ) === 'builder';
	}

	/**
	 * @param string $content  Elementor template content.
	 * @param array  $settings Error page settings.
	 *
	 * @return string
	 */
	private static function apply_error_settings_to_content( string $content, array $settings ): string {
		if ( ! class_exists( '\DOMDocument' ) || ! class_exists( '\DOMXPath' ) ) {
			return $content;
		}

		$document = new \DOMDocument( '1.0', 'UTF-8' );
		$previous = libxml_use_internal_errors( true );
		$loaded   = $document->loadHTML(
			'<?xml encoding="UTF-8"><div id="prodigy-error-template-root">' . $content . '</div>',
			LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
		);
		libxml_clear_errors();
		libxml_use_internal_errors( $previous );

		if ( ! $loaded ) {
			return $content;
		}

		$xpath = new \DOMXPath( $document );

		$heading = self::get_first_node_by_class( $xpath, 'elementor-heading-title' );
		if ( $heading instanceof \DOMElement ) {
			$heading->textContent = (string) ( $settings['error_number'] ?? '' );
		}

		$text_containers = self::get_text_editor_containers( $xpath );
		if ( $text_containers->length > 0 ) {
			self::replace_container_text( $document, $text_containers->item( 0 ), (string) ( $settings['error_title'] ?? '' ) );
		}

		$error_body = (string) ( $settings['error_body'] ?? '' );
		if ( $text_containers->length > 1 ) {
			self::replace_container_text( $document, $text_containers->item( 1 ), $error_body );
		} elseif ( $error_body !== '' && $text_containers->length > 0 ) {
			self::append_body_text( $document, $text_containers->item( 0 ), $error_body );
		}

		$root = $document->getElementById( 'prodigy-error-template-root' );
		if ( ! $root ) {
			return $content;
		}

		$output = '';
		foreach ( $root->childNodes as $child ) {
			$output .= $document->saveHTML( $child );
		}

		return $output ? $output : $content;
	}

	/**
	 * @param \DOMXPath $xpath      DOM XPath helper.
	 * @param string    $class_name Class name to find.
	 *
	 * @return \DOMElement|null
	 */
	private static function get_first_node_by_class( \DOMXPath $xpath, string $class_name ): ?\DOMElement {
		$nodes = $xpath->query( self::class_query( $class_name ) );
		if ( ! $nodes || $nodes->length === 0 ) {
			return null;
		}

		$node = $nodes->item( 0 );

		return $node instanceof \DOMElement ? $node : null;
	}

	/**
	 * @param \DOMXPath $xpath DOM XPath helper.
	 *
	 * @return \DOMNodeList
	 */
	private static function get_text_editor_containers( \DOMXPath $xpath ): \DOMNodeList {
		return $xpath->query(
			self::class_query( 'elementor-widget-text-editor' ) . self::class_query( 'elementor-widget-container' )
		);
	}

	/**
	 * @param string $class_name Class name to find.
	 *
	 * @return string
	 */
	private static function class_query( string $class_name ): string {
		return "//*[contains(concat(' ', normalize-space(@class), ' '), ' " . $class_name . " ')]";
	}

	/**
	 * @param \DOMDocument $document  DOM document.
	 * @param \DOMNode     $container Text container node.
	 * @param string       $text      Text to insert.
	 *
	 * @return void
	 */
	private static function replace_container_text( \DOMDocument $document, \DOMNode $container, string $text ): void {
		while ( $container->firstChild ) {
			$container->removeChild( $container->firstChild );
		}

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

		$paragraph = $document->createElement( 'p' );
		$paragraph->appendChild( $document->createTextNode( $text ) );
		$container->appendChild( $paragraph );
	}

	/**
	 * @param \DOMDocument $document  DOM document.
	 * @param \DOMNode     $container Text container node.
	 * @param string       $text      Text to append.
	 *
	 * @return void
	 */
	private static function append_body_text( \DOMDocument $document, \DOMNode $container, string $text ): void {
		$paragraph = $document->createElement( 'p' );
		$paragraph->setAttribute( 'class', 'prodigy-elementor-error-template__body' );
		$paragraph->appendChild( $document->createTextNode( $text ) );

		$container->appendChild( $paragraph );
	}
}
