<?php
namespace SabaiApps\Directories\Component\Form\Helper;

use SabaiApps\Directories\Application;
use SabaiApps\Directories\Component\Form;
use SabaiApps\Directories\Exception;

/**
 * ValidateHelper provides validation methods for form fields.
 * Includes text, email, URL, date, and time validation logic.
 */
class ValidateHelper {

	/**
	 * Validates a text field value according to the element's settings.
	 *
	 * @param Application $application
	 * @param Form\Form   $form
	 * @param mixed       $value
	 * @param array       $element
	 * @param bool        $checkRequired
	 * @param bool        $isHtml
	 * @return mixed
	 */
	public function text( Application $application, $value, array $element, ?Form\Form $form = null, $checkRequired = true, $isHtml = false ) {
		$value = (string) $value;
		if ( ! empty( $element['#char_validation'] )
			&& in_array( $element['#char_validation'], array( 'integer', 'numeric', 'alnum', 'alpha', 'lower', 'upper', 'url', 'email', 'date' ) )
		) {
			$element[ '#' . $element['#char_validation'] ] = true;
		}

		if ( empty( $element['#no_trim'] ) ) {
			$value = trim( function_exists( 'mb_convert_kana' ) ? mb_convert_kana( $value, 's', 'UTF-8' ) : $value );
		}

		// Remove value sent from placeholder
		if ( $element['#type'] === 'url' && $value === 'https://' ) {
			$value = '';
		}

		if ( strlen( $value ) === 0 ) {
			if ( $checkRequired ) {
				if ( isset( $form )
					&& $form->isFieldRequired( $element )
				) {
					$form->setError( isset( $element['#required_error_message'] ) ? $element['#required_error_message'] : __( 'Please fill out this field.', 'directories-pro' ), $element );
					return false;
				}
			}
			return $value;
		}

		if ( ! empty( $element['#integer'] ) ) {
			if ( ! preg_match( '/^-?\d+$/', $value ) ) {
				if ( isset( $form ) ) {
					$form->setError( __( 'The input value must be an integer.', 'directories-pro' ), $element );
				}
				return false;
			}
			if ( empty( $element['#integer_allow_string'] ) ) {
				$value = intval( $value );
			}
		} elseif ( ! empty( $element['#numeric'] ) ) {
			if ( ! is_numeric( $value ) ) {
				if ( isset( $form ) ) {
					$form->setError( __( 'The input value must be a numeric value.', 'directories-pro' ), $element );
				}
				return false;
			}
		} elseif ( ! empty( $element['#alpha'] ) ) {
			if ( ! ctype_alpha( $value ) ) {
				if ( isset( $form ) ) {
					$form->setError( __( 'The input value must consist of alphabetic characters only.', 'directories-pro' ), $element );
				}
				return false;
			}
		} elseif ( ! empty( $element['#alnum'] ) ) {
			if ( ! ctype_alnum( $value ) ) {
				if ( isset( $form ) ) {
					$form->setError( __( 'The input value must consist of alphanumeric characters only.', 'directories-pro' ), $element );
				}
				return false;
			}
		} elseif ( ! empty( $element['#lower'] ) ) {
			if ( ! ctype_lower( $value ) ) {
				if ( isset( $form ) ) {
					$form->setError( __( 'The input value must consist of lowercase characters only.', 'directories-pro' ), $element );
				}
				return false;
			}
		} elseif ( ! empty( $element['#upper'] ) ) {
			if ( ! ctype_upper( $value ) ) {
				if ( isset( $form ) ) {
					$form->setError( __( 'The input value must consist of uppercase characters only.', 'directories-pro' ), $element );
				}
				return false;
			}
		} elseif ( $element['#type'] === 'url' || ! empty( $element['#url'] ) ) {
			try {
				$value = $this->url( $application, $value, ! empty( $element['#allow_url_no_protocol'] ) );
			} catch ( Exception\IException $e ) {
				if ( isset( $form ) ) {
					$form->setError( $e->getMessage(), $element );
				}
				return false;
			}
		} elseif ( $element['#type'] === 'email' || ! empty( $element['#email'] ) ) {
			try {
				$value = $this->email( $application, $value, ! empty( $element['#check_mx'] ), ! empty( $element['#check_exists'] ) );
			} catch ( Exception\IException $e ) {
				if ( isset( $form ) ) {
					$form->setError( $e->getMessage(), $element );
				}
				return false;
			}
		} elseif ( $element['#type'] === 'date' || ! empty( $element['#date'] ) ) {
			if ( ! $this->date( $application, $value, $element, $form ) ) {
				return false;
			}
		}

		// Check min/max length
		$min_length = empty( $element['#min_length'] ) ? null : (int) $element['#min_length'];
		$max_length = empty( $element['#max_length'] ) ? null : (int) $element['#max_length'];
		if ( isset( $min_length ) || isset( $max_length ) ) {
			$value_length = $application->System_MB_strlen( $isHtml ? strip_tags( html_entity_decode( $value ) ) : $value, 'UTF-8' );
			if ( $max_length && $min_length ) {
				if ( $max_length === $min_length ) {
					if ( $value_length !== $max_length ) {
						if ( isset( $form ) ) {
							$form->setError( sprintf( __( 'The input value must be exactly %d characters.', 'directories-pro' ), $max_length ), $element );
						}
						return false;
					}
				} elseif ( $value_length < $min_length || $value_length > $max_length ) {
					if ( isset( $form ) ) {
						$form->setError( sprintf( __( 'The input value must be between %1$d and %2$d characters.', 'directories-pro' ), $min_length, $max_length ), $element );
					}
						return false;
				}
			} elseif ( $max_length ) {
				if ( $value_length > $max_length ) {
					if ( isset( $form ) ) {
						$form->setError( sprintf( __( 'The input value must not exceed %d characters.', 'directories-pro' ), $max_length ), $element );
					}
					return false;
				}
			} elseif ( $min_length ) {
				if ( $value_length < $min_length ) {
					if ( isset( $form ) ) {
						$form->setError( sprintf( __( 'The input value must be at least %d characters.', 'directories-pro' ), $min_length ), $element );
					}
					return false;
				}
			}
		}

		if ( ! empty( $element['#integer'] ) || ! empty( $element['#numeric'] ) ) {
			if ( isset( $element['#min_value'] ) ) {
				if ( $value < $element['#min_value'] ) {
					if ( isset( $form ) ) {
						$form->setError( sprintf( __( 'The value must not be less than %s.', 'directories-pro' ), $element['#min_value'] ), $element );
					}
					return false;
				}
			}
			if ( isset( $element['#max_value'] ) ) {
				if ( $value > $element['#max_value'] ) {
					if ( isset( $form ) ) {
						$form->setError( sprintf( __( 'The value must not exceed %s.', 'directories-pro' ), $element['#max_value'] ), $element );
					}
					return false;
				}
			}
		}

		// Validate against regex?
		if ( isset( $element['#regex'] ) && strlen( $element['#regex'] ) ) {
			if ( ! preg_match( $element['#regex'], $value, $matches ) ) {
				if ( isset( $form ) ) {
					$form->setError( isset( $element['#regex_error_message'] ) ? $element['#regex_error_message'] : __( 'The input value is not in the correct format.', 'directories-pro' ), $element );
				}
				return false;
			}
		}

		return $application->Filter( 'form_validate_field_value', $value, array( $element, $form, $checkRequired, $isHtml ) );
	}

	/**
	 * Validates an email address.
	 *
	 * @param Application $application
	 * @param string      $value
	 * @param bool        $checkMx
	 * @param bool        $checkExists
	 * @return string
	 * @throws Exception\RuntimeException
	 */
	public function email( Application $application, $value, $checkMx = false, $checkExists = false ) {
		if ( ! filter_var( $value, FILTER_VALIDATE_EMAIL ) ) {
			throw new Exception\RuntimeException( __( 'The input value is not a valid E-mail address.', 'directories-pro' ) );
		}
		if ( ! empty( $checkMx )
			&& $application->System_Util_canCheckMx()
		) {
			list(, $domain) = explode( '@', $value );
			if ( ! $domain
				|| ! checkdnsrr( $domain, 'MX' )
			) {
				throw new Exception\RuntimeException( __( 'Invalid domain name.', 'directories-pro' ) );
			}
		}
		if ( ! empty( $checkExists ) ) {
			$identity = $application->getPlatform()->getUserIdentityFetcher()->fetchByEmail( $value );
			if ( ! $identity->isAnonymous() ) {
				// There is already a registered user with that email address
				throw new Exception\RuntimeException( __( 'The email address is already registered and may not be used.', 'directories-pro' ) );
			}
		}

		return $value;
	}

	/**
	 * Validates a URL.
	 *
	 * @param Application $application
	 * @param string      $value
	 * @param bool        $allowNoProtocol
	 * @return string
	 * @throws Exception\RuntimeException
	 */
	public function url( Application $application, $value, $allowNoProtocol = false ) {
		$value          = preg_replace( '/\s+/S', '', $value );
		$value_to_check = $value;
		if ( 0 === strpos( $value, '//' ) ) {
			if ( ! empty( $allowNoProtocol ) ) {
				$value_to_check = 'http:' . $value;
			}
		} elseif ( false === strpos( $value, '://' ) ) {
			$value_to_check = $value = 'https://' . $value;
		}
		if ( ! preg_match( '#\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))#', $value_to_check ) ) { // supports IDN
			// if (!filter_var($value_to_check, FILTER_VALIDATE_URL)) {
			throw new Exception\RuntimeException( __( 'The input value is not a valid URL.', 'directories-pro' ) );
		}

		return $value;
	}

	/**
	 * Validates a date value.
	 *
	 * @param Application $application
	 * @param string      $value
	 * @param array       $element
	 * @param Form\Form   $form
	 * @param string      $format
	 * @return mixed
	 */
	public function date( Application $application, $value, array $element, ?Form\Form $form = null, $format = 'Y-m-d' ) {
		$value = trim( (string) $value );

		// Required?
		if ( strlen( $value ) === 0 ) {
			if ( isset( $form )
				&& $form->isFieldRequired( $element )
			) {
				$form->setError( isset( $element['#required_error_message'] ) ? $element['#required_error_message'] : __( 'Please select a date.', 'directories-pro' ), $element );
				return false;
			}
			return isset( $element['#empty_value'] ) ? $element['#empty_value'] : null;
		}

		return $this->_checkDate( $application, $value, $element, $form, $format );
	}

	protected function _checkDate( Application $application, $value, array $element, ?Form\Form $form = null, $format = 'Y-m-d' ) {
		// Check date
		if ( ! $_value = $application->System_Date_strToTime( $value, true, $format ) ) {
			if ( isset( $form ) ) {
				$form->setError( sprintf( __( 'Invalid date. Date string "%1$s" could not be converted to timestamp using format %2$s.', 'directories-pro' ), $value, $format ), $element );
			}
			return false;
		}
		$value                    = $_value;
		list($year, $month, $day) = $this->_getDate( $value );
		if ( ! checkdate( $month, $day, $year ) ) {
			if ( isset( $form ) ) {
				$form->setError( sprintf( __( 'Invalid date. Month: %1$s, Day: %2$s, Year: %3$s', 'directories-pro' ), $month, $day, $year ), $element );
			}
			return false;
		}

		// Make sure the submitted date falls between allowed date rage
		if ( isset( $element['#min_date'] ) && isset( $element['#max_date'] ) ) {
			if ( $value < $element['#min_date']
				|| $value > $element['#max_date']
			) {
				$min_date_str = $application->System_Date_datetime( $element['#min_date'] );
				$max_date_str = $application->System_Date_datetime( $element['#max_date'] );
				if ( isset( $form ) ) {
					$form->setError( sprintf( __( 'Date must be between %1$s and %2$s.', 'directories-pro' ), $min_date_str, $max_date_str ), $element );
				}
				return false;
			}
		} elseif ( isset( $element['#min_date'] ) ) {
			if ( $value < $element['#min_date'] ) {
				$min_date_str = $application->System_Date_datetime( $element['#min_date'] );
				if ( isset( $form ) ) {
					$form->setError( sprintf( __( 'Date must be later than %s.', 'directories-pro' ), $min_date_str ), $element );
				}
				return false;
			}
		} elseif ( isset( $element['#max_date'] ) ) {
			if ( $value > $element['#max_date'] ) {
				$max_date_str = $application->System_Date_datetime( $element['#max_date'] );
				if ( isset( $form ) ) {
					$form->setError( sprintf( __( 'Date must be earlier than %s.', 'directories-pro' ), $max_date_str ), $element );
				}
				return false;
			}
		}

		return $value;
	}

	/**
	 * Helper to extract date parts from a timestamp.
	 *
	 * @param int $timestamp
	 * @return array
	 */
	protected function _getDate( $timestamp ) {
		$date = getdate( $timestamp );

		return array( $date['year'], $date['mon'], $date['mday'], $date['hours'], $date['minutes'] );
	}

	/**
	 * Validates a time value and optionally converts it to seconds.
	 *
	 * @param Application $application
	 * @param string      $value
	 * @param bool        $convert
	 * @return mixed
	 */
	public function time( Application $application, $value, $convert = false ) {
		$value = trim( $value );
		if ( strpos( $value, ' ' ) ) {
			if ( ! $parts = explode( ' ', $value ) ) {
				return false;
			}

			if ( isset( $parts[1] ) ) {
				$ampm = strtoupper( $parts[1] );
				if ( ! in_array( $ampm, array( 'AM', 'PM' ) ) ) {
					return false;
				}
			}
			$value = $parts[0];
		}

		$time = array_map( 'trim', explode( ':', $value ) );
		if ( count( $time ) !== 2 || ! is_numeric( $time[0] ) || ! is_numeric( $time[1] ) ) {
			return false;
		}

		$time[0] = intval( $time[0] );
		$time[1] = intval( $time[1] );
		if ( $time[0] < 0 || $time[0] > 23 || $time[1] < 0 || $time[1] > 59 ) {
			return false;
		}

		if ( isset( $ampm ) ) {
			if ( $time[0] > 12 ) {
				return false;
			}

			if ( $ampm === 'PM' ) {
				$time[0] += 12;
			}
		}

		if ( ! $convert ) {
			return $time;
		}

		return mktime( $time[0], $time[1], 0 ) % 86400;
	}
}
