<?php

/**
 * Card 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\ModuleLibrary\ModuleRegistration;
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 "Card Module" module.
 */
class Card implements DependencyInterface
{

	/**
	 * Grey SVG placeholder for new inserts only — not a user-selected image.
	 */
	public const PLACEHOLDER_IMAGE_SRC = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTA4MCIgaGVpZ2h0PSI1NDAiIHZpZXdCb3g9IjAgMCAxMDgwIDU0MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPHBhdGggZmlsbD0iI0VCRUJFQiIgZD0iTTAgMGgxMDgwdjU0MEgweiIvPgogICAgICAgIDxwYXRoIGQ9Ik00NDUuNjQ5IDU0MGgtOTguOTk1TDE0NC42NDkgMzM3Ljk5NSAwIDQ4Mi42NDR2LTk4Ljk5NWwxMTYuMzY1LTExNi4zNjVjMTUuNjItMTUuNjIgNDAuOTQ3LTE1LjYyIDU2LjU2OCAwTDQ0NS42NSA1NDB6IiBmaWxsLW9wYWNpdHk9Ii4xIiBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz4KICAgICAgICA8Y2lyY2xlIGZpbGwtb3BhY2l0eT0iLjA1IiBmaWxsPSIjMDAwIiBjeD0iMzMxIiBjeT0iMTQ4IiByPSI3MCIvPgogICAgICAgIDxwYXRoIGQ9Ik0xMDgwIDM3OXYxMTMuMTM3TDcyOC4xNjIgMTQwLjMgMzI4LjQ2MiA1NDBIMjE1LjMyNEw2OTkuODc4IDU1LjQ0NmMxNS42Mi0xNS42MiA0MC45NDgtMTUuNjIgNTYuNTY4IDBMMTA4MCAzNzl6IiBmaWxsLW9wYWNpdHk9Ii4yIiBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz4KICAgIDwvZz4KPC9zdmc+Cg==';

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

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

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

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

	private static function is_placeholder_image_src(string $src): bool
	{
		return '' !== $src && self::PLACEHOLDER_IMAGE_SRC === $src;
	}

	/**
	 * 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 $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 the module has a usable image attribute value.
	 *
	 * @param array $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_image_upload_attr_to_background_url($image_raw);
				return '' !== $resolved;
			}
		}

		return false;
	}

	/**
	 * First usable image URL (or data URI string) for inline <img>, matching breakpoint scan in has_card_image().
	 *
	 * @param array $attrs Module attributes.
	 * @return string
	 */
	private static function get_card_image_src(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)) {
				continue;
			}

			if (! empty($image_raw['src'])) {
				return (string) $image_raw['src'];
			}

			if (! empty($image_raw['url'])) {
				return (string) $image_raw['url'];
			}

			if (! empty($image_raw['id'])) {
				$attachment_id = absint($image_raw['id']);
				if ($attachment_id > 0) {
					$url = wp_get_attachment_image_url($attachment_id, 'full');
					if (is_string($url) && '' !== $url) {
						return $url;
					}
				}
			}
		}

		return '';
	}

	/**
	 * Escape value for an <img src=""> attribute (esc_url for URLs; data: URIs need esc_attr).
	 *
	 * @param string $src Raw src.
	 * @return string
	 */
	private static function esc_img_src_attribute(string $src)
	{
		if ('' === $src) {
			return '';
		}

		if (0 === strpos($src, 'data:')) {
			return esc_attr($src);
		}

		return esc_url($src);
	}

	/**
	 * Resolved image-as-background toggle (align with JS `getCardImageAsBackgroundMode`).
	 *
	 * @param array $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';
	}

	/**
	 * Normalize upload / innerContent to a URL for CSS background-image (CommonStyle may pass a leaf or full innerContent).
	 *
	 * @param mixed $raw attrValue from CommonStyle.
	 * @return string
	 */
	private static function resolve_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_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_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_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 should fall back to 300ms (matches D4 min / builder Range min).
	 *
	 * @param mixed $speed Raw attr value.
	 * @return bool
	 */
	/**
	 * True when innerContent desktop toggle is "on" (string, bool, or 1 from API/save).
	 *
	 * @param array  $attrs    Module attrs.
	 * @param string $attr_key Attribute key.
	 */
	private static function is_inner_toggle_on(array $attrs, string $attr_key): bool
	{
		if (! isset($attrs[ $attr_key ]['innerContent']) || ! is_array($attrs[ $attr_key ]['innerContent'])) {
			return false;
		}

		$raw = $attrs[ $attr_key ]['innerContent']['desktop']['value'] ?? null;

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

		return 'on' === $raw;
	}

	/**
	 * Flatten color field attrValue for CSS (string hex/rgba or D5 color object).
	 *
	 * @param mixed $raw Style callback attrValue.
	 */
	private static function resolve_simple_color_css($raw): string
	{
		if (is_string($raw)) {
			$t = trim($raw);

			return '' !== $t ? $t : '';
		}

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

		if (isset($raw['hex']) && is_string($raw['hex']) && '' !== trim($raw['hex'])) {
			return trim($raw['hex']);
		}

		if (isset($raw['rgba']) && is_string($raw['rgba']) && '' !== trim($raw['rgba'])) {
			return trim($raw['rgba']);
		}

		return '';
	}

	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 legacy `transition: all {speed} ease` behavior).
	 *
	 * @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 icon_style_declaration($params)
	{
		return LegacyModuleIconStyles::build_pseudo_icon_declaration( $params );
	}


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

	public static function icon_border_style_declaration($params)
	{
		$border_color_attr = $params['attrValue'] ?? '';
		$resolved          = is_string($border_color_attr)
			? trim($border_color_attr)
			: self::resolve_simple_color_css($border_color_attr);
		if ('' === $resolved) {
			$resolved = '#333333';
		}

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

		$style_declarations->add('border-style', 'solid');
		$style_declarations->add('border-width', '2px');
		$style_declarations->add('border-color', $resolved);

		return $style_declarations->value();
	}

	/**
	 * Structural CSS so Circle Icon background reads as a circle (scoped to content area icon only).
	 *
	 * @param array $params Divi style callback params.
	 * @return string
	 */
	public static function content_icon_circle_shape_declaration($params)
	{
		$important = $params['important'] ?? false;

		$style_declarations = new StyleDeclarations(
			array(
				'returnType' => 'string',
				'important' => is_array($important) ? $important : array(),
			)
		);

		$style_declarations->add('border-radius', '50%');
		$style_declarations->add('display', 'inline-flex');
		$style_declarations->add('align-items', 'center');
		$style_declarations->add('justify-content', 'center');
		$style_declarations->add('padding', '0.35em');
		$style_declarations->add('line-height', '1');
		$style_declarations->add('box-sizing', 'border-box');
		$style_declarations->add('vertical-align', 'middle');
		$style_declarations->add('aspect-ratio', '1');

		return $style_declarations->value();
	}

	/**
	 * Circle fill: must use declarationFunction so color objects resolve to valid CSS.
	 *
	 * @param array $params Style callback params.
	 */
	public static function content_icon_circle_fill_declaration($params)
	{
		$raw       = $params['attrValue'] ?? null;
		$color     = self::resolve_simple_color_css($raw);
		$important = $params['important'] ?? false;

		if ('' === $color) {
			$color = '#f4f4f4';
		}

		$style_declarations = new StyleDeclarations(
			array(
				'returnType' => 'string',
				'important' => is_array($important) ? $important : array(),
			)
		);

		$style_declarations->add('background-color', $color);

		return $style_declarations->value();
	}

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

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

		$style_declarations->add('flex', "0 0 $image_width_attr");

		return $style_declarations->value();
	}

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

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

		if ('flex-start' === $attr) {
			$style_declarations->add('align-items', 'flex-start');
		}

		if ('center' === $attr) {
			$style_declarations->add('align-items', 'center');
		}

		if ('flex-end' === $attr) {
			$style_declarations->add('align-items', 'flex-end');
		}

		return $style_declarations->value();
	}


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

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

		$style_declarations->add('display', 'flex');

		if ('left' === $attr) {
			$style_declarations->add('justify-content', 'flex-start');
		}

		if ('center' === $attr) {
			$style_declarations->add('justify-content', 'center');
		}

		if ('right' === $attr) {
			$style_declarations->add('justify-content', 'flex-end');
		}

		return $style_declarations->value();
	}

	/**
	 * Wrapper height beside fixed equal-height image strip (D4 parity: equal columns + Use Equal Height).
	 *
	 * @param array $params Style declaration params including attrValue.
	 * @return string
	 */
	public static function equal_height_content_wrapper_calc_declaration($params)
	{
		$attr = $params['attrValue'] ?? '';

		if (! is_string($attr) || '' === trim($attr)) {
			return '';
		}

		$h = sanitize_text_field(trim($attr));

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

		$style_declarations->add('height', 'calc(100% - ' . $h . ')');

		return $style_declarations->value();
	}

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

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

		$order_alignment_value = 'left' === $attr ? '0' : '1';
		$style_declarations->add('order', $order_alignment_value);

		return $style_declarations->value();
	}

	/**
	 * Effective card layout at a style breakpoint (inherits like Divi responsive).
	 *
	 * @param array  $attrs      Module attrs.
	 * @param string $breakpoint desktop|tablet|phone.
	 * @return string stacked|inline
	 */
	private static function get_card_resolved_layout(array $attrs, string $breakpoint)
	{
		$inner = $attrs['layout']['innerContent'] ?? array();
		$desktop = isset($inner['desktop']['value']) ? trim((string) $inner['desktop']['value']) : '';
		if ('' === $desktop) {
			$desktop = 'stacked';
		}
		if ('desktop' === $breakpoint) {
			return $desktop;
		}
		$tablet = isset($inner['tablet']['value']) ? trim((string) $inner['tablet']['value']) : '';
		if ('tablet' === $breakpoint) {
			return '' !== $tablet ? $tablet : $desktop;
		}
		$phone = isset($inner['phone']['value']) ? trim((string) $inner['phone']['value']) : '';
		if ('' !== $phone) {
			return $phone;
		}
		if ('' !== $tablet) {
			return $tablet;
		}
		return $desktop;
	}

	/**
	 * Selector for inline image column width (figure).
	 *
	 * @param array  $attrs       Module attrs.
	 * @param string $order_class Module order class.
	 * @param string $breakpoint  desktop|tablet|phone.
	 * @return string Empty when not inline at breakpoint.
	 */
	private static function get_card_inline_image_wrapper_selector(array $attrs, string $order_class, string $breakpoint)
	{
		if ('inline' !== self::get_card_resolved_layout($attrs, $breakpoint)) {
			return '';
		}
		$inner = $attrs['layout']['innerContent'] ?? array();
		$tablet_raw = isset($inner['tablet']['value']) ? trim((string) $inner['tablet']['value']) : '';
		$phone_raw = isset($inner['phone']['value']) ? trim((string) $inner['phone']['value']) : '';

		if ('desktop' === $breakpoint) {
			return "{$order_class}.dsm_card_layout_inline .dsm_card_image_wrapper";
		}
		if ('tablet' === $breakpoint) {
			return '' !== $tablet_raw
				? "{$order_class}.dsm_card_tablet_layout_inline .dsm_card_image_wrapper"
				: "{$order_class}.dsm_card_layout_inline .dsm_card_image_wrapper";
		}
		if ('' !== $phone_raw) {
			return "{$order_class}.dsm_card_phone_layout_inline .dsm_card_image_wrapper";
		}
		if ('' !== $tablet_raw) {
			return "{$order_class}.dsm_card_tablet_layout_inline .dsm_card_image_wrapper";
		}
		return "{$order_class}.dsm_card_layout_inline .dsm_card_image_wrapper";
	}

	/**
	 * D4 parity: align-items on module root + .et_pb_module_inner when inline, plus .dsm_card_image_animation for VB (no inner wrapper).
	 *
	 * @param array  $attrs       Module attrs.
	 * @param string $order_class Module order class.
	 * @param string $breakpoint  desktop|tablet|phone.
	 * @return string
	 */
	private static function get_card_inline_align_items_selector(array $attrs, string $order_class, string $breakpoint)
	{
		$img = self::get_card_inline_image_wrapper_selector($attrs, $order_class, $breakpoint);
		if ('' === $img) {
			return '';
		}
		$root = str_replace(' .dsm_card_image_wrapper', '', $img);

		return "{$root}, {$root} .et_pb_module_inner, {$order_class} .dsm_card_image_animation";
	}

	/**
	 * 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): string
	{
		$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);
	}

	/**
	 * 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 = $attrs['button']['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'), 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();
	}

	/**
	 * Render module style.
	 *
	 * @param array $args - Arguments.
	 */
	public static function module_styles($args)
	{
		$attrs = $args['attrs'] ?? array();
		$elements = $args['elements'];
		$badge_custom_position = $attrs['badge_custom_position']['innerContent']['desktop']['value'] ?? '';
		$use_circle            = self::is_inner_toggle_on($attrs, 'use_circle') ? 'on' : 'off';
		$use_circle_border     = self::is_inner_toggle_on($attrs, 'use_circle_border') ? 'on' : 'off';
		$use_icon_font_size    = self::is_inner_toggle_on($attrs, 'use_icon_font_size') ? 'on' : 'off';
		$use_icon              = $attrs['use_icon']['innerContent']['desktop']['value'] ?? '';
		$image_as_background                 = self::get_image_as_background_mode($attrs);
		$content_equal_height_on             = self::is_inner_toggle_on($attrs, 'content_equal_height');
		$apply_image_background_wrapper_height = 'on' === $image_as_background && self::has_card_image($attrs) && ! $content_equal_height_on;

		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,
								),
							),
						)
					),

					TextStyle::style(
						array(
							'selector' => $args['orderClass'],
							'attr' => $attrs['module']['advanced']['text'] ?? array(),
						)
					),

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

					$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' => 'badge_text',
						)
					),

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

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

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

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

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

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

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

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

					'on' === $use_icon ?
					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .et_overlay::before",
							'attr' => $attrs['hover_icon']['innerContent'] ?? array(),
							'declarationFunction' => array(static::class, 'hover_icon_declaration'),
						)
					) : '',

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_icon .et-pb-icon",
							'attr' => $attrs['font_icon']['innerContent'] ?? array(),
							'declarationFunction' => array(static::class, 'icon_style_declaration'),
						)
					),

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

					// D4: justify-content on .dsm_card_wrapper (stacked + inline). FE wraps in .et_pb_module_inner; VB edit has no inner.
					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .et_pb_module_inner .dsm_card_wrapper, {$args['orderClass']} .dsm_card_wrapper",
							'attr' => $attrs['content_horizontal_alignment']['innerContent'] ?? array(),
							'property' => 'justify-content',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']}.dsm_card_layout_inline .et_pb_module_inner",
							'attr' => $attrs['content_horizontal_alignment']['innerContent'] ?? array(),
							'selectorFunction' => static function ($params) use ($attrs, $args) {
								return self::get_card_inline_align_items_selector($attrs, $args['orderClass'], $params['breakpoint'] ?? 'desktop');
							},
							'declarationFunction' => static function ($params) use ($attrs) {
								if ('inline' !== self::get_card_resolved_layout($attrs, $params['breakpoint'] ?? 'desktop')) {
									return '';
								}
								return self::content_horizontal_alignment_declaration($params);
							},
						)
					),

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

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_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_image_background",
							'attr' => $attrs['image_background_repeat']['innerContent'] ?? array(),
							'declarationFunction' => array(static::class, 'image_bg_repeat_declaration'),
						)
					),

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

					$content_equal_height_on ?

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

					$content_equal_height_on ?

					CommonStyle::style(
						array(
							'selector' => ".et_pb_equal_columns {$args['orderClass']} .dsm_card_wrapper",
							'attr' => $attrs['equal_height_image_height']['innerContent'] ?? array(),
							'declarationFunction' => array(static::class, 'equal_height_content_wrapper_calc_declaration'),
						)
					) : '',

					$apply_image_background_wrapper_height ?

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

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_image_wrapper .dsm_card_img, {$args['orderClass']} .dsm_card_image_wrapper .et_overlay",
							'attr' => $attrs['image_width']['innerContent'] ?? array(),
							'property' => 'max-width',
						)
					),

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

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

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

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

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_icon .et-pb-icon",
							'attr' => $attrs['icon_color']['innerContent'] ?? array(),
							'property' => 'color',
						)
					),

					'on' === $use_circle ?
					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_icon .et-pb-icon",
							'attr' => $attrs['use_circle']['innerContent'] ?? array(
								'desktop' => array(
									'value' => 'on',
								),
							),
							'declarationFunction' => array(static::class, 'content_icon_circle_shape_declaration'),
						)
					) : '',

					'on' === $use_circle ?
					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_icon .et-pb-icon",
							'attr' => $attrs['circle_color']['innerContent'] ?? array(),
							'declarationFunction' => array(static::class, 'content_icon_circle_fill_declaration'),
						)
					) : '',

					'on' === $use_circle_border ?
					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_icon .et-pb-icon",
							'attr' => $attrs['circle_border_color']['innerContent'] ?? array(),
							'declarationFunction' => array(static::class, 'icon_border_style_declaration'),
						)
					) : '',

					'on' === $use_icon_font_size ?
					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_card_icon .et-pb-icon",
							'attr' => $attrs['icon_content_font_size']['innerContent'] ?? array(),
							'property' => 'font-size',
						)
					) : '',

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

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']}.dsm_card_layout_inline .dsm_card_image_wrapper",
							'attr' => $attrs['layout_inline_image_width']['innerContent'] ?? array(),
							'selectorFunction' => static function ($params) use ($attrs, $args) {
								return self::get_card_inline_image_wrapper_selector($attrs, $args['orderClass'], $params['breakpoint'] ?? 'desktop');
							},
							'declarationFunction' => static function ($params) use ($attrs) {
								if ('inline' !== self::get_card_resolved_layout($attrs, $params['breakpoint'] ?? 'desktop')) {
									return '';
								}
								return self::image_width_style_declaration($params);
							},
						)
					),

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

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .et_overlay::before",
							'attr' => $attrs['icon_font_size']['innerContent'] ?? array(),
							'property' => 'font-size',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .et_overlay::before",
							'attr' => $attrs['overlay_icon_color']['innerContent'] ?? array(),
							'property' => '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'];
		$layout = $attrs['layout']['innerContent']['desktop']['value'] ?? '';
		if ('' === $layout) {
			$layout = 'stacked';
		}
		$layout_tablet = $attrs['layout']['innerContent']['tablet']['value'] ?? '';
		$layout_phone  = $attrs['layout']['innerContent']['phone']['value'] ?? '';
		$image_as_background = self::get_image_as_background_mode($attrs);
		$image_background_animation = $attrs['image_background_animation']['innerContent']['desktop']['value'] ?? '';
		$text_color = $attrs['module']['advanced']['text']['text']['desktop']['value']['color'] ?? '';
		$overlay_on_hover = $attrs['overlay_on_hover']['innerContent']['desktop']['value'] ?? '';
		$module_link = $attrs['module']['advanced']['link']['desktop']['value']['url'] ?? array();

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

		$classnames_instance->add('dsm_card_layout_' . $layout);

		if ('' !== trim((string) $layout_tablet)) {
			$classnames_instance->add('dsm_card_tablet_layout_' . $layout_tablet);
		}
		if ('' !== trim((string) $layout_phone)) {
			$classnames_instance->add('dsm_card_phone_layout_' . $layout_phone);
		}

		if ('on' === $image_as_background) {
			$classnames_instance->add('dsm_card_image_as_background');
		}
		$classnames_instance->add('dsm_card_image_animation dsm_card_image_animation_' . $image_background_animation);
		$classnames_instance->add('et_pb_bg_layout_' . $text_color);
		$classnames_instance->add("dsm-card-overlay-$overlay_on_hover");

		if (! empty($module_link)) {
			$classnames_instance->add('dsm-link');
		}
	}

	/**
	 * 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)
	{

		$image_src = self::get_card_image_src($attrs);
		$image_as_background = self::get_image_as_background_mode($attrs);
		$stretch_inline_image = ('off' === $image_as_background) && self::is_inner_toggle_on($attrs, 'stretch_image');
		$inline_img_class = $stretch_inline_image ? 'dsm_card_img dsm_card_image_stretch' : 'dsm_card_img';
		$badge_text = $attrs['badge_text']['innerContent']['desktop']['value'] ?? '';
		$subtitle_raw = $attrs['subtitle']['innerContent']['desktop']['value'] ?? '';
		$subtitle = '';
		if (is_string($subtitle_raw)) {
			$subtitle = $subtitle_raw;
		} elseif (is_array($subtitle_raw) && isset($subtitle_raw['text']) && is_string($subtitle_raw['text'])) {
			$subtitle = $subtitle_raw['text'];
		}
		$content = $attrs['content']['innerContent']['desktop']['value']['text'] ?? '';
		$button_raw  = $attrs['button_text']['innerContent']['desktop']['value'] ?? '';
		$button_text = '';
		if (is_array($button_raw) && isset($button_raw['text'])) {
			$button_text = is_string($button_raw['text']) ? $button_raw['text'] : '';
		} elseif (is_string($button_raw)) {
			$button_text = $button_raw;
		}
		$button_url = $attrs['button_url']['innerContent']['desktop']['value'] ?? '';
		$button_url_new_window = $attrs['button_url_new_window']['innerContent']['desktop']['value'] ?? '';
		$badge_position = $attrs['badge_position']['innerContent']['desktop']['value'] ?? '';
		if ('' === $badge_position) {
			$badge_position = 'top_right';
		}
		$use_icon = $attrs['use_content_icon']['innerContent']['desktop']['value'] ?? '';
		$badge_custom_position = $attrs['badge_custom_position']['innerContent']['desktop']['value'] ?? '';
		$use_overlay = $attrs['use_overlay']['innerContent']['desktop']['value'] ?? '';
		$badge_url = $attrs['badge_url']['innerContent']['desktop']['value'] ?? '';
		$badge_url_new_window = $attrs['badge_url_new_window']['innerContent']['desktop']['value'] ?? '';
		$text_color = $attrs['module']['advanced']['text']['text']['desktop']['value']['color'] ?? '';

		$icon_settings = is_array( $attrs['font_icon']['innerContent']['desktop']['value'] ?? null )
			? $attrs['font_icon']['innerContent']['desktop']['value']
			: array();
		$icon_value    = Utils::process_font_icon( $icon_settings );
		$icon          = HTMLUtility::render(
			array(
				'tag'               => 'span',
				'attributes'        => array(
					'class' => 'et-pb-icon',
				),
				'childrenSanitizer' => 'et_core_esc_previously',
				'children'          => is_string( $icon_value ) ? $icon_value : '',
			)
		);

		$icon_data_attrs_html = self::render_button_icon_data_attributes_html($attrs);
		$button_icon_class = '' !== trim($icon_data_attrs_html) ? ' et_pb_custom_button_icon' : '';
		$button_bg_layout = '' !== $text_color ? $text_color : 'dark';
		$button_class = sprintf(
			'et_pb_button et_pb_bg_layout_%s%s',
			esc_attr($button_bg_layout),
			$button_icon_class
		);
		$button_markup = sprintf(
			'<div class="et_pb_button_module_wrapper">
			  <a %2$s %3$s %4$s class="%5$s"><span>%1$s</span></a>
			</div>',
			esc_html( $button_text ),
			'' !== $button_url ? sprintf('href="%1$s" ', esc_url($button_url)) : '',
			'on' === $button_url_new_window ? 'target="_blank" ' : '',
			$icon_data_attrs_html,
			$button_class
		);

		$title_text = $elements->render(
			array(
				'attrName' => 'title',
			)
		);

		$badge_markup = '';
		if ('' !== $badge_text) {
			$has_badge_link = is_string($badge_url) && '' !== trim($badge_url);
			if ($has_badge_link) {
				$badge_attrs = array(
					'class' => 'dsm_card_badge_text',
					'href' => esc_url($badge_url),
				);
				if ('on' === $badge_url_new_window) {
					$badge_attrs['target'] = '_blank';
				}
				$badge_markup = HTMLUtility::render(
					array(
						'tag' => 'a',
						'attributes' => $badge_attrs,
						'children' => $badge_text,
						'childrenSanitizer' => 'et_core_esc_previously',
					)
				);
			} else {
				$badge_markup = HTMLUtility::render(
					array(
						'tag' => 'div',
						'attributes' => array(
							'class' => 'dsm_card_badge_text',
						),
						'children' => $badge_text,
						'childrenSanitizer' => 'et_core_esc_previously',
					)
				);
			}
		}

		$wrapper = sprintf(
			'<figure class="dsm_card_image_wrapper %8$s">
			   %1$s
			   %2$s
			   %3$s
			   %10$s
			</figure>
			<div class="et_pb_module dsm_card_wrapper">
			   %9$s
			   %4$s
			   %5$s
			   %6$s
			   %7$s
			</div>',
			'on' === $image_as_background && self::has_card_image($attrs) ? "<div class='dsm_card_image_background dsm_card_img'></div>" : '',
			'off' === $image_as_background && '' !== $image_src ? sprintf('<img src="%1$s" class="%2$s" alt=""/>', self::esc_img_src_attribute($image_src), esc_attr($inline_img_class)) : '',
			$badge_markup,
			$title_text,
			'' !== $subtitle ? sprintf('<div class="dsm_card_subtitle">%1$s</div>', $subtitle) : '',
			'' !== $content ? sprintf('<div class="dsm_card_description">%1$s</div>', $content) : '',
			'' !== $button_text ? $button_markup : '',
			'on' !== $badge_custom_position ? 'dsm_card_badge_' . $badge_position : '',
			'on' === $use_icon ? sprintf('<div class="dsm_card_icon"> <span class="dsm_card_icon_wrap">%1$s</span></div>', $icon) : '',
			'on' === $use_overlay ? sprintf('<span class="et_overlay"></span>', ) : ''
		);

		// 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' => $wrapper,
			)
		);

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