<?php
/**
 * Handles shared plugin actions, settings, and assets.
 *
 * @link       https://dcatl.diviaccessories.com/
 * @since      1.0.0
 *
 * @package    WordPress
 * @subpackage display-category-and-taxonomy-list
 */

namespace DCATL\Public;

/**
 * Handles settings registration, validation, and asset loading.
 *
 * @since 1.0.0
 */
class Action {

	use \DCATL\Trait\Singleton;

	/**
	 * Plugin slug.
	 *
	 * @var string
	 */
	private $plugin_name;

	/**
	 * Plugin version.
	 *
	 * @var string
	 */
	private $version;

	/**
	 * Whether frontend assets have already been enqueued.
	 *
	 * @var bool
	 */
	private $frontend_assets_enqueued = false;

	/**
	 * Initialize the class.
	 */
	public function __construct() {
		$this->plugin_name = DCATL_NAME;
		$this->version     = DCATL_VERSION;
	}

	/**
	 * Load plugin translations.
	 *
	 * @return void
	 */
	public function load_textdomain() {
		load_plugin_textdomain(
			'display-category-and-taxonomy-list',
			false,
			dirname( plugin_basename( DCATL_PATH . 'display-category-and-taxonomy-list.php' ) ) . '/languages'
		);
	}

	/**
	 * Enqueue admin assets only on screens that use them.
	 *
	 * @param string $hook_suffix Current admin page hook.
	 * @return void
	 */
	public function enqueue_admin_resources( $hook_suffix ) {
		$screen           = get_current_screen();
		$is_settings_page = 'toplevel_page_display-category-and-taxonomy-list' === $hook_suffix;
		$is_term_screen   = false;

		if ( $screen && in_array( $hook_suffix, array( 'edit-tags.php', 'term.php' ), true ) ) {
			$taxonomy = isset( $screen->taxonomy ) ? sanitize_key( $screen->taxonomy ) : '';
			$settings = $this->get_settings();
			$selected = isset( $settings['dcatl_taxonomy_list'] ) && is_array( $settings['dcatl_taxonomy_list'] )
				? $settings['dcatl_taxonomy_list']
				: array();

			$is_term_screen = $taxonomy && in_array( $taxonomy, $selected, true );
		}

		$install_time     = (int) get_user_meta( get_current_user_id(), 'dcatl_install_time', true );
		$review_notice_due = current_user_can( 'manage_options' )
			&& $install_time > 0
			&& ( time() - $install_time ) >= WEEK_IN_SECONDS;

		if ( ! $is_settings_page && ! $is_term_screen && ! $review_notice_due ) {
			return;
		}

		$admin_css = DCATL_PATH . 'assets/admin/css/dcatl-admin.css';
		if ( file_exists( $admin_css ) ) {
			wp_enqueue_style(
				$this->plugin_name . '-admin',
				DCATL_URL . 'assets/admin/css/dcatl-admin.css',
				array(),
				filemtime( $admin_css )
			);
		}

		if ( $is_settings_page || $review_notice_due ) {
			$admin_js = DCATL_PATH . 'assets/admin/js/dcatl-admin.js';
			if ( file_exists( $admin_js ) ) {
				wp_enqueue_script(
					$this->plugin_name . '-admin',
					DCATL_URL . 'assets/admin/js/dcatl-admin.js',
					array( 'jquery' ),
					filemtime( $admin_js ),
					true
				);

				wp_localize_script(
					$this->plugin_name . '-admin',
					'dcatl_ajax_obj',
					array(
						'ajax_url' => admin_url( 'admin-ajax.php' ),
						'nonce'    => wp_create_nonce( 'dcatl_nonce' ),
					)
				);
			}
		}

		if ( $is_term_screen ) {
			$media_js = DCATL_PATH . 'assets/admin/js/dcatl-media-uploader.js';
			wp_enqueue_media();

			if ( file_exists( $media_js ) ) {
				wp_enqueue_script(
					'dcatl-meta-image',
					DCATL_URL . 'assets/admin/js/dcatl-media-uploader.js',
					array( 'jquery' ),
					filemtime( $media_js ),
					true
				);

				wp_localize_script(
					'dcatl-meta-image',
					'dcatl_meta_image',
					array(
						'title'       => esc_html__( 'Upload an Image', 'display-category-and-taxonomy-list' ),
						'button'      => esc_html__( 'Use this Image', 'display-category-and-taxonomy-list' ),
						'placeholder' => DCATL_URL . 'assets/admin/img/dcatl-upload-placeholder.png',
					)
				);
			}
		}
	}

	/**
	 * Enqueue the compiled React settings app.
	 *
	 * @param string $admin_page Current admin page hook.
	 * @return void
	 */
	public function enqueue_build_resources( $admin_page ) {
		if ( 'toplevel_page_display-category-and-taxonomy-list' !== $admin_page ) {
			return;
		}

		$asset_file = DCATL_PATH . 'build/index.asset.php';
		$script_file = DCATL_PATH . 'build/index.js';
		$style_file  = DCATL_PATH . 'build/index.css';

		if ( ! file_exists( $asset_file ) || ! file_exists( $script_file ) || ! file_exists( $style_file ) ) {
			return;
		}

		$asset = include $asset_file;
		if ( ! is_array( $asset ) ) {
			$asset = array();
		}

		$dependencies = isset( $asset['dependencies'] ) && is_array( $asset['dependencies'] )
			? $asset['dependencies']
			: array();
		$version      = isset( $asset['version'] ) ? $asset['version'] : filemtime( $script_file );
		$style_handle = $this->plugin_name . '-build-css';
		$script_handle = $this->plugin_name . '-build-script';

		wp_enqueue_style( 'wp-components' );
		wp_enqueue_style(
			$style_handle,
			DCATL_URL . 'build/index.css',
			array( 'wp-components' ),
			$version
		);
		wp_style_add_data( $style_handle, 'rtl', 'replace' );

		wp_enqueue_script(
			$script_handle,
			DCATL_URL . 'build/index.js',
			$dependencies,
			$version,
			array( 'in_footer' => true )
		);

		wp_set_script_translations(
			$script_handle,
			'display-category-and-taxonomy-list',
			DCATL_PATH . 'languages'
		);
	}

	/**
	 * Register frontend styles without loading them globally.
	 *
	 * @return void
	 */
	public function register_public_resources() {
		$public_css = DCATL_PATH . 'assets/public/css/dcatl-public.css';
		if ( file_exists( $public_css ) ) {
			wp_register_style(
				$this->plugin_name . '-public',
				DCATL_URL . 'assets/public/css/dcatl-public.css',
				array(),
				filemtime( $public_css )
			);
		}

		foreach ( array( 1, 2 ) as $template_style ) {
			$template_css = DCATL_PATH . 'public/template/style-' . $template_style . '/style.css';
			if ( file_exists( $template_css ) ) {
				wp_register_style(
					$this->plugin_name . '-style-' . $template_style,
					DCATL_URL . 'public/template/style-' . $template_style . '/style.css',
					array( $this->plugin_name . '-public' ),
					filemtime( $template_css )
				);
			}
		}
	}

	/**
	 * Detect the shortcode in normal post content and enqueue styles early.
	 *
	 * @return void
	 */
	public function maybe_enqueue_public_resources() {
		if ( is_admin() ) {
			return;
		}

		global $post;
		if ( $post instanceof \WP_Post && has_shortcode( $post->post_content, 'dcatl_category_list' ) ) {
			$this->enqueue_frontend_assets();
		}
	}

	/**
	 * Enqueue assets used by the shortcode.
	 *
	 * @return void
	 */
	public function enqueue_frontend_assets() {
		if ( $this->frontend_assets_enqueued ) {
			return;
		}

		if ( ! wp_style_is( $this->plugin_name . '-public', 'registered' ) ) {
			$this->register_public_resources();
		}

		$settings       = $this->get_settings();
		$template_style = $this->sanitize_template_style( $settings['dcatl_template_style'] );
		$public_handle  = $this->plugin_name . '-public';
		$template_handle = $this->plugin_name . '-style-' . $template_style;

		if ( wp_style_is( $public_handle, 'registered' ) ) {
			wp_enqueue_style( $public_handle );
			wp_add_inline_style( $public_handle, $this->build_dynamic_css( $settings ) );
		}

		if ( wp_style_is( $template_handle, 'registered' ) ) {
			wp_enqueue_style( $template_handle );
		}

		$this->frontend_assets_enqueued = true;
	}

	/**
	 * Return grouped defaults.
	 *
	 * @return array
	 */
	public function set_default_values() {
		return array(
			'dcatl_string' => array(
				'dcatl_template_style'                => '1',
				'dcatl_wrapper_height'                => '500px',
				'dcatl_content_wrapper_padding'       => '5px',
				'dcatl_img_bg_size'                   => 'cover',
				'dcatl_img_border_radius'             => '5px',
				'dcatl_item_order'                    => 'ASC',
				'dcatl_item_order_by'                 => 'name',
				'dcatl_parent_wrapper_bg_color'       => '#ffffff',
				'dcatl_content_wrapper_bg_color'      => '#ffffff',
				'dcatl_title_text_color'              => '#333333',
				'dcatl_title_text_hover_color'        => '#333333',
				'dcatl_title_text_size'               => '16px',
				'dcatl_desc_text_color'               => '#aaaaaa',
				'dcatl_desc_text_hover_color'         => '#aaaaaa',
				'dcatl_desc_text_size'                => '14px',
				'dcatl_wrapper_overlay_color'         => 'linear-gradient(135deg,rgba(0,0,0,0.48) 0%,rgba(255,255,255,0.32) 100%)',
				'dcatl_wrapper_boxshadow_color'       => '#000A1EBF',
				'dcatl_wrapper_boxshadow_hover_color' => '#000000',
				'dcatl_button_bg_color'               => '#000000',
				'dcatl_button_bg_hover_color'         => '#007CBA',
				'dcatl_button_text_color'             => '#ffffff',
				'dcatl_button_text_hover_color'       => '#cccccc',
				'dcatl_button_text_size'              => '16px',
				'dcatl_counter_bg_color'              => '#000000',
				'dcatl_counter_text_color'            => '#ffffff',
				'dcatl_counter_text_size'             => '14px',
				'dcatl_item_per_column'               => '5',
				'dcatl_button_padding'                => '3px 10px 3px 10px',
				'dcatl_line_bg_color'                 => '#757575',
			),
			'dcatl_bool'   => array(
				'dcatl_wrapper_height_auto'            => true,
				'dcatl_hide_child_item'                => true,
				'dcatl_hide_uncategorized'             => true,
				'dcatl_hide_empty_cat'                 => true,
				'dcatl_enable_wrapper_overlay'         => false,
				'dcatl_enable_wrapper_boxshadow_hover' => false,
				'dcatl_hide_title'                     => false,
				'dcatl_hide_divider'                   => false,
				'dcatl_hide_description'               => false,
				'dcatl_hide_button'                    => false,
				'dcatl_hide_counter'                   => false,
			),
			'dcatl_array'  => array(
				'dcatl_taxonomy_list' => array( 'category' ),
			),
		);
	}

	/**
	 * Return flat defaults.
	 *
	 * @param array $defaults Existing defaults supplied by a filter.
	 * @return array
	 */
	public function get_default_settings( $defaults = array() ) {
		$grouped = $this->set_default_values();
		$core    = array_merge( $grouped['dcatl_string'], $grouped['dcatl_bool'], $grouped['dcatl_array'] );

		return array_replace( is_array( $defaults ) ? $defaults : array(), $core );
	}

	/**
	 * Register the option and REST schema.
	 *
	 * @return void
	 */
	public function register_dcatl_admin_settings() {
		$defaults   = $this->get_default_settings();
		$properties = array();

		foreach ( $defaults as $key => $default ) {
			if ( is_bool( $default ) ) {
				$properties[ $key ] = array(
					'type'    => 'boolean',
					'default' => $default,
				);
			} elseif ( is_array( $default ) ) {
				$properties[ $key ] = array(
					'type'     => 'array',
					'default'  => $default,
					'items'    => array( 'type' => 'string' ),
					'minItems' => 1,
					'maxItems' => 1,
				);
			} else {
				$properties[ $key ] = array(
					'type'    => 'string',
					'default' => $default,
				);
			}
		}

		register_setting(
			'options',
			'dcatl_admin_style_settings',
			array(
				'type'              => 'object',
				'default'           => $defaults,
				'show_in_rest'      => array(
					'schema' => array(
						'type'                 => 'object',
						'properties'           => $properties,
						'additionalProperties' => false,
					),
				),
				'sanitize_callback' => array( $this, 'sanitize_admin_style_settings' ),
			)
		);
	}

	/**
	 * Sanitize the complete settings object field by field.
	 *
	 * @param mixed $input Submitted settings.
	 * @return array
	 */
	public function sanitize_admin_style_settings( $input ) {
		$defaults = $this->get_default_settings();
		$current  = get_option( 'dcatl_admin_style_settings', array() );
		$current  = is_array( $current ) ? $current : array();
		$input    = is_array( $input ) ? $input : array();
		$values   = array_replace( $defaults, $current, $input );

		$sanitized = array();

		$sanitized['dcatl_template_style']  = $this->sanitize_template_style( $values['dcatl_template_style'] );
		$sanitized['dcatl_wrapper_height']  = $this->sanitize_css_dimension( $values['dcatl_wrapper_height'], $defaults['dcatl_wrapper_height'] );
		$sanitized['dcatl_content_wrapper_padding'] = $this->sanitize_css_box_values( $values['dcatl_content_wrapper_padding'], $defaults['dcatl_content_wrapper_padding'] );
		$sanitized['dcatl_img_bg_size']     = $this->sanitize_choice( $values['dcatl_img_bg_size'], array( 'auto', 'cover', 'contain' ), $defaults['dcatl_img_bg_size'] );
		$sanitized['dcatl_img_border_radius'] = $this->sanitize_css_box_values( $values['dcatl_img_border_radius'], $defaults['dcatl_img_border_radius'] );
		$sanitized['dcatl_item_order']      = $this->sanitize_choice( strtoupper( (string) $values['dcatl_item_order'] ), array( 'ASC', 'DESC' ), $defaults['dcatl_item_order'] );
		$sanitized['dcatl_item_order_by']   = $this->sanitize_choice(
			(string) $values['dcatl_item_order_by'],
			array( 'name', 'slug', 'term_id', 'id', 'count', 'description', 'parent', 'none', 'term_order' ),
			$defaults['dcatl_item_order_by']
		);

		$color_keys = array(
			'dcatl_parent_wrapper_bg_color',
			'dcatl_content_wrapper_bg_color',
			'dcatl_title_text_color',
			'dcatl_title_text_hover_color',
			'dcatl_desc_text_color',
			'dcatl_desc_text_hover_color',
			'dcatl_wrapper_boxshadow_color',
			'dcatl_wrapper_boxshadow_hover_color',
			'dcatl_button_bg_color',
			'dcatl_button_bg_hover_color',
			'dcatl_button_text_color',
			'dcatl_button_text_hover_color',
			'dcatl_counter_bg_color',
			'dcatl_counter_text_color',
			'dcatl_line_bg_color',
		);

		foreach ( $color_keys as $key ) {
			$sanitized[ $key ] = $this->sanitize_css_color( $values[ $key ], $defaults[ $key ] );
		}

		$dimension_keys = array(
			'dcatl_title_text_size',
			'dcatl_desc_text_size',
			'dcatl_button_text_size',
			'dcatl_counter_text_size',
		);

		foreach ( $dimension_keys as $key ) {
			$sanitized[ $key ] = $this->sanitize_css_dimension( $values[ $key ], $defaults[ $key ] );
		}

		$sanitized['dcatl_wrapper_overlay_color'] = $this->sanitize_css_gradient_or_color(
			$values['dcatl_wrapper_overlay_color'],
			$defaults['dcatl_wrapper_overlay_color']
		);
		$sanitized['dcatl_button_padding'] = $this->sanitize_css_box_values( $values['dcatl_button_padding'], $defaults['dcatl_button_padding'] );
		$sanitized['dcatl_item_per_column'] = (string) min( 10, max( 1, absint( $values['dcatl_item_per_column'] ) ) );

		$boolean_keys = array_keys( $this->set_default_values()['dcatl_bool'] );
		foreach ( $boolean_keys as $key ) {
			$sanitized[ $key ] = rest_sanitize_boolean( $values[ $key ] );
		}

		$sanitized['dcatl_taxonomy_list'] = $this->sanitize_taxonomy_list( $values['dcatl_taxonomy_list'] );

		return $sanitized;
	}

	/**
	 * Return validated settings merged with defaults.
	 *
	 * @return array
	 */
	public function get_settings() {
		$saved = get_option( 'dcatl_admin_style_settings', array() );
		$saved = is_array( $saved ) ? $saved : array();

		return $this->sanitize_admin_style_settings( $saved );
	}

	/**
	 * Filter callback for retrieving settings.
	 *
	 * @param array $settings Existing filter value.
	 * @return array
	 */
	public function admin_style_settings( $settings = array() ) {
		return array_replace( is_array( $settings ) ? $settings : array(), $this->get_settings() );
	}

	/**
	 * Build sanitized CSS custom properties.
	 *
	 * @param array $settings Validated settings.
	 * @return string
	 */
	private function build_dynamic_css( $settings ) {
		$columns         = min( 10, max( 1, absint( $settings['dcatl_item_per_column'] ) ) );
		$tablet_columns  = min( $columns, 3 );
		$mobile_columns  = min( $columns, 2 );

		$variables = array(
			'--dcatl-parent-wrapper-bg-color'       => $settings['dcatl_parent_wrapper_bg_color'],
			'--dcatl-content-wrapper-bg-color'      => $settings['dcatl_content_wrapper_bg_color'],
			'--dcatl-wrapper-height'                => $settings['dcatl_wrapper_height'],
			'--dcatl-content-wrapper-padding'       => $settings['dcatl_content_wrapper_padding'],
			'--dcatl-img-bg-size'                   => $settings['dcatl_img_bg_size'],
			'--dcatl-img-border-radius'             => $settings['dcatl_img_border_radius'],
			'--dcatl-item-per-column'               => (string) $columns,
			'--dcatl-item-per-column-tablet'        => (string) $tablet_columns,
			'--dcatl-item-per-column-mobile'        => (string) $mobile_columns,
			'--dcatl-title-text-color'              => $settings['dcatl_title_text_color'],
			'--dcatl-title-text-hover-color'        => $settings['dcatl_title_text_hover_color'],
			'--dcatl-title-text-size'               => $settings['dcatl_title_text_size'],
			'--dcatl-desc-text-color'               => $settings['dcatl_desc_text_color'],
			'--dcatl-desc-text-hover-color'         => $settings['dcatl_desc_text_hover_color'],
			'--dcatl-desc-text-size'                => $settings['dcatl_desc_text_size'],
			'--dcatl-wrapper-overlay-color'         => $settings['dcatl_wrapper_overlay_color'],
			'--dcatl-wrapper-boxshadow-color'       => $settings['dcatl_wrapper_boxshadow_color'],
			'--dcatl-wrapper-boxshadow-hover-color' => $settings['dcatl_wrapper_boxshadow_hover_color'],
			'--dcatl-button-bg-color'               => $settings['dcatl_button_bg_color'],
			'--dcatl-button-bg-hover-color'         => $settings['dcatl_button_bg_hover_color'],
			'--dcatl-button-text-color'             => $settings['dcatl_button_text_color'],
			'--dcatl-button-text-hover-color'       => $settings['dcatl_button_text_hover_color'],
			'--dcatl-button-text-size'              => $settings['dcatl_button_text_size'],
			'--dcatl-button-padding'                => $settings['dcatl_button_padding'],
			'--dcatl-counter-bg-color'              => $settings['dcatl_counter_bg_color'],
			'--dcatl-counter-text-color'            => $settings['dcatl_counter_text_color'],
			'--dcatl-counter-text-size'             => $settings['dcatl_counter_text_size'],
			'--dcatl-line-bg-color'                 => $settings['dcatl_line_bg_color'],
		);

		$css = ':root{';
		foreach ( $variables as $name => $value ) {
			$css .= $name . ':' . $value . ';';
		}
		$css .= '}';

		return $css;
	}

	/**
	 * Sanitize template ID.
	 *
	 * @param mixed $value Submitted value.
	 * @return string
	 */
	private function sanitize_template_style( $value ) {
		return $this->sanitize_choice( (string) $value, array( '1', '2' ), '1' );
	}

	/**
	 * Sanitize an enum-like value.
	 *
	 * @param mixed  $value Submitted value.
	 * @param array  $allowed Allowed values.
	 * @param string $fallback Fallback value.
	 * @return string
	 */
	private function sanitize_choice( $value, $allowed, $fallback ) {
		$value = sanitize_text_field( (string) $value );
		return in_array( $value, $allowed, true ) ? $value : $fallback;
	}

	/**
	 * Sanitize a single CSS dimension.
	 *
	 * @param mixed  $value Submitted value.
	 * @param string $fallback Fallback value.
	 * @return string
	 */
	private function sanitize_css_dimension( $value, $fallback ) {
		$value = trim( (string) $value );
		if ( preg_match( '/^(?:0|(?:\d+(?:\.\d+)?|\.\d+)(?:px|em|rem|%|vh|vw|vmin|vmax|ch|ex|pt))$/i', $value ) ) {
			return $value;
		}

		return $fallback;
	}

	/**
	 * Sanitize one to four CSS box dimensions.
	 *
	 * @param mixed  $value Submitted value.
	 * @param string $fallback Fallback value.
	 * @return string
	 */
	private function sanitize_css_box_values( $value, $fallback ) {
		$parts = preg_split( '/\s+/', trim( (string) $value ) );
		if ( empty( $parts ) || count( $parts ) > 4 ) {
			return $fallback;
		}

		$sanitized = array();
		foreach ( $parts as $part ) {
			$clean = $this->sanitize_css_dimension( $part, '' );
			if ( '' === $clean ) {
				return $fallback;
			}
			$sanitized[] = $clean;
		}

		return implode( ' ', $sanitized );
	}

	/**
	 * Sanitize a CSS color, including alpha formats.
	 *
	 * @param mixed  $value Submitted value.
	 * @param string $fallback Fallback value.
	 * @return string
	 */
	private function sanitize_css_color( $value, $fallback ) {
		$value = trim( (string) $value );

		if ( preg_match( '/^#(?:[a-f0-9]{3}|[a-f0-9]{4}|[a-f0-9]{6}|[a-f0-9]{8})$/i', $value ) ) {
			return $value;
		}

		if ( preg_match( '/^rgba?\(\s*(?:\d{1,3}\s*,\s*){2}\d{1,3}(?:\s*,\s*(?:0|1|0?\.\d+))?\s*\)$/i', $value ) ) {
			return $value;
		}

		if ( preg_match( '/^hsla?\(\s*-?\d+(?:\.\d+)?(?:deg)?\s*,\s*\d+(?:\.\d+)?%\s*,\s*\d+(?:\.\d+)?%(?:\s*,\s*(?:0|1|0?\.\d+))?\s*\)$/i', $value ) ) {
			return $value;
		}

		return $fallback;
	}

	/**
	 * Sanitize a gradient or color value.
	 *
	 * @param mixed  $value Submitted value.
	 * @param string $fallback Fallback value.
	 * @return string
	 */
	private function sanitize_css_gradient_or_color( $value, $fallback ) {
		$value = trim( (string) $value );
		$color = $this->sanitize_css_color( $value, '' );
		if ( '' !== $color ) {
			return $color;
		}

		if (
			preg_match( '/^(?:linear|radial)-gradient\([#%(),.\s\-+a-z0-9]+\)$/i', $value )
			&& false === stripos( $value, 'url(' )
			&& false === strpos( $value, ';' )
			&& false === strpos( $value, '{' )
			&& false === strpos( $value, '}' )
		) {
			return $value;
		}

		return $fallback;
	}

	/**
	 * Validate the selected public taxonomy.
	 *
	 * @param mixed $value Submitted taxonomy list.
	 * @return array
	 */
	private function sanitize_taxonomy_list( $value ) {
		$available = get_taxonomies(
			array(
				'public'       => true,
				'show_ui'      => true,
				'show_in_rest' => true,
			),
			'names'
		);

		$available = is_array( $available ) ? array_values( $available ) : array();
		$submitted = is_array( $value ) ? $value : array( $value );

		foreach ( $submitted as $taxonomy ) {
			$taxonomy = sanitize_key( $taxonomy );
			if ( in_array( $taxonomy, $available, true ) ) {
				return array( $taxonomy );
			}
		}

		if ( in_array( 'category', $available, true ) ) {
			return array( 'category' );
		}

		return ! empty( $available ) ? array( reset( $available ) ) : array( 'category' );
	}
}
