<?php
/**
 * AJAX handler class.
 *
 * @package Zluck_Ajax_Posts_Filter_Elementor
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * AJAX handler class.
 */
class AAPF_Ajax_Handler {

	/**
	 * Single instance of the class.
	 *
	 * @var AAPF_Ajax_Handler
	 */
	private static $instance = null;

	/**
	 * Get instance of the class.
	 *
	 * @return AAPF_Ajax_Handler
	 */
	public static function get_instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Constructor.
	 */
	public function __construct() {
		add_action( 'wp_ajax_aapf_filter_posts', array( $this, 'filter_posts' ) );
		add_action( 'wp_ajax_nopriv_aapf_filter_posts', array( $this, 'filter_posts' ) );
	}

	/**
	 * Handle AJAX filter request.
	 */
	public function filter_posts() {
		// Verify nonce
		if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'aapf_ajax_nonce' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security check failed.', 'zluck-ajax-posts-filter-for-elementor' ) ) );
		}

		// Get and sanitize settings
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized via aapf_sanitize_settings() below
		$settings_raw = isset( $_POST['settings'] ) ? wp_unslash( $_POST['settings'] ) : '';
		$settings = array();
		
		if ( ! empty( $settings_raw ) ) {
			if ( is_string( $settings_raw ) ) {
				$settings = json_decode( $settings_raw, true );
				if ( json_last_error() !== JSON_ERROR_NONE ) {
					wp_send_json_error( array( 'message' => esc_html__( 'Invalid settings data.', 'zluck-ajax-posts-filter-for-elementor' ) ) );
				}
			} elseif ( is_array( $settings_raw ) ) {
				$settings = $settings_raw;
			}
		}
		
		$settings = aapf_sanitize_settings( $settings );

		// Ensure settings is an array (handle edge cases in editor)
		if ( ! is_array( $settings ) || empty( $settings ) ) {
			// In editor preview, return empty response instead of error
			$is_editor = false;
			// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce already verified above
			if ( isset( $_POST['editor'] ) ) {
				$is_editor = ! empty( sanitize_text_field( wp_unslash( $_POST['editor'] ) ) );
			} elseif ( defined( 'ELEMENTOR_VERSION' ) && class_exists( '\Elementor\Plugin' ) ) {
				try {
					if ( isset( \Elementor\Plugin::$instance->editor ) && method_exists( \Elementor\Plugin::$instance->editor, 'is_edit_mode' ) ) {
						$is_editor = \Elementor\Plugin::$instance->editor->is_edit_mode();
					}
				} catch ( Exception $e ) {
					// Editor not available, treat as frontend
				}
			}
			if ( $is_editor ) {
				wp_send_json_success( array(
					'posts'      => '<div class="aapf-no-posts"><p>' . esc_html__( 'Please configure widget settings.', 'zluck-ajax-posts-filter-for-elementor' ) . '</p></div>',
					'pagination' => '',
					'max_pages'  => 0,
					'found'      => 0,
				) );
				return;
			}
			wp_send_json_error( array( 'message' => esc_html__( 'Invalid settings data.', 'zluck-ajax-posts-filter-for-elementor' ) ) );
			return;
		}

		// Validate post type exists
		$post_type = isset( $settings['post_type'] ) ? sanitize_key( $settings['post_type'] ) : 'post';
		if ( ! post_type_exists( $post_type ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Invalid post type.', 'zluck-ajax-posts-filter-for-elementor' ) ) );
			return;
		}

		// Get selected terms
		$selected_terms = isset( $_POST['terms'] ) ? array_map( 'absint', (array) $_POST['terms'] ) : array();

		// Build query args
		$query_args = aapf_build_query_args( $settings );

		// Override taxonomy filter with selected terms
		if ( ! empty( $settings['filter_taxonomy'] ) ) {
			$taxonomy = sanitize_key( $settings['filter_taxonomy'] );
			
			// Validate taxonomy exists
			if ( ! taxonomy_exists( $taxonomy ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Invalid taxonomy.', 'zluck-ajax-posts-filter-for-elementor' ) ) );
			}
			
			if ( ! empty( $selected_terms ) ) {
				// Validate terms exist
				$valid_terms = get_terms(
					array(
						'taxonomy'   => $taxonomy,
						'include'    => $selected_terms,
						'hide_empty' => false,
						'fields'     => 'ids',
					)
				);
				
				if ( ! is_wp_error( $valid_terms ) && ! empty( $valid_terms ) ) {
					// Filter by selected terms
					// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Required for taxonomy filtering functionality
					$query_args['tax_query'] = array(
						array(
							'taxonomy' => $taxonomy,
							'field'    => 'term_id',
							'terms'    => $valid_terms,
							'operator' => 'IN',
						),
					);
				}
			}
			// If empty terms array, show all posts (no tax_query restriction)
		}

		// Get pagination
		$paged = isset( $_POST['paged'] ) ? absint( $_POST['paged'] ) : 1;
		$query_args['paged'] = $paged;

		/**
		 * Filter query arguments before execution.
		 *
		 * @param array $query_args Query arguments.
		 * @param array $settings Widget settings.
		 */
		$query_args = apply_filters( 'aapf_before_query', $query_args, $settings );

		// Execute query with error handling
		try {
			$query = new WP_Query( $query_args );

			// Check for query errors
			if ( is_wp_error( $query ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Query error occurred.', 'zluck-ajax-posts-filter-for-elementor' ) ) );
				return;
			}

			// Build response
			$response = array(
				'posts'      => array(),
				'pagination' => '',
				'max_pages'  => $query->max_num_pages,
				'found'      => $query->found_posts,
			);

			if ( $query->have_posts() ) {
				ob_start();

				$layout = isset( $settings['layout'] ) ? $settings['layout'] : 'grid';

				while ( $query->have_posts() ) {
					$query->the_post();
					try {
						if ( 'card' === $layout ) {
							aapf_render_card_layout( get_post(), $settings );
						} else {
							aapf_render_post_card( get_post(), $settings );
						}
					} catch ( Exception $e ) {
						// Skip problematic posts but continue rendering others
						continue;
					}
				}

				$response['posts'] = ob_get_clean();

				// Reset post data
				wp_reset_postdata();

				// Generate pagination if needed
				if ( ! empty( $settings['pagination'] ) && 'none' !== $settings['pagination'] ) {
					ob_start();
					try {
						$this->render_pagination( $query, $settings );
						$response['pagination'] = ob_get_clean();
					} catch ( Exception $e ) {
						ob_end_clean();
						$response['pagination'] = '';
					}
				}
			} else {
				ob_start();
				include AAPF_PLUGIN_DIR . 'templates/no-posts.php';
				$response['posts'] = ob_get_clean();
			}

			wp_send_json_success( $response );
		} catch ( Exception $e ) {
			// Catch any unexpected errors and return a user-friendly message
			wp_send_json_error( array( 'message' => esc_html__( 'An error occurred while loading posts. Please try again.', 'zluck-ajax-posts-filter-for-elementor' ) ) );
		}
	}

	/**
	 * Render pagination.
	 *
	 * @param WP_Query $query Query object.
	 * @param array    $settings Widget settings.
	 */
	private function render_pagination( $query, $settings ) {
		$pagination_type = isset( $settings['pagination'] ) ? $settings['pagination'] : 'none';

		if ( 'numbers' === $pagination_type ) {
			$paged = max( 1, $query->get( 'paged' ) ? $query->get( 'paged' ) : 1 );
			// Get widget ID from settings if available
			$widget_id = isset( $settings['_element_id'] ) ? $settings['_element_id'] : '';

			// Get prev/next text and icon settings
			$prev_text = isset( $settings['pagination_prev_text'] ) ? $settings['pagination_prev_text'] : esc_html__( 'Previous', 'zluck-ajax-posts-filter-for-elementor' );
			$next_text = isset( $settings['pagination_next_text'] ) ? $settings['pagination_next_text'] : esc_html__( 'Next', 'zluck-ajax-posts-filter-for-elementor' );
			$show_icons = ! empty( $settings['pagination_show_icons'] ) && 'yes' === $settings['pagination_show_icons'];

			// Build prev text with icon - if icons enabled, hide text
			$prev_text_final = '';
			if ( $show_icons && ! empty( $settings['pagination_prev_icon']['value'] ) ) {
				$prev_icon = $this->render_icon( $settings['pagination_prev_icon'] );
				$prev_text_final = '<span class="aapf-pagination-icon-wrapper">' . $prev_icon . '</span>';
			} else {
				$prev_text_final = $prev_text;
			}

			// Build next text with icon - if icons enabled, hide text
			$next_text_final = '';
			if ( $show_icons && ! empty( $settings['pagination_next_icon']['value'] ) ) {
				$next_icon = $this->render_icon( $settings['pagination_next_icon'] );
				$next_text_final = '<span class="aapf-pagination-icon-wrapper">' . $next_icon . '</span>';
			} else {
				$next_text_final = $next_text;
			}

			echo '<div class="aapf-pagination aapf-pagination-numbers"' . ( $widget_id ? ' data-widget-id="' . esc_attr( $widget_id ) . '"' : '' ) . '>';
			$pagination_links = paginate_links(
				array(
					'total'     => $query->max_num_pages,
					'current'   => $paged,
					'prev_text' => $prev_text_final,
					'next_text' => $next_text_final,
					'type'      => 'plain',
				)
			);

			// Add data attributes to pagination links for AJAX
			if ( $pagination_links ) {
				// Add classes to prev/next links - match links containing prev/next text (case insensitive, with HTML)
				$pagination_links = preg_replace_callback(
					'/<a([^>]*class=["\'])([^"\']*)(["\'][^>]*>)(.*?)(<\/a>)/is',
					function( $matches ) use ( $prev_text, $next_text, $show_icons ) {
						$full_link = $matches[0];
						$link_content_html = $matches[4];
						$link_content = wp_strip_all_tags( $link_content_html );
						
						// Check if it's a prev or next link
						$is_prev = false;
						$is_next = false;
						
						// Check by text content
						if ( stripos( $link_content, $prev_text ) !== false || stripos( $link_content, 'prev' ) !== false ) {
							$is_prev = true;
						} elseif ( stripos( $link_content, $next_text ) !== false || stripos( $link_content, 'next' ) !== false ) {
							$is_next = true;
						} elseif ( $show_icons && stripos( $link_content_html, 'aapf-pagination-icon-wrapper' ) !== false ) {
							// When icons are enabled, check icon classes
							if ( stripos( $link_content_html, 'chevron-left' ) !== false || 
								   stripos( $link_content_html, 'arrow-left' ) !== false ||
								   stripos( $link_content_html, 'angle-left' ) !== false ||
								   stripos( $link_content_html, 'fa-arrow-left' ) !== false ||
								   stripos( $link_content_html, 'fa-angle-left' ) !== false ||
								   stripos( $link_content_html, 'fa-chevron-left' ) !== false ) {
								$is_prev = true;
							} elseif ( stripos( $link_content_html, 'chevron-right' ) !== false || 
									   stripos( $link_content_html, 'arrow-right' ) !== false ||
									   stripos( $link_content_html, 'angle-right' ) !== false ||
									   stripos( $link_content_html, 'fa-arrow-right' ) !== false ||
									   stripos( $link_content_html, 'fa-angle-right' ) !== false ||
									   stripos( $link_content_html, 'fa-chevron-right' ) !== false ) {
								$is_next = true;
							}
						}
						
						if ( $is_prev ) {
							$full_link = str_replace( 'class="', 'class="prev ', $full_link );
							$full_link = str_replace( "class='", "class='prev ", $full_link );
						} elseif ( $is_next ) {
							$full_link = str_replace( 'class="', 'class="next ', $full_link );
							$full_link = str_replace( "class='", "class='next ", $full_link );
						}
						
						return $full_link;
					},
					$pagination_links
				);
				$pagination_links = str_replace( '<a ', '<a class="aapf-pagination-link page-numbers" ', $pagination_links );
				$pagination_links = preg_replace_callback(
					'/<a[^>]+href=["\']([^"\']*)["\'][^>]*>(.*?)<\/a>/is',
					function( $matches ) use ( $paged, $query, $prev_text, $next_text, $show_icons ) {
						$href = $matches[1];
						$link_content = $matches[2];
						$full_match = $matches[0];
						
						// Extract page number from URL
						$page = null;
						preg_match( '/page\/(\d+)\//', $href, $page_matches );
						if ( ! $page_matches ) {
							preg_match( '/paged=(\d+)/', $href, $page_matches );
						}
						if ( ! $page_matches ) {
							// Check if it's prev/next link
							$link_text_clean = wp_strip_all_tags( $link_content );
							
							// Check for prev/next by text content
							$is_prev = false;
							$is_next = false;
							
							if ( stripos( $link_text_clean, 'prev' ) !== false || stripos( $link_content, '«' ) !== false ) {
								$is_prev = true;
							} elseif ( stripos( $link_text_clean, 'next' ) !== false || stripos( $link_content, '»' ) !== false ) {
								$is_next = true;
							} elseif ( $show_icons ) {
								// When icons are enabled, check for icon wrapper and determine direction
								// Check if link contains icon wrapper (prev/next icons)
								if ( stripos( $link_content, 'aapf-pagination-icon-wrapper' ) !== false ) {
									// Check for prev icon class (usually contains 'left' or 'prev' or 'chevron-left')
									if ( stripos( $link_content, 'chevron-left' ) !== false || 
										 stripos( $link_content, 'arrow-left' ) !== false ||
										 stripos( $link_content, 'angle-left' ) !== false ||
										 stripos( $link_content, 'fa-arrow-left' ) !== false ||
										 stripos( $link_content, 'fa-angle-left' ) !== false ||
										 stripos( $link_content, 'fa-chevron-left' ) !== false ) {
										$is_prev = true;
									} elseif ( stripos( $link_content, 'chevron-right' ) !== false || 
											   stripos( $link_content, 'arrow-right' ) !== false ||
											   stripos( $link_content, 'angle-right' ) !== false ||
											   stripos( $link_content, 'fa-arrow-right' ) !== false ||
											   stripos( $link_content, 'fa-angle-right' ) !== false ||
											   stripos( $link_content, 'fa-chevron-right' ) !== false ) {
										$is_next = true;
									}
								}
							}
							
							if ( $is_prev ) {
								$page = max( 1, $paged - 1 );
							} elseif ( $is_next ) {
								$page = min( $query->max_num_pages, $paged + 1 );
							} else {
								// Try to extract number from link text
								preg_match( '/\d+/', $link_text_clean, $text_matches );
								$page = ! empty( $text_matches[0] ) ? absint( $text_matches[0] ) : 1;
							}
						} else {
							$page = absint( $page_matches[1] );
						}
						
						// Replace href and add data-page - use javascript:void(0) to avoid Elementor editor selector errors
						$new_link = str_replace( 'href="' . esc_url( $href ) . '"', 'href="javascript:void(0)" data-page="' . esc_attr( $page ) . '" role="button" tabindex="0"', $full_match );
						return $new_link;
					},
					$pagination_links
				);
				// Handle current page span
				$pagination_links = str_replace( '<span class="page-numbers current">', '<span class="page-numbers current aapf-pagination-current" data-page="' . esc_attr( $paged ) . '">', $pagination_links );
				// paginate_links() returns escaped HTML, and we only modify it with sanitized values
				echo wp_kses_post( $pagination_links );
			}
			echo '</div>';
		} elseif ( 'load_more' === $pagination_type ) {
			$load_more_text = isset( $settings['load_more_text'] ) ? $settings['load_more_text'] : esc_html__( 'Load More', 'zluck-ajax-posts-filter-for-elementor' );
			$max_pages = $query->max_num_pages;
			$current_page = max( 1, $query->get( 'paged' ) ? $query->get( 'paged' ) : 1 );

			if ( $current_page < $max_pages ) {
				echo '<div class="aapf-pagination aapf-pagination-load-more">';
				echo '<button class="aapf-load-more-btn" data-page="' . esc_attr( $current_page ) . '" data-max-pages="' . esc_attr( $max_pages ) . '">';
				echo esc_html( $load_more_text );
				echo '</button>';
				echo '</div>';
			}
		} elseif ( 'infinite' === $pagination_type ) {
			$max_pages = $query->max_num_pages;
			$current_page = max( 1, $query->get( 'paged' ) ? $query->get( 'paged' ) : 1 );

			if ( $current_page < $max_pages ) {
				echo '<div class="aapf-pagination aapf-pagination-infinite" data-page="' . esc_attr( $current_page ) . '" data-max-pages="' . esc_attr( $max_pages ) . '"></div>';
			}
		}
	}

	/**
	 * Render icon for pagination.
	 *
	 * @param array $icon Icon settings.
	 * @return string Icon HTML.
	 */
	private function render_icon( $icon ) {
		if ( empty( $icon ) || ! is_array( $icon ) ) {
			return '';
		}

		$icon_value = isset( $icon['value'] ) ? $icon['value'] : '';
		if ( empty( $icon_value ) ) {
			return '';
		}

		$icon_library = isset( $icon['library'] ) ? $icon['library'] : '';

		$icon_html = '';
		if ( 'svg' === $icon_library && is_array( $icon_value ) ) {
			$width = isset( $icon_value['width'] ) ? absint( $icon_value['width'] ) : 24;
			$height = isset( $icon_value['height'] ) ? absint( $icon_value['height'] ) : 24;
			$path = isset( $icon_value['path'] ) ? $icon_value['path'] : '';
			$viewbox = isset( $icon_value['viewBox'] ) ? $icon_value['viewBox'] : "0 0 {$width} {$height}";
			
			// Sanitize SVG path - only allow safe SVG path characters
			// Note: SVG paths from Elementor are already validated, but we add extra safety
			$path = preg_replace( '/[^MmLlHhVvCcSsQqTtAaZz0-9\s,.-]/', '', $path );
			
			// Validate viewBox format (should be "x y width height")
			$viewbox = sanitize_text_field( $viewbox );
			if ( ! preg_match( '/^\d+\s+\d+\s+\d+\s+\d+$/', $viewbox ) ) {
				$viewbox = "0 0 {$width} {$height}";
			}
			
			$icon_html = '<svg class="aapf-pagination-icon" aria-hidden="true" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" width="' . esc_attr( $width ) . '" height="' . esc_attr( $height ) . '" viewBox="' . esc_attr( $viewbox ) . '"><path d="' . esc_attr( $path ) . '"></path></svg>';
		} else {
			$icon_class = is_string( $icon_value ) ? $icon_value : ( isset( $icon_value['url'] ) ? $icon_value['url'] : '' );
			if ( ! empty( $icon_class ) ) {
				$icon_html = '<i class="' . esc_attr( $icon_class ) . ' aapf-pagination-icon"></i>';
			}
		}

		return $icon_html;
	}
}

// Initialize the AJAX handler
function aapf_init_ajax_handler() {
	return AAPF_Ajax_Handler::get_instance();
}
add_action( 'init', 'aapf_init_ajax_handler' );

