<?php

/**
 * Animated Gradient Text 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\FrontEnd\Module\Style;
use ET\Builder\Packages\Module\Module;
use ET\Builder\Packages\Module\Options\Css\CssStyle;
use ET\Builder\Packages\Module\Options\Element\ElementClassnames;
use ET\Builder\Packages\Module\Options\Text\TextStyle;
use ET\Builder\Packages\Module\Layout\Components\StyleCommon\CommonStyle;
use ET\Builder\Packages\ModuleLibrary\ModuleRegistration;
use ET\Builder\Packages\ModuleUtils\ModuleUtils;
use ET\Builder\Packages\StyleLibrary\Utils\StyleDeclarations;

/**
 * Class that handles "AnimatedGradientText Module" module.
 */
class AnimatedGradientText implements DependencyInterface
{

	/**
	 * Extract color value from either hex or variable format (matches module-styles.js).
	 *
	 * @param mixed $color_value The color value to process.
	 * @return string The processed color value.
	 */
	private static function extract_color_value($color_value): string
	{
		if (!is_string($color_value) || '' === $color_value) {
			return is_string($color_value) ? $color_value : '';
		}

		if (strpos($color_value, '$variable') !== 0) {
			return $color_value;
		}

		$variable_data = substr($color_value, 10, -2);
		$decoded_data = json_decode($variable_data, true);

		if (is_array($decoded_data) && isset($decoded_data['value']['name']) && is_string($decoded_data['value']['name'])) {
			return 'var(--' . $decoded_data['value']['name'] . ')';
		}

		return $color_value;
	}

	/**
	 * Older saves may store `innerContent.*.value` as a plain string; Card-style fields use `{ text: string }`.
	 *
	 * @param array<string, mixed> $attrs Reference to module attrs.
	 */
	private static function ensure_animated_gradient_text_heading_link_shape(array &$attrs): void
	{
		if (!isset($attrs['animated_gradient_text']['innerContent']) || !is_array($attrs['animated_gradient_text']['innerContent'])) {
			return;
		}

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

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

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

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

			if (is_string($value)) {
				$value = array(
					'text' => $value,
				);
			}
		}
	}

	/**
	 * Register module.
	 * `DependencyInterface` interface ensures class method name `load()` is executed for initialization.
	 */
	public function load(): void
	{
		add_action('init', array(self::class, 'register_module'));
	}

	/**
	 * Initialises the module.
	 */
	public static function dsm_initialise(): void
	{
		add_action(
			'divi_module_library_modules_dependency_tree',
			static function ($dependency_tree) {
				$dependency_tree->add_dependency(new self());
			}
		);
	}

	/**
	 * Register module.
	 */
	public static function register_module(): void
	{
		$module_json_path = DSM_DIR_PATH . 'divi-5/public/packages/module-library/animated-gradient-text';

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

	/**
	 * Gradient + animation CSS for `.dsm_animated_gradient_text` (per breakpoint; matches module-styles.js + getAttrByMode).
	 *
	 * @param array<string, mixed> $params Declaration params.
	 * @param array<string, mixed> $attrs  Full module attrs.
	 * @return string
	 */
	public static function gradient_text_declaration(array $params, array $attrs): string
	{
		$breakpoint = isset($params['breakpoint']) && is_string($params['breakpoint']) ? $params['breakpoint'] : 'desktop';
		$state = isset($params['state']) && is_string($params['state']) ? $params['state'] : 'value';

		$direction = ModuleUtils::use_attr_value(
			array(
				'attr' => $attrs['animated_gradient_direction']['innerContent'] ?? array(),
				'breakpoint' => $breakpoint,
				'state' => $state,
				'mode' => 'getOrInheritAll',
				'defaultValue' => '-45deg',
			)
		);
		if (!is_string($direction) || '' === $direction) {
			$direction = '-45deg';
		}

		$speed_raw = ModuleUtils::use_attr_value(
			array(
				'attr' => $attrs['animated_gradient_speed']['innerContent'] ?? array(),
				'breakpoint' => $breakpoint,
				'state' => $state,
				'mode' => 'getOrInheritAll',
				'defaultValue' => '8',
			)
		);
		$speed_str = is_string($speed_raw) ? rtrim($speed_raw, 's') : '8';
		if ('' === $speed_str) {
			$speed_str = '8';
		}
		$speed_css = esc_attr($speed_str) . 's';

		$defaults = array(
			'gradient_one_color' => '#cc2b5e',
			'gradient_two_color' => '#753a88',
			'gradient_three_color' => '#ec008c',
			'gradient_four_color' => '#6dd5ed',
		);

		$colors = array();
		foreach ($defaults as $key => $default_hex) {
			$v = ModuleUtils::use_attr_value(
				array(
					'attr' => isset($attrs[$key]['innerContent']) && is_array($attrs[$key]['innerContent']) ? $attrs[$key]['innerContent'] : array(),
					'breakpoint' => $breakpoint,
					'state' => $state,
					'mode' => 'getOrInheritAll',
					'defaultValue' => $default_hex,
				)
			);
			$colors[] = self::extract_color_value(is_string($v) ? $v : $default_hex);
		}

		$dir_css = esc_attr($direction);
		$c0 = esc_attr($colors[0]);
		$c1 = esc_attr($colors[1]);
		$c2 = esc_attr($colors[2]);
		$c3 = esc_attr($colors[3]);

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

		$style_declarations->add(
			'background',
			"linear-gradient({$dir_css},{$c0},{$c1},{$c2},{$c3}); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-size: 300%; animation: dsm-animated-gradient-flow {$speed_css} ease-in-out infinite; -webkit-animation: dsm-animated-gradient-flow {$speed_css} ease-in-out infinite;"
		);

		return $style_declarations->value();
	}

	/**
	 * Render module style.
	 *
	 * @param array<string, mixed> $args Arguments.
	 */
	public static function module_styles($args): void
	{
		$args = \DiviSupreme\Includes\DsmDiviBuilderCssScope::normalize_module_styles_args( $args );
		$attrs = isset($args['attrs']) && is_array($args['attrs']) ? $args['attrs'] : array();
		$elements = $args['elements'];

		$declaration = static function (array $params) use ($attrs) {
			return self::gradient_text_declaration($params, $attrs);
		};

		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,
								),
							),
						)
					),
					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' => 'header',
						)
					),
					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']}.dsm_animated_gradient_text",
							'attr' => $attrs['animated_gradient_direction']['innerContent'] ?? array(),
							'declarationFunction' => $declaration,
							'important' => true,
						)
					),
				),
			)
		);
	}

	/**
	 * Render module script data.
	 *
	 * @param array<string, mixed> $args Arguments.
	 */
	public static function module_script_data($args): void
	{
		$elements = $args['elements'];

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

	/**
	 * Decoration attrs for ElementClassnames, including link from Advanced → Link (Divi stores it under module.advanced.link).
	 *
	 * @param array<string, mixed> $attrs Module attrs.
	 * @return array<string, mixed>
	 */
	private static function get_module_element_classnames_attrs(array $attrs): array
	{
		$decoration = $attrs['module']['decoration'] ?? array();
		$link = $attrs['module']['advanced']['link'] ?? null;

		if (!empty($link) && is_array($link)) {
			$decoration['link'] = $link;
		}

		return $decoration;
	}

	/**
	 * Render module classnames.
	 *
	 * @param array<string, mixed> $args Arguments.
	 */
	public static function module_classnames($args): void
	{
		$classnames_instance = $args['classnamesInstance'];
		$attrs = $args['attrs'];

		$classnames_instance->add(
			ElementClassnames::classnames(
				array(
					'attrs' => self::get_module_element_classnames_attrs($attrs),
				)
			)
		);
	}

	/**
	 * Sanitize heading tag for output.
	 *
	 * @param string $tag Raw tag.
	 * @return string
	 */
	private static function sanitize_heading_tag(string $tag): string
	{
		$tag = strtolower(trim($tag));
		$allowed = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'span', 'p');

		return in_array($tag, $allowed, true) ? $tag : 'h2';
	}

	/**
	 * Render module HTML output.
	 *
	 * @param array<string, mixed> $attrs   Module attrs.
	 * @param string               $content Inner content (unused).
	 * @param \WP_Block            $block   Block instance.
	 * @param mixed                $elements Module elements API.
	 */
	public static function render_callback($attrs, $content, $block, $elements): string
	{
		if (isset($elements->module_attrs) && is_array($elements->module_attrs)) {
			self::ensure_animated_gradient_text_heading_link_shape($elements->module_attrs);
		}
		if (is_array($attrs)) {
			self::ensure_animated_gradient_text_heading_link_shape($attrs);
		}

		$heading_raw = $attrs['heading_html_tag']['innerContent']['desktop']['value'] ?? 'h2';
		$heading_tag = is_string($heading_raw) ? self::sanitize_heading_tag($heading_raw) : 'h2';

		$text_html = $elements->render(
			array(
				'attrName' => 'animated_gradient_text',
			)
		);

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

		// Single root: semantic tag from Heading HTML Tag + module classes (see module_classnames) + inner span from elements->render().
		$module_container_children = $module_elements . $text_html;

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