<?php
/**
 * Svg Animation 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\Layout\Components\StyleCommon\CommonStyle;
use ET\Builder\Packages\Module\Options\Css\CssStyle;
use ET\Builder\Packages\Module\Options\Element\ElementClassnames;
use ET\Builder\Packages\ModuleLibrary\ModuleRegistration;
use WP_Error;
use WP_REST_Request;
use WP_REST_Server;

/**
 * Class that handles Supreme SVG Animation (dsm/svg-animation).
 */
class SvgAnimation implements DependencyInterface
{

	/**
	 * Register module (D4→D5: conversion outline + shared decoration merge only).
	 *
	 * @return void
	 */
	public function load()
	{
		add_action('init', array(self::class, 'register_module'));
		add_action('rest_api_init', array(self::class, 'register_rest_routes'));
	}

	/**
	 * REST: same-origin proxy so the Visual Builder can load SVG markup without browser CORS (mirrors {@see render_callback()} server fetch).
	 *
	 * @return void
	 */
	public static function register_rest_routes(): void
	{
		register_rest_route(
			'dsm/v1',
			'/svg-animation-source',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array(self::class, 'rest_svg_animation_source'),
				'permission_callback' => function () {
					return current_user_can('edit_posts');
				},
				'args'                => array(
					'attachment_id' => array(
						'type'              => 'integer',
						'required'          => false,
						'sanitize_callback' => 'absint',
					),
					'src'           => array(
						'type'              => 'string',
						'required'          => false,
						'sanitize_callback' => 'esc_url_raw',
					),
				),
			)
		);
	}

	/**
	 * Return raw SVG markup for the module preview (authenticated editors only).
	 *
	 * @param WP_REST_Request $request Request.
	 * @return \WP_REST_Response|WP_Error
	 */
	public static function rest_svg_animation_source(WP_REST_Request $request)
	{
		$id  = (int) $request->get_param('attachment_id');
		$src = (string) $request->get_param('src');
		$src = esc_url_raw($src);

		if ($id <= 0 && '' === $src) {
			return new WP_Error(
				'dsm_svg_missing',
				__('No SVG source specified.', 'dsm-supreme-modules-pro-for-divi'),
				array( 'status' => 400 )
			);
		}

		if ('' !== $src && function_exists('wp_http_validate_url') && ! wp_http_validate_url($src)) {
			return new WP_Error(
				'dsm_svg_invalid_url',
				__('Invalid SVG URL.', 'dsm-supreme-modules-pro-for-divi'),
				array( 'status' => 400 )
			);
		}

		if ($id > 0) {
			$post = get_post($id);
			if (! $post instanceof \WP_Post || 'attachment' !== $post->post_type) {
				return new WP_Error(
					'dsm_svg_invalid_attachment',
					__('Invalid attachment.', 'dsm-supreme-modules-pro-for-divi'),
					array( 'status' => 404 )
				);
			}
			if (! current_user_can('read_post', $id)) {
				return new WP_Error(
					'dsm_svg_forbidden',
					__('You cannot access this file.', 'dsm-supreme-modules-pro-for-divi'),
					array( 'status' => 403 )
				);
			}
		}

		$upload_raw = array();
		if ($id > 0) {
			$upload_raw['id'] = $id;
		}
		if ('' !== $src) {
			$upload_raw['src'] = $src;
		}

		$markup = self::get_svg_markup_from_upload($upload_raw);
		if ('' === $markup) {
			return new WP_Error(
				'dsm_svg_empty',
				__('Could not load SVG.', 'dsm-supreme-modules-pro-for-divi'),
				array( 'status' => 404 )
			);
		}

		return rest_ensure_response(
			array(
				'markup' => $markup,
			)
		);
	}

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

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

	/**
	 * Resolve SVG URL from D5 upload innerContent (object or legacy string).
	 *
	 * @param mixed $raw Raw `innerContent.desktop.value`.
	 * @return string
	 */
	private static function get_upload_src($raw)
	{
		if (is_string($raw)) {
			return $raw;
		}
		if (is_array($raw)) {
			if (! empty($raw['src']) && is_string($raw['src'])) {
				return $raw['src'];
			}
			if (! empty($raw['url']) && is_string($raw['url'])) {
				return $raw['url'];
			}
		}

		return '';
	}

	/**
	 * Read SVG/SVGZ bytes from a validated local path.
	 *
	 * @param string $path Absolute filesystem path.
	 * @return string Markup or empty string.
	 */
	private static function read_svg_file_contents(string $path): string
	{
		if ('' === $path || ! is_readable($path)) {
			return '';
		}
		$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
		if (! in_array($ext, array( 'svg', 'svgz' ), true)) {
			return '';
		}
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- intentional read of local media path.
		$body = file_get_contents($path);

		return is_string($body) && '' !== $body ? $body : '';
	}

	/**
	 * Load SVG markup when the URL belongs to this site without HTTP (fixes loopback/SSL failures in local dev).
	 *
	 * @param string $url Sanitized URL (e.g. uploads file URL).
	 * @return string Markup or empty string.
	 */
	private static function get_svg_markup_from_local_url(string $url): string
	{
		if ('' === $url) {
			return '';
		}

		$attachment_id = attachment_url_to_postid($url);
		if ($attachment_id > 0 && current_user_can('read_post', $attachment_id)) {
			$path = get_attached_file($attachment_id);
			if (is_string($path) && '' !== $path) {
				$out = self::read_svg_file_contents($path);
				if ('' !== $out) {
					return $out;
				}
			}
		}

		$upload_dir = wp_get_upload_dir();
		$baseurl    = isset($upload_dir['baseurl']) ? (string) $upload_dir['baseurl'] : '';
		$basedir    = isset($upload_dir['basedir']) ? (string) $upload_dir['basedir'] : '';
		if ('' === $baseurl || '' === $basedir) {
			return '';
		}
		if (0 !== strpos($url, $baseurl)) {
			return '';
		}
		$relative = (string) substr($url, strlen($baseurl));
		$relative = ltrim(str_replace('\\', '/', $relative), '/');
		$candidate = trailingslashit($basedir) . $relative;
		$candidate = wp_normalize_path($candidate);
		$real_base = realpath($basedir);
		$real_file = realpath($candidate);
		if (false === $real_base || false === $real_file || ! str_starts_with($real_file, $real_base)) {
			return '';
		}

		return self::read_svg_file_contents($real_file);
	}

	/**
	 * Load raw SVG markup from an upload attr (attachment id and/or URL).
	 *
	 * @param mixed $upload_raw `innerContent.desktop.value` (object with id/src or string URL).
	 * @return string SVG source or empty string.
	 */
	private static function get_svg_markup_from_upload($upload_raw)
	{
		if (is_array($upload_raw) && ! empty($upload_raw['id']) && is_numeric($upload_raw['id'])) {
			$path = get_attached_file((int) $upload_raw['id']);
			if (is_string($path) && '' !== $path) {
				$body = self::read_svg_file_contents($path);
				if ('' !== $body) {
					return $body;
				}
			}
		}

		$url = self::get_upload_src($upload_raw);
		if ('' === $url) {
			return '';
		}

		$svg_url = esc_url_raw($url);
		if ('' === $svg_url) {
			return '';
		}

		$local = self::get_svg_markup_from_local_url($svg_url);
		if ('' !== $local) {
			return $local;
		}

		$response = wp_remote_get(
			$svg_url,
			array(
				'timeout'   => 15,
				'sslverify' => apply_filters('https_local_ssl_verify', true),
			)
		);

		if (is_wp_error($response)) {
			return '';
		}

		$code = wp_remote_retrieve_response_code($response);
		if ($code < 200 || $code >= 300) {
			return '';
		}

		$body = wp_remote_retrieve_body($response);

		return is_string($body) ? $body : '';
	}

	/**
	 * Register module styles for the Style API.
	 *
	 * @param array<string, mixed> $args Arguments.
	 * @return void
	 */
	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(
					$elements->style(
						array(
							'attrName' => 'module',
							'styleProps' => array(
								'disabledOn' => array(
									'disabledModuleVisibility' => $args['settings']['disabledModuleVisibility'] ?? null,
								),
							),
						)
					),
					CssStyle::style(
						array(
							'selector' => $args['orderClass'],
							'attr' => $attrs['css'] ?? array(),
						)
					),
					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm-svg-animation-container svg *",
							'attr' => $attrs['dsm_svg_color']['innerContent'] ?? array(),
							'property' => 'stroke',
						)
					),
					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']} .dsm-svg-animation-container svg",
							'attr' => $attrs['dsm_width']['innerContent'] ?? array(),
							'property' => 'width',
						)
					),
					CommonStyle::style(
						array(
							'selector' => "{$args['orderClass']}",
							'attr' => $attrs['dsm_alignment']['innerContent'] ?? array(),
							'property' => 'text-align',
						)
					),
				),
			)
		);
	}

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

	/**
	 * Render module classnames.
	 *
	 * @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(),
				)
			)
		);
	}

	/**
	 * Render module HTML output.
	 *
	 * @param array<string, mixed> $attrs    Module 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)
	{
		wp_enqueue_script('dsm-svg-animation');

		$raw_use_svg_code = $attrs['dsm_use_svg_code']['innerContent']['desktop']['value'] ?? '';
		$uses_inline_svg = 'on' === (string) $raw_use_svg_code;

		$dsm_width = $attrs['dsm_width']['innerContent']['desktop']['value'] ?? '';
		$dsm_trigger_type = $attrs['dsm_trigger_type']['innerContent']['desktop']['value'] ?? '';
		$dsm_svg_code = $attrs['dsm_svg_code']['innerContent']['desktop']['value'] ?? '';
		$upload_raw = $attrs['dsm_upload_svg']['innerContent']['desktop']['value'] ?? '';
		$dsm_speed = $attrs['dsm_speed']['innerContent']['desktop']['value'] ?? '';
		$dsm_delay = $attrs['dsm_delay']['innerContent']['desktop']['value'] ?? '';
		$dsm_animation_type = $attrs['dsm_animation_type']['innerContent']['desktop']['value'] ?? '';
		$dsm_loop = $attrs['dsm_loop']['innerContent']['desktop']['value'] ?? '';
		$dsm_unlimited = $attrs['dsm_unlimited']['innerContent']['desktop']['value'] ?? '';
		$dsm_count = $attrs['dsm_count']['innerContent']['desktop']['value'] ?? '';
		$dsm_animation_function = $attrs['dsm_animation_function']['innerContent']['desktop']['value'] ?? '';
		$dsm_visible_viewport           = $attrs['dsm_visible_viewport']['innerContent']['desktop']['value'] ?? '';
		$dsm_use_trigger      = $attrs['dsm_use_trigger']['innerContent']['desktop']['value'] ?? 'off';

		$svg_from_upload = '';
		if (! $uses_inline_svg) {
			$svg_from_upload = self::get_svg_markup_from_upload($upload_raw);
		}

		if (is_array($dsm_svg_code)) {
			if (isset($dsm_svg_code['text'])) {
				$dsm_svg_code = (string) $dsm_svg_code['text'];
			} else {
				$dsm_svg_code = '';
			}
		}

		$dsm_svg_code = str_ireplace('<p>', '', (string) $dsm_svg_code);
		$dsm_svg_code = str_ireplace('</p>', '', $dsm_svg_code);

		$container = sprintf(
			'<div class="dsm-svg-animation-container" data-speed="%2$s" data-delay="%3$s" data-animation_type="%4$s" data-loop="%5$s" data-unlimited="%6$s" data-count="%7$s" data-use_trigger="%12$s" data-trigger_type="%8$s" data-animation_timing_func="%9$s" data-visibleviewport="%11$s">
		    %1$s
		    %10$s
		</div>',
			$uses_inline_svg ? $dsm_svg_code : '',
			esc_attr((string) $dsm_speed),
			esc_attr((string) $dsm_delay),
			esc_attr((string) $dsm_animation_type),
			esc_attr((string) $dsm_loop),
			esc_attr((string) $dsm_unlimited),
			esc_attr((string) $dsm_count),
			esc_attr((string) $dsm_trigger_type),
			esc_attr((string) $dsm_animation_function),
			! $uses_inline_svg ? $svg_from_upload : '',
			'dsm_viewport' === (string) $dsm_trigger_type ? esc_attr((string) $dsm_visible_viewport) : '',
			esc_attr((string) $dsm_use_trigger)
		);

		$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'],
				'name' => $block->block_type->name,
				'moduleClassName' => 'dsm_svg_animation',
				'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,
			)
		);
	}
}
