<?php
/**
 * Blog: BlogController.
 *
 * @package Builder\Framework\Route
 * @since ??
 */

namespace ET\Builder\Packages\ModuleLibrary\Blog;

if ( ! defined( 'ABSPATH' ) ) {
	die( 'Direct access forbidden.' );
}

use ET\Builder\Framework\Controllers\RESTController;
use ET\Builder\Framework\UserRole\UserRole;
use ET\Builder\Framework\Utility\PostUtility;
use ET\Builder\Packages\ModuleUtils\ModuleUtils;
use ET\Builder\Packages\ModuleUtils\ImageUtils;
use WP_REST_Request;
use WP_REST_Response;

// phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited -- disabled intentionally.

/**
 * Blog REST Controller class.
 *
 * @since ??
 */
class BlogController extends RESTController {
	/**
	 * Return posts/pages for Blog module.
	 *
	 * @since ??
	 *
	 * @param WP_REST_Request $request REST request object.
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public static function index( WP_REST_Request $request ): WP_REST_Response {
		$posts = [];

		$args = [
			'post_type'       => $request->get_param( 'postType' ),
			'posts_per_page'  => $request->get_param( 'postsPerPage' ),
			'paged'           => $request->get_param( 'paged' ),
			'categories'      => $request->get_param( 'categories' ),
			'fullwidth'       => $request->get_param( 'fullwidth' ),
			'flexType'        => $request->get_param( 'flexType' ),
			'date_format'     => $request->get_param( 'dateFormat' ),
			'excerpt_content' => $request->get_param( 'excerptContent' ),
			'excerpt_length'  => $request->get_param( 'excerptLength' ),
			'show_excerpt'    => $request->get_param( 'showExcerpt' ),
			'manual_excerpt'  => $request->get_param( 'manualExcerpt' ),
			'offset'          => $request->get_param( 'offset' ),
			'orderby'         => $request->get_param( 'orderby' ),
		];

		$query_args = array(
			'posts_per_page' => $args['posts_per_page'],
			'post_status'    => array( 'publish', 'private' ),
			'perm'           => 'readable',
			'post_type'      => $args['post_type'],
			'paged'          => $args['paged'],
		);

		if ( 'date_desc' !== $args['orderby'] ) {
			switch ( $args['orderby'] ) {
				case 'date_asc':
					$query_args['orderby'] = 'date';
					$query_args['order']   = 'ASC';
					break;
				case 'title_asc':
					$query_args['orderby'] = 'title';
					$query_args['order']   = 'ASC';
					break;
				case 'title_desc':
					$query_args['orderby'] = 'title';
					$query_args['order']   = 'DESC';
					break;
				case 'rand':
					$query_args['orderby'] = 'rand';
					break;
			}
		}

		// Apply category filtering using the consolidated utility method.
		$query_args = ModuleUtils::add_category_query_args( $query_args, $args['categories'], $args['post_type'] );

		// Check if "All Categories" is selected for sticky posts logic.
		$categories_normalized = $args['categories'];
		if ( is_string( $categories_normalized ) ) {
			$categories_normalized = trim( $categories_normalized );
			if ( empty( $categories_normalized ) || 'undefined' === $categories_normalized || 'null' === $categories_normalized ) {
				$categories_normalized = [];
			} else {
				// Convert comma-separated string to array.
				$categories_normalized = array_map( 'trim', explode( ',', $categories_normalized ) );
			}
		}

		$is_all_category_selected = empty( $categories_normalized ) || in_array( 'all', $categories_normalized, true );

		// WP_Query doesn't return sticky posts when it performed via Ajax and no filtering is applied.
		// This happens because `is_home` is false in this case, but on FE it's true if no category set for the query.
		// Set `is_home` = true to emulate the FE behavior with sticky posts in VB when showing all posts.
		if ( $is_all_category_selected && 'post' === $args['post_type'] ) {
			add_action(
				'pre_get_posts',
				function( $query ) {
					if ( true === $query->get( 'et_is_home' ) ) {
						$query->is_home = true;
					}
				}
			);

			$query_args['et_is_home'] = true;
		}

		if ( '' !== $args['offset'] && ! empty( $args['offset'] ) ) {
			/**
			 * Offset + pagination don't play well. Manual offset calculation required.
			 *
			 * @see: https://codex.wordpress.org/Making_Custom_Queries_using_Offset_and_Pagination.
			 */
			if ( $args['paged'] > 1 ) {
				$query_args['offset'] = ( ( $args['paged'] - 1 ) * $args['posts_per_page'] ) + $args['offset'];
			} else {
				$query_args['offset'] = $args['offset'];
			}
		}

		// Exclude current post from results if we're on a single post (similar to D4 pattern).
		// In REST/AJAX context, try multiple ways to get current post ID.
		$current_post_id = self::_get_current_post_id( $request );
		if ( $current_post_id > 0 ) {
			if ( isset( $query_args['post__not_in'] ) ) {
				$query_args['post__not_in'] = array_unique( array_merge( $query_args['post__not_in'], array( $current_post_id ) ) );
			} else {
				$query_args['post__not_in'] = array( $current_post_id );
			}
		}

		// Get query.
		$query = new \WP_Query( $query_args );

		$sticky_posts = get_option( 'sticky_posts' );

		if ( $query->have_posts() ) {
			// Display sticky posts first.
			if ( ! empty( $sticky_posts ) ) {
				$sticky_args = array(
					'post_type'      => $args['post_type'],
					'post__in'       => $sticky_posts,
					'posts_per_page' => -1,
				);

				// Add category filtering for sticky posts if categories are specified.
				$sticky_args = ModuleUtils::add_category_query_args( $sticky_args, $args['categories'], $args['post_type'] );

				$sticky_query = new \WP_Query( $sticky_args );
				while ( $sticky_query->have_posts() ) {
					$sticky_query->the_post();
					$posts[] = self::process_post_data( $sticky_query->post, $args );
				}
				wp_reset_postdata();
			}

			// Display non-sticky posts.
			while ( $query->have_posts() ) {
				$query->the_post();
				if ( ! in_array( get_the_ID(), $sticky_posts, true ) ) {
					$posts[] = self::process_post_data( $query->post, $args );
				}
			}
		}

		$metadata = [
			'maxNumPages' => $query->max_num_pages,
		];

		// Adds WP-PageNavi plugin support.
		$metadata['wpPagenavi'] = function_exists( 'wp_pagenavi' ) ? \wp_pagenavi(
			[
				'query' => $query,
				'echo'  => false,
			]
		) : null;

		wp_reset_postdata();

		$response = [
			'posts'    => $posts,
			'metadata' => $metadata,
		];

		return self::response_success( $response );
	}
	/**
	 * Index action arguments.
	 *
	 * Endpoint arguments as used in `register_rest_route()`.
	 *
	 * @return array
	 */
	public static function index_args(): array {
		return [
			'postType'       => [
				'type'              => 'string',
				'default'           => 'post',
				'validate_callback' => function( $param, $request, $key ) {
					return is_string( $param );
				},
			],
			'postsPerPage'   => [
				'type'              => 'string',
				'default'           => '10',
				'validate_callback' => function( $param, $request, $key ) {
					return is_numeric( $param );
				},
				'sanitize_callback' => function( $value, $request, $param ) {
					return (int) $value;
				},
			],
			'paged'          => [
				'type'              => 'string',
				'default'           => '1',
				'validate_callback' => function( $param, $request, $key ) {
					return is_numeric( $param );
				},
				'sanitize_callback' => function( $value, $request, $param ) {
					return (int) $value;
				},
			],
			'categories'     => [
				'type'              => 'string',
				'default'           => '',
				'sanitize_callback' => function( $value, $request, $param ) {
					return explode( ',', $value );
				},
			],
			'fullwidth'      => [
				'type'              => 'string',
				'default'           => 'on',
				'validate_callback' => function( $param, $request, $key ) {
					return 'on' === $param || 'off' === $param;
				},
			],
			'dateFormat'     => [
				'type'              => 'string',
				'default'           => 'M j, Y',
				'validate_callback' => function( $param, $request, $key ) {
					return is_string( $param );
				},
			],
			'excerptContent' => [
				'type'              => 'string',
				'default'           => 'off',
				'validate_callback' => function( $param, $request, $key ) {
					return 'on' === $param || 'off' === $param;
				},
			],
			'excerptLength'  => [
				'type'              => 'string',
				'default'           => '270',
				'validate_callback' => function( $param, $request, $key ) {
					return is_numeric( $param );
				},
				'sanitize_callback' => function( $value, $request, $param ) {
					return (int) $value;
				},
			],
			'showExcerpt'    => [
				'type'              => 'string',
				'default'           => 'on',
				'validate_callback' => function( $param, $request, $key ) {
					return 'on' === $param || 'off' === $param;
				},
			],
			'manualExcerpt'  => [
				'type'              => 'string',
				'default'           => 'on',
				'validate_callback' => function( $param, $request, $key ) {
					return 'on' === $param || 'off' === $param;
				},
			],
			'offset'         => [
				'type'              => 'string',
				'default'           => '0',
				'validate_callback' => function( $param, $request, $key ) {
					return is_numeric( $param );
				},
				'sanitize_callback' => function( $value, $request, $param ) {
					return (int) $value;
				},
			],
			'orderby'        => [
				'type'              => 'string',
				'default'           => 'date_desc',
				'validate_callback' => function( $param, $request, $key ) {
					return is_string( $param );
				},
			],
			'current_post_id' => [
				'type'              => 'integer',
				'default'           => 0,
				'validate_callback' => function( $param, $request, $key ) {
					return is_numeric( $param );
				},
				'sanitize_callback' => function( $value, $request, $param ) {
					return (int) $value;
				},
			],
		];
	}
	/**
	 * Index action permission.
	 *
	 * Endpoint permission callback as used in `register_rest_route()`.
	 *
	 * @return bool
	 */
	public static function index_permission(): bool {
		return UserRole::can_current_user_use_visual_builder();
	}

	/**
	 * Process post data.
	 *
	 * @param \WP_Post $post Post object.
	 * @param array    $args Arguments.
	 *
	 * @return array
	 */
	public static function process_post_data( $post, $args ) {
		global $et_theme_image_sizes;
		$title = get_the_title( $post );

		$thumbnail = [];
		$thumb     = '';

		if ( has_post_thumbnail( $post ) || 'attachment' === get_post_type( $post ) ) {
			$thumb  = '';
			$layout = 'on' === $args['fullwidth'] ? 'fullwidth' : 'grid';

			// Use ImageUtils if flexType data is provided by Visual Builder.
			if ( isset( $args['flexType'] ) && ! empty( $args['flexType'] ) ) {
				// Decode JSON flexType data from Visual Builder.
				$flexType_data = json_decode( $args['flexType'], true );

				// Create attrs structure for ImageUtils with complete responsive object.
				$attrs = [
					'blogGrid' => [
						'advanced' => [
							'flexType' => $flexType_data ?: []
						]
					]
				];
				$selected_image_size = ImageUtils::select_optimal_image_size( $attrs, $layout, 'blogGrid' );

				// Get image dimensions from WordPress image size.
				$image_size_data = wp_get_attachment_image_src( get_post_thumbnail_id( $post ), $selected_image_size );
				$width           = is_array( $image_size_data ) ? (int) $image_size_data[1] : ( 'fullwidth' === $layout ? 1080 : 400 );
				$height          = is_array( $image_size_data ) ? (int) $image_size_data[2] : ( 'fullwidth' === $layout ? 675 : 250 );
			} else {
				// Fallback: Conservative approach for backward compatibility when flexType is not provided.
				$width  = 'on' === $args['fullwidth'] ? 1080 : 400;
				$height = 'on' === $args['fullwidth'] ? 675 : 250;
			}

			$width          = (int) apply_filters( 'et_pb_blog_image_width', $width );
			$height         = (int) apply_filters( 'et_pb_blog_image_height', $height );
			$class          = 'on' === $args['fullwidth'] ? 'et_pb_post_main_image' : '';
			$alt            = get_post_meta( get_post_thumbnail_id( $post ), '_wp_attachment_image_alt', true );
			$thumbnail_data = get_thumbnail( $width, $height, $class, $alt, $title, false, 'Blogimage' );
			$thumb          = $thumbnail_data['thumb'];
			$alt_text       = get_post_meta( get_post_thumbnail_id( $post ), '_wp_attachment_image_alt', true );

			// Get thubmnail with size.
			$image_size_name = $width . 'x' . $height;
			$et_size         = isset( $et_theme_image_sizes ) && array_key_exists( $image_size_name, $et_theme_image_sizes ) ? $et_theme_image_sizes[ $image_size_name ] : array( $width, $height );

			$et_attachment_image_attributes = wp_get_attachment_image_src( get_post_thumbnail_id( $post ), $et_size );

			$thumbnail_with_size = ! empty( $et_attachment_image_attributes[0] ) ? $et_attachment_image_attributes[0] : '';

			$thumbnail = [
				'src'    => $thumb,
				'alt'    => ! empty( $alt_text ) ? $alt_text : esc_attr( get_the_title( $post ) ),
				'width'  => $width,
				'height' => $height,
				'class'  => $class,
			];

			if ( $width < 480 && et_is_responsive_images_enabled() ) {
				$thumbnail['srcset'] = $thumb . ' 479w, ' . $thumbnail_with_size . ' 480w';
				$thumbnail['sizes']  = '(max-width:479px) 479px, 100vw';
			}
		}

		$post_type  = get_post_type( $post );
		$taxonomy   = ModuleUtils::get_taxonomy_for_post_type( $post_type );
		$terms      = get_the_terms( $post, $taxonomy );
		$categories = [];

		if ( ! empty( $terms ) ) {
			foreach ( $terms as $term ) {
				$categories[] = [
					'id'   => $term->term_id,
					'name' => $term->name,
					'slug' => $term->slug,
					'link' => get_term_link( $term, $taxonomy ),
				];
			}
		}

		$content = BlogModule::render_content(
			[
				'excerpt_content' => $args['excerpt_content'],
				'show_excerpt'    => $args['show_excerpt'],
				'excerpt_manual'  => $args['manual_excerpt'],
				'excerpt_length'  => $args['excerpt_length'],
				'post_id'         => $post->ID,
			]
		);

		ob_start();
		et_pb_gallery_images( 'slider' );
		$post_gallery = ob_get_clean();

		// Post background color.
		$post_use_background_color = get_post_meta( $post->ID, '_et_post_use_bg_color', true ) ? true : false;
		$background_color          = get_post_meta( $post->ID, '_et_post_bg_color', true );
		$post_background_color     = $background_color && '' !== $background_color ? $background_color : '#ffffff';

		return [
			'id'                 => $post->ID,
			'classNames'         => get_post_class( '', $post->ID ),
			/**
			 * Process the data with the html_entity_decode function.
			 *
			 * This function is used to decode HTML entities in the given data.
			 * It is specifically required for data consumed by VB REST requests.
			 * HTML entities are special characters that are represented in HTML using entity references,
			 * such as `&amp;` for the ampersand character (&).
			 * By decoding these entities, the original characters are restored,
			 * ensuring that the data is correctly interpreted by the VB REST requests.
			 */
			'title'              => html_entity_decode( get_the_title( $post ) ),
			'isPasswordRequired' => post_password_required( $post ),
			'permalink'          => get_permalink( $post ),
			'thumbnail'          => ! empty( $thumb ) ? $thumbnail : null,
			'content'            => $content,
			'date'               => get_the_date( $args['date_format'], $post ),
			'comment'            => sprintf( esc_html( _nx( '%s Comment', '%s Comments', get_comments_number( $post ), 'number of comments', 'et_builder_5' ) ), number_format_i18n( get_comments_number( $post ) ) ),
			'author'             => [
				'name' => get_the_author_meta( 'display_name', $post->post_author ),
				'link' => get_author_posts_url( $post->post_author ),
			],
			'categories'         => $categories,
			'postFormat'         => [
				'type'            => et_pb_post_format(),
				'video'           => PostUtility::get_first_video(),
				'gallery'         => $post_gallery,
				'audio'           => et_core_intentionally_unescaped( et_pb_get_audio_player(), 'html' ),
				'quote'           => et_core_intentionally_unescaped( et_get_blockquote_in_content(), 'html' ),
				'link'            => esc_html( et_get_link_url() ),
				'textColorClass'  => et_divi_get_post_text_color(),
				'backgroundColor' => $post_use_background_color ? $post_background_color : '',
			],
		];
	}

	/**
	 * Get the current post ID from various sources in REST/AJAX context.
	 *
	 * @since ??
	 *
	 * @param WP_REST_Request $request REST request object.
	 *
	 * @return int Current post ID, or 0 if not found.
	 */
	private static function _get_current_post_id( WP_REST_Request $request ): int {
		// Try multiple parameter names that might contain current post ID.
		$param_names = [ 'current_post_id', 'currentPostId', 'et_post_id', 'post' ];
		foreach ( $param_names as $param_name ) {
			$post_id = $request->get_param( $param_name );
			if ( ! empty( $post_id ) && is_numeric( $post_id ) ) {
				return (int) $post_id;
			}
		}

		// Try to get from global state as fallback.
		$current_post_id = get_the_ID();
		if ( ! empty( $current_post_id ) ) {
			return (int) $current_post_id;
		}

		// Last resort: try HTTP referer (similar to QueryResultsController pattern).
		if ( isset( $_SERVER['HTTP_REFERER'] ) ) {
			$referer_url   = esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) );
			$referer_parts = wp_parse_url( $referer_url );
			$referer_query = [];

			if ( isset( $referer_parts['query'] ) ) {
				parse_str( $referer_parts['query'], $referer_query );
			}

			if ( isset( $referer_query['post'] ) && is_numeric( $referer_query['post'] ) ) {
				return (int) $referer_query['post'];
			}
		}

		return 0;
	}
}
