<?php
/**
 * Form component.
 *
 * @package HivePress\Components
 */

namespace HivePress\Components;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Handles forms.
 */
final class Form extends Component {

	/**
	 * Class constructor.
	 *
	 * @param array $args Component arguments.
	 */
	public function __construct( $args = [] ) {

		// Set field arguments.
		add_filter( 'hivepress/v1/fields/field', [ $this, 'set_field_arguments' ] );

		// Set field options.
		add_filter( 'hivepress/v1/fields/field/options', [ $this, 'set_field_options' ], 10, 2 );

		// Manage captcha.
		if ( $this->is_captcha_enabled() ) {
			add_filter( 'hivepress/v1/forms/form/meta', [ $this, 'set_captcha' ] );
			add_filter( 'hivepress/v1/forms/form', [ $this, 'add_captcha' ], 10, 2 );
			add_filter( 'hivepress/v1/forms/form/errors', [ $this, 'validate_captcha' ], 10, 2 );
		}

		// Enqueue scripts.
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
		add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] );

		// Print scripts.
		add_action( 'admin_print_footer_scripts', [ $this, 'print_scripts' ] );
		add_action( 'wp_print_footer_scripts', [ $this, 'print_scripts' ] );

		parent::__construct( $args );
	}

	/**
	 * Gets field options.
	 *
	 * @param string $type Options type.
	 * @param array  $args Option arguments.
	 * @param mixed  $value Current value.
	 * @return array
	 */
	protected function get_field_options( $type, $args, $value = null ) {
		$options = [];

		// Set method.
		$method = 'get_' . $type;

		// Get options.
		if ( method_exists( $this, $method ) ) {
			$options = call_user_func_array( [ $this, $method ], [ $args, $value ] );
		} else {
			$options = hivepress()->get_config( $type );
		}

		return $options;
	}

	/**
	 * Sets field arguments.
	 *
	 * @param array $args Field arguments.
	 * @return array
	 */
	public function set_field_arguments( $args ) {
		if ( isset( $args['options'] ) && ! is_array( $args['options'] ) ) {

			// Set attributes.
			if ( 'icons' === $args['options'] ) {
				$args['attributes']['data-template'] = 'icon';
			}

			// Set options.
			if ( ! isset( $args['source'] ) ) {
				$args['options'] = $this->get_field_options( $args['options'], hp\get_array_value( $args, 'option_args', [] ) );
			}
		}

		return $args;
	}

	/**
	 * Sets field options.
	 *
	 * @param array  $options Field options.
	 * @param object $field Field object.
	 * @return array
	 */
	public function set_field_options( $options, $field ) {
		return $this->get_field_options(
			$field->get_arg( 'options' ),
			(array) $field->get_arg( 'option_args' ),
			$field->get_value()
		);
	}

	/**
	 * Gets user objects.
	 *
	 * @param array $args User arguments.
	 * @param mixed $value Current value.
	 * @return array
	 */
	protected function get_users( $args, $value ) {

		// Set default arguments.
		$args = array_merge(
			[
				'fields' => [ 'ID', 'user_login' ],
			],
			$args
		);

		// Set IDs.
		if ( $value ) {
			$args['include'] = (array) $value;
		}

		// Get users.
		$users = wp_list_pluck( get_users( $args ), 'user_login', 'ID' );

		return $users;
	}

	/**
	 * Gets post objects.
	 *
	 * @param array $args Post arguments.
	 * @param mixed $value Current value.
	 * @return array
	 */
	protected function get_posts( $args, $value ) {

		// Set default arguments.
		$args = array_merge(
			[
				'post_type'        => 'post',
				'post_status'      => 'publish',
				'posts_per_page'   => -1,
				'orderby'          => 'title',
				'order'            => 'ASC',
				'suppress_filters' => ! hivepress()->translator->is_multilingual(),
			],
			$args
		);

		// Set IDs.
		if ( $value ) {
			$args['post__in'] = (array) $value;
		}

		// Get options.
		$options = [];

		if ( post_type_exists( $args['post_type'] ) ) {

			// Check post type.
			$is_default = strpos( $args['post_type'], 'hp_' ) === 0;

			// Get cache group.
			$cache_group = hivepress()->model->get_cache_group( 'post', $args['post_type'] );

			// Get cached options.
			$options = null;

			if ( $is_default ) {
				$options = hivepress()->cache->get_cache( array_merge( $args, [ 'fields' => 'titles' ] ), $cache_group );
			}

			if ( is_null( $options ) ) {
				$options = wp_list_pluck( get_posts( $args ), 'post_title', 'ID' );

				// Cache options.
				if ( $is_default && count( $options ) <= 1000 ) {
					hivepress()->cache->set_cache( array_merge( $args, [ 'fields' => 'titles' ] ), $cache_group, $options );
				}
			}
		}

		return $options;
	}

	/**
	 * Gets term objects.
	 *
	 * @param array $args Term arguments.
	 * @param mixed $value Current value.
	 * @return array
	 */
	protected function get_terms( $args, $value ) {

		// Set default arguments.
		$args = array_merge(
			[
				'taxonomy'   => 'category',
				'hide_empty' => false,
			],
			$args
		);

		// Set IDs.
		if ( $value ) {
			$args['include'] = (array) $value;
		}

		// Get options.
		$options = [];

		if ( taxonomy_exists( $args['taxonomy'] ) ) {

			// Check taxonomy.
			$is_default = strpos( $args['taxonomy'], 'hp_' ) === 0;

			// Get cache group.
			$cache_group = hivepress()->model->get_cache_group( 'term', $args['taxonomy'] );

			// Get cached options.
			$options = null;

			if ( $is_default ) {
				$options = hivepress()->cache->get_cache( $args, $cache_group );
			}

			if ( is_null( $options ) ) {
				$options = [];

				// Set custom order.
				if ( $is_default && ! isset( $args['orderby'] ) && get_terms(
					array_merge(
						$args,
						[
							'number'     => 1,
							'fields'     => 'ids',

							'meta_query' => [
								[
									'key'     => 'hp_sort_order',
									'value'   => 0,
									'compare' => '>',
									'type'    => 'NUMERIC',
								],
							],
						]
					)
				) ) {

					// Get terms.
					$terms = get_terms(
						array_merge(
							$args,
							[
								'orderby'    => 'meta_value_num',

								'meta_query' => [
									'relation' => 'OR',

									[
										'key'     => 'hp_sort_order',
										'type'    => 'NUMERIC',
										'compare' => 'EXISTS',
									],

									[
										'key'     => 'hp_sort_order',
										'type'    => 'NUMERIC',
										'compare' => 'NOT EXISTS',
									],
								],
							]
						)
					);
				} else {

					// Get terms.
					$terms = get_terms( $args );
				}

				// Add options.
				foreach ( $terms as $term ) {
					$options[ $term->term_id ] = [
						'label'  => $term->name,
						'slug'   => $term->slug,
						'parent' => $term->parent ? $term->parent : null,
					];

					if ( $is_default ) {

						// Set icon.
						$icon = get_term_meta( $term->term_id, 'hp_icon', true );

						$options[ $term->term_id ]['icon'] = $icon ? $icon : null;
					}
				}

				// Cache options.
				if ( $is_default && count( $options ) <= 1000 ) {
					hivepress()->cache->set_cache( $args, $cache_group, $options );
				}
			}
		}

		return $options;
	}

	/**
	 * Gets form names.
	 *
	 * @param array $args Form arguments.
	 * @param mixed $value Current value.
	 * @return array
	 */
	protected function get_forms( $args, $value ) {
		$options = [];

		foreach ( hivepress()->get_classes( 'forms' ) as $form_name => $form ) {
			if ( $form::get_meta( 'label' ) && ! array_diff( $args, array_intersect_key( $form::get_meta(), $args ) ) ) {
				$options[ $form_name ] = $form::get_meta( 'label' );
			}
		}

		asort( $options );

		return $options;
	}

	/**
	 * Gets field names.
	 *
	 * @param array $args Field arguments.
	 * @param mixed $value Current value.
	 * @return array
	 */
	protected function get_fields( $args, $value ) {
		$options = [];

		foreach ( hivepress()->get_classes( 'fields' ) as $field_name => $field ) {
			if ( $field::get_meta( 'label' ) && ! array_diff( $args, array_intersect_key( $field::get_meta(), $args ) ) ) {
				$options[ $field_name ] = $field::get_meta( 'label' );
			}
		}

		asort( $options );

		return $options;
	}

	/**
	 * Gets MIME types.
	 *
	 * @param array $args MIME type arguments.
	 * @param mixed $value Current value.
	 * @return array
	 */
	protected function get_mime_types( $args, $value ) {
		return get_allowed_mime_types();
	}

	/**
	 * Gets template names.
	 *
	 * @param array $args Template arguments.
	 * @param mixed $value Current value.
	 * @return array
	 */
	protected function get_templates( $args, $value ) {
		$options = [];

		foreach ( hivepress()->get_classes( 'templates' ) as $template_name => $template ) {
			if ( $template::get_meta( 'label' ) ) {
				$options[ $template_name ] = $template::get_meta( 'label' );
			}
		}

		asort( $options );

		return $options;
	}

	/**
	 * Gets days of the week.
	 *
	 * @param array $args Day arguments.
	 * @param mixed $value Current value.
	 * @return array
	 */
	protected function get_days( $args, $value ) {
		$days = [
			0 => 'sunday',
			1 => 'monday',
			2 => 'tuesday',
			3 => 'wednesday',
			4 => 'thursday',
			5 => 'friday',
			6 => 'saturday',
		];

		$start = absint( get_option( 'start_of_week' ) );

		if ( $start > 0 ) {
			$days = array_slice( $days, $start, null, true ) + array_slice( $days, 0, $start, true );
		}

		return array_map(
			function ( $day ) {
				return date_i18n( 'D', strtotime( $day ) );
			},
			$days
		);
	}

	/**
	 * Gets time zones.
	 *
	 * @return array
	 */
	protected function get_timezones() {

		// Load textdomain.
		if ( ! is_textdomain_loaded( 'continents-cities' ) ) {
			load_textdomain( 'continents-cities', WP_LANG_DIR . '/continents-cities-' . get_locale() . '.mo' );
		}

		// Get timezones.
		$timezones = [];

		foreach ( timezone_identifiers_list() as $name ) {
			$labels = [];

			foreach ( explode( '/', str_replace( '_', ' ', $name ) ) as $label ) {
				$labels[] = translate( $label, 'continents-cities' );
			}

			if ( count( $labels ) > 1 ) {
				array_shift( $labels );
			}

			$timezones[ $name ] = implode( ' - ', $labels );
		}

		// Sort timezones.
		asort( $timezones );

		return $timezones;
	}

	/**
	 * Gets email names.
	 *
	 * @param array $args Email arguments.
	 * @param mixed $value Current value.
	 * @return array
	 */
	protected function get_emails( $args, $value ) {
		$options = [];

		foreach ( hivepress()->get_classes( 'emails' ) as $email_name => $email ) {
			if ( $email::get_meta( 'label' ) ) {
				$options[ $email_name ] = $email::get_meta( 'label' );
			}
		}

		asort( $options );

		return $options;
	}

	/**
	 * Checks if captcha is enabled.
	 *
	 * @return bool
	 */
	protected function is_captcha_enabled() {
		return get_option( 'hp_recaptcha_site_key' ) && get_option( 'hp_recaptcha_secret_key' );
	}

	/**
	 * Sets captcha flag.
	 *
	 * @param string $meta Class meta.
	 * @return array
	 */
	public function set_captcha( $meta ) {
		if ( isset( $meta['captcha'] ) && in_array( $meta['name'], (array) get_option( 'hp_recaptcha_forms' ), true ) ) {

			// Set captcha flag.
			$meta['captcha'] = true;
		}

		return $meta;
	}

	/**
	 * Adds captcha field.
	 *
	 * @param array  $args Form arguments.
	 * @param object $form Form object.
	 * @return array
	 */
	public function add_captcha( $args, $form ) {
		if ( $form::get_meta( 'captcha' ) ) {

			// Add captcha field.
			$args['fields']['_captcha'] = [
				'type'      => 'captcha',
				'_separate' => true,
				'_order'    => 10000,
			];
		}

		return $args;
	}

	/**
	 * Validates captcha.
	 *
	 * @param array  $errors Form errors.
	 * @param object $form Form object.
	 * @return array
	 */
	public function validate_captcha( $errors, $form ) {
		if ( $form::get_meta( 'captcha' ) ) {

			// Get ReCAPTCHA response.
			$response = json_decode(
				wp_remote_retrieve_body(
					wp_remote_get(
						'https://www.google.com/recaptcha/api/siteverify?' . http_build_query(
							[
								'secret'   => get_option( 'hp_recaptcha_secret_key' ),
								'response' => sanitize_text_field( hp\get_array_value( $_POST, 'g-recaptcha-response' ) ),
							]
						)
					)
				),
				true
			);

			// Add form error.
			if ( ! hp\get_array_value( $response, 'success', false ) ) {
				$errors[] = esc_html__( 'Captcha is invalid.', 'hivepress' );
			}
		}

		return $errors;
	}

	/**
	 * Enqueues scripts.
	 */
	public function enqueue_scripts() {

		// Get language.
		$language = hivepress()->translator->get_language();

		// Enqueue Flatpickr.
		$filepath = '/node_modules/flatpickr/dist/l10n/' . ( 'el' === $language ? 'gr' : $language ) . '.js';

		if ( file_exists( hivepress()->get_path() . $filepath ) ) {
			wp_enqueue_script(
				'flatpickr-' . $language,
				hivepress()->get_url() . $filepath,
				[ 'flatpickr' ],
				hivepress()->get_version(),
				true
			);
		}

		if ( ! is_admin() ) {

			// Enqueue Select2.
			$filepath = '/node_modules/select2/dist/js/i18n/' . $language . '.js';

			if ( file_exists( hivepress()->get_path() . $filepath ) ) {
				wp_enqueue_script(
					'select2-' . $language,
					hivepress()->get_url() . $filepath,
					[ 'select2-full' ],
					hivepress()->get_version(),
					true
				);
			}

			// Enqueue ReCAPTCHA.
			if ( $this->is_captcha_enabled() ) {
				wp_enqueue_script(
					'recaptcha',
					'https://www.google.com/recaptcha/api.js',
					[],
					null,
					false
				);

				wp_script_add_data( 'recaptcha', 'async', true );
				wp_script_add_data( 'recaptcha', 'defer', true );
			}
		}
	}

	/**
	 * Prints scripts.
	 */
	public function print_scripts() {
		$scripts = '';

		// Get language.
		$language = hivepress()->translator->get_language();

		if ( ! $language ) {
			return;
		}

		// Enqueue intlTelInput.
		$filepath = '/node_modules/intl-tel-input/build/js/i18n/' . $language . '/index.js';

		if ( file_exists( hivepress()->get_path() . $filepath ) ) {
			$scripts = '<script type="module">
				import language from "' . esc_url( hivepress()->get_url() . $filepath ) . '";

				window.intlTelInputi18n = language;
			</script>';
		}

		echo $scripts;
	}
}
