<?php

/**
 * Post Carousel Module.
 *
 * @package DiviSupreme.
 */

declare(strict_types=1);

namespace DiviSupreme\Modules;

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

use ET\Builder\Framework\DependencyManagement\Interfaces\DependencyInterface;
use ET\Builder\Framework\Utility\HTMLUtility;
use ET\Builder\Framework\Utility\PostUtility;
use ET\Builder\FrontEnd\Module\Style;
use ET\Builder\Packages\Module\Module;
use ET\Builder\Packages\Module\Options\Element\ElementClassnames;
use ET\Builder\Packages\ModuleLibrary\ModuleRegistration;
use ET\Builder\Packages\ModuleUtils\ModuleUtils;
use ET\Builder\Packages\Module\Layout\Components\StyleCommon\CommonStyle;
use ET\Builder\Packages\IconLibrary\IconFont\Utils;
use ET\Builder\Packages\Module\Options\Text\TextStyle;
use ET\Builder\Packages\StyleLibrary\Utils\StyleDeclarations;
use ET\Builder\Packages\Module\Options\Css\CssStyle;
use DiviSupreme\Extensions\IconLibraries\LegacyModuleIconStyles;

/**
 * Class that handles "PostCarousel Module" module.
 */
class PostCarousel implements DependencyInterface {

	/**
	 * Guards against nested frontend renders (mirrors Blog module `$_rendering`).
	 *
	 * @var bool
	 */
	private static $dsm_post_carousel_rendering = false;

	/**
	 * Register module.
	 * `DependencyInterface` interface ensures class method name `load()` is executed for initialization.
	 */
	public function load() {
		add_action( 'init', array( self::class, 'register_module' ) );
	}

	/**
	 * Initilises the module.
	 *
	 * @return void
	 */
	public static function dsm_initialise() {
		add_action(
			'divi_module_library_modules_dependency_tree',
			function ( $dependency_tree ) {
				$dependency_tree->add_dependency( new self() );
			}
		);
	}

	public static function prev_icon_style_declaration( $params ) {
		return LegacyModuleIconStyles::build_pseudo_icon_declaration( $params );
	}

	public static function next_icon_style_declaration( $params ) {
		return LegacyModuleIconStyles::build_pseudo_icon_declaration( $params );
	}


	/**
	 * Normalize range/offset values: D4 often saved unitless numbers (e.g. `-40`).
	 *
	 * @param mixed $raw CommonStyle `attrValue`.
	 * @return string Normalized length or empty string.
	 */
	private static function normalize_css_length_scalar( $raw ): string {
		if ( is_array( $raw ) ) {
			$raw = '';
		}

		$s = trim( (string) $raw );

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

		if ( preg_match( '/calc\s*\(/i', $s ) || 0 === strpos( $s, 'var(' ) ) {
			return $s;
		}

		if ( preg_match( '/^([-+]?\d*\.?\d+)([a-z%]*)$/i', $s, $m ) ) {
			return '' !== $m[2] ? $s : $m[1] . 'px';
		}

		return $s;
	}

	/**
	 * Space-prefixed `data-icon` / `data-icon-*` for the readmore anchor (matches core ButtonModule).
	 *
	 * @param array<string, mixed> $attrs Full module attrs (`readmore.decoration.button`).
	 * @return string HTML attribute fragment (leading space), or empty string.
	 */
	private static function render_readmore_icon_data_attributes_html( array $attrs ) {
		$readmore = $attrs['readmore'] ?? null;
		if ( ! is_array( $readmore ) ) {
			return '';
		}

		$icon_enable_raw   = $readmore['decoration']['button']['desktop']['value']['icon']['enable'] ?? 'off';
		$is_icon_enabled   = 'on' === strtolower( (string) $icon_enable_raw );
		$button_decoration = $readmore['decoration']['button'] ?? array();
		if ( ! is_array( $button_decoration ) ) {
			return '';
		}

		$parts = array();
		foreach ( $button_decoration as $breakpoint => $breakpoint_value ) {
			if ( ! is_array( $breakpoint_value ) ) {
				continue;
			}
			$icon_settings = $breakpoint_value['value']['icon']['settings'] ?? null;
			if ( ! $is_icon_enabled || ! isset( $icon_settings ) || ! is_array( $icon_settings ) ) {
				continue;
			}
			$processed_icon = Utils::process_font_icon( $icon_settings );
			if ( '' === $processed_icon || null === $processed_icon ) {
				continue;
			}
			$bp = is_string( $breakpoint ) ? $breakpoint : (string) $breakpoint;
			if ( 'desktop' === $bp ) {
				$data_attr_name = 'data-icon';
			} else {
				$data_attr_name = 'data-icon-' . strtolower( (string) preg_replace( '/([A-Z])/', '-$1', $bp ) );
			}
			$parts[] = sprintf( '%s="%s"', esc_attr( $data_attr_name ), esc_attr( $processed_icon ) );
		}

		if ( array() === $parts ) {
			return '';
		}

		return ' ' . implode( ' ', $parts );
	}

	/**
	 * Font size for Swiper nav arrows; migrates unitless D4 `navi_size` to px.
	 *
	 * @param array $params CommonStyle declaration params.
	 * @return string
	 */
	public static function navi_arrow_font_size_declaration( $params ) {
		$normalized = self::normalize_css_length_scalar( $params['attrValue'] ?? '' );

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

		$style_declarations = new StyleDeclarations(
			array(
				'returnType' => 'string',
			)
		);

		$style_declarations->add( 'font-size', $normalized );

		return $style_declarations->value();
	}

	/**
	 * Swiper 12+: `--swiper-navigation-size` on module root from arrow size + 20px touch (matches module-styles.js).
	 *
	 * @param array $params CommonStyle declaration params.
	 * @return string
	 */
	public static function swiper_navigation_size_var_declaration( $params ) {
		$normalized = self::normalize_css_length_scalar( $params['attrValue'] ?? '' );

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

		$n = floatval( $normalized );

		if ( ! is_finite( $n ) || $n < 0 ) {
			return '';
		}

		$touch = $n + 20;

		$style_declarations = new StyleDeclarations(
			array(
				'returnType' => 'string',
			)
		);

		$style_declarations->add( '--swiper-navigation-size', $touch . 'px' );

		return $style_declarations->value();
	}

	/**
	 * Horizontal carousel: pagination `bottom` offset (unitless D4 → px).
	 *
	 * @param array $params CommonStyle declaration params.
	 * @return string
	 */
	public static function pagi_position_bottom_declaration( $params ) {
		$normalized = self::normalize_css_length_scalar( $params['attrValue'] ?? '' );

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

		$style_declarations = new StyleDeclarations(
			array(
				'returnType' => 'string',
			)
		);

		$style_declarations->add( 'bottom', $normalized );

		return $style_declarations->value();
	}

	/**
	 * Entry thumbnail image: object-fit. Cover fills tile; contain uses intrinsic img box (overlay matches picture).
	 *
	 * @param array $params CommonStyle declaration params.
	 * @return string
	 */
	public static function thumbnail_entry_image_object_fit_declaration( $params ) {
		$raw = $params['attrValue'] ?? '';
		if ( ! is_string( $raw ) || '' === trim( $raw ) ) {
			$raw = 'cover';
		}
		$type = strtolower( trim( $raw ) );
		$fit  = 'contain' === $type ? 'contain' : 'cover';

		$style_declarations = new StyleDeclarations(
			array(
				'returnType' => 'string',
			)
		);

		$style_declarations->add( 'object-fit', $fit );

		if ( 'contain' === $fit ) {
			$style_declarations->add( 'max-width', '100%' );
			$style_declarations->add( 'max-height', '100%' );
			$style_declarations->add( 'width', 'auto' );
			$style_declarations->add( 'height', 'auto' );
		} else {
			$style_declarations->add( 'width', '100%' );
			$style_declarations->add( 'height', '100%' );
		}

		return $style_declarations->value();
	}

	public static function overlay_icon_style_declaration( $params ) {
		return LegacyModuleIconStyles::build_pseudo_icon_declaration( $params );
	}

	/**
	 * Build innerContent-shaped attr for read-more button wrapper `text-align` (matches `module-styles.js`).
	 *
	 * @param array $attrs Module attrs.
	 * @return array<string, mixed> Attr payload for CommonStyle; may include empty `innerContent`.
	 */
	private static function readmore_button_alignment_inner_content_for_style( array $attrs ): array {
		$button = $attrs['readmore']['decoration']['button'] ?? null;
		if ( ! is_array( $button ) ) {
			return array( 'innerContent' => array() );
		}

		$inner_content = array();
		foreach ( array( 'desktop', 'tablet', 'phone' ) as $bp ) {
			$branch = $button[ $bp ] ?? null;
			if ( ! is_array( $branch ) ) {
				continue;
			}
			$value = $branch['value'] ?? null;
			if ( ! is_array( $value ) ) {
				continue;
			}
			$raw = $value['textAlign'] ?? $value['alignment'] ?? '';
			if ( ! is_string( $raw ) || '' === trim( $raw ) ) {
				continue;
			}
			$lower = strtolower( trim( $raw ) );
			if ( in_array( $lower, array( 'left', 'center', 'right', 'justify' ), true ) ) {
				$inner_content[ $bp ] = array( 'value' => $lower );
			}
		}

		return array( 'innerContent' => $inner_content );
	}

	/**
	 * Register module.
	 */
	public static function register_module() {

		$module_json_path = DSM_DIR_PATH . 'divi-5/public/packages/module-library/post-carousel';

		ModuleRegistration::register_module(
			$module_json_path,
			array(
				'render_callback' => array( self::class, 'render_callback' ),
			)
		);
	}

	/**
	 * Render module style.
	 *
	 * @param array $args - Arguments.
	 */
	public static function module_styles( $args ) {
		$args = \DiviSupreme\Includes\DsmDiviBuilderCssScope::normalize_module_styles_args( $args );
		$attrs              = $args['attrs'] ?? array();
		$elements           = $args['elements'];
		$pagi_ver_placement = $attrs['pagi_ver_placement']['innerContent']['desktop']['value'] ?? '';
		$readmore_align_attr = is_array( $attrs ) ? self::readmore_button_alignment_inner_content_for_style( $attrs ) : array( 'innerContent' => array() );

		Style::add(
			array(
				'id'            => $args['id'],
				'name'          => $args['name'],
				'orderIndex'    => $args['orderIndex'],
				'storeInstance' => $args['storeInstance'],
				'styles'        => array_values(
					array_filter(
						array(
					// Module.
					$elements->style(
						array(
							'attrName'   => 'module',
							'styleProps' => array(
								'disabledOn' => array(
									'disabledModuleVisibility' => $args['settings']['disabledModuleVisibility'] ?? null,
								),
							),
						)
					),

					CssStyle::style(
						array(
							'selector' => $args['orderClass'],
							'attr'     => $attrs['css'] ?? array(),
						)
					),

					CommonStyle::style(
						array(
							'selector'            => $args['orderClass'],
							'attr'                => $attrs['navi_size']['innerContent'] ?? array(),
							'declarationFunction' => array( static::class, 'swiper_navigation_size_var_declaration' ),
						)
					),

					$elements->style(
						array(
							'attrName' => 'navigationStyle',
						)
					),

					$elements->style(
						array(
							'attrName' => 'postItemStyle',
						)
					),

					$elements->style(
						array(
							'attrName' => 'thumbnailStyle',
						)
					),

					$elements->style(
						array(
							'attrName' => 'title',
						)
					),

					$elements->style(
						array(
							'attrName' => 'body',
						)
					),

					$elements->style(
						array(
							'attrName' => 'date',
						)
					),

					$elements->style(
						array(
							'attrName' => 'author',
						)
					),

					$elements->style(
						array(
							'attrName' => 'category',
						)
					),

					$elements->style(
						array(
							'attrName' => 'bottom_date',
						)
					),

					$elements->style(
						array(
							'attrName' => 'bottom_author',
						)
					),

					$elements->style(
						array(
							'attrName' => 'bottomStyle',
						)
					),

					$elements->style(
						array(
							'attrName' => 'readmore',
						)
					),

					( ! empty( $readmore_align_attr['innerContent'] )
						? CommonStyle::style(
							array(
								'selector' => "{$args['orderClass']} .dsm-readmore-wrap .et_pb_button_module_wrapper",
								'attr'     => $readmore_align_attr,
								'property' => 'text-align',
							)
						)
						: null
					),

					$elements->style(
						array(
							'attrName' => 'content_wrapper',
						)
					),

					$elements->style(
						array(
							'attrName' => 'entryMetaContainer',
						)
					),

					$elements->style(
						array(
							'attrName' => 'bottomMetaContainer',
						)
					),

					$elements->style(
						array(
							'attrName' => 'post_padding',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']}.dsm-post-carousel-slider-horizontal .swiper-button-next, {$args['orderClass']}.dsm-post-carousel-slider-horizontal .swiper-button-prev",
							'attr'     => $attrs['navi_position']['innerContent'] ?? array(),
							'property' => 'top',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']}.dsm-post-carousel-slider-horizontal .swiper-button-prev",
							'attr'     => $attrs['left_navi_position']['innerContent'] ?? array(),
							'property' => 'left',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']}.dsm-post-carousel-slider-horizontal .swiper-button-next",
							'attr'     => $attrs['right_navi_position']['innerContent'] ?? array(),
							'property' => 'right',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']}.dsm-post-carousel-slider-vertical .swiper-button-next, {$args['orderClass']}.dsm-post-carousel-slider-vertical .swiper-button-prev",
							'attr'     => $attrs['navi_hori_position']['innerContent'] ?? array(),
							'property' => 'left',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']}.dsm-post-carousel-slider-vertical .swiper-button-prev",
							'attr'     => $attrs['top_navi_ver_position']['innerContent'] ?? array(),
							'property' => 'top',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']}.dsm-post-carousel-slider-vertical .swiper-button-next",
							'attr'     => $attrs['bottom_navi_ver_position']['innerContent'] ?? array(),
							'property' => 'bottom',
						)
					),

					CommonStyle::style(
						array(
							'selector'            => "{$args['orderClass']} .swiper-button-prev::before",
							'attr'                => $attrs['prev_icon']['innerContent'] ?? array(),
							'declarationFunction' => array( static::class, 'prev_icon_style_declaration' ),
						)
					),

					CommonStyle::style(
						array(
							'selector'            => "{$args['orderClass']} .swiper-pagination.swiper-pagination-vertical",
							'attr'                => $attrs['pagi_position']['innerContent'] ?? array(),
							'declarationFunction' => function ( $params ) use ( $pagi_ver_placement ) {
								$offset = self::normalize_css_length_scalar( $params['attrValue'] ?? '' );

								$style_declarations = new StyleDeclarations(
									array(
										'returnType' => 'string',
										'important'  => array(
											'background' => true,
										),
									)
								);

								if ( '' === $offset ) {
									return $style_declarations->value();
								}

								if ( 'left' === $pagi_ver_placement ) {
									$style_declarations->add( 'right', 'auto' );
									$style_declarations->add( 'left', $offset );
								}

								if ( 'right' === $pagi_ver_placement ) {
									$style_declarations->add( 'right', $offset );
									$style_declarations->add( 'left', 'auto' );
								}

								return $style_declarations->value();
							},
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .swiper-pagination.swiper-pagination-vertical",
							'attr'     => $attrs['pagi_ver_position']['innerContent'] ?? array(),
							'property' => 'top',
						)
					),

					CommonStyle::style(
						array(
							'selector'            => "{$args['orderClass']} .swiper-button-next::before",
							'attr'                => $attrs['next_icon']['innerContent'] ?? array(),
							'declarationFunction' => array( static::class, 'next_icon_style_declaration' ),
						)
					),

					CommonStyle::style(
						array(
							'selector'            => "{$args['orderClass']}.dsm-overlay-enabled .dsm-entry-image .dsm-entry-thumbnail > a::before",
							'attr'                => $attrs['overlay_icon']['innerContent'] ?? array(),
							'declarationFunction' => array( static::class, 'overlay_icon_style_declaration' ),
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm-entry-image .dsm-entry-thumbnail > a::before",
							'attr'     => $attrs['overlay_icon_color']['innerContent'] ?? array(),
							'property' => 'color',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm-entry-image .dsm-entry-thumbnail > a::before",
							'attr'     => $attrs['overlay_icon_size']['innerContent'] ?? array(),
							'property' => 'font-size',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']}.dsm_post_carousel.dsm-post-carousel-slider-vertical .swiper-container",
							'attr'     => $attrs['slider_container_height']['innerContent'] ?? array(),
							'property' => 'height',
						)
					),

					CommonStyle::style(
						array(
							'selector'            => "{$args['orderClass']} .swiper-button-next, {$args['orderClass']} .swiper-button-prev",
							'attr'                => $attrs['navi_size']['innerContent'] ?? array(),
							'declarationFunction' => array( static::class, 'navi_arrow_font_size_declaration' ),
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .swiper-button-next::before, {$args['orderClass']} .swiper-button-prev::before",
							'attr'     => $attrs['navi_color']['innerContent'] ?? array(),
							'property' => 'color',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .swiper-button-next, {$args['orderClass']} .swiper-button-prev",
							'attr'     => $attrs['navi_background_color']['innerContent'] ?? array(),
							'property' => 'background-color',
						)
					),

					CommonStyle::style(
						array(
							'selector'            => "{$args['orderClass']}.dsm-post-carousel-slider-horizontal .swiper-pagination-bullets, {$args['orderClass']}.dsm-post-carousel-slider-horizontal .swiper-pagination-fraction, {$args['orderClass']}.dsm-post-carousel-slider-horizontal .swiper-pagination-custom",
							'attr'                => $attrs['pagi_position']['innerContent'] ?? array(),
							'declarationFunction' => array( static::class, 'pagi_position_bottom_declaration' ),
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .swiper-pagination-bullet, {$args['orderClass']} .swiper-pagination-progressbar",
							'attr'     => $attrs['pagi_color']['innerContent'] ?? array(),
							'property' => 'background',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .swiper-pagination-bullet.swiper-pagination-bullet-active, {$args['orderClass']} .swiper-pagination-progressbar .swiper-pagination-progressbar-fill",
							'attr'     => $attrs['pagi_active_color']['innerContent'] ?? array(),
							'property' => 'background',
						)
					),

					CommonStyle::style(
						array(
							'selector' => " {$args['orderClass']} .dsm-post-carousel-item",
							'attr'     => $attrs['post_item_bg_color']['innerContent'] ?? array(),
							'property' => 'background',
						)
					),

					CommonStyle::style(
						array(
							'selector' => " {$args['orderClass']} .dsm-entry-image .dsm-entry-thumbnail",
							'attr'     => $attrs['thumbnail_height']['innerContent'] ?? array(),
							'property' => 'height',
						)
					),

					CommonStyle::style(
						array(
							'selector'            => " {$args['orderClass']} .dsm-entry-image .dsm-entry-thumbnail img",
							'attr'                => $attrs['thumbnail_img_type']['innerContent'] ?? array(),
							'declarationFunction' => array( static::class, 'thumbnail_entry_image_object_fit_declaration' ),
						)
					),

					CommonStyle::style(
						array(
							'selector' => " {$args['orderClass']} .dsm-bottom-meta .dsm-bottom-meta-image img",
							'attr'     => $attrs['image_width']['innerContent'] ?? array(),
							'property' => 'width',
						)
					),

					CommonStyle::style(
						array(
							'selector' => " {$args['orderClass']} .dsm-entry-image .dsm-entry-thumbnail > a::before",
							'attr'     => $attrs['hover_overlay_color']['innerContent'] ?? array(),
							'property' => 'background',
						)
					),

					CommonStyle::style(
						array(
							'selector' => " {$args['orderClass']} .dsm-bottom-meta .dsm-bottom-meta-image img",
							'attr'     => $attrs['image_width']['innerContent'] ?? array(),
							'property' => 'width',
						)
					),
						)
					)
				),
			)
		);
	}

	/**
	 * Render module script data.
	 *
	 * @param array $args - Arguments.
	 */
	public static function module_script_data( $args ) {
		$elements = $args['elements'];

		// Element Script Data Options.
		$elements->script_data(
			array(
				'attrName' => 'module',
			)
		);
	}

	/**
	 * Render module classnames.
	 *
	 * @param array $args - Arguments.
	 */
	public static function module_classnames( $args ) {
		$classnames_instance = $args['classnamesInstance'];
		$attrs               = $args['attrs'];
		$post_equal_height   = $attrs['post_equal_height']['innerContent']['desktop']['value'] ?? '';
		$slider_orientation  = $attrs['slider_orientation']['innerContent']['desktop']['value'] ?? '';

		// Module.
		$classnames_instance->add(
			ElementClassnames::classnames(
				array(
					'attrs' => $attrs['module']['decoration'] ?? array(),
				)
			)
		);

		$classnames_instance->add( "dsm-post-carousel-slider-$slider_orientation" );

		if ( self::pc_toggle_is_on( $post_equal_height ) ) {
			$classnames_instance->add( 'dsm-post-item-equal-height' );
		}

		$use_overlay = self::get_pc_inner_desktop_value( $attrs, 'use_overlay' ) ?? '';
		if ( self::pc_toggle_is_on( $use_overlay ) ) {
			$classnames_instance->add( 'dsm-overlay-enabled' );
		}
	}

	/**
	 * Cached default render attrs (matches module-default-render-attributes.json / VB defaultAttr + getAttrByMode).
	 *
	 * @var array<string, mixed>|null
	 */
	private static $post_carousel_default_render_attrs = null;

	/**
	 * Load Post Carousel defaults from public package JSON.
	 *
	 * @return array<string, mixed>
	 */
	private static function get_post_carousel_default_render_attributes(): array {
		if ( null !== self::$post_carousel_default_render_attrs ) {
			return self::$post_carousel_default_render_attrs;
		}

		$path = DSM_DIR_PATH . 'divi-5/public/packages/module-library/post-carousel/module-default-render-attributes.json';
		if ( ! is_readable( $path ) ) {
			self::$post_carousel_default_render_attrs = array();
			return self::$post_carousel_default_render_attrs;
		}

		$decoded                                  = wp_json_file_decode( $path, array( 'associative' => true ) );
		self::$post_carousel_default_render_attrs = is_array( $decoded ) ? $decoded : array();

		return self::$post_carousel_default_render_attrs;
	}

	/**
	 * Effective innerContent desktop.value when Divi omitted keys that matched schema defaults.
	 *
	 * @param array  $attrs Module attrs.
	 * @param string $key   Attribute key.
	 * @return mixed|null
	 */
	private static function get_pc_inner_desktop_value( array $attrs, string $key ) {
		if ( isset( $attrs[ $key ]['innerContent']['desktop']['value'] ) ) {
			return $attrs[ $key ]['innerContent']['desktop']['value'];
		}

		$defaults = self::get_post_carousel_default_render_attributes();

		return $defaults[ $key ]['innerContent']['desktop']['value'] ?? null;
	}

	/**
	 * Whether a module "on/off" innerContent value is on (matches VB strict checks + common coercions).
	 *
	 * @param mixed $value Raw value.
	 * @return bool
	 */
	private static function pc_toggle_is_on( $value ): bool {
		return true === $value || 1 === $value || '1' === $value || 'on' === $value;
	}

	/**
	 * Normalize Pages "Included Categories" checkboxes for ModuleUtils (Blog-style `all` / `current` tokens).
	 *
	 * @param mixed $raw innerContent desktop value (array of strings, or legacy select string).
	 * @return array<int, string>
	 */
	private static function dsm_normalize_page_included_categories( $raw ): array {
		if ( is_string( $raw ) ) {
			$raw = trim( $raw );

			return ( 'current' === $raw ) ? array( 'current' ) : array( 'all' );
		}

		if ( ! is_array( $raw ) ) {
			return array( 'all' );
		}

		$out = array();
		foreach ( $raw as $item ) {
			if ( 'all' === $item || 'current' === $item ) {
				$out[] = $item;
			}
		}

		$out = array_values( array_unique( $out ) );

		return ! empty( $out ) ? $out : array( 'all' );
	}

	/**
	 * Post excerpt for carousel slides.
	 *
	 * Uses a word limit to match the Visual Builder (`limitExcerpt` on REST content). Auto-generated
	 * excerpts use {@see PostUtility::truncate_post()} with word mode (block-aware in Divi 5).
	 *
	 * Returns plain text suitable for the carousel markup (which escapes output).
	 *
	 * @param int $limit Maximum words (module default 50 when empty or invalid; matches module JSON / VB).
	 * @return string
	 */
	static function dsm_post_excerpt( $limit ) {
		$post_id = (int) get_the_ID();
		if ( $post_id <= 0 ) {
			return '';
		}

		$post = get_post( $post_id );
		if ( ! $post instanceof \WP_Post ) {
			return '';
		}

		$word_limit = (int) $limit;
		if ( $word_limit <= 0 ) {
			$word_limit = 50;
		}

		// Match Visual Builder `limitExcerpt` (three ASCII dots, not `excerpt_more` / HTML entity).
		$excerpt_more = '...';

		if ( ! function_exists( 'et_strip_shortcodes' ) || ! class_exists( PostUtility::class ) ) {
			$raw = wp_strip_all_tags( (string) $post->post_content );
			if ( '' === $raw ) {
				return '';
			}

			return wp_trim_words( $raw, $word_limit, $excerpt_more );
		}

		// Same source as BlogModule::render_content() for the excerpt vs full-content decision.
		$post_content = et_strip_shortcodes(
			PostUtility::delete_post_first_video(
				get_the_content( null, false, $post_id )
			),
			true
		);

		// Manual excerpt: trim by words to match VB `limitExcerpt` on displayed text.
		if ( function_exists( 'has_excerpt' ) && has_excerpt( $post_id ) ) {
			$manual_excerpt = get_the_excerpt( $post_id );
			$html           = apply_filters( 'the_excerpt', $manual_excerpt );
			$plain          = wp_strip_all_tags( $html );

			return wp_trim_words( $plain, $word_limit, $excerpt_more );
		}

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

		// PostUtility::truncate_post() applies `excerpt_more`; force `...` to match the builder.
		$dsm_excerpt_more = static function (): string {
			return '...';
		};
		add_filter( 'excerpt_more', $dsm_excerpt_more, 9999 );

		$GLOBALS['divi_generating_excerpt'] = true;
		try {
			// Fifth argument: word-based truncation (VB uses word count; Blog FE uses characters).
			$truncated = PostUtility::truncate_post( $word_limit, false, $post, true, true );
		} finally {
			$GLOBALS['divi_generating_excerpt'] = false;
			remove_filter( 'excerpt_more', $dsm_excerpt_more, 9999 );
		}

		if ( ! is_string( $truncated ) ) {
			return '';
		}

		return PostUtility::delete_post_first_video( strip_shortcodes( $truncated ) );
	}

	// protected static $rendering = false;

	/**
	 * Provides a list of all assigned public taxonomies to a particular post type.
	 *
	 * @param string $post_type - Post type to find taxonomies of.
	 *
	 * @return \WP_Taxonomy[] List of taxonomy slug.
	 */
	// public static function get_assigned_taxonomies($post_type)
	// {

	// Step 1: Safety check for incorrect type.
	// if (!is_string($post_type)) {
	// return array();
	// }

	// Step 2: Provide taxonomies.
	// $taxonomies = get_object_taxonomies($post_type, 'object');

	// Step 3: Collect public taxonomies.
	// $public_taxonomies = array();

	// foreach ($taxonomies as $taxonomy) {

	// Querying taxonomies that are publically available.
	// if (!$taxonomy->public || !$taxonomy->show_ui) {
	// continue;
	// }

	// $public_taxonomies[] = $taxonomy;
	// }

	// return $public_taxonomies;
	// }

	/**
	 * Constructs the taxonomy query based on given arguments.
	 *
	 * Note: Term ids are mixed for different taxonomies in this argument.
	 *
	 * @param int[]  $term_ids - Assigned term ids (mixed taxonomies).
	 * @param string $post_type - Assigned post type.
	 *
	 * @return array Taxonomy query args.
	 */
	static function construct_tax_query( $term_ids, $post_type ) {

		if ( 0 === count( $term_ids ) ) {
			return array();
		}

		// Parse term ids.
		$term_ids = array_map(
			function ( $term_id ) {
				$chunks = explode( ':', $term_id );
				return $chunks[1];
			},
			$term_ids
		);

		$tax_query_group = array();

		foreach ( $term_ids as $term_id ) {
			$current_term_id = (int) $term_id;
			$current_term    = get_term( $current_term_id );

			if ( ! ( $current_term instanceof \WP_Term ) ) {
				continue;
			}

			$taxonomy = get_taxonomy( $current_term->taxonomy );

			if ( ! ( $taxonomy instanceof \WP_Taxonomy ) ) {
				continue;
			}

			$is_attached_term = in_array( $post_type, $taxonomy->object_type );

			if ( ! $is_attached_term ) {
				continue;
			}

			if ( isset( $tax_query_group[ $taxonomy->name ] ) ) {
				$tax_query_group[ $taxonomy->name ] = array(
					'field'    => 'term_id',
					'taxonomy' => $taxonomy->name,
					'terms'    => array_merge(
						$tax_query_group[ $taxonomy->name ]['terms'],
						array( $current_term_id )
					),
				);
			} else {
				$tax_query_group[ $taxonomy->name ] = array(
					'field'    => 'term_id',
					'taxonomy' => $taxonomy->name,
					'terms'    => array( $current_term_id ),
				);
			}
		}

		return array(
			'relation' => 'AND',
			array_values(
				$tax_query_group
			),
		);
	}

	/**
	 * Resolves the post ID to exclude from carousel queries on singular views.
	 *
	 * Matches Divi Blog: skip the post being viewed so excerpt generation cannot re-enter the same layout.
	 *
	 * @return int Post ID or 0.
	 */
	private static function dsm_get_current_post_id_for_carousel_exclusion(): int {
		$is_theme_builder = class_exists( '\ET_Theme_Builder_Layout' ) && \ET_Theme_Builder_Layout::is_theme_builder_layout();

		if ( $is_theme_builder ) {
			$main_post_id = class_exists( '\ET_Post_Stack' ) ? (int) \ET_Post_Stack::get_main_post_id() : 0;
			if ( $main_post_id > 0 ) {
				return $main_post_id;
			}
		}

		if ( is_singular() ) {
			global $post;
			if ( $post && $post->ID > 0 ) {
				return (int) $post->ID;
			}
		}

		$post_id = (int) get_the_ID();

		return ( $post_id > 0 && is_singular() ) ? $post_id : 0;
	}

	static function get_blogposts_html( $args = array(), $conditional_tags = array(), $current_page = array() ) {

		global $paged, $post, $wp_query;

		$post_type         = self::get_pc_inner_desktop_value( $args, 'post_type' ) ?? 'page';
		$posts_number      = self::get_pc_inner_desktop_value( $args, 'posts_number' ) ?? 10;
		$post_orderby      = self::get_pc_inner_desktop_value( $args, 'post_orderby' ) ?? '';
		$post_sortby       = self::get_pc_inner_desktop_value( $args, 'post_sortby' ) ?? '';
		$posts_offset      = self::get_pc_inner_desktop_value( $args, 'posts_offset' ) ?? '';
		$use_thumbnail     = self::get_pc_inner_desktop_value( $args, 'use_thumbnail' ) ?? '';
		$lazyload          = self::get_pc_inner_desktop_value( $args, 'lazyload' ) ?? '';
		$use_title         = self::get_pc_inner_desktop_value( $args, 'use_title' ) ?? '';
		$use_date          = self::get_pc_inner_desktop_value( $args, 'use_date' ) ?? '';
		$meta_date         = self::get_pc_inner_desktop_value( $args, 'meta_date' ) ?? '';
		$use_excerpt          = self::get_pc_inner_desktop_value( $args, 'use_excerpt' ) ?? '';
		$excerpt_length       = self::get_pc_inner_desktop_value( $args, 'excerpt_length' ) ?? '';
		$include_current_post = self::get_pc_inner_desktop_value( $args, 'include_current_post' ) ?? '';
		$page_categories_tokens    = self::dsm_normalize_page_included_categories(
			self::get_pc_inner_desktop_value( $args, 'page_included_categories' )
		);
		$page_has_current_category = in_array( 'current', $page_categories_tokens, true );
		$use_author                = self::get_pc_inner_desktop_value( $args, 'use_author' ) ?? '';
		$meta_seperator    = self::get_pc_inner_desktop_value( $args, 'meta_seperator' ) ?? '';
		$use_readmore      = self::get_pc_inner_desktop_value( $args, 'use_readmore' ) ?? '';
		$use_bottom        = self::get_pc_inner_desktop_value( $args, 'use_bottom' ) ?? '';
		$bottom_date       = self::get_pc_inner_desktop_value( $args, 'bottom_date' ) ?? '';
		$bottom_author     = self::get_pc_inner_desktop_value( $args, 'bottom_author' ) ?? '';
		$use_category      = self::get_pc_inner_desktop_value( $args, 'use_category' ) ?? '';
		$use_comments      = self::get_pc_inner_desktop_value( $args, 'use_comments' ) ?? '';
		$assigned_term_ids = self::get_pc_inner_desktop_value( $args, 'term_ids' );
		if ( ! is_array( $assigned_term_ids ) ) {
			$assigned_term_ids = array();
		}
		$heading_level = $args['title']['decoration']['font']['font']['desktop']['value']['headingLevel'] ?? '';
		if ( '' === $heading_level ) {
			$defs          = self::get_post_carousel_default_render_attributes();
			$heading_level = $defs['title']['decoration']['font']['font']['desktop']['value']['headingLevel'] ?? 'h2';
		}
		$readmore_text = self::get_pc_inner_desktop_value( $args, 'readmore_text' ) ?? '';

		$defaults = array(
			'post_type'      => 'page',
			'posts_number'   => '',
			'post_orderby'   => '',
			'post_sortby'    => '',
			'posts_offset'   => '',
			'use_overlay'    => '',
			'title_level'    => '',
			'use_thumbnail'  => '',
			'lazyload'       => '',
			'use_title'      => '',
			'use_date'       => '',
			'meta_date'      => '',
			'use_excerpt'          => '',
			'excerpt_length'       => '',
			'include_current_post'     => '',
			'page_included_categories' => '',
			'use_author'               => '',
			'meta_seperator' => '',
			'use_readmore'   => '',
			'use_bottom'     => '',
			'bottom_date'    => '',
			'bottom_author'  => '',
			'use_comments'   => '',
		);

		$args = wp_parse_args( $args, $defaults );

		$thumbnail_img_type_raw = self::get_pc_inner_desktop_value( $args, 'thumbnail_img_type' ) ?? 'cover';
		$thumbnail_fit_class    = ( is_string( $thumbnail_img_type_raw ) && 'contain' === strtolower( trim( $thumbnail_img_type_raw ) ) )
			? 'dsm-thumbnail-fit-contain'
			: 'dsm-thumbnail-fit-cover';

		$is_single = et_fb_conditional_tag( 'is_single', $conditional_tags );
		$post_id   = isset( $current_page['id'] ) ? (int) $current_page['id'] : 0;

		$query_args = array(
			'posts_per_page'                     => intval( $posts_number ),
			'post_status'                        => 'publish',
			'post_type'                          => $post_type,
			'orderby'                            => $post_orderby,
			'order'                              => $post_sortby,
			'offset'                             => intval( $posts_offset ),
			'dsm_suppress_filters_post_carousel' => true,
		);

		// Pages: match Divi Blog — only "All Categories" / "Current Category" via ModuleUtils (not per-term tax UI).
		if ( 'page' !== $post_type ) {
			$tax_query = static::construct_tax_query( $assigned_term_ids, $post_type );
			if ( ! empty( $tax_query ) ) {
				$query_args['tax_query'] = $tax_query;
			}
		}

		if ( 'page' === $post_type && class_exists( ModuleUtils::class ) ) {
			$query_args = ModuleUtils::add_category_query_args(
				$query_args,
				$page_categories_tokens,
				'page',
				self::dsm_get_current_post_id_for_carousel_exclusion()
			);
		}

		// Exclude the viewed post on singular URLs unless opted in; always exclude when Pages use
		// "Current Category" (Divi Blog pattern — avoids an empty/self-referential slide).
		$exclude_singular  = is_singular() && ! self::pc_toggle_is_on( $include_current_post );
		$exclude_page_curr = is_singular() && 'page' === $post_type && $page_has_current_category;
		if ( $exclude_singular || $exclude_page_curr ) {
			$exclude_post_id = self::dsm_get_current_post_id_for_carousel_exclusion();
			if ( $exclude_post_id > 0 ) {
				if ( isset( $query_args['post__not_in'] ) ) {
					$query_args['post__not_in'] = array_unique(
						array_merge( (array) $query_args['post__not_in'], array( $exclude_post_id ) )
					);
				} else {
					$query_args['post__not_in'] = array( $exclude_post_id );
				}
			}
		}

		$query = new \WP_Query( $query_args );

		$wp_query_page = $wp_query;

		$readmore_icon_data_attrs = self::render_readmore_icon_data_attributes_html( $args );
		$readmore_icon_css_class  = '' !== $readmore_icon_data_attrs ? ' et_pb_custom_button_icon' : '';

		$query->et_pb_blog_query = true;

		ob_start();

		if ( $query->have_posts() ) :

			while ( $query->have_posts() ) :
				$query->the_post();

				$post_id        = get_the_ID(); // Get the current post ID
				$width          = (int) apply_filters( 'et_pb_blog_image_width', 1080 );
				$height         = (int) apply_filters( 'et_pb_blog_image_height', 675 );
				$blog_thumbnail = get_the_post_thumbnail(
					get_the_ID(),
					array( $width, $height ),
					array( 'class' => self::pc_toggle_is_on( $lazyload ) ? 'swiper-lazy' : 'skip-lazy' )
				);
				$excerpt_length = '' !== $excerpt_length && null !== $excerpt_length ? intval( $excerpt_length ) : 50;

				global $authordata;

				?>
				<article class="dsm-post-carousel-item swiper-slide">
					<div class="dsm-grid-post-holder-inner">

						<?php if ( self::pc_toggle_is_on( $use_thumbnail ) && ! empty( $blog_thumbnail ) ) : ?>
							<div class="dsm-entry-image">
								<div class="dsm-entry-thumbnail <?php echo esc_attr( $thumbnail_fit_class ); ?>">
									<a href="<?php echo esc_url( get_permalink( $post_id ) ); ?>">
										<?php echo et_core_esc_previously( $blog_thumbnail ); ?>
									</a>
								</div>
							</div>
						<?php endif; ?>

						<div class="dsm-entry-wrapper">
							<?php if ( self::pc_toggle_is_on( $use_title ) ) : ?>
								<header class="dsm-entry-header">
									<<?php echo esc_attr( $heading_level ); ?> class="dsm-entry-title">
										<a href="<?php echo esc_url( get_permalink( $post_id ) ); ?>" title="<?php the_title(); ?>">
											<?php the_title(); ?>
										</a>
									</<?php echo esc_attr( $heading_level ); ?>>
								</header>
							<?php endif; ?>
							<?php if ( self::pc_toggle_is_on( $use_author ) || self::pc_toggle_is_on( $use_date ) || self::pc_toggle_is_on( $use_category ) || self::pc_toggle_is_on( $use_comments ) ) : ?>
								<?php
								$meta_categories = '';
								if ( self::pc_toggle_is_on( $use_category ) ) {
									$meta_categories = et_core_esc_previously( et_builder_get_the_term_list( ', ' ) );
								}
								?>
								<div class="dsm-entry-meta">
									<?php if ( self::pc_toggle_is_on( $use_author ) ) : ?>
										<span
											class="dsm-posted-by"><?php echo et_core_esc_previously( et_pb_get_the_author_posts_link() ); ?></span>
									<?php endif; ?>
									<?php if ( self::pc_toggle_is_on( $use_date ) ) : ?>
										<span class="dsm-meta-seperator"><?php echo et_core_esc_previously( $meta_seperator ); ?></span>
										<span class="dsm-posted-on"><time
												datetime="<?php echo esc_html( get_the_date( $meta_date ) ); ?>"><?php echo esc_html( get_the_date( $meta_date ) ); ?></time></span>
									<?php endif; ?>

									<?php if ( self::pc_toggle_is_on( $use_category ) ) : ?>
										<?php
										if ( $meta_categories ) :
											?>
											<span class="dsm-meta-seperator"><?php echo et_core_esc_previously( $meta_seperator ); ?></span>
										<?php endif; ?>
										<span class="dsm-posted-category"><?php echo et_core_esc_previously( $meta_categories ); ?></span>
									<?php endif; ?>

									<?php if ( self::pc_toggle_is_on( $use_comments ) ) : ?>
										<?php
										$has_meta_before_comments = self::pc_toggle_is_on( $use_author )
											|| self::pc_toggle_is_on( $use_date )
											|| ( self::pc_toggle_is_on( $use_category ) && '' !== $meta_categories );
										if ( $has_meta_before_comments ) :
											?>
											<span class="dsm-meta-seperator"><?php echo et_core_esc_previously( $meta_seperator ); ?></span>
										<?php endif; ?>
										<span class="dsm-posted-comments">
											<a href="<?php echo esc_url( get_comments_link( $post_id ) ); ?>">
												<?php
												$comments_number = (int) get_comments_number( $post_id );
												echo esc_html(
													sprintf(
														/* translators: %s: number of comments (matches Divi Blog `et_builder` / `et_builder_5` meta). */
														_nx(
															'%s Comment',
															'%s Comments',
															$comments_number,
															'number of comments',
															'dsm-supreme-modules-pro-for-divi'
														),
														number_format_i18n( $comments_number )
													)
												);
												?>
											</a>
										</span>
									<?php endif; ?>
								</div>
							<?php endif; ?>

							<?php
							$dsm_post_carousel_excerpt = self::pc_toggle_is_on( $use_excerpt )
								? static::dsm_post_excerpt( $excerpt_length )
								: '';
							?>
							<?php if ( '' !== $dsm_post_carousel_excerpt ) : ?>
								<div class="dsm-entry-content">
									<div class="dsm-post-excerpt">
										<p><?php echo esc_attr( $dsm_post_carousel_excerpt ); ?></p>
									</div>
								</div>
							<?php endif; ?>

							<?php if ( self::pc_toggle_is_on( $use_readmore ) ) : ?>
								<div class="dsm-readmore-wrap et_pb_bg_layout_light">
									<div class="et_pb_button_module_wrapper">
										<a href="<?php echo esc_url( get_permalink( $post_id ) ); ?>"
											class="dsm-readmore-btn et_pb_button<?php echo esc_attr( $readmore_icon_css_class ); ?>"
											<?php echo et_core_esc_previously( $readmore_icon_data_attrs ); ?>>
											<?php echo esc_attr( $readmore_text ); ?>
										</a>
									</div>
								</div>
							<?php endif ?>

							<?php if ( self::pc_toggle_is_on( $use_bottom ) ) : ?>
								<div class="dsm-bottom-meta">
									<span class="dsm-bottom-meta-image">
										<img src="<?php echo et_core_esc_previously( get_avatar_url( $authordata->ID ) ); ?>">
									</span>
									<div class="dsm-bottom-meta-right">
										<?php if ( self::pc_toggle_is_on( $bottom_author ) ) : ?>
											<span class="dsm-posted-by">
												<?php echo et_core_esc_previously( et_pb_get_the_author_posts_link() ); ?>
											</span>
										<?php endif; ?>

										<?php if ( self::pc_toggle_is_on( $bottom_date ) ) : ?>
											<span class="dsm-posted-on">
												<time datetime="<?php echo et_core_esc_previously( get_the_date( $meta_date ) ); ?>">
													<?php echo et_core_esc_previously( get_the_date( $meta_date ) ); ?>
												</time>
											</span>
										<?php endif; ?>
									</div>
								</div>
							<?php endif; ?>
						</div>
					</div>
				</article>

				<?php
			endwhile;
		endif;

		unset( $wp_query->et_pb_blog_query );

		$query = $wp_query_page;

		$posts = ob_get_clean();

		return $posts;
	}

	/**
	 * Render module HTML output.
	 *
	 * @param array     $attrs - Modules Attributes.
	 * @param string    $content - Content.
	 * @param \WP_Block $block - Block.
	 * @param mixed     $elements - Elements.
	 */
	public static function render_callback( $attrs, $content, $block, $elements ) {

		if ( self::$dsm_post_carousel_rendering ) {
			return '';
		}

		self::$dsm_post_carousel_rendering = true;

		try {

		$slider_orientation             = $attrs['slider_orientation']['innerContent']['desktop']['value'] ?? '';
		$autoplay_viewport              = $attrs['autoplay_viewport']['innerContent']['desktop']['value'] ?? '';
		$slider_effect                  = $attrs['slider_effect']['innerContent']['desktop']['value'] ?? '';
		$speed                          = $attrs['speed']['innerContent']['desktop']['value'] ?? '';
		$autoplay                       = $attrs['autoplay']['innerContent']['desktop']['value'] ?? '';
		$autoplay_speed                 = $attrs['autoplay_speed']['innerContent']['desktop']['value'] ?? '';
		$slider_effect_shadows          = $attrs['slider_effect_shadows']['innerContent']['desktop']['value'] ?? '';
		$slider_effect_coverflow_rotate = $attrs['slider_effect_coverflow_rotate']['innerContent']['desktop']['value'] ?? '';
		$slider_effect_coverflow_depth  = $attrs['slider_effect_coverflow_depth']['innerContent']['desktop']['value'] ?? '';
		$centered_slides                = $attrs['centered_slides']['innerContent']['desktop']['value'] ?? '';
		$multiple_slide_row             = $attrs['multiple_slide_row']['innerContent']['desktop']['value'] ?? '';
		$pagi_style                     = $attrs['pagi_style']['innerContent']['desktop']['value'] ?? '';
		$pause_on_hover                 = $attrs['pause_on_hover']['innerContent']['desktop']['value'] ?? '';
		$touch_move                     = $attrs['touch_move']['innerContent']['desktop']['value'] ?? '';
		$grab                           = $attrs['grab']['innerContent']['desktop']['value'] ?? '';
		$mousewheel                     = $attrs['mousewheel']['innerContent']['desktop']['value'] ?? '';
		$loop                           = $attrs['loop']['innerContent']['desktop']['value'] ?? '';
		$spacing                        = $attrs['spacing']['innerContent']['desktop']['value'] ?? '';
		$spacing_tablet                 = $attrs['spacing']['innerContent']['tablet']['value'] ?? '';
		$spacing_phone                  = $attrs['spacing']['innerContent']['phone']['value'] ?? '';
		$slide_row                      = $attrs['slide_row']['innerContent']['desktop']['value'] ?? '';
		$slide_row_tablet               = $attrs['slide_row']['innerContent']['tablet']['value'] ?? '';
		$slide_row_phone                = $attrs['slide_row']['innerContent']['phone']['value'] ?? '';
		$rows_per_view                  = $attrs['rows_per_view']['innerContent']['desktop']['value'] ?? '';
		$rows_per_view_tablet           = $attrs['rows_per_view']['innerContent']['tablet']['value'] ?? '';
		$rows_per_view_phone            = $attrs['rows_per_view']['innerContent']['phone']['value'] ?? '';
		$columns                        = $attrs['columns']['innerContent']['desktop']['value'] ?? '';
		$columns_tablet                 = $attrs['columns']['innerContent']['tablet']['value'] ?? '';
		$columns_phone                  = $attrs['columns']['innerContent']['phone']['value'] ?? '';
		$slide_to_scroll_ic             = ( $attrs['slide_to_scroll'] ?? array() )['innerContent'] ?? array();
		$slide_to_scroll                = $slide_to_scroll_ic['desktop']['value'] ?? '1';
		$slide_to_scroll_tablet         = $slide_to_scroll_ic['tablet']['value'] ?? '';
		$slide_to_scroll_phone          = $slide_to_scroll_ic['phone']['value'] ?? '';
		$use_navi                       = self::get_pc_inner_desktop_value( $attrs, 'use_navi' ) ?? '';
		$use_pagi                       = self::get_pc_inner_desktop_value( $attrs, 'use_pagi' ) ?? '';

		$post_orderby = $attrs['post_orderby']['innerContent']['desktop']['value'] ?? '';
		$post_sortby  = $attrs['post_sortby']['innerContent']['desktop']['value'] ?? '';

		if ( \DiviSupreme\Includes\DsmFrontendAssetRegistry::should_wp_enqueue_module_styles( 'dsm/post-carousel' ) ) {
			wp_enqueue_style( \DiviSupreme\Includes\DsmFrontendModuleAssetRegistry::SWIPER_LIBRARY_STYLE_HANDLE );
		}
		if ( \DiviSupreme\Includes\DsmFrontendAssetRegistry::should_wp_enqueue_module_scripts( 'dsm/post-carousel' ) ) {
			wp_enqueue_script( 'dsm-swiper-library' );
			wp_enqueue_script( 'dsm-post-carousel' );
		}

		$html_content = self::get_blogposts_html( $attrs );
		$options      = array();

		$loop_check             = 'off' !== $loop ? true : false;
		$slides_per_view        = 'horizontal' === $slider_orientation ? $columns : $rows_per_view;
		$slides_per_view_tablet = 'horizontal' === $slider_orientation ? $columns_tablet : $rows_per_view_tablet;
		$slides_per_view_phone  = 'horizontal' === $slider_orientation ? $columns_phone : $rows_per_view_phone;

		$options['data-orientation']             = esc_attr( $slider_orientation );
		$options['data-loop']                    = 'off' !== $multiple_slide_row ? false : $loop_check;
		$options['data-speed']                   = esc_attr( $speed );
		$options['data-columnsdesktop']          = esc_attr( $slides_per_view );
		$options['data-columnsphone']            = '' === $slides_per_view_phone ? 1 : esc_attr( $slides_per_view_phone );
		$options['data-columnstablet']           = '' === $slides_per_view_tablet ? 2 : esc_attr( $slides_per_view_tablet );
		$options['data-autoplay']                = esc_attr( $autoplay );
		$options['data-autoplayspeed']           = esc_attr( $autoplay_speed );
		$options['data-ordernumber']             = esc_attr( $block->parsed_block['orderIndex'] );
		$options['data-spacing']                 = esc_attr( $spacing );
		$options['data-spacingtablet']           = esc_attr( $spacing_tablet );
		$options['data-spacingphone']            = esc_attr( $spacing_phone );
		$options['data-effect']                  = esc_attr( $slider_effect );
		$options['data-effect-shadows']          = esc_attr( $slider_effect_shadows );
		$options['data-effect-coverflow-rotate'] = esc_attr( $slider_effect_coverflow_rotate );
		$options['data-effect-coverflow-depth']  = esc_attr( $slider_effect_coverflow_depth );
		$options['data-centered']                = esc_attr( $centered_slides );
		$options['data-multi-row']               = esc_attr( $multiple_slide_row );
		$options['data-row']                     = esc_attr( $slide_row );
		$options['data-rowstablet']              = esc_attr( $slide_row_tablet );
		$options['data-rowsphone']               = esc_attr( $slide_row_phone );
		$options['data-slide-to-scroll-desktop'] = esc_attr( '' === $slide_to_scroll ? '1' : $slide_to_scroll );
		$options['data-slide-to-scroll-tablet']  = esc_attr( '' === $slide_to_scroll_tablet ? '1' : $slide_to_scroll_tablet );
		$options['data-slide-to-scroll-phone']   = esc_attr( '' === $slide_to_scroll_phone ? '1' : $slide_to_scroll_phone );
		$options['data-pagi-button-style']       = esc_attr( $pagi_style );
		$options['data-pause-on-hover']          = esc_attr( $pause_on_hover );
		$options['data-touch-move']              = esc_attr( $touch_move );
		$options['data-grab']                    = esc_attr( $grab );
		$options['data-mousewheel']              = esc_attr( $mousewheel );
		if ( 'off' !== $autoplay ) {
			$resolved_autoplay_viewport = '' !== trim( (string) $autoplay_viewport )
				? $autoplay_viewport
				: '50%';
			$options['data-autoplay-viewport'] = esc_attr( $resolved_autoplay_viewport );
		}

		$options = implode(
			' ',
			array_map(
				function ( $keys, $values ) {
					return "{$keys}='{$values}'";
				},
				array_keys( $options ),
				$options
			)
		);

		$navigation = sprintf(
			'<div class="swiper-button-next swiper-arrow-button dsm-post-arrow-button-next%1$s"></div>
			<div class="swiper-button-prev swiper-arrow-button dsm-post-arrow-button-prev%1$s"></div>',
			$block->parsed_block['orderIndex']
		);

		$pagination = sprintf(
			'<div class="swiper-pagination dsm-post-pagination%1$s"></div>',
			$block->parsed_block['orderIndex']
		);

		$container = sprintf(
			'<div class="dsm-post-carousel" %2$s>
			    <div class="swiper-container dsm-post-carousel-container" data-effect="%3$s">
				   <div class="dsm-post-carousel-wrapper swiper-wrapper">
				         %1$s
				   </div>
				</div>
				%4$s
				%5$s
			</div>',
			et_core_intentionally_unescaped( $html_content, 'html' ),
			et_core_esc_previously( $options ),
			esc_attr( $slider_effect ),
			self::pc_toggle_is_on( $use_navi ) ? et_core_intentionally_unescaped( $navigation, 'html' ) : '',
			self::pc_toggle_is_on( $use_pagi ) ? et_core_intentionally_unescaped( $pagination, 'html' ) : '',
		);

		// Module Inner.
		// Essentially, this is the module content.
		// Were wrapping the title and content in a div with class `et_pb_module_inner`.
		$module_inner = HTMLUtility::render(
			array(
				'tag'               => 'div',
				'attributes'        => array(
					'class' => 'et_pb_module_inner',
				),
				'childrenSanitizer' => 'et_core_esc_previously',
				'children'          => $container,
			)
		);

		// This are the module elements that will be rendered in the frontend.
		$module_elements = $elements->style_components(
			array(
				'attrName' => 'module',
			)
		);

		// This are the children of the module container, which are the module elements and the module inner.
		$module_container_children = $module_elements . $module_inner;

			return Module::render(
				array(
					// FE only.
					'orderIndex'          => $block->parsed_block['orderIndex'],
					'storeInstance'       => $block->parsed_block['storeInstance'],
					// VB equivalent.
					'attrs'               => $attrs,
					'elements'            => $elements,
					'id'                  => $block->parsed_block['id'],
					'name'                => $block->block_type->name,
					'classnamesFunction'  => array( self::class, 'module_classnames' ),
					'moduleCategory'      => $block->block_type->category,
					'stylesComponent'     => array( self::class, 'module_styles' ),
					'scriptDataComponent' => array( self::class, 'module_script_data' ),
					'children'            => $module_container_children,
				)
			);
		} finally {
			self::$dsm_post_carousel_rendering = false;
		}
	}
}
