<?php
declare(strict_types=1);

/**
 * Before After Module.
 *
 * @package DiviSupreme.
 */

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;

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

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

		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( $src ) {
		$src = (string) $src;
		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.value` as string (supports ColorPicker / plain fields).
	 *
	 * @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;
	}

	/**
	 * Whether a toggle attribute’s desktop value is `on`.
	 *
	 * @param array  $attrs       Module attrs.
	 * @param string $key         Attribute key.
	 * @param string $when_missing Value if desktop value missing (usually `on` or `off`).
	 * @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 );
	}

	/**
	 * Read compare_* toggle with legacy icv_* fallback (layouts saved before rename).
	 *
	 * @param array  $attrs          Module attrs.
	 * @param string $key            Canonical key (`compare_*`).
	 * @param string $legacy_key     Previous key (`icv_*`) or empty.
	 * @param string $when_missing   Default raw toggle when branch missing (`on` / `off`).
	 * @return bool
	 */
	private static function compare_toggle_is_on( array $attrs, string $key, string $legacy_key, string $when_missing = 'off' ): bool {
		if ( isset( $attrs[ $key ]['innerContent'] ) ) {
			return self::inner_toggle_is_on( $attrs, $key, $when_missing );
		}
		if ( '' !== $legacy_key && isset( $attrs[ $legacy_key ]['innerContent'] ) ) {
			return self::inner_toggle_is_on( $attrs, $legacy_key, $when_missing );
		}

		return 'on' === $when_missing;
	}

	/**
	 * Read compare_* desktop string with legacy icv_* fallback.
	 *
	 * @param array  $attrs       Module attrs.
	 * @param string $key         Canonical key.
	 * @param string $legacy_key  Legacy `icv_*` key or empty.
	 * @param string $fallback    When both missing.
	 * @return string
	 */
	private static function compare_inner_desktop_string( array $attrs, string $key, string $legacy_key, string $fallback ): string {
		$primary = self::get_inner_desktop_value_string( $attrs, $key, '' );
		if ( '' !== $primary ) {
			return $primary;
		}
		if ( '' !== $legacy_key ) {
			return self::get_inner_desktop_value_string( $attrs, $legacy_key, $fallback );
		}

		return $fallback;
	}

	/**
	 * 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/before-after-slider';

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

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

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

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

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

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

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

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

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

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .icv__circle",
							'attr'     => $attrs['handle_border_radius']['innerContent'] ?? array(),
							'property' => 'border-radius',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .icv__arrow-wrapper svg path",
							'attr'     => $attrs['handle_arrow_color']['innerContent'] ?? array(),
							'property' => 'fill',
						)
					),

					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .icv__arrow-wrapper svg path",
							'attr'     => $attrs['handle_arrow_color']['innerContent'] ?? array(),
							'property' => 'stroke',
						)
					),
				),
			)
		);
	}

	/**
	 * 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'];
		$show_label_on_hover = $attrs['show_label_hover']['innerContent']['desktop']['value'] ?? '';

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

		if ( 'off' === $show_label_on_hover ) {
			$classnames_instance->add( 'dsm-before-after-image-show-labels' );
		}
	}

	/**
	 * 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();
		}

		$before_bucket           = $attrs['before_src']['innerContent']['desktop'] ?? array();
		$before_raw              = $before_bucket['value'] ?? $before_bucket['src'] ?? '';
		$before_image_src        = self::get_upload_image_src( $before_raw );
		$before_image_alt_text   = $attrs['before_alt']['innerContent']['desktop']['value'] ?? '';
		$before_image_title_text = $attrs['before_title_text']['innerContent']['desktop']['value'] ?? '';
		$after_bucket            = $attrs['after_src']['innerContent']['desktop'] ?? array();
		$after_raw               = $after_bucket['value'] ?? $after_bucket['src'] ?? '';
		$after_image_src         = self::get_upload_image_src( $after_raw );
		$after_image_alt_text    = $attrs['after_alt']['innerContent']['desktop']['value'] ?? '';
		$after_image_title_text  = $attrs['after_title_text']['innerContent']['desktop']['value'] ?? '';
		$orientation             = self::get_inner_desktop_value_string( $attrs, 'orientation', 'horizontal' );
		$before_label            = self::get_inner_desktop_value_string( $attrs, 'before_label_text', 'Before' );
		$after_label             = self::get_inner_desktop_value_string( $attrs, 'after_label_text', 'After' );

		$label_align = self::get_inner_desktop_value_string( $attrs, 'label_align', '' );
		if ( '' === $label_align ) {
			$label_align = self::get_inner_desktop_value_string( $attrs, 'label_vertical_align', 'end' );
		}
		$allowed_label_align = array( 'start', 'center', 'end' );
		if ( ! in_array( $label_align, $allowed_label_align, true ) ) {
			$label_align = 'end';
		}
		$label_align_class = 'dsm-icv-label-align-' . $label_align;

		$offset_raw    = self::get_inner_desktop_value_string( $attrs, 'default_offset_pct', '0.5' );
		$offset        = floatval( $offset_raw );
		$offset        = max( 0.0, min( 1.0, $offset ) );
		$starting_point = $offset * 100.0;

		$smooth_amt_raw = self::compare_inner_desktop_string( $attrs, 'compare_smoothing_amount', 'icv_smoothing_amount', '100' );
		$smooth_amt     = absint( $smooth_amt_raw );
		$smooth_amt     = min( $smooth_amt, 2000 );

		$control_color = self::get_inner_desktop_value_string( $attrs, 'handle_border_color', '#FFFFFF' );
		if ( '' === $control_color ) {
			$control_color = '#FFFFFF';
		}

		$no_overlay_str = self::get_inner_desktop_value_string( $attrs, 'no_overlay', 'off' );
		$show_labels    = ( 'on' !== $no_overlay_str );

		wp_enqueue_style( \DiviSupreme\Includes\DsmFrontendModuleAssetRegistry::IMAGE_COMPARE_VIEWER_STYLE_HANDLE );
		wp_enqueue_script( 'dsm-before-after-library' );
		wp_enqueue_script( 'dsm-before-after-slider' );

		$before_src_escaped = self::escape_img_src_attribute( $before_image_src );
		$after_src_escaped  = self::escape_img_src_attribute( $after_image_src );

		$before_image_html = sprintf(
			'<img src="%1$s" %2$s %3$s />',
			$before_src_escaped,
			'' !== trim( (string) $before_image_alt_text ) ? sprintf( 'alt="%1$s"', esc_attr( $before_image_alt_text ) ) : '',
			'' !== trim( (string) $before_image_title_text ) ? sprintf( 'title="%1$s"', esc_attr( $before_image_title_text ) ) : ''
		);

		$after_image_html = sprintf(
			'<img src="%1$s" %2$s %3$s />',
			$after_src_escaped,
			'' !== trim( (string) $after_image_alt_text ) ? sprintf( 'alt="%1$s"', esc_attr( $after_image_alt_text ) ) : '',
			'' !== trim( (string) $after_image_title_text ) ? sprintf( 'title="%1$s"', esc_attr( $after_image_title_text ) ) : ''
		);

		$data_attrs = array(
			'controlColor'     => $control_color,
			'controlShadow'    => self::compare_toggle_is_on( $attrs, 'compare_control_shadow', 'icv_control_shadow', 'on' ),
			'addCircle'        => self::compare_toggle_is_on( $attrs, 'compare_add_circle', 'icv_add_circle', 'on' ),
			'addCircleBlur'    => self::compare_toggle_is_on( $attrs, 'compare_add_circle_blur', 'icv_add_circle_blur', 'on' ),
			'showLabels'       => $show_labels,
			'beforeLabel'      => $before_label,
			'afterLabel'       => $after_label,
			'labelOnHover'     => self::inner_toggle_is_on( $attrs, 'show_label_hover', 'on' ),
			'smoothing'        => self::compare_toggle_is_on( $attrs, 'compare_smoothing', 'icv_smoothing', 'on' ),
			'smoothingAmount'  => $smooth_amt,
			'hoverStart'       => self::inner_toggle_is_on( $attrs, 'move_slider_on_hover', 'off' ),
			'verticalMode'     => ( 'vertical' === $orientation ),
			'startingPoint'    => $starting_point,
			'fluidMode'        => self::compare_toggle_is_on( $attrs, 'compare_fluid_mode', 'icv_fluid_mode', 'on' ),
		);

		$wrapper_class = implode(
			' ',
			array(
				'dsm_before_after_image_wrapper',
				'dsm-before-after-image-slider-container',
				'dsm_before_after_slider_frontend',
				$label_align_class,
			)
		);

		$container = sprintf(
			'<div class="%4$s" data-params="%3$s">
				%1$s
				%2$s
			</div>',
			$before_image_html,
			$after_image_html,
			esc_attr( wp_json_encode( $data_attrs ) ),
			esc_attr( $wrapper_class )
		);

		// 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'],
				'moduleClassName'     => 'dsm_before_after_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,
			)
		);
	}
}
