<?php
/**
 * Image Text Reveal Module (D5).
 *
 * @package DiviSupreme
 */

declare(strict_types=1);

namespace DiviSupreme\Modules;

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

use DiviSupreme\Includes\DsmFrontendDynamicAssets;
use DiviSupreme\Includes\Divi5Conversion\ConversionAttrs;
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\Module\Options\Css\CssStyle;

/**
 * Handles `dsm/image-text-reveal` (D4: `dsm_image_reveal`).
 */
class ImageReveal implements DependencyInterface {

	/**
	 * Register hook for module registration.
	 *
	 * @return void
	 */
	public function load() {
		add_action( 'init', array( self::class, 'register_module' ) );
	}

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

	/**
	 * Registers the block with Divi 5.
	 *
	 * @return void
	 */
	public static function register_module() {
		$module_json_path = DSM_DIR_PATH . 'divi-5/public/packages/module-library/image-reveal';

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

	/**
	 * Module styles callback.
	 *
	 * @param array<string, mixed> $args Arguments.
	 * @return void
	 */
	public static function module_styles( $args ) {
		$args = \DiviSupreme\Includes\DsmDiviBuilderCssScope::normalize_module_styles_args( $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(
					$elements->style(
						array(
							'attrName'   => 'module',
							'styleProps' => array(
								'disabledOn' => array(
									'disabledModuleVisibility' => $args['settings']['disabledModuleVisibility'] ?? null,
								),
							),
						)
					),
					CssStyle::style(
						array(
							'selector' => $args['orderClass'],
							'attr'     => $attrs['css'] ?? array(),
						)
					),
					$elements->style(
						array(
							'attrName' => 'reveal_text',
						)
					),
					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm-image-reveal-overlay",
							'attr'     => $attrs['reveal_overlay_color']['innerContent'] ?? array(),
							'property' => 'background-color',
						)
					),
					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm-image-wrapper",
							'attr'     => $attrs['align']['innerContent'] ?? array(),
							'property' => 'text-align',
						)
					),
				),
			)
		);
	}

	/**
	 * Script data (module element).
	 *
	 * @param array<string, mixed> $args Arguments.
	 * @return void
	 */
	public static function module_script_data( $args ) {
		$elements = $args['elements'];
		$elements->script_data(
			array(
				'attrName' => 'module',
			)
		);
	}

	/**
	 * Root classnames: D4 parity (`dsm-image-reveal`, `et_pb_bg_layout_*`, mobile center).
	 *
	 * @param array<string, mixed> $args Arguments.
	 * @return void
	 */
	public static function module_classnames( $args ) {
		$classnames_instance = $args['classnamesInstance'];
		$attrs               = $args['attrs'];

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

		$text_advanced = $attrs['module']['advanced']['text'] ?? array();
		$text_classes  = trim( TextClassnames::text_options_classnames( $text_advanced ) );
		if ( '' === $text_classes ) {
			$text_classes = 'et_pb_bg_layout_dark';
		} elseif ( false === strpos( $text_classes, 'et_pb_bg_layout_' ) ) {
			$color = $text_advanced['text']['desktop']['value']['color'] ?? 'dark';
			if ( ! is_string( $color ) || '' === $color ) {
				$color = 'dark';
			}
			if ( in_array( $color, array( 'light', 'dark' ), true ) ) {
				$text_classes .= ' et_pb_bg_layout_' . $color;
			}
		}
		$classnames_instance->add( $text_classes, true );

		$always_center = $attrs['always_center_on_mobile']['innerContent']['desktop']['value'] ?? 'on';
		if ( 'on' === $always_center ) {
			$classnames_instance->add( 'et_always_center_on_mobile' );
		}
	}

	/**
	 * PlainText / migrated string → display string.
	 *
	 * @param mixed $raw Raw `innerContent.desktop.value`.
	 * @return string
	 */
	private static function plain_text_from_inner_value( $raw ): string {
		if ( is_string( $raw ) ) {
			return $raw;
		}
		if ( is_array( $raw ) && isset( $raw['text'] ) && is_string( $raw['text'] ) ) {
			return $raw['text'];
		}
		return '';
	}

	/**
	 * Range / inner value → valid CSS time for `data-*-animation` (matches VB `reveal-timing.js`).
	 *
	 * @param mixed  $raw     `innerContent.desktop.value` or similar.
	 * @param string $default Fallback when empty or invalid.
	 * @return string
	 */
	private static function format_reveal_css_time( $raw, string $default ): string {
		if ( is_array( $raw ) && isset( $raw['value'] ) && is_numeric( $raw['value'] ) ) {
			$v = (string) (float) $raw['value'];
			$u = isset( $raw['unit'] ) && is_string( $raw['unit'] ) ? strtolower( $raw['unit'] ) : 's';
			return ( 'ms' === $u ) ? ( $v . 'ms' ) : ( $v . 's' );
		}
		if ( is_int( $raw ) || is_float( $raw ) ) {
			return (string) (float) $raw . 's';
		}
		if ( is_string( $raw ) ) {
			$t = trim( $raw );
			if ( '' === $t ) {
				return $default;
			}
			if ( preg_match( '/^[\d.]+$/', $t ) ) {
				return $t . 's';
			}
			return $t;
		}
		return $default;
	}

	/**
	 * Image `src` URL from upload-shaped or legacy attrs.
	 *
	 * @param array<string, mixed> $attrs Module attrs.
	 * @return string
	 */
	private static function image_src_url( array $attrs ): string {
		$raw = $attrs['src']['innerContent']['desktop']['value'] ?? '';
		if ( is_string( $raw ) && '' !== $raw ) {
			return $raw;
		}
		if ( is_array( $raw ) && isset( $raw['src'] ) && is_string( $raw['src'] ) ) {
			return $raw['src'];
		}
		return '';
	}

	/**
	 * Toggle innerContent.desktop.value === 'on'.
	 *
	 * @param array<string, mixed> $attrs Module attrs.
	 * @param string               $key   Attribute key.
	 * @param string               $off   Value when not on.
	 */
	private static function inner_toggle_is_on( array $attrs, string $key, string $off = 'off' ): bool {
		$raw = $attrs[ $key ]['innerContent']['desktop']['value'] ?? $off;
		return 'on' === $raw;
	}

	/**
	 * Overlay wipe direction (desktop; matches VB `getOverlayDirectionFromAttrs` source).
	 *
	 * @param array<string, mixed> $attrs Module attrs.
	 * @return string
	 */
	private static function overlay_direction_from_attrs( array $attrs ): string {
		$allowed = array( 'lr', 'rl', 'tb', 'bt', 'center' );
		$raw     = $attrs['reveal_overlay_direction']['innerContent']['desktop']['value'] ?? 'lr';
		if ( ! is_string( $raw ) ) {
			return 'lr';
		}
		$slug = trim( $raw );
		if ( '' === $slug ) {
			return 'lr';
		}
		return in_array( $slug, $allowed, true ) ? $slug : 'lr';
	}

	/**
	 * Reveal trigger: hover | click | scroll.
	 *
	 * @param array<string, mixed> $attrs Module attrs.
	 * @return string
	 */
	private static function reveal_trigger_from_attrs( array $attrs ): string {
		$allowed = array( 'hover', 'click', 'scroll' );
		$raw     = $attrs['reveal_trigger']['innerContent']['desktop']['value'] ?? 'hover';
		if ( ! is_string( $raw ) ) {
			return 'hover';
		}
		$slug = trim( $raw );
		if ( '' === $slug ) {
			return 'hover';
		}
		return in_array( $slug, $allowed, true ) ? $slug : 'hover';
	}

	/**
	 * Image link URL field (plainText inner shape).
	 *
	 * @param array<string, mixed> $attrs Module attrs.
	 * @return string
	 */
	private static function image_link_url_field( array $attrs ): string {
		$raw = $attrs['url']['innerContent']['desktop']['value'] ?? '';
		if ( is_string( $raw ) ) {
			return trim( $raw );
		}
		if ( is_array( $raw ) && isset( $raw['text'] ) && is_string( $raw['text'] ) ) {
			return trim( $raw['text'] );
		}
		return '';
	}

	/**
	 * Module Advanced → Link URL / target (fallback when image URL field empty).
	 *
	 * @param array<string, mixed> $attrs Module attrs.
	 * @return array{0: string, 1: string} URL, new window flag on|off.
	 */
	private static function module_link_url_and_target( array $attrs ): array {
		$url        = '';
		$new_window = 'off';
		$link_branch = $attrs['module']['advanced']['link'] ?? null;
		if ( is_array( $link_branch ) ) {
			$bucket = null;
			if ( isset( $link_branch['innerContent']['desktop']['value'] ) && is_array( $link_branch['innerContent']['desktop']['value'] ) ) {
				$bucket = $link_branch['innerContent']['desktop']['value'];
			} elseif ( isset( $link_branch['desktop']['value'] ) && is_array( $link_branch['desktop']['value'] ) ) {
				$bucket = $link_branch['desktop']['value'];
			}
			if ( is_array( $bucket ) ) {
				if ( isset( $bucket['url'] ) && is_string( $bucket['url'] ) ) {
					$url = trim( $bucket['url'] );
				}
				$target = $bucket['target'] ?? $bucket['linkTarget'] ?? '';
				if ( 'on' === $target || true === $target || 1 === $target || 'yes' === $target || '_blank' === $target ) {
					$new_window = 'on';
				}
			}
		}
		return array( $url, $new_window );
	}

	/**
	 * Renders module HTML.
	 *
	 * @param array<string, mixed> $attrs    Block attrs.
	 * @param string               $content  Inner content.
	 * @param \WP_Block            $block    Block instance.
	 * @param mixed                $elements Module elements.
	 * @return string
	 */
	public static function render_callback( $attrs, $content, $block, $elements ) {
		$reveal_text = $attrs['reveal_text']['innerContent']['desktop']['value']['text'] ?? '';
		$heading_level = $attrs['reveal_text']['decoration']['font']['font']['desktop']['value']['headingLevel'] ?? 'h3';
		if ( ! is_string( $heading_level ) || ! preg_match( '/^h[1-6]$/', strtolower( $heading_level ) ) ) {
			$heading_level = 'h3';
		}

		$src = self::image_src_url( $attrs );
		$alt = $attrs['alt']['innerContent']['desktop']['value']['text'] ?? '';
		if ( ! is_string( $alt ) ) {
			$alt = self::plain_text_from_inner_value( $attrs['alt']['innerContent']['desktop']['value'] ?? '' );
		}

		$title_val     = $attrs['title_text']['innerContent']['desktop']['value'] ?? '';
		$title_text    = self::plain_text_from_inner_value( $title_val );
		$animation_raw = $attrs['reveal_text_animation']['innerContent']['desktop']['value'] ?? 'fadeIn';
		$allowed_anim  = ConversionAttrs::get_image_text_reveal_animation_allowlist();
		$animation     = ( is_string( $animation_raw ) && '' !== trim( $animation_raw ) && in_array( $animation_raw, $allowed_anim, true ) )
			? $animation_raw
			: 'fadeIn';
		$text_orientation = $attrs['module']['advanced']['text']['text']['desktop']['value']['orientation'] ?? 'center';
		$duration = self::format_reveal_css_time(
			$attrs['dsm_duration_reveal_text']['innerContent']['desktop']['value'] ?? null,
			'0.5s'
		);
		$delay = self::format_reveal_css_time(
			$attrs['dsm_delay_reveal_text']['innerContent']['desktop']['value'] ?? null,
			'0s'
		);

		$overlay_direction = self::overlay_direction_from_attrs( $attrs );
		$reveal_trigger    = self::reveal_trigger_from_attrs( $attrs );
		$show_lb           = self::inner_toggle_is_on( $attrs, 'show_in_lightbox', 'off' );
		$url_field         = self::image_link_url_field( $attrs );
		list( $module_link_url, $module_link_target ) = self::module_link_url_and_target( $attrs );
		$url_new_win_attr  = $attrs['url_new_window']['innerContent']['desktop']['value'] ?? 'off';
		$url_new_window    = is_string( $url_new_win_attr ) && 'on' === trim( $url_new_win_attr ) ? 'on' : 'off';

		$wrap_href   = '';
		$wrap_lb     = false;
		$wrap_target = '';
		if ( 'click' !== $reveal_trigger ) {
			if ( $show_lb ) {
				$wrap_lb = true;
			} elseif ( '' !== $url_field ) {
				$wrap_href   = $url_field;
				$wrap_target = $url_new_window;
			} elseif ( '' !== $module_link_url ) {
				$wrap_href   = $module_link_url;
				$wrap_target = $module_link_target;
			}
		}

		DsmFrontendDynamicAssets::enqueue_supreme_animate_if_needed( true );
		wp_enqueue_script( 'dsm-gsap' );
		wp_enqueue_script( 'dsm-splitting' );
		wp_enqueue_script( 'dsm-text-reveal' );

		$src_esc = ( is_string( $src ) && str_starts_with( $src, 'data:' ) ) ? esc_attr( $src ) : esc_url( $src );

		$reveal_html = '';
		if ( is_string( $reveal_text ) && '' !== $reveal_text ) {
			$reveal_html = sprintf(
				'<div class="dsm-image-reveal-text-wrapper"><%1$s class="dsm-image-reveal-text et_pb_module_header et_pb_text_align_%2$s" data-splitting>%3$s</%1$s></div>',
				esc_attr( $heading_level ),
				esc_attr( is_string( $text_orientation ) ? $text_orientation : 'center' ),
				et_core_esc_previously( $reveal_text )
			);
		}

		$title_attr = '' !== $title_text ? sprintf( ' title="%s"', esc_attr( $title_text ) ) : '';

		$inner_span = sprintf(
			'<span class="et_pb_image_wrap"><div class="dsm-image-reveal-overlay"></div><img src="%s" alt="%s"%s />%s</span>',
			$src_esc,
			esc_attr( $alt ),
			$title_attr,
			$reveal_html
		);

		$wrapped = $inner_span;
		if ( $wrap_lb && is_string( $src ) && '' !== $src ) {
			$lb_title = '' !== $alt ? sprintf( ' title="%s"', esc_attr( $alt ) ) : '';
			$wrapped  = sprintf(
				'<a href="%s" class="et_pb_lightbox_image dsm-image-reveal-link"%s>%s</a>',
				esc_url( $src ),
				$lb_title,
				$inner_span
			);
		} elseif ( '' !== $wrap_href ) {
			$target_attr = 'on' === $wrap_target ? ' target="_blank" rel="noopener noreferrer"' : '';
			$wrapped     = sprintf(
				'<a href="%s" class="dsm-image-reveal-link"%s>%s</a>',
				esc_url( $wrap_href ),
				$target_attr,
				$inner_span
			);
		}

		$container = sprintf(
			'<div class="dsm-image-wrapper" data-reveal-animation="%1$s" data-duration-animation="%2$s" data-delay-animation="%3$s" data-overlay-direction="%4$s" data-reveal-trigger="%5$s">%6$s</div>',
			esc_attr( is_string( $animation ) ? $animation : 'fadeIn' ),
			esc_attr( $duration ),
			esc_attr( $delay ),
			esc_attr( $overlay_direction ),
			esc_attr( $reveal_trigger ),
			$wrapped
		);

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

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

		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_image_text_reveal dsm-image-reveal et_pb_module',
				'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_elements . $module_inner,
			)
		);
	}
}
