 <?php
/**
 * Post Grid block — server-side render.
 *
 * CSS is generated at save time by Post_Grid_CSS and printed inline on the front
 * end. This file runs a WP_Query built from the sanitized block attributes and
 * outputs a grid of post cards (featured image, meta, title, excerpt, read-more)
 * plus optional numbered pagination. Every dynamic value is escaped on output.
 *
 * @package Flexa\Block
 *
 * @var array    $attributes Block attributes.
 * @var string   $content    Save content (unused — dynamic block).
 * @var WP_Block $block      Block instance.
 */

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

// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- $attributes provided by WP block API.

use Flexa\Block\HTML_Helpers;

$block_id = $attributes['blockId'] ?? '';
$anchor   = $attributes['anchor'] ?? '';
$html_tag = HTML_Helpers::get_html_tag( $attributes, 'section' );

$container_type = 'full-width' === ( $attributes['containerType'] ?? 'boxed' ) ? 'full-width' : 'boxed';

// --- Query attributes (sanitized). ---
$post_type = sanitize_key( (string) ( $attributes['postType'] ?? 'post' ) );
if ( '' === $post_type || ! post_type_exists( $post_type ) ) {
	$post_type = 'post';
}
$per_page    = max( 1, min( 100, (int) ( $attributes['postsPerPage'] ?? 6 ) ) );
$base_offset = max( 0, (int) ( $attributes['offset'] ?? 0 ) );
$order_by    = in_array( $attributes['orderBy'] ?? 'date', [ 'date', 'title', 'menu_order', 'rand', 'comment_count' ], true ) ? $attributes['orderBy'] : 'date';
$order       = 'asc' === strtolower( (string) ( $attributes['order'] ?? 'desc' ) ) ? 'ASC' : 'DESC';
$taxonomy    = sanitize_key( (string) ( $attributes['taxonomy'] ?? '' ) );
$terms_raw   = (string) ( $attributes['terms'] ?? '' );
$exclude_cur = ! empty( $attributes['excludeCurrent'] );

// --- Element toggles. ---
$show_image      = false !== ( $attributes['showImage'] ?? true );
$show_title      = false !== ( $attributes['showTitle'] ?? true );
$title_tag       = in_array( $attributes['titleTag'] ?? 'h3', [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'div' ], true ) ? $attributes['titleTag'] : 'h3';
$show_meta       = false !== ( $attributes['showMeta'] ?? true );
$show_author     = false !== ( $attributes['showAuthor'] ?? true );
$show_avatar     = ! empty( $attributes['showAvatar'] );
$avatar_size     = max( 12, min( 96, (int) ( $attributes['avatarSize'] ?? 24 ) ) );
$show_date       = false !== ( $attributes['showDate'] ?? true );
$show_comments   = ! empty( $attributes['showComments'] );
$show_taxonomy   = ! empty( $attributes['showTaxonomy'] );
$show_excerpt    = false !== ( $attributes['showExcerpt'] ?? true );
$excerpt_length  = max( 0, (int) ( $attributes['excerptLength'] ?? 20 ) );
$show_read_more  = false !== ( $attributes['showReadMore'] ?? true );
$read_more_text  = (string) ( $attributes['readMoreText'] ?? '' );
$read_more_text  = '' !== trim( $read_more_text ) ? $read_more_text : __( 'Read more', 'flexa-block' );
$image_size      = sanitize_key( (string) ( $attributes['imageSize'] ?? 'large' ) );
$image_size      = '' !== $image_size ? $image_size : 'large';
// Inline aspect-ratio for the "no image" placeholder so it matches the chosen ratio.
$image_ratio     = trim( (string) ( $attributes['imageRatio'] ?? '' ) );
$ratio_style     = ( '' !== $image_ratio && preg_match( '#^[0-9]+\s*/\s*[0-9]+$#', $image_ratio ) ) ? 'aspect-ratio:' . $image_ratio : '';
$pag_type        = in_array( $attributes['paginationType'] ?? 'numbered', [ 'none', 'numbered', 'loadmore' ], true ) ? $attributes['paginationType'] : 'numbered';
$show_pagination = 'numbered' === $pag_type;
$is_loadmore     = 'loadmore' === $pag_type;
$load_more_text  = (string) ( $attributes['loadMoreText'] ?? '' );
$load_more_text  = '' !== trim( $load_more_text ) ? $load_more_text : __( 'Load more', 'flexa-block' );

// Pagination previous/next labels (user text, default arrows).
$prev_label = (string) ( $attributes['prevLabel'] ?? '' );
$prev_label = '' !== trim( $prev_label ) ? $prev_label : __( '‹', 'flexa-block' );
$next_label = (string) ( $attributes['nextLabel'] ?? '' );
$next_label = '' !== trim( $next_label ) ? $next_label : __( '›', 'flexa-block' );

// --- Pagination: a per-block query arg so multiple grids page independently. ---
$page_key = '' !== $block_id ? 'pg_' . sanitize_key( $block_id ) : 'pg';
$paged    = 1;
if ( isset( $_GET[ $page_key ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only paging, no state change.
	$paged = max( 1, absint( wp_unslash( $_GET[ $page_key ] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
} else {
	$qv    = (int) get_query_var( 'paged' );
	$paged = $qv > 1 ? $qv : max( 1, (int) get_query_var( 'page' ) );
}

// --- Build WP_Query args. ---
$args = [
	'post_type'           => $post_type,
	'post_status'         => 'publish',
	'orderby'             => $order_by,
	'order'               => $order,
	'posts_per_page'      => $per_page,
	'offset'              => $base_offset + ( $show_pagination ? ( $paged - 1 ) * $per_page : 0 ),
	'ignore_sticky_posts' => true,
];

if ( '' !== $taxonomy && taxonomy_exists( $taxonomy ) && '' !== trim( $terms_raw ) ) {
	$term_list = array_values( array_filter( array_map( 'trim', explode( ',', $terms_raw ) ) ) );
	if ( ! empty( $term_list ) ) {
		$numeric = count( $term_list ) === count( array_filter( $term_list, 'is_numeric' ) );
		$args['tax_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
			[
				'taxonomy' => $taxonomy,
				'field'    => $numeric ? 'term_id' : 'slug',
				'terms'    => $numeric ? array_map( 'absint', $term_list ) : $term_list,
			],
		];
	}
}

if ( $exclude_cur ) {
	$current_id = (int) get_queried_object_id();
	if ( $current_id > 0 ) {
		// phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- single current post excluded from the grid; negligible cost.
		$args['post__not_in'] = [ $current_id ];
	}
}

$query = new WP_Query( $args );

// --- Wrapper classes + attributes. ---
$classes = [ 'flexa-post-grid', 'flexa-post-grid--' . $container_type ];
if ( '' !== $block_id ) {
	$classes[] = 'flexa-post-grid-' . sanitize_html_class( $block_id );
}
$classes = HTML_Helpers::build_wrapper_classes( $classes, $attributes );

$wrapper_args = [ 'class' => implode( ' ', $classes ) ];
if ( $anchor ) {
	$wrapper_args['id'] = sanitize_html_class( $anchor );
}
// Per-instance key: the view script uses it to scope AJAX paging to this grid.
$wrapper_args['data-flexa-post-grid'] = $page_key;
if ( $is_loadmore ) {
	// The view script fetches the next page's URL and appends its cards.
	$wrapper_args['data-flexa-loadmore'] = '1';
}
$wrapper_attributes = get_block_wrapper_attributes( $wrapper_args );
$data_attrs         = HTML_Helpers::build_data_attrs( $attributes );

// Lazy background marker on the styled inner element.
$background  = $attributes['background'] ?? [];
$is_lazy_bg  = ! empty( $background['lazyLoad'] ) && 'image' === ( $background['type'] ?? 'none' ) && '' !== ( $background['image']['url'] ?? '' );
$lazy_marker = $is_lazy_bg ? ' data-flexa-lazy-bg' : '';

// --- Build the cards. ---
$cards_html = '';
if ( $query->have_posts() ) {
	while ( $query->have_posts() ) {
		$query->the_post();
		$post_id   = get_the_ID();
		$permalink = get_permalink( $post_id );

		// Featured image (WP-escaped markup), linked to the post — or a neutral
		// placeholder glyph when the post has no thumbnail, so cards stay uniform.
		$image_html = '';
		if ( $show_image ) {
			if ( has_post_thumbnail( $post_id ) ) {
				$thumb = get_the_post_thumbnail( $post_id, $image_size, [ 'class' => 'flexa-post-grid__image', 'loading' => 'lazy' ] );
				if ( '' !== $thumb ) {
					$image_html = '<a class="flexa-post-grid__image-link" href="' . esc_url( $permalink ) . '" tabindex="-1" aria-hidden="true">'
						. '<span class="flexa-post-grid__image-wrap">' . $thumb . '</span></a>';
				}
			}
			if ( '' === $image_html ) {
				$image_html = '<span class="flexa-post-grid__image-wrap">' . HTML_Helpers::image_placeholder( '', $ratio_style ) . '</span>';
			}
		}

		// Meta line.
		$meta_html = '';
		if ( $show_meta ) {
			$parts = [];
			if ( $show_author ) {
				$author_inner = '';
				if ( $show_avatar ) {
					$author_inner .= get_avatar( get_the_author_meta( 'ID' ), $avatar_size, '', get_the_author(), [ 'class' => 'flexa-post-grid__avatar' ] );
				}
				$author_inner .= esc_html( get_the_author() );
				$parts[] = '<span class="flexa-post-grid__author">' . $author_inner . '</span>';
			}
			if ( $show_date ) {
				$parts[] = '<time class="flexa-post-grid__date" datetime="' . esc_attr( get_the_date( 'c', $post_id ) ) . '">' . esc_html( get_the_date( '', $post_id ) ) . '</time>';
			}
			if ( $show_comments ) {
				$comments_number = (int) get_comments_number( $post_id );
				/* translators: %d: number of comments on the post. */
				$comments_label  = sprintf( _n( '%d comment', '%d comments', $comments_number, 'flexa-block' ), $comments_number );
				$parts[]         = '<span class="flexa-post-grid__comments">' . esc_html( $comments_label ) . '</span>';
			}
			if ( $show_taxonomy ) {
				$tax_for_terms = '' !== $taxonomy && taxonomy_exists( $taxonomy ) ? $taxonomy : ( 'post' === $post_type ? 'category' : '' );
				if ( '' !== $tax_for_terms ) {
					$term_names = get_the_term_list( $post_id, $tax_for_terms, '', ', ' );
					if ( ! is_wp_error( $term_names ) && ! empty( $term_names ) ) {
						$parts[] = '<span class="flexa-post-grid__terms">' . wp_kses( $term_names, [ 'a' => [ 'href' => true, 'rel' => true ] ] ) . '</span>';
					}
				}
			}
			if ( ! empty( $parts ) ) {
				$meta_html = '<div class="flexa-post-grid__meta">' . implode( '<span class="flexa-post-grid__meta-sep" aria-hidden="true">·</span>', $parts ) . '</div>';
			}
		}

		// Title (linked).
		$title_html = '';
		if ( $show_title ) {
			$title_text = get_the_title( $post_id );
			$title_html = '<' . $title_tag . ' class="flexa-post-grid__title"><a href="' . esc_url( $permalink ) . '">' . esc_html( $title_text ) . '</a></' . $title_tag . '>';
		}

		// Excerpt (trimmed, formatting stripped to plain text).
		$excerpt_html = '';
		if ( $show_excerpt && $excerpt_length > 0 ) {
			$raw     = has_excerpt( $post_id ) ? get_the_excerpt( $post_id ) : wp_strip_all_tags( (string) get_post_field( 'post_content', $post_id ) );
			$trimmed = wp_trim_words( $raw, $excerpt_length, '…' );
			if ( '' !== trim( $trimmed ) ) {
				$excerpt_html = '<div class="flexa-post-grid__excerpt">' . esc_html( $trimmed ) . '</div>';
			}
		}

		// Read-more button.
		$read_more_html = '';
		if ( $show_read_more ) {
			$read_more_html = '<a class="flexa-post-grid__readmore wp-element-button" href="' . esc_url( $permalink ) . '">' . esc_html( $read_more_text ) . '</a>';
		}

		$body_html = '<div class="flexa-post-grid__body">' . $meta_html . $title_html . $excerpt_html . $read_more_html . '</div>';

		$cards_html .= '<article class="flexa-post-grid__item">' . $image_html . $body_html . '</article>';
	}
	wp_reset_postdata();
}

// --- Pagination. Numbered = server-rendered links (progressive: works with no
// JS; the view script upgrades to AJAX). Load more = a button the view script
// wires to fetch + append the next page. ---
$pagination_html = '';
$total           = max( 0, (int) $query->found_posts - $base_offset );
$total_pages     = (int) ceil( $total / $per_page );

if ( $show_pagination && $total_pages > 1 ) {
	$links = paginate_links(
		[
			'base'      => esc_url_raw( add_query_arg( $page_key, '%#%' ) ),
			'format'    => '',
			'current'   => $paged,
			'total'     => $total_pages,
			'type'      => 'plain',
			'prev_text' => esc_html( $prev_label ),
			'next_text' => esc_html( $next_label ),
		]
	);
	if ( $links ) {
		$pagination_html = '<nav class="flexa-pagination" aria-label="' . esc_attr__( 'Posts navigation', 'flexa-block' ) . '">' . $links . '</nav>';
	}
} elseif ( $is_loadmore && $total_pages > 1 ) {
	$next_url        = esc_url( add_query_arg( $page_key, 2 ) );
	$pagination_html = '<div class="flexa-pagination-loadmore" data-next="' . esc_attr( $next_url ) . '" data-page="1" data-total="' . esc_attr( (string) $total_pages ) . '" data-key="' . esc_attr( $page_key ) . '">'
		. '<button type="button" class="flexa-pagination-loadmore__btn wp-element-button">' . esc_html( $load_more_text ) . '</button></div>';
}

// --- Body: the grid, or a graceful empty state. ---
if ( '' !== $cards_html ) {
	$inner_html = '<div class="flexa-post-grid__grid">' . $cards_html . '</div>' . $pagination_html;
} else {
	$inner_html = '<p class="flexa-post-grid__empty">' . esc_html__( 'No posts found.', 'flexa-block' ) . '</p>';
}

printf(
	'<%1$s %2$s%3$s><div class="flexa-post-grid__inner"%4$s>%5$s</div></%1$s>',
	esc_html( $html_tag ),
	$wrapper_attributes, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built via get_block_wrapper_attributes.
	$data_attrs,         // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- keys sanitized, values escaped in helper.
	$lazy_marker,        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static literal.
	$inner_html          // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- titles/dates esc_html'd, urls esc_url'd, thumbnail + paginate_links WP-escaped.
);
