<?php
/**
 * Tilt Image 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 "Tilt Image Module" module.
 */
class TiltImage implements DependencyInterface
{


	/**
	 * 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/tilt-image';

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

	/**
	 * Resolve image URL from UploadContainer innerContent value.
	 *
	 * @param mixed $image_raw Raw `innerContent.desktop.value`.
	 * @return string
	 */
	private static function get_upload_image_src( $image_raw ) {
		if ( is_string( $image_raw ) || is_numeric( $image_raw ) ) {
			return (string) $image_raw;
		}
		if ( is_array( $image_raw ) && ! empty( $image_raw['src'] ) ) {
			return (string) $image_raw['src'];
		}

		return '';
	}

	/**
	 * Escape `src` for an `<img>` tag (URLs and data URIs).
	 *
	 * @param string $src Raw image URL or data URI.
	 * @return string
	 */
	private static function escape_img_src_attribute( string $src ): string {
		if ( preg_match( '#^data:image/[a-zA-Z0-9.+-]+;base64,[a-zA-Z0-9+/=]+$#', $src ) ) {
			return esc_attr( $src );
		}

		return esc_url( $src );
	}

	/**
	 * Read desktop innerContent as a scalar-ish string (toggles, URLs, ranges).
	 *
	 * @param array  $attrs Module attrs.
	 * @param string $key Attribute key.
	 * @param string $fallback Fallback when empty.
	 * @return string
	 */
	private static function get_inner_desktop_value_string( array $attrs, string $key, string $fallback = '' ): string {
		if ( ! isset( $attrs[ $key ]['innerContent']['desktop']['value'] ) ) {
			return $fallback;
		}

		$raw = $attrs[ $key ]['innerContent']['desktop']['value'];

		if ( is_string( $raw ) || is_numeric( $raw ) ) {
			$out = trim( (string) $raw );

			return '' !== $out ? $out : $fallback;
		}

		if ( is_array( $raw ) && isset( $raw['value'] ) && ( is_string( $raw['value'] ) || is_numeric( $raw['value'] ) ) ) {
			$out = trim( (string) $raw['value'] );

			return '' !== $out ? $out : $fallback;
		}

		return $fallback;
	}

	/**
	 * Plain or rich innerContent (string or `{ text }`, used for titles / HTML).
	 *
	 * @param array  $attrs Module attrs.
	 * @param string $key Attribute key.
	 * @param string $fallback Fallback when empty.
	 * @return string
	 */
	private static function get_inner_desktop_text_field( array $attrs, string $key, string $fallback = '' ): string {
		if ( ! isset( $attrs[ $key ]['innerContent']['desktop']['value'] ) ) {
			return $fallback;
		}

		$raw = $attrs[ $key ]['innerContent']['desktop']['value'];

		if ( is_string( $raw ) || is_numeric( $raw ) ) {
			$out = trim( (string) $raw );

			return '' !== $out ? $out : $fallback;
		}

		if ( is_array( $raw ) && isset( $raw['text'] ) && is_string( $raw['text'] ) ) {
			$out = trim( $raw['text'] );

			return '' !== $out ? $out : $fallback;
		}

		return $fallback;
	}

	/**
	 * Whether a toggle desktop value is `on`.
	 *
	 * @param array  $attrs Module attrs.
	 * @param string $key Attribute key.
	 * @param string $when_missing Value if desktop value missing.
	 * @return bool
	 */
	private static function inner_toggle_is_on( array $attrs, string $key, string $when_missing = 'off' ): bool {
		return 'on' === self::get_inner_desktop_value_string( $attrs, $key, $when_missing );
	}

	/**
	 * Normalize overlay color attr to a CSS color string.
	 *
	 * @param mixed $raw Desktop color value.
	 * @return string
	 */
	private static function stringify_overlay_color( $raw ): string {
		if ( is_string( $raw ) || is_numeric( $raw ) ) {
			return trim( (string) $raw );
		}
		if ( is_array( $raw ) && isset( $raw['value'] ) && is_scalar( $raw['value'] ) ) {
			return trim( (string) $raw['value'] );
		}

		return '';
	}

	/**
	 * Limit heading tag to h1–h6.
	 *
	 * @param string $tag Requested tag.
	 * @return string
	 */
	private static function sanitize_heading_tag( string $tag ): string {
		$tag = strtolower( $tag );

		return in_array( $tag, array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ), true ) ? $tag : 'h3';
	}

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

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

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

		$style_declarations->add('transform', "translateZ($attr_value)");

		return $style_declarations->value();
	}

	/**
	 * Render module style.
	 *
	 * @param array $args - Arguments.
	 */
	public static function module_styles($args)
	{
		$attrs = $args['attrs'] ?? array();
		$elements = $args['elements'];

		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']} .dsm-tilt-image-wrapper",
							'attr' => $attrs['module']['advanced']['text'] ?? array(),
						)
					),

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

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

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

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

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

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

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

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

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

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

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']}",
							'attr' => $attrs['align']['innerContent'] ?? array(),
							'property' => 'text-align',
						)
					),

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

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

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_tilt_overlay",
							'attr' => $attrs['dsm_overlay_content_orientation']['innerContent'] ?? array(),
							'property' => 'align-items',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm_tilt_overlay",
							'attr' => $attrs['dsm_tilt_parallax']['innerContent'] ?? array(),
							'declarationFunction' => array(static::class, 'popup_out_style_declaration'),
						)
					),
				),
			)
		);
	}

	/**
	 * Normalize `innerContent` breakpoint entries to Divi 5 shape `{ value: … }` instead of a bare scalar.
	 *
	 * Legacy saves or incorrect `module.json` defaults yield `desktop: "30px"`, which breaks
	 * {@see ModuleUtils::sort_states()} during style render. Keeps dedicated migration PHP optional.
	 *
	 * @param array $attrs Module attributes.
	 * @return array
	 */
	private static function normalize_tilt_inner_content_breakpoints( array $attrs ): array {
		$breakpoints = array( 'desktop', 'tablet', 'phone' );

		foreach ( $attrs as $key => $attr_val ) {
			if ( ! is_array( $attr_val ) || ! isset( $attr_val['innerContent'] ) || ! is_array( $attr_val['innerContent'] ) ) {
				continue;
			}
			foreach ( $breakpoints as $bp ) {
				if ( ! array_key_exists( $bp, $attr_val['innerContent'] ) ) {
					continue;
				}
				$leaf = $attr_val['innerContent'][ $bp ];
				if ( is_string( $leaf ) || is_numeric( $leaf ) ) {
					$attrs[ $key ]['innerContent'][ $bp ] = array(
						'value' => (string) $leaf,
					);
				}
			}
		}

		return $attrs;
	}

	/**
	 * 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'];

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

		if ( self::inner_toggle_is_on( $attrs, 'force_fullwidth', 'off' ) ) {
			$classnames_instance->add( 'dsm_tilt_force_fullwidth' );
		}
	}

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

		$attrs = self::normalize_tilt_inner_content_breakpoints( $attrs );

		wp_enqueue_script( 'dsm-vanilla-tilt' );
		wp_enqueue_script( 'dsm-tilt-image' );

		$image_raw     = $attrs['src']['innerContent']['desktop']['value'] ?? '';
		$image_src     = self::get_upload_image_src( $image_raw );
		$img_src_esc   = self::escape_img_src_attribute( $image_src );
		$alt_text      = self::get_inner_desktop_value_string( $attrs, 'alt', '' );
		$title_text    = self::get_inner_desktop_value_string( $attrs, 'title_text', '' );

		$tilt_max         = self::get_inner_desktop_value_string( $attrs, 'dsm_tilt_max', '35' );
		$tilt_speed       = self::get_inner_desktop_value_string( $attrs, 'dsm_tilt_speed', '300' );
		$tilt_perspective = self::get_inner_desktop_value_string( $attrs, 'dsm_tilt_perspective', '1000' );
		$tilt_scale       = self::get_inner_desktop_value_string( $attrs, 'dsm_tilt_scale', '1' );
		$tilt_max         = '' === trim( (string) $tilt_max ) ? '35' : (string) floatval( $tilt_max );
		$tilt_speed       = '' === trim( (string) $tilt_speed ) ? '300' : (string) floatval( $tilt_speed );
		$tilt_perspective = '' === trim( (string) $tilt_perspective ) ? '1000' : (string) floatval( $tilt_perspective );
		$tilt_scale       = '' === trim( (string) $tilt_scale ) ? '1' : (string) floatval( $tilt_scale );

		$overlay_color_raw = $attrs['dsm_overlay_color']['innerContent']['desktop']['value'] ?? null;
		$overlay_color     = self::stringify_overlay_color( $overlay_color_raw );

		$dsm_overlay_title_text = self::get_inner_desktop_text_field( $attrs, 'dsm_overlay_title', '' );
		$heading_raw            = 'h3';
		if (
			isset( $attrs['dsm_overlay_title']['decoration']['font']['font']['desktop']['value']['headingLevel'] )
			&& is_string( $attrs['dsm_overlay_title']['decoration']['font']['font']['desktop']['value']['headingLevel'] )
		) {
			$heading_raw = $attrs['dsm_overlay_title']['decoration']['font']['font']['desktop']['value']['headingLevel'];
		}
		$overlay_title_tag = self::sanitize_heading_tag( $heading_raw );
		$content_raw              = self::get_inner_desktop_text_field( $attrs, 'content', '' );
		$use_icon                 = self::get_inner_desktop_value_string( $attrs, 'use_overlay', 'off' );
		$dsm_tilt_mobile          = self::get_inner_desktop_value_string( $attrs, 'dsm_tilt_mobile', 'off' );
		$dsm_tilt_glare           = self::get_inner_desktop_value_string( $attrs, 'dsm_tilt_glare', '1' );
		$url                      = self::get_inner_desktop_value_string( $attrs, 'url', '' );
		$url_new_window           = self::get_inner_desktop_value_string( $attrs, 'url_new_window', 'off' );

		$show_lb        = self::inner_toggle_is_on( $attrs, 'show_in_lightbox', 'off' );
		$use_glare      = self::inner_toggle_is_on( $attrs, 'dsm_use_glare', 'off' );
		$use_parallax   = self::inner_toggle_is_on( $attrs, 'dsm_use_parallax', 'off' );
		$tilt_reverse   = self::inner_toggle_is_on( $attrs, 'dsm_tilt_reverse', 'off' );
		$overlay_hover  = self::inner_toggle_is_on( $attrs, 'dsm_overlay_on_hover', 'off' );

		$content_stripped   = trim( wp_strip_all_tags( $content_raw ) );
		$url_trim           = trim( $url );
		$is_overlay_applied =
			( '' !== trim( wp_strip_all_tags( $dsm_overlay_title_text ) ) )
			|| ( '' !== $content_stripped )
			|| ( 'on' === $use_icon )
			|| $show_lb
			|| ( ! $show_lb && '' !== $url_trim );

		$icon_value = Utils::process_font_icon( $attrs['icon']['innerContent']['desktop']['value'] ?? array() );

		$icon_markup = '';
		if ( 'on' === $use_icon ) {
			$icon_markup = HTMLUtility::render(
				array(
					'tag' => 'span',
					'attributes' => array(
						'class' => 'et-pb-icon',
					),
					'childrenSanitizer' => 'et_core_esc_previously',
					'children' => is_string( $icon_value ) ? $icon_value : '',
				)
			);
		}

		$title_html = '';
		if ( '' !== trim( wp_strip_all_tags( $dsm_overlay_title_text ) ) ) {
			$title_html = sprintf(
				'<%1$s class="dsm_tilt_overlay_title"><span>%2$s</%1$s>',
				esc_attr( $overlay_title_tag ),
				esc_html( $dsm_overlay_title_text )
			);
		}

		$content_html = '';
		if ( '' !== $content_stripped ) {
			$content_html = sprintf(
				'<div class="dsm_tilt_overlay_content">%s</div>',
				wp_kses_post( $content_raw )
			);
		}

		$overlay_block = '';
		if ( 'off' !== $use_icon || '' !== trim( wp_strip_all_tags( $dsm_overlay_title_text ) ) || '' !== $content_stripped ) {
			$overlay_block = sprintf(
				'<div class="dsm_tilt_overlay et_pb_bg_layout_dark"><div class="dsm_tilt_overlay_wrapper">%1$s%2$s%3$s</div></div>',
				'on' === $use_icon ? $icon_markup : '',
				$title_html,
				$content_html
			);
		}

		$overlay_color_span = '' !== $overlay_color ? '<span class="dsm_image_overlay"></span>' : '';

		$glare_fragment = '';
		if ( $use_glare ) {
			$glare_amount = '' === trim( (string) $dsm_tilt_glare ) ? '1' : (string) floatval( $dsm_tilt_glare );
			$glare_fragment = sprintf( ' data-tilt-glare data-tilt-max-glare="%s"', esc_attr( $glare_amount ) );
		}

		$reverse_fragment = $tilt_reverse ? ' data-tilt-reverse="true"' : ' data-tilt-reverse="false"';

		$wrapper_classes = array( 'dsm-tilt-image-wrapper' );
		if ( $is_overlay_applied ) {
			$wrapper_classes[] = 'et_pb_has_overlay';
		}
		if ( $overlay_hover ) {
			$wrapper_classes[] = 'dsm_overlay_on_hover';
		}
		if ( $use_parallax ) {
			$wrapper_classes[] = 'dsm_parallax_tilt';
		}
		if ( $show_lb || '' !== $url_trim ) {
			$wrapper_classes[] = 'dsm_tilt_image_link';
		}

		$wrapper_class_attr = implode( ' ', array_filter( $wrapper_classes ) );

		$data_tilt = sprintf(
			' data-tilt data-tilt-max="%1$s" data-tilt-speed="%2$s" data-tilt-perspective="%3$s" data-tilt-scale="%4$s" data-tilt-mobile-disable="%5$s"%6$s%7$s',
			esc_attr( $tilt_max ),
			esc_attr( $tilt_speed ),
			esc_attr( $tilt_perspective ),
			esc_attr( $tilt_scale ),
			esc_attr( $dsm_tilt_mobile ),
			$glare_fragment,
			$reverse_fragment
		);

		$inner_wrap = sprintf(
			'<span class="et_pb_image_wrap">%1$s<img src="%2$s"%3$s%4$s />%5$s</span>',
			$overlay_color_span,
			$img_src_esc,
			'' !== $alt_text ? sprintf( ' alt="%s"', esc_attr( $alt_text ) ) : '',
			'' !== $title_text ? sprintf( ' title="%s"', esc_attr( $title_text ) ) : '',
			$overlay_block
		);

		$outer_tag = ( $show_lb || '' !== $url_trim ) ? 'a' : 'div';
		$href_open  = '';
		$href_close = '';

		if ( 'a' === $outer_tag ) {
			if ( $show_lb ) {
				$lb_title = '' !== $alt_text ? sprintf( ' title="%s"', esc_attr( $alt_text ) ) : '';
				$href_open  = sprintf(
					'<a href="%1$s" class="%2$s et_pb_lightbox_image dsm_tilt_image_link"%3$s%4$s>',
					esc_url( $image_src ),
					esc_attr( $wrapper_class_attr ),
					$lb_title,
					$data_tilt
				);
				$href_close = '</a>';
			} else {
				$target = 'on' === $url_new_window ? ' target="_blank" rel="noopener noreferrer"' : '';
				$href_open  = sprintf(
					'<a href="%1$s" class="%2$s dsm_tilt_image_link"%3$s%4$s>',
					esc_url( $url_trim ),
					esc_attr( $wrapper_class_attr ),
					$target,
					$data_tilt
				);
				$href_close = '</a>';
			}
			$output = $href_open . $inner_wrap . $href_close;
		} else {
			$output = sprintf(
				'<div class="%1$s"%2$s>%3$s</div>',
				esc_attr( $wrapper_class_attr ),
				$data_tilt,
				$inner_wrap
			);
		}

		$module_inner = HTMLUtility::render(
			array(
				'tag' => 'div',
				'attributes' => array(
					'class' => 'et_pb_module_inner',
				),
				'childrenSanitizer' => 'et_core_esc_previously',
				'children' => $output,
			)
		);

		$module_elements           = $elements->style_components(
			array(
				'attrName' => 'module',
			)
		);
		$module_container_children = $module_elements . $module_inner;

		return Module::render(
			array(
				'orderIndex' => $block->parsed_block['orderIndex'],
				'storeInstance' => $block->parsed_block['storeInstance'],
				'attrs' => $attrs,
				'elements' => $elements,
				'id' => $block->parsed_block['id'],
				'moduleClassName' => 'dsm_tilt_image',
				'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,
			)
		);
	}
}
