<?php

/**
 * Card Carousel Child 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\FrontEnd\Module\Style;
use ET\Builder\Packages\Module\Module;
use ET\Builder\Packages\Module\Options\Element\ElementClassnames;
use ET\Builder\Packages\Module\Options\Text\TextClassnames;
use ET\Builder\Packages\ModuleLibrary\ModuleRegistration;
use ET\Builder\Packages\Module\Layout\Components\StyleCommon\CommonStyle;
use ET\Builder\Packages\StyleLibrary\Utils\StyleDeclarations;
use ET\Builder\FrontEnd\BlockParser\BlockParserStore;
use ET\Builder\Packages\ModuleUtils\ChildrenUtils;
use ET\Builder\Packages\Module\Options\Css\CssStyle;
use ET\Builder\Packages\IconLibrary\IconFont\Utils;
use DiviSupreme\Includes\ModuleRegisterAttrsNormalize;
use DiviSupreme\Includes\Divi5Conversion\ConversionAttrs;
use DiviSupreme\Extensions\IconLibraries\LegacyModuleIconStyles;
use DiviSupreme\Includes\UploadImageMarkup;


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


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

	/**
	 * Normalize migrated rich text `content` after merged attrs (matches flipbox-child pattern).
	 *
	 * @param array<string, mixed> $module_attrs Merged module attrs.
	 * @param array<string, mixed> $filter_args  Contains `name`, `attrs`, `id`, `storeInstance`.
	 * @return array<string, mixed>
	 */
	public static function filter_register_module_attrs($module_attrs, $filter_args)
	{
		if (! is_array($module_attrs) || ! is_array($filter_args)) {
			return is_array($module_attrs) ? $module_attrs : array();
		}

		if (($filter_args['name'] ?? '') !== 'dsm/card-carousel-child') {
			return $module_attrs;
		}

		ModuleRegisterAttrsNormalize::card_carousel_child_merged($module_attrs);

		$parent_attrs = $filter_args['parentAttrs'] ?? null;
		if (is_array($parent_attrs)) {
			self::merge_parent_button_icon_into_child_attrs($module_attrs, $parent_attrs);
		}

		return $module_attrs;
	}

	/**
	 * 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());
			}
		);
	}

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

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

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


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


	/**
	 * Normalize upload / innerContent to a URL for CSS background-image.
	 *
	 * @param mixed $raw attrValue from CommonStyle.
	 * @return string
	 */
	private static function resolve_carousel_child_image_upload_attr_to_background_url($raw)
	{
		if (null === $raw || '' === $raw) {
			return '';
		}
		if (is_string($raw)) {
			return $raw;
		}
		if (! is_array($raw)) {
			return '';
		}
		if (! empty($raw['src']) && is_string($raw['src'])) {
			return $raw['src'];
		}
		if (! empty($raw['url']) && is_string($raw['url'])) {
			return $raw['url'];
		}
		if (! empty($raw['id'])) {
			$attachment_id = absint($raw['id']);
			if ($attachment_id > 0) {
				$url = wp_get_attachment_image_url($attachment_id, 'full');
				return is_string($url) && '' !== $url ? $url : '';
			}
		}
		if (array_key_exists('value', $raw)) {
			$nested = self::resolve_carousel_child_image_upload_attr_to_background_url($raw['value']);
			if ('' !== $nested) {
				return $nested;
			}
		}
		foreach (array('desktop', 'tablet', 'phone') as $breakpoint) {
			if (! isset($raw[$breakpoint]) || ! is_array($raw[$breakpoint])) {
				continue;
			}
			$v = $raw[$breakpoint]['value'] ?? null;
			$nested = self::resolve_carousel_child_image_upload_attr_to_background_url($v);
			if ('' !== $nested) {
				return $nested;
			}
		}
		return '';
	}

	public static function image_bg_style_declaration($params)
	{
		$image_attr = $params['attrValue'] ?? null;
		$url = self::resolve_carousel_child_image_upload_attr_to_background_url($image_attr);

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

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

		$safe = str_replace(array('\\', "'"), array('\\\\', "\\'"), $url);
		$style_declarations->add('background-image', "url('" . $safe . "')");
		$style_declarations->add('width', '100%');
		$style_declarations->add('height', '100%');

		return $style_declarations->value();
	}

	public static function image_bg_position_declaration($params)
	{
		$image_positon_attr = $params['attrValue'] ?? array();

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

		$position = str_replace('_', ' ', $image_positon_attr);

		$style_declarations->add('background-position', "$position");

		return $style_declarations->value();
	}

	public static function image_bg_repeat_declaration($params)
	{
		$image_positon_attr = $params['attrValue'] ?? array();

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

		$repeat_value = str_replace('_', '-', $image_positon_attr);

		$style_declarations->add('background-repeat', "$repeat_value");

		return $style_declarations->value();
	}

	/**
	 * Whether animation speed is empty or zero (matches Card module).
	 *
	 * @param mixed $speed Raw speed value.
	 * @return bool
	 */
	private static function dsm_card_animation_speed_is_empty_or_zero($speed)
	{
		if (null === $speed || '' === $speed) {
			return true;
		}
		if (is_numeric($speed) && (float) $speed === 0.0) {
			return true;
		}
		if (is_string($speed)) {
			$t = trim(strtolower($speed));
			if (in_array($t, array('0', '0px', '0ms', '0.0', '0.0px', '0.0ms'), true)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Background image hover transition (matches dsm/card: `transition: all {speed} ease`).
	 *
	 * @param array $params Declaration params from CommonStyle.
	 * @return string
	 */
	public static function image_bg_animation_declaration($params)
	{
		$speed = $params['attrValue'] ?? '';
		if (is_array($speed)) {
			$speed = $speed['value'] ?? '';
		}
		if (self::dsm_card_animation_speed_is_empty_or_zero($speed)) {
			$speed = '300ms';
		}

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

		$style_declarations->add('transition', "all $speed ease");

		return $style_declarations->value();
	}

	public static function image_alignment_decalaration($params)
	{
		$attr = $params['attrValue'] ?? array();

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

		$align_value = '';

		switch ($attr) {
			case 'left':
				$align_value = 'flex-start';
				break;
			case 'center':
				$align_value = 'center';
				break;
			case 'right':
				$align_value = 'flex-end';
				break;
			default:
				$align_value = 'center';
		}

		$style_declarations->add('display', "flex");
		$style_declarations->add('justify-content', $align_value);

		return $style_declarations->value();
	}

	/**
	 * @param mixed  $value   Raw value.
	 * @param string $default Fallback.
	 * @return string
	 */
	private static function normalize_transition_time_property($value, string $default): string
	{
		if (is_int($value)) {
			return (string) $value . 'ms';
		}
		if (is_float($value)) {
			return (string) (int) round($value) . 'ms';
		}
		if (! is_string($value)) {
			return $default;
		}
		$t = trim($value);
		if ('' === $t) {
			return $default;
		}
		if (preg_match('/[a-z%]/i', $t)) {
			return $t;
		}
		if (is_numeric($t)) {
			return (string) (int) round((float) $t) . 'ms';
		}

		return $t;
	}

	/**
	 * D4 parity: parent Text group → `et_pb_bg_layout_*` + `et_pb_text_align_*` on `.dsm_card_wrapper`.
	 *
	 * @param array<string, mixed> $parent_attrs Parent (card carousel) attrs.
	 * @return string Space-separated classes (sanitized for class attribute).
	 */
	private static function card_carousel_child_text_group_classnames(array $parent_attrs): string
	{
		$text_advanced = array();
		if (isset($parent_attrs['module']['advanced']['text']) && is_array($parent_attrs['module']['advanced']['text'])) {
			$text_advanced = $parent_attrs['module']['advanced']['text'];
		}

		$classes = trim(TextClassnames::text_options_classnames($text_advanced));
		if ('' === $classes) {
			return 'et_pb_bg_layout_light et_pb_text_align_left';
		}
		if (false === strpos($classes, 'et_pb_bg_layout_')) {
			$color = $text_advanced['text']['desktop']['value']['color'] ?? 'light';
			if (! is_string($color) || '' === $color) {
				$color = 'light';
			}
			if (in_array($color, array('light', 'dark'), true)) {
				$classes .= ' et_pb_bg_layout_' . $color;
			}
		}

		return trim($classes);
	}

	/**
	 * @param array<string, mixed> $attrs      Module attrs.
	 * @param string                 $breakpoint desktop|tablet|phone.
	 * @return array{duration: string, delay: string, speedCurve: string}
	 */
	private static function read_module_transition_leaf_at_breakpoint(array $attrs, string $breakpoint): array
	{
		$defaults = array(
			'duration' => '300ms',
			'delay' => '0ms',
			'speedCurve' => 'ease',
		);
		$module = $attrs['module'] ?? array();
		if (! is_array($module)) {
			return $defaults;
		}
		$decoration = $module['decoration'] ?? array();
		$trans = is_array($decoration) ? ($decoration['transition'] ?? null) : null;
		if (! is_array($trans)) {
			return $defaults;
		}
		$order = array_values(
			array_unique(
				array_merge(
					array($breakpoint),
					array('desktop', 'tablet', 'phone')
				)
			)
		);
		foreach ($order as $bp) {
			if (! isset($trans[ $bp ]) || ! is_array($trans[ $bp ])) {
				continue;
			}
			$node = $trans[ $bp ];
			if (! isset($node['value']) || ! is_array($node['value'])) {
				continue;
			}
			$leaf = $node['value'];
			$out = $defaults;
			if (isset($leaf['duration'])) {
				$out['duration'] = self::normalize_transition_time_property($leaf['duration'], $defaults['duration']);
			}
			if (isset($leaf['delay'])) {
				$out['delay'] = self::normalize_transition_time_property($leaf['delay'], $defaults['delay']);
			}
			if (isset($leaf['speedCurve']) && is_string($leaf['speedCurve']) && '' !== $leaf['speedCurve']) {
				$out['speedCurve'] = $leaf['speedCurve'];
			}
			return $out;
		}

		return $defaults;
	}

	/**
	 * @param array  $attrs      Module attrs.
	 * @param string $breakpoint desktop|tablet|phone.
	 * @return string
	 */
	private static function card_carousel_child_hover_transition_css(array $attrs, string $breakpoint): string
	{
		$t = self::read_module_transition_leaf_at_breakpoint($attrs, $breakpoint);
		$d = esc_attr($t['duration']);
		$c = esc_attr($t['speedCurve']);
		$s = esc_attr($t['delay']);
		$triple = "{$d} {$c} {$s}";

		$style_declarations = new StyleDeclarations(
			array(
				'returnType' => 'string',
			)
		);
		$style_declarations->add(
			'transition',
			"background {$triple}, box-shadow {$triple}, border {$triple}, padding {$triple}, border-radius {$triple}, transform {$triple}"
		);

		return $style_declarations->value();
	}

	/**
	 * Build innerContent-shaped attr for button wrapper alignment (matches module-styles.js).
	 *
	 * @param array<string, mixed> $attrs Module attrs.
	 * @return array<string, mixed>
	 */
	private static function button_decoration_alignment_inner_content_for_style(array $attrs): array
	{
		$button_attr = $attrs['button'] ?? null;
		if (! is_array($button_attr)) {
			return array('innerContent' => array());
		}

		$sizing = $button_attr['decoration']['sizing'] ?? null;
		$button = $button_attr['decoration']['button'] ?? null;

		$inner_content = array();
		foreach (array('desktop', 'tablet', 'phone') as $bp) {
			$raw = '';

			if (is_array($sizing) && isset($sizing[ $bp ]['value']) && is_array($sizing[ $bp ]['value'])) {
				$alignment = $sizing[ $bp ]['value']['alignment'] ?? '';
				if (is_string($alignment) && '' !== trim($alignment)) {
					$raw = $alignment;
				}
			}

			if ('' === trim((string) $raw) && is_array($button) && isset($button[ $bp ]['value']) && is_array($button[ $bp ]['value'])) {
				$value = $button[ $bp ]['value'];
				$raw   = $value['textAlign'] ?? $value['alignment'] ?? '';
			}

			if (! is_string($raw) || '' === trim($raw)) {
				continue;
			}
			$lower = strtolower(trim($raw));
			if (in_array($lower, array('left', 'center', 'right'), true)) {
				$inner_content[ $bp ] = array('value' => $lower);
			}
		}

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

	/**
	 * Wrapper alignment: text-align + margin auto (fit-content wrapper in common.scss).
	 *
	 * @param array<string, mixed> $params CommonStyle params.
	 * @return string
	 */
	public static function button_wrapper_align_declaration(array $params): string
	{
		$alignment = $params['attrValue'] ?? '';

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

		if ('left' === $alignment) {
			$style_declarations->add('text-align', 'left');
			$style_declarations->add('margin-left', '0');
			$style_declarations->add('margin-right', 'auto');
		} elseif ('center' === $alignment) {
			$style_declarations->add('text-align', 'center');
			$style_declarations->add('margin-left', 'auto');
			$style_declarations->add('margin-right', 'auto');
		} elseif ('right' === $alignment) {
			$style_declarations->add('text-align', 'right');
			$style_declarations->add('margin-left', 'auto');
			$style_declarations->add('margin-right', '0');
		}

		return $style_declarations->value();
	}

	/**
	 * Parent-scoped selector for card carousel child styles (CPT/TB-safe).
	 *
	 * @param string $order_class Module order class.
	 * @param string $suffix      Suffix after order class (may be glued, e.g. `.dsm-show-overlay …`).
	 * @return string
	 */
	private static function card_carousel_parent_child_selector( string $order_class, string $suffix = '' ): string {
		return \DiviSupreme\Includes\DsmDiviBuilderCssScope::parent_child_selector(
			'dsm_card_carousel',
			$order_class,
			$suffix
		);
	}

	/**
	 * Render module style.
	 *
	 * @param array $args - Arguments.
	 */
	public static function module_styles($args)
	{
		$args = \DiviSupreme\Includes\DsmDiviBuilderCssScope::normalize_module_styles_args( $args );
		$order_class = $args['orderClass'] ?? '';
		$cc = static function ( string $suffix ) use ( $order_class ): string {
			return self::card_carousel_parent_child_selector( $order_class, $suffix );
		};
		$attrs = $args['attrs'] ?? array();
		$elements = $args['elements'];
		$badge_custom_position = $attrs['badge_custom_position']['innerContent']['desktop']['value'] ?? 'off';
		$image_as_background = self::get_image_as_background_mode($attrs);

		$module = isset($attrs['module']) && is_array($attrs['module']) ? $attrs['module'] : array();
		$decoration = isset($module['decoration']) && is_array($module['decoration']) ? $module['decoration'] : array();
		$transition_attr = isset($decoration['transition']) && is_array($decoration['transition']) ? $decoration['transition'] : array();
		if (empty($transition_attr)) {
			$transition_attr = array(
				'desktop' => array(
					'value' => array(
						'duration' => '300ms',
						'delay' => '0ms',
						'speedCurve' => 'ease',
					),
				),
			);
		}

		Style::add(
			array(
				'id' => $args['id'],
				'name' => $args['name'],
				'orderIndex' => $args['orderIndex'],
				'storeInstance' => $args['storeInstance'],
				'styles' => 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']}.dsm_card_carousel_child > div:first-of-type, {$args['orderClass']} .dsm_card_carousel_child_badge_text, {$args['orderClass']} .dsm_card_carousel_child_image_background",
							'attr' => $transition_attr,
							'declarationFunction' => static function ($params) use ($attrs) {
								$state = isset($params['state']) && is_string($params['state']) ? $params['state'] : 'value';
								if ('value' !== $state) {
									return '';
								}
								$breakpoint = isset($params['breakpoint']) && is_string($params['breakpoint']) ? $params['breakpoint'] : 'desktop';
								return self::card_carousel_child_hover_transition_css($attrs, $breakpoint);
							},
						)
					),

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

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

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

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

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

					( ! empty(self::button_decoration_alignment_inner_content_for_style($attrs)['innerContent'])
						? CommonStyle::style(
							array(
								'selector' => "{$args['orderClass']} .et_pb_button_module_wrapper",
								'attr' => self::button_decoration_alignment_inner_content_for_style($attrs),
								'declarationFunction' => array(static::class, 'button_wrapper_align_declaration'),
							)
						)
						: '' ),

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

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

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

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

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

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

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

					CommonStyle::style(
						array(
							'selector' => $cc( '.dsm-show-overlay .et_overlay.dsm-icon.et_pb_inline_icon::before' ),
							'attr' => $attrs['hover_icon']['innerContent'] ?? array(),
							'declarationFunction' => array(static::class, 'overlay_icon_style_declaration'),
						)
					),

					CommonStyle::style(
						array(
							'selector' => $cc( '.dsm-show-overlay .et_overlay.et_pb_inline_icon::before' ),
							'attr' => $attrs['icon_font_size']['innerContent'] ?? array(),
							'property' => 'font-size',
						)
					),

					CommonStyle::style(
						array(
							'selector' => $cc( '.dsm-show-overlay .et_overlay.et_pb_inline_icon::before' ),
							'attr' => $attrs['overlay_icon_color']['innerContent'] ?? array(),
							'property' => 'color',
						)
					),

					CommonStyle::style(
						array(
							'selector' => $cc( ' .dsm_card_carousel_child_image_wrapper' ),
							'attr' => $attrs['image_alignment']['innerContent'] ?? array(),
							'declarationFunction' => array(static::class, 'image_alignment_decalaration'),
						)
					),

					CommonStyle::style(
						array(
							'selector' => $cc( ' .dsm_card_carousel_child_image_wrapper::before' ),
							'attr' => $attrs['image_alignment']['innerContent'] ?? array(),
							'declarationFunction' => array(static::class, 'image_alignment_decalaration'),
						)
					),

					'on' === $image_as_background && self::has_card_image($attrs) ?
					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_carousel_child_image_wrapper",
							'attr' => $attrs['image_background_height']['innerContent'] ?? array(),
							'property' => 'height',
						)
					)
					: '',

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_carousel_child_image_background",
							'attr' => $attrs['image_background_size']['innerContent'] ?? array(),
							'property' => 'background-size',
						)
					),

					CommonStyle::style(
						array(
							'selector' => $cc( '.dsm-show-overlay .dsm_card_carousel_child_image_wrapper .et_overlay::before' ),
							'attr' => $attrs['overlay_color']['innerContent'] ?? array(),
							'property' => 'background-color',
						)
					),

					CommonStyle::style(
						array(
							'selector' => $cc( ' .dsm_card_carousel_child_image_wrapper .dsm_card_carousel_child_image_background' ) . ', ' . $cc( ' .dsm_card_carousel_child_image_wrapper img' ),
							'attr' => $attrs['image_width']['innerContent'] ?? array(),
							'property' => 'max-width',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_carousel_child_image_background",
							'attr' => $attrs['image']['innerContent'] ?? array(),
							'declarationFunction' => array(static::class, 'image_bg_style_declaration'),
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_carousel_child_image_background",
							'attr' => $attrs['image_background_position']['innerContent'] ?? array(),
							'declarationFunction' => array(static::class, 'image_bg_position_declaration'),
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_carousel_child_image_background",
							'attr' => $attrs['image_background_repeat']['innerContent'] ?? array(),
							'declarationFunction' => array(static::class, 'image_bg_repeat_declaration'),
						)
					),

					'on' === $image_as_background ?
					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_carousel_child_image_background",
							'attr' => $attrs['image_background_animation_speed']['innerContent'] ?? array(),
							'declarationFunction' => array(static::class, 'image_bg_animation_declaration'),
						)
					)
					: '',

					'on' === $badge_custom_position ?

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_carousel_child_badge_text",
							'attr' => $attrs['badge_left_position']['innerContent'] ?? array(),
							'property' => 'left',
						)
					) : '',

					'on' === $badge_custom_position ?
					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_carousel_child_badge_text",
							'attr' => $attrs['badge_top_position']['innerContent'] ?? array(),
							'property' => 'top',
						)
					) : '',

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_carousel_child_badge_text",
							'attr' => $attrs['badge_background_color']['innerContent'] ?? array(),
							'property' => 'background-color',
						)
					),
				),
			)
		);
	}

	/**
	 * 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'];
		$use_overlay = $attrs['use_overlay']['innerContent']['desktop']['value'] ?? '';
		$overlay_on_hover = $attrs['overlay_on_hover']['innerContent']['desktop']['value'] ?? 'on';
		$image_bg_animation_attr = $attrs['image_background_animation']['innerContent']['desktop']['value'] ?? 'none';

		$badge_raw = $attrs['badge_text']['innerContent']['desktop']['value'] ?? null;
		$badge_str   = '';
		if (is_string($badge_raw)) {
			$badge_str = $badge_raw;
		} elseif (is_array($badge_raw) && isset($badge_raw['text']) && is_string($badge_raw['text'])) {
			$badge_str = $badge_raw['text'];
		}
		$has_badge = '' !== trim($badge_str);
		$has_image = self::has_card_image($attrs);

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

		$classnames_instance->add('swiper-slide');

		if ('none' !== $image_bg_animation_attr && '' !== $image_bg_animation_attr) {
			$classnames_instance->add('dsm_card_carousel_child_image_animation');
			$classnames_instance->add('dsm_card_carousel_child_image_animation_' . $image_bg_animation_attr);
		}

		if ('on' === $use_overlay) {
			$classnames_instance->add('et_pb_has_overlay');
		}
		if ('on' === $use_overlay && 'off' === $overlay_on_hover) {
			$classnames_instance->add('dsm-card-overlay-off');
		}

		if ('on' === $use_overlay && ($has_image || $has_badge)) {
			$classnames_instance->add('dsm-show-overlay');
		}
	}

	/**
	 * Whether an upload leaf value is an intentional clear (removed image).
	 *
	 * @param mixed $value Upload innerContent leaf value.
	 * @return bool
	 */
	private static function is_upload_value_explicitly_empty($value): bool
	{
		if ('' === $value || null === $value) {
			return true;
		}

		if (! is_array($value)) {
			return false;
		}

		if (! empty($value['id'])) {
			return false;
		}

		$src = $value['src'] ?? null;
		$url = $value['url'] ?? null;

		if (is_string($src) && '' !== $src) {
			return false;
		}

		if (is_string($url) && '' !== $url) {
			return false;
		}

		if (array_key_exists('src', $value) || array_key_exists('url', $value)) {
			return true;
		}

		return array() === $value;
	}

	/**
	 * User removed the image in the builder (empty upload stored).
	 *
	 * @param array<string, mixed> $attrs Module attributes.
	 * @return bool
	 */
	private static function is_card_image_explicitly_empty(array $attrs): bool
	{
		$inner = $attrs['image']['innerContent'] ?? array();

		foreach (array('desktop', 'tablet', 'phone') as $breakpoint) {
			if (! isset($inner[$breakpoint]) || ! is_array($inner[$breakpoint])) {
				continue;
			}

			if (! array_key_exists('value', $inner[$breakpoint])) {
				continue;
			}

			if (self::is_upload_value_explicitly_empty($inner[$breakpoint]['value'])) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Whether any breakpoint has a usable image (matches Card module / VB `cardModuleHasPreviewImage`).
	 *
	 * @param array<string, mixed> $attrs Module attributes.
	 * @return bool
	 */
	private static function has_card_image(array $attrs)
	{
		if (self::is_card_image_explicitly_empty($attrs)) {
			return false;
		}

		$inner = $attrs['image']['innerContent'] ?? array();

		foreach (array('desktop', 'tablet', 'phone') as $breakpoint) {
			$image_raw = $inner[$breakpoint]['value'] ?? null;

			if (is_string($image_raw) && '' !== $image_raw) {
				return true;
			}

			if (is_array($image_raw) && (! empty($image_raw['src']) || ! empty($image_raw['url']) || ! empty($image_raw['id']))) {
				$resolved = self::resolve_carousel_child_image_upload_attr_to_background_url($image_raw);
				return '' !== $resolved;
			}
		}

		return false;
	}

	/**
	 * First usable upload innerContent value for responsive `<img>` (string or `{ src, id }`).
	 *
	 * @param array<string, mixed> $attrs Module attributes.
	 * @return mixed
	 */
	private static function get_carousel_child_image_upload_raw(array $attrs)
	{
		if (self::is_card_image_explicitly_empty($attrs)) {
			return '';
		}

		$inner = $attrs['image']['innerContent'] ?? array();

		foreach (array('desktop', 'tablet', 'phone') as $breakpoint) {
			$image_raw = $inner[$breakpoint]['value'] ?? null;

			if (null === $image_raw || '' === $image_raw) {
				continue;
			}

			if (is_string($image_raw)) {
				return $image_raw;
			}

			if (is_array($image_raw) && (! empty($image_raw['src']) || ! empty($image_raw['url']) || ! empty($image_raw['id']))) {
				return $image_raw;
			}
		}

		return '';
	}

	/**
	 * Parent carousel sets the default button icon; children inherit when they have no icon glyph.
	 * If the child has a renderable icon for a breakpoint (Design → Button) or icon is disabled, parent is not applied.
	 *
	 * @param array<string, mixed> $child_attrs  Reference — module attrs for the child.
	 * @param array<string, mixed> $parent_attrs Parent (card carousel) attrs.
	 * @return void
	 */
	private static function merge_parent_button_icon_into_child_attrs(array &$child_attrs, array $parent_attrs): void
	{
		if (self::child_uses_own_button_icon($child_attrs)) {
			return;
		}

		if (! isset($parent_attrs['button']) || ! is_array($parent_attrs['button'])) {
			return;
		}
		$parent_dec_button = $parent_attrs['button']['decoration']['button'] ?? null;
		if (! is_array($parent_dec_button)) {
			return;
		}

		if (! isset($child_attrs['button']) || ! is_array($child_attrs['button'])) {
			$child_attrs['button'] = array();
		}
		if (! isset($child_attrs['button']['decoration']) || ! is_array($child_attrs['button']['decoration'])) {
			$child_attrs['button']['decoration'] = array();
		}
		if (! isset($child_attrs['button']['decoration']['button']) || ! is_array($child_attrs['button']['decoration']['button'])) {
			$child_attrs['button']['decoration']['button'] = array();
		}

		$child_dec = &$child_attrs['button']['decoration']['button'];

		foreach ($parent_dec_button as $bp => $parent_bp_val) {
			if (! is_array($parent_bp_val)) {
				continue;
			}
			$bps = is_string($bp) ? $bp : (string) $bp;
			$parent_icon = $parent_bp_val['value']['icon'] ?? null;
			if (! is_array($parent_icon)) {
				continue;
			}

			$child_bp_val = $child_dec[ $bps ] ?? array();
			$child_icon   = is_array($child_bp_val) ? ($child_bp_val['value']['icon'] ?? null) : null;
			if (is_array($child_icon) && isset($child_icon['enable']) && 'off' === strtolower((string) $child_icon['enable'])) {
				continue;
			}
			if (self::button_icon_branch_has_renderable_icon($child_icon)) {
				continue;
			}

			if (! isset($child_dec[ $bps ]) || ! is_array($child_dec[ $bps ])) {
				$child_dec[ $bps ] = array(
					'value' => array(),
				);
			}
			if (! isset($child_dec[ $bps ]['value']) || ! is_array($child_dec[ $bps ]['value'])) {
				$child_dec[ $bps ]['value'] = array();
			}
			$child_dec[ $bps ]['value']['icon'] = $parent_icon;
		}
	}

	/**
	 * True when this slide owns its button icon (custom toggle or explicit Design → Button glyph).
	 *
	 * @param array<string, mixed> $child_attrs Child module attrs.
	 */
	private static function child_uses_own_button_icon(array $child_attrs): bool
	{
		if (self::inner_content_desktop_toggle_is_on($child_attrs, 'custom_button')) {
			return true;
		}

		$child_dec = $child_attrs['button']['decoration']['button'] ?? null;
		if (! is_array($child_dec)) {
			return false;
		}

		foreach ($child_dec as $bp_val) {
			if (! is_array($bp_val)) {
				continue;
			}
			$child_icon = $bp_val['value']['icon'] ?? null;
			if (self::button_icon_branch_has_renderable_icon($child_icon)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * True when the child icon branch should win over the parent (matches `processFontIcon` in the VB).
	 *
	 * @param mixed $icon_branch `button.decoration.button.*.value.icon`.
	 */
	private static function button_icon_branch_has_renderable_icon($icon_branch): bool
	{
		if (! is_array($icon_branch)) {
			return false;
		}
		$settings = $icon_branch['settings'] ?? null;
		if (is_string($settings) && '' !== trim($settings)) {
			return true;
		}
		if (! is_array($settings)) {
			return false;
		}
		$processed = Utils::process_font_icon($settings);
		return is_string($processed) && '' !== trim($processed);
	}

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

		$icon_enable_raw = $button['decoration']['button']['desktop']['value']['icon']['enable'] ?? 'on';
		$is_icon_enabled = 'off' !== strtolower((string) $icon_enable_raw);
		$button_decoration = $button['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);
	}

	/**
	 * Resolved image-as-background toggle (matches Card::get_image_as_background_mode / JS).
	 *
	 * @param array<string, mixed> $attrs Module attributes.
	 * @return string
	 */
	private static function get_image_as_background_mode(array $attrs)
	{
		$raw = $attrs['image_as_background']['innerContent']['desktop']['value'] ?? null;

		if (null === $raw || '' === $raw) {
			return 'on';
		}

		if (true === $raw || 1 === $raw || '1' === $raw) {
			return 'on';
		}

		if (false === $raw || 0 === $raw || '0' === $raw) {
			return 'off';
		}

		if (is_string($raw)) {
			$s = strtolower($raw);
			if ('off' === $s) {
				return 'off';
			}
		}

		return 'on';
	}

	/**
	 * Whether innerContent.desktop.value is a truthy on-style toggle.
	 *
	 * @param array<string, mixed> $attrs    Module attrs.
	 * @param string               $attr_key Top-level attr name.
	 */
	private static function inner_content_desktop_toggle_is_on(array $attrs, string $attr_key): bool
	{
		if (! isset($attrs[ $attr_key ]['innerContent']['desktop']['value'])) {
			return false;
		}
		$raw = $attrs[ $attr_key ]['innerContent']['desktop']['value'];
		if (true === $raw || 1 === $raw || '1' === $raw) {
			return true;
		}

		return 'on' === $raw;
	}

	/**
	 * D4: parent carousel `stretch_image` sets global stretch; else child `stretch_image`.
	 * Migrated layouts may only set `parent_stretch_image` on the child.
	 *
	 * @param array<string, mixed> $attrs        Child attrs.
	 * @param array<string, mixed> $parent_attrs Parent carousel attrs.
	 */
	private static function carousel_child_effective_stretch_inline_image(array $attrs, array $parent_attrs): bool
	{
		if (self::inner_content_desktop_toggle_is_on($parent_attrs, 'stretch_image')) {
			return true;
		}
		if (self::inner_content_desktop_toggle_is_on($attrs, 'parent_stretch_image')) {
			return true;
		}

		return self::inner_content_desktop_toggle_is_on($attrs, 'stretch_image');
	}

	/**
	 * Render module HTML output.
	 *
	 * @param array     $attrs - Modules Attributes.
	 * @param string    $child_modules_content - Nested inner module HTML.
	 * @param \WP_Block $block - Block.
	 * @param mixed     $elements - Elements.
	 */
	public static function render_callback($attrs, $child_modules_content, $block, $elements)
	{
		ConversionAttrs::ensure_heading_link_inner_content_for_module_elements( $attrs, $elements, 'title' );
		ConversionAttrs::ensure_heading_link_inner_content_for_module_elements( $attrs, $elements, 'subtitle' );
		ConversionAttrs::ensure_inner_content_text_shape_for_render( $attrs, 'content' );

		$children_ids = ChildrenUtils::extract_children_ids($block);
		$has_nested_modules = '' !== trim((string) $child_modules_content) || ! empty($children_ids);
		$layout_display_value = $attrs['module']['decoration']['layout']['desktop']['value']['display'] ?? 'flex';
		$nestable_classes = HTMLUtility::classnames(
			'dsm-card-nestable',
			array(
				'et_flex_module' => 'flex' === $layout_display_value,
				'et_grid_module' => 'grid' === $layout_display_value,
			)
		);

		$parent = BlockParserStore::get_parent($block->parsed_block['id'], $block->parsed_block['storeInstance']);
		$parent_attrs = $parent->attrs ?? array();
		$parent_badge_position = $parent_attrs['badge_position']['innerContent']['desktop']['value'] ?? 'top_right';
		$sub_title_raw = $attrs['subtitle']['innerContent']['desktop']['value'] ?? null;
		$sub_title_attr = '';
		if (is_string($sub_title_raw)) {
			$sub_title_attr = $sub_title_raw;
		} elseif (is_array($sub_title_raw) && isset($sub_title_raw['text']) && is_string($sub_title_raw['text'])) {
			$sub_title_attr = $sub_title_raw['text'];
		}
		$content_value  = $attrs['content']['innerContent']['desktop']['value'] ?? null;
		if (is_array($content_value)) {
			$content_attr = $content_value['text'] ?? '';
		} elseif (is_string($content_value)) {
			$content_attr = $content_value;
		} else {
			$content_attr = '';
		}
		$badge_text_raw = $attrs['badge_text']['innerContent']['desktop']['value'] ?? null;
		$badge_text_attr = '';
		if (is_string($badge_text_raw)) {
			$badge_text_attr = $badge_text_raw;
		} elseif (is_array($badge_text_raw) && isset($badge_text_raw['text']) && is_string($badge_text_raw['text'])) {
			$badge_text_attr = $badge_text_raw['text'];
		}
		$badge_position_attr = $attrs['badge_position']['innerContent']['desktop']['value'] ?? '';
		$button_text_attr = $attrs['button_text']['innerContent']['desktop']['value'] ?? '';
		$image_as_background_mode = self::get_image_as_background_mode($attrs);
		$image_bg_animation_attr = $attrs['image_background_animation']['innerContent']['desktop']['value'] ?? 'none';
		$badge_custom_position_attr = $attrs['badge_custom_position']['innerContent']['desktop']['value'] ?? 'off';
		$title_raw = $attrs['title']['innerContent']['desktop']['value'] ?? null;
		$title     = '';
		if (is_string($title_raw)) {
			$title = $title_raw;
		} elseif (is_array($title_raw) && isset($title_raw['text']) && is_string($title_raw['text'])) {
			$title = $title_raw['text'];
		}
		$heading_level = $attrs['title']['decoration']['font']['font']['desktop']['value']['headingLevel'] ?? '';
		$parent_heading_level = $parent_attrs['title']['decoration']['font']['font']['desktop']['value']['headingLevel'] ?? 'h4';
		$overlay_on_hover = $attrs['overlay_on_hover']['innerContent']['desktop']['value'] ?? 'on';
		$use_icon = $attrs['use_icon']['innerContent']['desktop']['value'] ?? 'on';
		$button_url = $attrs['button_url']['innerContent']['desktop']['value'] ?? '';
		$button_url_new_window = $attrs['button_url_new_window']['innerContent']['desktop']['value'] ?? 'same_window';

		$content_for_empty_check = html_entity_decode((string) $content_attr, ENT_QUOTES | ENT_HTML5, 'UTF-8');
		$content_has_text         = '' !== trim(wp_strip_all_tags($content_for_empty_check));
		$has_card_wrapper         = $has_nested_modules
			|| '' !== trim($title)
			|| '' !== trim($sub_title_attr)
			|| $content_has_text
			|| '' !== trim((string) $button_text_attr);

		$image_popup = $attrs['image_popup']['innerContent']['desktop']['value'] ?? 'off';
		$image_popup_src = $attrs['image_popup_src']['innerContent']['desktop']['value'] ?? '';
		$image_video_popup = $attrs['image_video_popup']['innerContent']['desktop']['value'] ?? 'off';
		$image_video_popup_url = $attrs['image_video_popup_url']['innerContent']['desktop']['value'] ?? '';

		$text_color = $parent_attrs['module']['advanced']['text']['text']['desktop']['value']['color'] ?? 'light';
		if (! is_string($text_color) || '' === $text_color) {
			$text_color = 'light';
		}
		if (! in_array($text_color, array('light', 'dark'), true)) {
			$text_color = 'light';
		}

		$is_custom_badge_position = 'off' === $badge_custom_position_attr ? sprintf('dsm_card_carousel_child_badge_%1$s', '' === $badge_position_attr ? $parent_badge_position : $badge_position_attr) : '';

		$image_upload_raw = self::get_carousel_child_image_upload_raw($attrs);
		$image_as_background_markup = '<div class="dsm_card_carousel_child_image_background dsm_card_carousel_child_img"></div>';
		$stretch_inline = ('off' === $image_as_background_mode)
			&& self::carousel_child_effective_stretch_inline_image($attrs, $parent_attrs);
		$inline_img_class = $stretch_inline
			? 'dsm_card_carousel_child_img dsm_card_carousel_image_stretch'
			: 'dsm_card_carousel_child_img';
		$image_inline_markup = '';
		if ('off' === $image_as_background_mode && self::has_card_image($attrs)) {
			$image_inline_markup = UploadImageMarkup::render_img_html(
				$image_upload_raw,
				'',
				array(
					'class' => $inline_img_class,
				)
			);
		}

		$figure_inner = '';
		if ('on' === $image_as_background_mode && self::has_card_image($attrs)) {
			$figure_inner .= $image_as_background_markup;
		} elseif ('off' === $image_as_background_mode && '' !== $image_inline_markup) {
			$figure_inner .= $image_inline_markup;
		}

		$has_badge = '' !== trim($badge_text_attr);
		$show_image_figure = self::has_card_image($attrs) || $has_badge;

		$icon_data_attrs_html = self::render_button_icon_data_attributes_html($attrs);
		$button_markup        = sprintf(
			'<div class="et_pb_button_module_wrapper">
			  <a %3$s %4$s %5$s class="et_pb_button %2$s"><span>%1$s</span></a>
			</div>',
			esc_html( (string) $button_text_attr ),
			sprintf('et_pb_bg_layout_%1$s', $text_color),
			'' !== $button_url ? sprintf('href="%1$s"', $button_url) : '',
			'new_tab' === $button_url_new_window ? sprintf('target="_blank" rel="noopener noreferrer"') : '',
			$icon_data_attrs_html,
		);

		$image_n_badge_markup = '';
		if ($show_image_figure) {
			$figure_class = trim('dsm_card_carousel_child_image_wrapper ' . $is_custom_badge_position);
			$image_n_badge_markup = sprintf(
				'<figure
			class="%1$s" %2$s %3$s>
			%4$s
			<span class="et_overlay et_pb_inline_icon %6$s %5$s"></span>
			%7$s
		</figure>',
				esc_attr($figure_class),
				'on' === $image_popup && 'off' === $image_video_popup ? sprintf('data-mfp-src="%1$s"', esc_attr($image_popup_src)) : '',
				'off' === $image_popup && 'on' === $image_video_popup ? sprintf('data-mfp-src="%1$s"', esc_attr($image_video_popup_url)) : '',
				$figure_inner,
				'on' === $overlay_on_hover ? 'dsm_show_hover_overlay' : '',
				'on' === $use_icon ? 'dsm-icon' : '',
				$has_badge ? sprintf('<div class="dsm_card_carousel_child_badge_text">%1$s</div>', $badge_text_attr) : ''
			);
		}

		$card_content_markup = '';
		if ($has_card_wrapper) {
			$wrapper_classes = trim('dsm_card_wrapper ' . self::card_carousel_child_text_group_classnames($parent_attrs));
			$nested_markup = ($has_nested_modules && '' !== trim((string) $child_modules_content))
				? sprintf(
					'<div class="%1$s">%2$s</div>',
					esc_attr($nestable_classes),
					et_core_esc_previously($child_modules_content)
				)
				: '';
			$card_content_markup = sprintf(
				'<div class="%1$s" %6$s %7$s>
		    %5$s
			%2$s
			%3$s
			%4$s
			%8$s
		 </div>',
				esc_attr($wrapper_classes),
				'' !== trim($sub_title_attr) ? sprintf('<div class="dsm_card_carousel_child_subtitle">%1$s</div>', $sub_title_attr) : '',
				$content_has_text ? sprintf('<div class="dsm_card_carousel_child_description">%1$s</div>', html_entity_decode($content_attr)) : '',
				'' !== trim((string) $button_text_attr) ? $button_markup : '',
				'' !== trim($title) ? sprintf('<%2$s class="dsm_card_title">%1$s</%2$s>', $title, '' === $heading_level ? $parent_heading_level : $heading_level) : '',
				'on' === $image_popup && 'off' === $image_video_popup ? sprintf('data-mfp-src="%1$s"', $image_popup_src) : '',
				'off' === $image_popup && 'on' === $image_video_popup ? sprintf('data-mfp-src="%1$s"', $image_video_popup_url) : '',
				$nested_markup,
			);
		}

		$container = sprintf(
			'%2$s	
		 %1$s',
			$card_content_markup,
			$image_n_badge_markup
		);

		// 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'],
				'moduleClassName' => 'dsm_card_carousel_child',
				/*
				 * Match VB (`hasModuleClassName={ false }`). Keeping `et_pb_module` applies
				 * Divi’s default module gutter + `:last-child { margin-bottom: 0 }`, which
				 * breaks equal-height when loop clones are appended last. Spacing still
				 * works via the order-class selector from Design → Spacing.
				 */
				'hasModuleClassName' => false,
				'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'),
				'childrenIds' => $children_ids,
				'children' => $module_container_children,
			)
		);
	}
}
