<?php
/**
 * Typing Effect 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 DiviSupreme\Includes\Divi5Conversion\ConversionAttrs;
use DiviSupreme\Includes\ModuleRegisterAttrsNormalize;
use ET\Builder\Packages\Module\Layout\Components\StyleCommon\CommonStyle;
use ET\Builder\Packages\IconLibrary\IconFont\Utils;
use ET\Builder\Packages\Module\Options\Text\TextClassnames;
use ET\Builder\Packages\Module\Options\Text\TextStyle;
use ET\Builder\Packages\StyleLibrary\Utils\StyleDeclarations;
use ET\Builder\Packages\Module\Options\Css\CssStyle;

/**
 * Class that handles "Typing Effect Module" module.
 */
class TypingEffect 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 typing_effect SortableList rows after merged attrs (import / legacy string).
	 *
	 * @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/typing-effect' ) {
			return $module_attrs;
		}

		ModuleRegisterAttrsNormalize::typing_effect_merged( $module_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/typing-effect';

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

	/**
	 * Render module style.
	 *
	 * @param array $args - Arguments.
	 */
	public static function module_styles($args)
	{
		$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,
								),
							),
						)
					),

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

					// Main heading font first; Before/Typing/After only add rules when they set their own font (otherwise inherit).
					$elements->style(
						array(
							'attrName' => 'header',
						)
					),

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

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

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

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

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

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

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

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

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm-before-typing-effect",
							'attr' => $attrs['before_display_type']['innerContent'] ?? array(),
							'property' => 'display',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm-after-typing-effect",
							'attr' => $attrs['after_display_type']['innerContent'] ?? array(),
							'property' => 'display',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm-typing-effect .typed-cursor",
							'attr' => $attrs['typing_cursor_color']['innerContent'] ?? array(),
							'property' => 'color',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm-typing-wrapper",
							'attr' => $attrs['typing_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'];

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

		// D4 parity: `et_pb_bg_layout_*` + `et_pb_text_align_*` from Text advanced (background layout + orientation).
		$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_light';
		} elseif ( false === strpos( $text_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 ) ) {
				$text_classes .= ' et_pb_bg_layout_' . $color;
			}
		}
		$classnames_instance->add( $text_classes, true );
	}

	/**
	 * Keep valid `{ value, dragID }` rows; skip rows with missing/non-string `value`.
	 *
	 * @param array<int|string, mixed> $rows Raw Sortable rows.
	 * @return array<int, array{value: string, dragID: string}>
	 */
	private static function normalize_typing_effect_rows( array $rows ): array {
		$list = array();

		foreach ( $rows as $item ) {
			if ( ! is_array( $item ) ) {
				continue;
			}

			if ( ! isset( $item['value'] ) || ! is_string( $item['value'] ) ) {
				continue;
			}

			$value = trim( $item['value'] );

			if ( '' === $value ) {
				continue;
			}

			$drag_id_raw = isset( $item['dragID'] ) && is_string( $item['dragID'] ) ? $item['dragID'] : '';
			$drag_id     = '' !== trim( $drag_id_raw ) ? trim( $drag_id_raw ) : wp_generate_uuid4();

			$list[] = array(
				'value'  => $value,
				'dragID' => $drag_id,
			);
		}

		return $list;
	}

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

		$before_text = $attrs['before_typing_effect']['innerContent']['desktop']['value'] ?? '';
		$before_new_line = $attrs['before_new_line']['innerContent']['desktop']['value'] ?? '';
		$typing_raw    = $attrs['typing_effect']['innerContent']['desktop']['value'] ?? '';
		$typing_effect = ConversionAttrs::normalize_typing_effect_sortable_rows_from_raw( $typing_raw );
		$typing_effect = self::normalize_typing_effect_rows( $typing_effect );
		$after_text = $attrs['after_typing_effect']['innerContent']['desktop']['value'] ?? '';
		$after_new_line = $attrs['after_new_line']['innerContent']['desktop']['value'] ?? '';
		$typing_speed = $attrs['typing_speed']['innerContent']['desktop']['value'] ?? '';
		$typing_backspeed = $attrs['typing_backspeed']['innerContent']['desktop']['value'] ?? '';
		$typing_backdelay = $attrs['typing_backdelay']['innerContent']['desktop']['value'] ?? '';
		$typing_loop = $attrs['typing_loop']['innerContent']['desktop']['value'] ?? '';
		$typing_remove_cursor = $attrs['typing_remove_cursor']['innerContent']['desktop']['value'] ?? '';
		$typing_fadeout = $attrs['typing_fadeout']['innerContent']['desktop']['value'] ?? '';
		$typing_shuffle = $attrs['typing_shuffle']['innerContent']['desktop']['value'] ?? '';
		$typing_cursor = $attrs['typing_cursor']['innerContent']['desktop']['value'] ?? '';
		$typing_viewport = $attrs['typing_viewport']['innerContent']['desktop']['value'] ?? '';
		$typing_viewport_repeat = $attrs['typing_viewport_repeat']['innerContent']['desktop']['value'] ?? '';
		$heading_level = $attrs['header']['decoration']['font']['font']['desktop']['value']['headingLevel'] ?? 'h1';
		if ( ! is_string( $heading_level ) || '' === $heading_level ) {
			$heading_level = 'h1';
		}

		wp_enqueue_script('dsm-typing-library');
		wp_enqueue_script('dsm-typing');



		$typing_phrases = array();
		foreach ( $typing_effect as $item ) {
			if ( ! is_array( $item ) || ! isset( $item['value'] ) || ! is_string( $item['value'] ) ) {
				continue;
			}
			$phrase = trim( $item['value'] );
			if ( '' !== $phrase ) {
				$typing_phrases[] = $phrase;
			}
		}

		$typing_strings_attr = '';
		if ( array() !== $typing_phrases ) {
			$typing_strings_attr = sprintf(
				' data-dsm-typing-strings="%s"',
				esc_attr(
					wp_json_encode(
						$typing_phrases,
						JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
					)
				)
			);
		}

		$container = sprintf(
			'<%4$s class="dsm-typing-effect et_pb_module_header">
			  %1$s
			  %2$s
			  %3$s
			</%4$s>',
			'' !== trim($before_text) ? sprintf('<span class="dsm-before-typing-effect">%1$s</span>%2$s', $before_text, 'on' === $before_new_line ? '<br>' : '') : '',
			! empty( $typing_effect ) ? sprintf(
				'<span class="dsm-typing-wrapper"><span class="dsm-typing-strings"></span>
			<span class="dsm-typing"%1$s%2$s%3$s%4$s%5$s%6$s%7$s%8$s%9$s%10$s%11$s></span></span>',
				$typing_strings_attr,
				'' !== $typing_speed ? sprintf('data-dsm-typing-speed="%1$s" ', esc_attr($typing_speed)) : '',
				'' !== $typing_backspeed ? sprintf('data-dsm-typing-backspeed="%1$s" ', esc_attr($typing_backspeed)) : '',
				'' !== $typing_backdelay ? sprintf('data-dsm-typing-backdelay="%1$s" ', esc_attr($typing_backdelay)) : '',
				$typing_loop ? sprintf('data-dsm-typing-loop="%1$s"', esc_attr($typing_loop)) : '',
				$typing_remove_cursor ? sprintf('data-dsm-typing-remove-cursor="%1$s"', esc_attr($typing_remove_cursor)) : '',
				$typing_cursor ? sprintf('data-dsm-typing-cursor="%1$s" ', esc_attr($typing_cursor)) : '',
				$typing_shuffle ? sprintf('data-dsm-typing-shuffle="%1$s" ', esc_attr($typing_shuffle)) : '',
				$typing_fadeout ? sprintf('data-dsm-typing-fadeout="%1$s" ', esc_attr($typing_fadeout)) : '',
				$typing_viewport ? sprintf('data-dsm-typing-viewport="%1$s" ', esc_attr($typing_viewport)) : '',
				$typing_viewport_repeat ? sprintf('data-dsm-typing-repeat="%1$s" ', esc_attr($typing_viewport_repeat)) : ''
			) : '',
			'' !== trim($after_text) ? sprintf('%2$s<span class="dsm-after-typing-effect">%1$s</span>', $after_text, 'on' === $after_new_line ? '<br>' : '') : '',
			$heading_level
		);

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

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

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

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