<?php
namespace Bricks;

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

class Element_Posts extends Custom_Render_Element {
	public $category     = 'wordpress';
	public $name         = 'posts';
	public $icon         = 'ti-layout-media-overlay';
	public $css_selector = '.bricks-layout-inner';
	public $scripts      = [ 'bricksIsotope' ];

	// @var array Arguments passed to WP_Query.
	public $query_vars = null;

	public function get_label() {
		return esc_html__( 'Posts', 'bricks' );
	}

	public function enqueue_scripts() {
		$layout = ! empty( $this->settings['layout'] ) ? $this->settings['layout'] : 'grid';

		// Load IsotopeJS
		if ( isset( $this->settings['filter'] ) || $layout === 'masonry' ) {
			wp_enqueue_script( 'bricks-isotope' );
			wp_enqueue_style( 'bricks-isotope' );
		}
	}

	public function set_control_groups() {
		$this->control_groups['layout'] = [
			'title' => esc_html__( 'Layout', 'bricks' ),
			'tab'   => 'content',
		];

		$this->control_groups['image'] = [
			'title' => esc_html__( 'Image', 'bricks' ),
			'tab'   => 'content',
		];

		$this->control_groups['fields'] = [
			'title' => esc_html__( 'Fields', 'bricks' ),
			'tab'   => 'content',
		];

		$this->control_groups['content'] = [
			'title' => esc_html__( 'Content', 'bricks' ),
			'tab'   => 'content',
		];

		$this->control_groups['overlay'] = [
			'title' => esc_html__( 'Overlay', 'bricks' ),
			'tab'   => 'content',
		];

		$this->control_groups['filter'] = [
			'title' => esc_html__( 'Filter', 'bricks' ),
			'tab'   => 'content',
		];

		$this->control_groups['pagination'] = [
			'title' => esc_html__( 'Pagination', 'bricks' ),
			'tab'   => 'content',
		];

	}

	public function set_controls() {
		$this->controls['_gradient']['css'][0]['selector'] = '.image';

		// QUERY

		$this->controls['query'] = [
			'tab'     => 'content',
			'label'   => esc_html__( 'Query', 'bricks' ),
			'type'    => 'query',
			'popup'   => true,
			'inline'  => true,
			'exclude' => [ 'objectType' ],
		];

		$this->controls['linkPost'] = [
			'tab'    => 'content',
			'label'  => esc_html__( 'Link entire post', 'bricks' ),
			'type'   => 'checkbox',
			'inline' => true,
		];

		// LAYOUT

		$this->controls['layout'] = [
			'tab'         => 'content',
			'group'       => 'layout',
			'label'       => esc_html__( 'Layout', 'bricks' ),
			'type'        => 'select',
			'options'     => [
				'list'    => esc_html__( 'List', 'bricks' ),
				'grid'    => esc_html__( 'Grid', 'bricks' ),
				'masonry' => 'Masonry',
				'metro'   => 'Metro',
			],
			'placeholder' => esc_html__( 'Grid', 'bricks' ),
			'inline'      => true,
		];

		$this->controls['direction'] = [
			'tab'      => 'content',
			'group'    => 'layout',
			'label'    => esc_html__( 'Direction', 'bricks' ) . ' (' . esc_html__( 'Item', 'bricks' ) . ')',
			'type'     => 'direction',
			'css'      => [
				[
					'property'  => 'flex-direction',
					'selector'  => '.bricks-layout-wrapper[data-layout=list] .bricks-layout-inner',
					'important' => true, // @since 1.11.1: Added back the !important to override the "alternate" option
				],
			],
			'inline'   => true,
			'required' => [ 'layout', '=', 'list' ],
		];

		$this->controls['columns'] = [
			'tab'         => 'content',
			'group'       => 'layout',
			'label'       => esc_html__( 'Columns', 'bricks' ),
			'type'        => 'number',
			'min'         => 1,
			'css'         => [
				[
					'property' => '--columns',
					'selector' => '.bricks-layout-wrapper',
				],
			],
			'rerender'    => true,
			'placeholder' => 2,
			'required'    => [ 'layout', '!=', [ 'list', 'metro' ] ],
		];

		$this->controls['columnsMetro'] = [
			'tab'         => 'content',
			'group'       => 'layout',
			'label'       => esc_html__( 'Columns', 'bricks' ),
			'info'        => 'Metro',
			'type'        => 'number',
			'min'         => 1,
			'css'         => [
				[
					'selector' => '.bricks-layout-wrapper',
					'property' => 'grid-template-columns',
					'value'    => 'repeat(%s, 1fr)',
				],
				[
					'selector' => '.bricks-layout-item',
					'property' => 'grid-column',
					'value'    => 'unset',
				],
				[
					'selector' => '.bricks-layout-item',
					'property' => 'grid-row',
					'value'    => 'unset',
				],
			],
			'placeholder' => 3,
			'rerender'    => true,
			'desc'        => esc_html__( 'Set number of columns on smaller breakpoints.', 'bricks' ),
			'required'    => [ 'layout', '=', 'metro' ],
		];

		$this->controls['gutter'] = [
			'tab'      => 'content',
			'group'    => 'layout',
			'label'    => esc_html__( 'Spacing', 'bricks' ),
			'type'     => 'number',
			'units'    => true,
			'css'      => [
				[
					'property' => '--gutter',
					'selector' => '.bricks-layout-wrapper',
				],
			],
			'rerender' => true,
			'default'  => '30px',
		];

		$this->controls['firstPostFullWidth'] = [
			'tab'      => 'content',
			'group'    => 'layout',
			'label'    => esc_html__( 'First post full width', 'bricks' ),
			'type'     => 'checkbox',
			'css'      => [
				// CSS Grid
				[
					'selector' => '[data-layout="grid"] .bricks-layout-item:first-child',
					'property' => 'grid-column',
					'value'    => '1 / -1',
				],

				// IsotopeJS ('filter' on)
				[
					'selector' => '[data-layout="grid"] .bricks-layout-item:first-child',
					'property' => 'width',
					'value'    => '100%',
				],
			],
			'rerender' => true,
			'required' => [ 'layout', '!=', [ 'list', 'masonry', 'metro' ] ],
		];

		// IMAGE

		$this->controls['imageDisable'] = [
			'tab'   => 'content',
			'group' => 'image',
			'label' => esc_html__( 'Disable image', 'bricks' ),
			'type'  => 'checkbox',
		];

		$this->controls['imageLink'] = [
			'tab'      => 'content',
			'group'    => 'image',
			'label'    => esc_html__( 'Link image', 'bricks' ),
			'type'     => 'checkbox',
			'required' => [ 'layout', '=', [ '', 'list', 'grid', 'masonry' ] ],
		];

		$this->controls['imageLinkAlt'] = [
			'group'  => 'image',
			'label'  => esc_html__( 'Image link alt text', 'bricks' ),
			'type'   => 'text',
			'inline' => true,
		];

		$this->controls['alternate'] = [
			'tab'      => 'content',
			'group'    => 'image',
			'label'    => esc_html__( 'Alternate images', 'bricks' ),
			'info'     => esc_html__( 'Sets direction to row', 'bricks' ),
			'type'     => 'checkbox',
			'required' => [ 'layout', '=', 'list' ],
		];

		$this->controls['imagePosition'] = [
			'tab'         => 'content',
			'group'       => 'image',
			'label'       => esc_html__( 'Image position', 'bricks' ),
			'type'        => 'select',
			'options'     => [
				'right' => esc_html__( 'Right', 'bricks' ),
				'left'  => esc_html__( 'Left', 'bricks' ),
			],
			'inline'      => true,
			'placeholder' => esc_html__( 'Left', 'bricks' ),
			'required'    => [
				[ 'layout', '=', 'list' ],
				[ 'alternate', '=', false ],
			],
		];

			$this->controls['width'] = [
				'tab'      => 'content',
				'group'    => 'image',
				'label'    => esc_html__( 'Image width', 'bricks' ),
				'type'     => 'number',
				'units'    => true,
				'css'      => [
					[
						'property' => 'max-width',
						'selector' => '.bricks-layout-wrapper[data-layout=list] .image-wrapper',
					],
					[
						'property' => 'max-width',
						'selector' => '.bricks-layout-wrapper[data-layout=grid] .image-wrapper',
					],
					[
						'property' => 'max-width',
						'selector' => '.bricks-layout-inner > a',
					],
					[
						'property' => 'max-width',
						'selector' => '.overlay-wrapper',
					],
				],
				'rerender' => true, // NOTE: Undocumented (causes AJAX call, so don't use on control 'slider' etc.)
				'required' => [ 'layout', '=', [ '', 'list', 'grid' ] ],
			];

			$this->controls['height'] = [
				'tab'      => 'content',
				'group'    => 'image',
				'label'    => esc_html__( 'Image height', 'bricks' ),
				'type'     => 'number',
				'units'    => true,
				'css'      => [
					[
						'property' => 'height',
						'selector' => '.bricks-layout-wrapper[data-layout=list] img',
					],
					[
						'property' => 'height',
						'selector' => '.bricks-layout-wrapper[data-layout=grid] img',
					],
					[
						'property' => 'height',
						'selector' => '.overlay-wrapper',
					],
				],
				'rerender' => true, // NOTE: Undocumented (causes AJAX call, so don't use on control 'slider' etc.)
				'required' => [ 'layout', '=', [ '', 'list', 'grid' ] ],
			];

			$this->controls['imageRatio'] = [
				'tab'         => 'content',
				'group'       => 'image',
				'label'       => esc_html__( 'Image ratio', 'bricks' ) . ' (' . esc_html__( 'Grid', 'bricks' ) . ')',
				'type'        => 'text',
				'inline'      => true,
				'css'         => [
					[
						'selector' => '.image',
						'property' => 'aspect-ratio',
					],
				],
				'placeholder' => esc_html__( 'None', 'bricks' ),
				'required'    => [ 'layout', '=', [ '', 'grid' ] ],
			];

			/**
			 * Custom aspect ratio (remove control from style tab)
			 *
			 * @since 1.9.7
			 */
			unset( $this->controls['_aspectRatio'] );
			$this->controls['_aspectRatio'] = [
				'tab'          => 'content',
				'group'        => 'image',
				'label'        => esc_html__( 'Aspect ratio', 'bricks' ) . ' (' . esc_html__( 'Grid', 'bricks' ) . ')',
				'type'         => 'text',
				'inline'       => true,
				'dd'           => false,
				'hasVariables' => true,
				'css'          => [
					[
						'selector' => '[data-layout="grid"] .image',
						'property' => 'aspect-ratio',
					],
				],
				'required'     => [ 'imageRatio', '=', 'custom' ],
			];

			$this->controls['imageSize'] = [
				'tab'      => 'content',
				'group'    => 'image',
				'label'    => esc_html__( 'Image size', 'bricks' ),
				'type'     => 'select',
				'options'  => $this->control_options['imageSizes'],
				'required' => [ 'imageDisable', '=', '' ],
			];

			// FIELDS

			$this->controls = array_replace_recursive( $this->controls, $this->get_post_fields() );

			// CONTENT

			$this->controls = array_replace_recursive( $this->controls, $this->get_post_content() );

			// OVERLAY

			$this->controls = array_replace_recursive( $this->controls, $this->get_post_overlay() );

			// FILTER

			$this->controls['filter'] = [
				'tab'         => 'content',
				'group'       => 'filter',
				'type'        => 'select',
				'label'       => esc_html__( 'Taxonomy', 'bricks' ),
				'options'     => Setup::$control_options['taxonomies'],
				'placeholder' => esc_html__( 'None', 'bricks' ),
			];

			$this->controls['filterTextAlign'] = [
				'tab'         => 'content',
				'group'       => 'filter',
				'type'        => 'text-align',
				'label'       => esc_html__( 'Text align', 'bricks' ),
				'css'         => [
					[
						'property' => 'text-align',
						'selector' => '.bricks-isotope-filters',
					],
				],
				'inline'      => true,
				'placeholder' => esc_html__( 'Center', 'bricks' ),
				'required'    => [ 'filter', '!=', '' ],
			];

			$this->controls['filterBackground'] = [
				'tab'      => 'content',
				'group'    => 'filter',
				'type'     => 'color',
				'label'    => esc_html__( 'Background', 'bricks' ),
				'css'      => [
					[
						'property' => 'background-color',
						'selector' => '.bricks-isotope-filters li',
					],
				],
				'required' => [ 'filter', '!=', '' ],
			];

			$this->controls['filterBackgroundActive'] = [
				'tab'      => 'content',
				'group'    => 'filter',
				'type'     => 'color',
				'label'    => esc_html__( 'Background active', 'bricks' ),
				'css'      => [
					[
						'property' => 'background',
						'selector' => '.bricks-isotope-filters .active',
					],
				],
				'required' => [ 'filter', '!=', '' ],
			];

			$this->controls['filterBorder'] = [
				'tab'      => 'content',
				'group'    => 'filter',
				'type'     => 'border',
				'label'    => esc_html__( 'Border', 'bricks' ),
				'css'      => [
					[
						'property' => 'border',
						'selector' => '.bricks-isotope-filters li',
					],
				],
				'required' => [ 'filter', '!=', '' ],
			];

			$this->controls['filterTypography'] = [
				'tab'      => 'content',
				'group'    => 'filter',
				'type'     => 'typography',
				'label'    => esc_html__( 'Typography', 'bricks' ),
				'css'      => [
					[
						'property' => 'font',
						'selector' => '.bricks-isotope-filters li',
					],
				],
				'required' => [ 'filter', '!=', '' ],
			];

			$this->controls['filterTypographyActive'] = [
				'tab'      => 'content',
				'group'    => 'filter',
				'type'     => 'typography',
				'label'    => esc_html__( 'Typography active', 'bricks' ),
				'css'      => [
					[
						'property' => 'font',
						'selector' => '.bricks-isotope-filters .active',
					],
				],
				'required' => [ 'filter', '!=', '' ],
			];

			$this->controls['filterMargin'] = [
				'tab'         => 'content',
				'group'       => 'filter',
				'type'        => 'spacing',
				'label'       => esc_html__( 'Margin', 'bricks' ),
				'css'         => [
					[
						'property' => 'margin',
						'selector' => '.bricks-isotope-filters li',
					],
				],
				'placeholder' => [
					'top'    => 0,
					'right'  => 0,
					'bottom' => 30,
					'left'   => 0,
				],
				'required'    => [ 'filter', '!=', '' ],
			];

			$this->controls['filterPadding'] = [
				'tab'         => 'content',
				'group'       => 'filter',
				'type'        => 'spacing',
				'label'       => esc_html__( 'Padding', 'bricks' ),
				'css'         => [
					[
						'property' => 'padding',
						'selector' => '.bricks-isotope-filters li',
					],
				],
				'placeholder' => [
					'top'    => 0,
					'right'  => 20,
					'bottom' => 0,
					'left'   => 20,
				],
				'required'    => [ 'filter', '!=', '' ],
			];

			// NAVIGATION

			$this->controls['postsNavigation'] = [
				'tab'   => 'content',
				'group' => 'pagination',
				'type'  => 'checkbox',
				'label' => esc_html__( 'Show', 'bricks' ),
			];

			$this->controls['postsNavigationMargin'] = [
				'deprecated'  => true, // @since 1.5
				'tab'         => 'content',
				'group'       => 'pagination',
				'type'        => 'spacing',
				'label'       => esc_html__( 'Spacing', 'bricks' ),
				'css'         => [
					[
						'property' => 'margin',
						'selector' => '.bricks-pagination ul li',
					],
				],
				'placeholder' => [
					'top'    => 0,
					'right'  => 10,
					'bottom' => 0,
					'left'   => 10,
				],
				'required'    => [ 'postsNavigation', '!=', '' ],
			];

			$this->controls['postsNavigationTextAlign'] = [
				'deprecated'  => true, // @since 1.5
				'tab'         => 'content',
				'group'       => 'pagination',
				'type'        => 'text-align',
				'label'       => esc_html__( 'Text align', 'bricks' ),
				'css'         => [
					[
						'property' => 'text-align',
						'selector' => '.bricks-pagination',
					],
					[
						'property' => 'display',
						'selector' => '.bricks-pagination',
						'value'    => 'block',
					],
				],
				'inline'      => true,
				'placeholder' => esc_html__( 'Center', 'bricks' ),
				'exclude'     => [
					'justify',
				],
				'required'    => [ 'postsNavigation', '!=', '' ],
			];

			$this->controls['postsNavigationJustifyContent'] = [
				'tab'     => 'content',
				'group'   => 'pagination',
				'label'   => esc_html__( 'Alignment', 'bricks' ),
				'type'    => 'justify-content',
				'exclude' => 'space',
				'inline'  => true,
				'css'     => [
					[
						'selector' => '.bricks-pagination ul',
						'property' => 'justify-content',
					],
				],
			];

			$this->controls['postsNavigationHeight'] = [
				'tab'      => 'content',
				'group'    => 'pagination',
				'type'     => 'number',
				'units'    => true,
				'label'    => esc_html__( 'Height', 'bricks' ),
				'css'      => [
					[
						'property' => 'height',
						'selector' => '.bricks-pagination ul .page-numbers',
					],
				],
				'required' => [ 'postsNavigation', '!=', '' ],
			];

			$this->controls['postsNavigationWidth'] = [
				'tab'      => 'content',
				'group'    => 'pagination',
				'type'     => 'number',
				'units'    => true,
				'label'    => esc_html__( 'Width', 'bricks' ),
				'css'      => [
					[
						'property' => 'width',
						'selector' => '.bricks-pagination ul .page-numbers',
					],
				],
				'required' => [ 'postsNavigation', '!=', '' ],
			];

			$this->controls['postsNavigationGap'] = [
				'tab'         => 'content',
				'group'       => 'pagination',
				'label'       => esc_html__( 'Spacing', 'bricks' ),
				'type'        => 'number',
				'units'       => true,
				'css'         => [
					[
						'selector' => '.bricks-pagination ul',
						'property' => 'gap',
					],
				],
				'placeholder' => 20,
				'required'    => [ 'postsNavigation', '!=', '' ],
			];

			$this->controls['postsNavigationBackground'] = [
				'tab'      => 'content',
				'group'    => 'pagination',
				'type'     => 'color',
				'label'    => esc_html__( 'Background', 'bricks' ),
				'css'      => [
					[
						'property' => 'background',
						'selector' => '.bricks-pagination ul .page-numbers',
					],
				],
				'required' => [ 'postsNavigation', '!=', '' ],
			];

			$this->controls['postsNavigationBorder'] = [
				'tab'      => 'content',
				'group'    => 'pagination',
				'type'     => 'border',
				'label'    => esc_html__( 'Border', 'bricks' ),
				'css'      => [
					[
						'property' => 'border',
						'selector' => '.bricks-pagination ul .page-numbers',
					],
				],
				'required' => [ 'postsNavigation', '!=', '' ],
			];

			$this->controls['postsNavigationTypography'] = [
				'tab'      => 'content',
				'group'    => 'pagination',
				'type'     => 'typography',
				'label'    => esc_html__( 'Typography', 'bricks' ),
				'css'      => [
					[
						'property' => 'font',
						'selector' => '.bricks-pagination .page-numbers',
					],
				],
				'required' => [ 'postsNavigation', '!=', '' ],
			];

			// CURRENT PAGE

			$this->controls['postsNavigationActiveSeparator'] = [
				'tab'      => 'content',
				'group'    => 'pagination',
				'type'     => 'separator',
				'label'    => esc_html__( 'Current', 'bricks' ),
				'required' => [ 'postsNavigation', '!=', '' ],
			];

			$this->controls['postsNavigationBackgroundActive'] = [
				'tab'      => 'content',
				'group'    => 'pagination',
				'type'     => 'color',
				'label'    => esc_html__( 'Background', 'bricks' ),
				'css'      => [
					[
						'property' => 'background',
						'selector' => '.bricks-pagination ul .page-numbers.current',
					],
				],
				'required' => [ 'postsNavigation', '!=', '' ],
			];

			$this->controls['postsNavigationBorderActive'] = [
				'tab'      => 'content',
				'group'    => 'pagination',
				'type'     => 'border',
				'label'    => esc_html__( 'Border', 'bricks' ),
				'css'      => [
					[
						'property' => 'border',
						'selector' => '.bricks-pagination ul .page-numbers.current',
					],
				],
				'required' => [ 'postsNavigation', '!=', '' ],
			];

			$this->controls['postsNavigationTypographyActive'] = [
				'tab'      => 'content',
				'group'    => 'pagination',
				'type'     => 'typography',
				'label'    => esc_html__( 'Typography', 'bricks' ),
				'css'      => [
					[
						'property' => 'font',
						'selector' => '.bricks-pagination ul .page-numbers.current',
					],
				],
				'required' => [ 'postsNavigation', '!=', '' ],
			];
	}

	public function render() {
		$settings    = $this->settings;
		$layout      = ! empty( $settings['layout'] ) ? $settings['layout'] : 'grid';
		$filter      = ! empty( $settings['filter'] ) ? $settings['filter'] : false;
		$use_isotope = $layout === 'masonry' || $filter;
		$link_post   = $settings['linkPost'] ?? false;

		/**
		 * Check if current request is a load more/infinite scroll request
		 *
		 * If so, do not render wrappers.
		 *
		 * 'query_result' API call is also considered a load more/infinite scroll request.
		 *
		 * @since 1.8.1
		 */
		$is_load_more_request = Api::is_current_endpoint( 'load_query_page' ) || Api::is_current_endpoint( 'query_result' );

		// Skip rendering wrappers if this is a load more request
		if ( ! $is_load_more_request ) {
			$wrapper_classes = [ 'bricks-layout-wrapper' ];

			// Load IsotopeJS
			if ( $use_isotope ) {
				$wrapper_classes[] = 'isotope';
				$wrapper_classes[] = 'isotope-before-init'; // Avoid unstyled content visible (@since 1.12)
			}

			if ( $layout === 'list' ) {
				if ( isset( $settings['alternate'] ) ) {
					$wrapper_classes[] = 'alternate';
				}

				if ( ! isset( $settings['imageDisable'] ) && ! empty( $settings['imagePosition'] ) ) {
					$wrapper_classes[] = "image-position-{$settings['imagePosition']}";
				}
			}

			$this->set_attribute( 'item-sizer', 'class', 'bricks-isotope-sizer' );

			$this->set_attribute( 'ul', 'class', $wrapper_classes );

			$this->set_attribute( 'ul', 'data-layout', $layout );
		}

		// Posts query
		add_filter( 'bricks/posts/query_vars', [ $this, 'maybe_set_preview_query' ], 10, 3 );

		$query = new Query(
			[
				'id'       => $this->id,
				'settings' => $settings,
			]
		);

		$posts_query = $query->query_result;

		remove_filter( 'bricks/posts/query_vars', [ $this, 'maybe_set_preview_query' ], 10, 3 );

		$no_result_content = '';

		// No results
		if ( ! $posts_query->found_posts ) {
			$no_result_content = $query->get_no_results_content();

			// Wrap no result content in a list item to match Posts element output
			$no_result_content = $this->no_results_html( $no_result_content );

			// Early return if this is a load more request and query has no results
			if ( $is_load_more_request ) {
				echo $no_result_content;
				return;
			}
		}

		$post_index = 0;

		// Set $bricks_query (@since 1.10.2)
		$this->set_bricks_query( $query );
		$this->start_iteration();

		while ( $posts_query->have_posts() ) {
			$posts_query->the_post();
			$post = get_post();

			// Include brxe-{element.id} class for AJAX calls if Pagination element AJAX enabled (@see #33nr345)
			$item_classes = [ 'bricks-layout-item', 'repeater-item', "brxe-{$this->id}" ];

			// Filter by category/post_tag
			if ( $filter ) {
				$post_terms = wp_get_post_terms( $post->ID, $filter );

				foreach ( $post_terms as $term ) {
					// Skip 'uncategorized' category
					if ( $term->slug !== 'uncategorized' ) {
						$item_classes[] = $term->slug;
					}
				}
			}

			$this->set_attribute( "item-$post_index", 'class', $item_classes );

			// Post wrapper
			$this->set_attribute( "post-wrapper-$post_index", 'class', 'bricks-layout-inner' );

			// Reset classes before next loop
			$item_classes = [];
			$post_index++;
		}

		wp_reset_postdata();

		// STEP: Render

		// Do not render the wrappers on infinite scroll request
		if ( ! $is_load_more_request ) {
			echo "<div {$this->render_attributes( '_root' )}>";

			if ( $filter ) {
				$terms = get_terms(
					[
						'taxonomy'   => $filter,
						'hide_empty' => false,
					]
				);

				echo '<ul id="bricks-isotope-filters-' . sanitize_html_class( $this->id ) . '" class="bricks-isotope-filters">';
				echo '<li class="active" data-filter="*">' . esc_html__( 'All', 'bricks' ) . '</li>';

				foreach ( $terms as $term ) {
					if ( $term->slug !== 'uncategorized' ) {
						echo '<li data-filter=".' . esc_attr( $term->slug ) . '">' . esc_attr( $term->name ) . '</li>';
					}
				}

				echo '</ul>';
			}

			echo "<ul {$this->render_attributes( 'ul' )}>";
		}

		// Figure image wrapper classes
		$image_wrapper_classes = [ 'image-wrapper' ];

		$image_atts = [ 'class' => 'image css-filter' ];

		if ( $this->lazy_load() ) {
			$image_atts['class'] .= ' bricks-lazy-hidden';
			$image_atts['class'] .= ' bricks-lazy-load-isotope';
		}

		$post_index = 0;

		while ( $posts_query->have_posts() ) {
			$posts_query->the_post();

			$post = get_post();

			// (@since 1.10.2)
			$this->set_loop_object( $post );

			// Overlay wrapper
			$overlay_wrapper_html = '';

			if ( isset( $settings['fields'] ) && is_array( $settings['fields'] ) ) {
				$overlay_fields = [];

				foreach ( $settings['fields'] as $field ) {
					if ( isset( $field['overlay'] ) || $layout === 'metro' ) {
						$overlay_fields[] = $field;
					}
				}

				if ( count( $overlay_fields ) ) {
					$this->set_attribute(
						"overlay-wrapper-$post_index",
						'class',
						[
							'overlay-wrapper',
							isset( $settings['overlayAlign'] ) ? $settings['overlayAlign'] : '',
							isset( $settings['overlayOnHover'] ) ? 'show-on-hover' : '',
							isset( $settings['overlayAnimation'] ) ? $settings['overlayAnimation'] : '',
						]
					);

					$overlay_wrapper_html .= "<div {$this->render_attributes( "overlay-wrapper-$post_index" )}>";
					$overlay_wrapper_html .= '<div class="overlay-inner">';
					$overlay_wrapper_html .= Frontend::get_content_wrapper( $settings, $overlay_fields, $post );
					$overlay_wrapper_html .= '</div>';
					$overlay_wrapper_html .= '</div>';
				}
			}

			$first_node = "<li {$this->render_attributes( "item-$post_index" )}>";

			// Insert Loop marker for the first loop item (@since 1.12.3)
			if ( ! $is_load_more_request && $post_index === 0 ) {
				$first_node = $query->maybe_add_loop_marker( $first_node );
			}

			echo $first_node;

			if ( $link_post ) {
				echo '<a href="' . get_the_permalink( $post->ID ) . '">';
			}

			echo "<div {$this->render_attributes( "post-wrapper-$post_index" )}>";

			$item_html = '';

			// Image
			$disable_image = isset( $settings['imageDisable'] );
			$has_image     = ! $disable_image && has_post_thumbnail( $post->ID );

			// Render overlay_wrapper_html (for layout 'metro' & 'list' even if there is no featured image)
			if ( $has_image || ( $layout === 'metro' || $layout === 'list' ) ) {
				// Render figure > img
				$this->set_attribute( "image-wrapper-$post_index", 'class', $image_wrapper_classes );

				$direction = $settings['direction'] ?? 'column';

				/**
				 * Render .image-wrapper <figure>
				 * - Direction is 'row'
				 * - 'column' && post has featured image
				 * - imagePosition is set (@since 1.10)
				 * - alternate is set (@since 1.10)
				 */
				$render_image_wrapper = $layout !== 'list' || ( $layout === 'list' && ! $disable_image && ( strpos( $direction, 'row' ) !== false || ( $direction === 'column' && $has_image ) || ! empty( $settings['imagePosition'] ) || ! empty( $settings['alternate'] ) ) );

				if ( $render_image_wrapper ) {
					$item_html .= "<figure {$this->render_attributes( "image-wrapper-$post_index" )}>";
				}

				// Link image
				$overlay_has_links = strpos( $overlay_wrapper_html, '<a ' ) !== false;

				if ( ! $link_post && isset( $settings['imageLink'] ) && ! $overlay_has_links ) {
					$item_html .= '<a href="' . get_the_permalink( $post->ID ) . '">';
				}

				if ( $has_image ) {
					$image_size = $settings['imageSize'] ?? BRICKS_DEFAULT_IMAGE_SIZE;

					// Prepare custom alt text (@since 1.11.1)
					$custom_alt = isset( $settings['imageLinkAlt'] ) ? $this->render_dynamic_data( $settings['imageLinkAlt'] ) : '';
					if ( $custom_alt ) {
						$image_atts['alt'] = esc_attr( $custom_alt );
					}

					$item_html .= wp_get_attachment_image( get_post_thumbnail_id( $post->ID ), $image_size, false, $image_atts );
				}

				$item_html .= $overlay_wrapper_html;

				if ( ! $link_post && isset( $settings['imageLink'] ) && ! $overlay_has_links ) {
					$item_html .= '</a>';
				}

				if ( $render_image_wrapper ) {
					$item_html .= '</figure>';
				}
			}

			// Content
			if ( isset( $settings['fields'] ) && is_array( $settings['fields'] ) ) {
				$content_fields = [];

				foreach ( $settings['fields'] as $field ) {
					if ( ! isset( $field['overlay'] ) && $layout !== 'metro' ) {
						$content_fields[] = $field;
					}
				}

				if ( count( $content_fields ) ) {
					$this->set_attribute(
						"content-wrapper-$post_index",
						'class',
						[
							'content-wrapper',
							isset( $settings['contentAlign'] ) ? $settings['contentAlign'] : '',
						]
					);

					$item_html .= "<div {$this->render_attributes( "content-wrapper-$post_index" )}>";
					$item_html .= Frontend::get_content_wrapper( $settings, $content_fields, $post );
					$item_html .= '</div>';
				}
			}

			// Remove any links from item HTML (@since 1.11)
			if ( $link_post ) {
				$item_html = preg_replace( '/<a\b[^>]*>(.*?)<\/a>/i', '$1', $item_html );
			}

			echo $item_html;

			echo '</div>';

			if ( $link_post ) {
				echo '</a>';
			}

			echo '</li>';

			$post_index++;

			// (@since 1.10.2)
			$this->next_iteration();
		}

		// (@since 1.10.2)
		$this->end_iteration();

		wp_reset_postdata();

		// No results content (Normal page load) (@since 2.0)
		if ( $no_result_content !== '' ) {
			// Add loop marker to no result content
			echo $query->maybe_add_loop_marker( $no_result_content );
		}

		// Add infinite scroll information to isotope sizer (Use $query which is the Bricks Query object @since 2.0.2)
		$this->render_query_loop_trail( $query, 'item-sizer' );

		// Skip rendering wrappers if this is a load more request
		if ( ! $is_load_more_request ) {
			// 'item-sizer' used to add infinite scroll attributes
			echo "<li {$this->render_attributes( 'item-sizer' )}></li>";

			if ( $use_isotope ) {
				echo '<li class="bricks-gutter-sizer"></li>';
			}

			echo '</ul>';

			if ( isset( $settings['postsNavigation'] ) ) {
				$current_page = isset( $posts_query->query_vars['paged'] ) ? $posts_query->query_vars['paged'] : 1;
				$total_pages  = $posts_query->max_num_pages;

				echo Helpers::posts_navigation( $current_page, $total_pages );
			}

			echo '</div>';
		}
	}

	/**
	 * Modify the no results content to match the Posts element output
	 *
	 * @since 2.0
	 */
	public function no_results_html( $html ) {
		if ( $html === '' ) {
			return '';
		}

		$settings = $this->settings;
		$layout   = $settings['layout'] ?? 'grid';

		// Start building the output (provide bricks-posts-no-results class in case user wants to style it)
		$output  = "<li class='bricks-layout-item repeater-item brxe-{$this->id} bricks-posts-no-results'>";
		$output .= "<div class='bricks-layout-inner'>";

		if ( $layout === 'metro' ) {
			$output .= $this->build_metro_layout( $html );
		} else {
			$output .= $this->build_default_layout( $html );
		}

		$output .= '</div>';
		$output .= '</li>';

		return $output;
	}

	private function build_metro_layout( $html ) {
		return "<figure class='image-wrapper'>
			<div class='overlay-wrapper'>
				<div class='overlay-inner'>
					{$html}
				</div>
			</div>
		</figure>";
	}

	private function build_default_layout( $html ) {
		return "<div class='content-wrapper'>{$html}</div>";
	}
}
