<?php

/**
 * GlitchText 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\Module\Options\Text\TextStyle;
use ET\Builder\Packages\StyleLibrary\Utils\StyleDeclarations;
use ET\Builder\Packages\Module\Options\Css\CssStyle;
use ET\Builder\Packages\Module\Options\Link\LinkUtils;

/**
 * Class that handles "GlitchText Module" module.
 */
class GlitchText 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/glitch-text';

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

	/**
	 * Text-shadow for glitch :after (effect one / two).
	 *
	 * @param array $params Style params.
	 * @return string
	 */
	public static function glitch_shadow_minus_declaration($params)
	{
		$attr_value = self::stringify_style_attr_value($params['attrValue'] ?? null);

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

		$style_declarations->add('text-shadow', '-1px 0 ' . $attr_value);

		return $style_declarations->value();
	}

	/**
	 * Text-shadow for glitch :before (effect one / two).
	 *
	 * @param array $params Style params.
	 * @return string
	 */
	public static function glitch_shadow_plus_declaration($params)
	{
		$attr_value = self::stringify_style_attr_value($params['attrValue'] ?? null);

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

		$style_declarations->add('text-shadow', '2px 0 ' . $attr_value);

		return $style_declarations->value();
	}

	/**
	 * Normalize color style callback input to a scalar string.
	 *
	 * @param mixed $raw Attr value from CommonStyle.
	 * @return string
	 */
	private static function stringify_style_attr_value($raw)
	{
		if (is_string($raw)) {
			return $raw;
		}
		if (is_array($raw) && isset($raw['desktop']['value']) && is_string($raw['desktop']['value'])) {
			return $raw['desktop']['value'];
		}

		return '';
	}

	/**
	 * Render module style.
	 *
	 * @param array $args Arguments.
	 */
	public static function module_styles($args)
	{
		$args = \DiviSupreme\Includes\DsmDiviBuilderCssScope::normalize_module_styles_args( $args );
		$attrs = $args['attrs'] ?? array();
		$elements = $args['elements'];

		$effect = $attrs['glitch_text_effect']['innerContent']['desktop']['value'] ?? 'one';

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

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

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

		if ('one' === $effect || 'four' === $effect) {
			$suffix = 'one' === $effect ? 'one' : 'four';
			$styles[] = CommonStyle::style(
				array(
					'selector' => "{$args['orderClass']} .dsm-glitch-effect-type-{$suffix}:after",
					'attr' => $attrs['glitch_one_color_one']['innerContent'] ?? array(),
					'declarationFunction' => array(static::class, 'glitch_shadow_minus_declaration'),
				)
			);
			$styles[] = CommonStyle::style(
				array(
					'selector' => "{$args['orderClass']} .dsm-glitch-effect-type-{$suffix}:before",
					'attr' => $attrs['glitch_one_color_two']['innerContent'] ?? array(),
					'declarationFunction' => array(static::class, 'glitch_shadow_plus_declaration'),
				)
			);
		} elseif ('two' === $effect || 'five' === $effect) {
			$suffix = 'two' === $effect ? 'two' : 'five';
			$styles[] = CommonStyle::style(
				array(
					'selector' => "{$args['orderClass']} .dsm-glitch-effect-type-{$suffix}:after",
					'attr' => $attrs['glitch_two_color_one']['innerContent'] ?? array(),
					'declarationFunction' => array(static::class, 'glitch_shadow_minus_declaration'),
				)
			);
			$styles[] = CommonStyle::style(
				array(
					'selector' => "{$args['orderClass']} .dsm-glitch-effect-type-{$suffix}:before",
					'attr' => $attrs['glitch_two_color_two']['innerContent'] ?? array(),
					'declarationFunction' => array(static::class, 'glitch_shadow_plus_declaration'),
				)
			);
		} elseif ('three' === $effect || 'six' === $effect) {
			$suffix = 'three' === $effect ? 'three' : 'six';
			$styles[] = CommonStyle::style(
				array(
					'selector' => "{$args['orderClass']} .dsm-glitch-effect-type-{$suffix}>span",
					'attr' => $attrs['glitch_one_color_one']['innerContent'] ?? array(),
					'property' => 'color',
				)
			);
			$styles[] = CommonStyle::style(
				array(
					'selector' => "{$args['orderClass']} .dsm-glitch-effect-type-{$suffix}:after",
					'attr' => $attrs['glitch_one_color_two']['innerContent'] ?? array(),
					'property' => 'color',
				)
			);
		}

		Style::add(
			array(
				'id' => $args['id'],
				'name' => $args['name'],
				'orderIndex' => $args['orderIndex'],
				'storeInstance' => $args['storeInstance'],
				'styles' => $styles,
			)
		);
	}

	/**
	 * Render module script data.
	 *
	 * @param array $args Arguments.
	 */
	public static function module_script_data($args)
	{
		$elements = $args['elements'];

		$elements->script_data(
			array(
				'attrName' => 'module',
			)
		);
		$elements->script_data(
			array(
				'attrName' => 'glitch_text',
			)
		);
	}

	/**
	 * Render module classnames.
	 *
	 * @param array $args Arguments.
	 */
	public static function module_classnames($args)
	{
		$classnames_instance = $args['classnamesInstance'];
		$attrs = $args['attrs'];
		$module_link_attr = $attrs['module']['advanced']['link'] ?? array();

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

		if (LinkUtils::is_enabled($module_link_attr)) {
			$classnames_instance->add('dsm-link');
		}
	}

	/**
	 * Sanitize heading tag for HTML output.
	 *
	 * @param mixed $heading_level Raw heading level.
	 * @return string
	 */
	private static function sanitize_heading_tag($heading_level)
	{
		if (!is_string($heading_level)) {
			return 'h1';
		}
		$t = strtolower(trim($heading_level));
		if (preg_match('/^h[1-6]$/', $t)) {
			return $t;
		}
		if (in_array($t, array('p', 'div'), true)) {
			return $t;
		}

		return 'h1';
	}

	/**
	 * Normalize `glitch_text` innerContent for `headingLink` server render (text/url/target shape).
	 *
	 * String leaves omit `url`/`target`, which breaks link output in ModuleElements.
	 *
	 * @param array<string, mixed> $attrs Module attrs (by reference).
	 * @return void
	 */
	private static function ensure_glitch_text_heading_link_shape(array &$attrs): void
	{
		if (!isset($attrs['glitch_text']['innerContent']) || !is_array($attrs['glitch_text']['innerContent'])) {
			return;
		}

		$inner = &$attrs['glitch_text']['innerContent'];

		foreach (array_keys($inner) as $breakpoint) {
			if (!isset($inner[$breakpoint]) || !is_array($inner[$breakpoint])) {
				continue;
			}

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

			$value = &$inner[$breakpoint]['value'];

			if (is_string($value)) {
				$value = array(
					'text' => $value,
					'url' => '',
					'target' => 'off',
				);

				continue;
			}

			if (!is_array($value)) {
				continue;
			}

			if (!array_key_exists('url', $value)) {
				$value['url'] = '';
			}

			if (!array_key_exists('target', $value)) {
				$value['target'] = 'off';
			}
		}
	}

	/**
	 * When `header` was incorrectly registered as `headingLink`, LinkGroup stored url/target on `header`
	 * while FE only renders `glitch_text`. Copy link fields into `glitch_text` when its URL is empty.
	 *
	 * @param array<string, mixed> $attrs Module attrs (by reference).
	 * @return void
	 */
	private static function migrate_glitch_link_from_legacy_header_headinglink(array &$attrs): void
	{
		if (!isset($attrs['header']['innerContent']) || !is_array($attrs['header']['innerContent'])) {
			return;
		}

		if (!isset($attrs['glitch_text']['innerContent']) || !is_array($attrs['glitch_text']['innerContent'])) {
			return;
		}

		foreach ($attrs['header']['innerContent'] as $breakpoint => $header_bp) {
			if (!is_string($breakpoint) || !is_array($header_bp) || !isset($header_bp['value'])) {
				continue;
			}

			$header_val = $header_bp['value'];
			if (!is_array($header_val)) {
				continue;
			}

			$url_raw = $header_val['url'] ?? '';
			$url = is_string($url_raw) ? trim($url_raw) : '';
			if ('' === $url) {
				continue;
			}

			if (!isset($attrs['glitch_text']['innerContent'][$breakpoint]) || !is_array($attrs['glitch_text']['innerContent'][$breakpoint])) {
				continue;
			}

			$gt_val = &$attrs['glitch_text']['innerContent'][$breakpoint]['value'];
			if (is_string($gt_val)) {
				$gt_val = array(
					'text' => $gt_val,
					'url' => '',
					'target' => 'off',
				);
			} elseif (!is_array($gt_val)) {
				continue;
			}

			$existing = isset($gt_val['url']) && is_string($gt_val['url']) ? trim($gt_val['url']) : '';
			if ('' !== $existing) {
				continue;
			}

			$gt_val['url'] = is_string($url_raw) ? trim($url_raw) : $url;
			$t_raw = $header_val['target'] ?? 'off';
			$gt_val['target'] = is_string($t_raw) && ('on' === $t_raw || 'off' === $t_raw) ? $t_raw : 'off';
		}
	}

	/**
	 * Render module HTML output.
	 *
	 * @param array     $attrs    Module attributes.
	 * @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)
	{
		if (isset($elements->module_attrs) && is_array($elements->module_attrs)) {
			self::ensure_glitch_text_heading_link_shape($elements->module_attrs);
			self::migrate_glitch_link_from_legacy_header_headinglink($elements->module_attrs);
		}

		if (is_array($attrs)) {
			self::ensure_glitch_text_heading_link_shape($attrs);
			self::migrate_glitch_link_from_legacy_header_headinglink($attrs);
		}

		$glitch_inner_html = $elements->render(
			array(
				'attrName' => 'glitch_text',
			)
		);

		$data_plain = wp_strip_all_tags((string) $glitch_inner_html, true);
		$data_plain = html_entity_decode($data_plain, ENT_QUOTES | ENT_HTML5, 'UTF-8');

		$heading_level = $attrs['header']['decoration']['font']['font']['desktop']['value']['headingLevel'] ?? 'h1';
		$tag = self::sanitize_heading_tag($heading_level);

		$glitch_text_effect = $attrs['glitch_text_effect']['innerContent']['desktop']['value'] ?? 'one';

		$class = 'dsm-glitch-text et_pb_module_header dsm-glitch-effect-type-' . sanitize_html_class($glitch_text_effect);
		if (in_array($glitch_text_effect, array('one', 'two', 'four', 'five'), true)) {
			$class .= ' dsm-glitch-text-effect';
		}

		if (in_array($glitch_text_effect, array('three', 'six'), true)) {
			$inner = '<span>' . $glitch_inner_html . '</span>';
		} else {
			$inner = $glitch_inner_html;
		}

		$container = sprintf(
			'<%1$s class="%2$s" data-dsm-glitch-text="%3$s">%4$s</%1$s>',
			esc_attr($tag),
			esc_attr($class),
			esc_attr($data_plain),
			$inner
		);

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

		$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_glitch_text',
				'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,
			)
		);
	}
}
