<?php

// Don't load directly.
if ( ! defined( 'ABSPATH' ) ) {
	die( '-1' );
}

/**
 * @package Pods\Fields
 */
class PodsField_Number extends PodsField {

	/**
	 * {@inheritdoc}
	 */
	public static $group = 'Number';

	/**
	 * {@inheritdoc}
	 */
	public static $type = 'number';

	/**
	 * {@inheritdoc}
	 */
	public static $label = 'Plain Number';

	/**
	 * {@inheritdoc}
	 */
	public static $prepare = '%d';

	/**
	 * {@inheritdoc}
	 */
	public function setup() {

		static::$group = __( 'Number', 'pods' );
		static::$label = __( 'Plain Number', 'pods' );
	}

	/**
	 * {@inheritdoc}
	 */
	public function options() {

		$options = [
			static::$type . '_format_type'      => [
				'label'                 => __( 'Input Type', 'pods' ),
				'default'               => 'number',
				'type'                  => 'pick',
				'data'                  => [
					'number' => __( 'Freeform Number', 'pods' ),
					'slider' => __( 'Slider', 'pods' ),
				],
				'pick_format_single'    => 'dropdown',
				'pick_show_select_text' => 0,
				'dependency'            => true,
			],
			static::$type . '_format'           => [
				'label'                 => __( 'Number Format', 'pods' ),
				'default'               => apply_filters( 'pods_form_ui_field_number_format_default', 'i18n' ),
				'type'                  => 'pick',
				'data'                  => [
					'i18n'      => __( 'Localized Default', 'pods' ),
					'9,999.99'  => '1,234.00',
					'9999.99'   => '1234.00',
					'9.999,99'  => '1.234,00',
					'9999,99'   => '1234,00',
					'9 999,99'  => '1 234,00',
					'9\'999.99' => '1\'234.00',
				],
				'pick_format_single'    => 'dropdown',
				'pick_show_select_text' => 0,
			],
			static::$type . '_decimals'         => [
				'label'      => __( 'Decimals', 'pods' ),
				'default'    => 0,
				'type'       => 'number',
				'dependency' => true,
				'help'       => __( 'Set to a positive number to enable decimals. The upper limit in the database for this field is 30 decimals.', 'pods' ),
			],
			static::$type . '_format_soft'      => [
				'label'       => __( 'Soft Formatting (deprecated, use Decimal handling instead)', 'pods' ),
				'help'        => __( 'Remove trailing decimals (0)', 'pods' ),
				'default'     => 0,
				'type'        => 'boolean',
				'excludes-on' => [ static::$type . '_decimals' => 0 ],
			],
			static::$type . '_decimal_handling' => [
				'label'                 => __( 'Decimal handling for trailing zero decimals', 'pods' ),
				'default'               => 'none',
				'type'                  => 'pick',
				'data'                  => [
					'none'             => __( 'Default (Examples: "1.00", "0.00")', 'pods' ),
					'remove'           => __( 'Remove decimals (Examples: "1", "0")', 'pods' ),
					'remove_only_zero' => __( 'Remove decimals only when number is zero (Examples: "1.00", "0")', 'pods' ),
					'dash'             => __( 'Convert to dash (Examples: "1.-", "0.-")', 'pods' ),
					'dash_only_zero'   => __( 'Convert to dash only when number is zero (Examples: "1.00", "0.-")', 'pods' ),
					'dash_whole_zero'  => __( 'Convert to the whole value to dash when number is zero (Examples: "1.00", "-")', 'pods' ),
				],
				'pick_format_single'    => 'dropdown',
				'pick_show_select_text' => 0,
			],
			static::$type . '_step'             => [
				'label'      => __( 'Slider Increment (Step)', 'pods' ),
				'depends-on' => [ static::$type . '_format_type' => 'slider' ],
				'default'    => 1,
				'type'       => 'text',
			],
			static::$type . '_min'              => [
				'label'          => __( 'Minimum Number', 'pods' ),
				'depends-on-any' => [
					static::$type . '_format_type' => 'slider',
					static::$type . '_html5'       => true,
				],
				'default'        => '',
				'type'           => 'text',
			],
			static::$type . '_max'              => [
				'label'          => __( 'Maximum Number', 'pods' ),
				'depends-on-any' => [
					static::$type . '_format_type' => 'slider',
					static::$type . '_html5'       => true,
				],
				'default'        => '',
				'type'           => 'text',
			],
			static::$type . '_max_length'       => [
				'label'   => __( 'Maximum Digits', 'pods' ),
				'default' => 12,
				'type'    => 'number',
				'help'    => __( 'Set to -1 for no limit. The upper limit in the database for this field is 64 digits.', 'pods' ),
			],
			static::$type . '_html5'            => [
				'label'      => __( 'Enable HTML5 Input Field', 'pods' ),
				'default'    => apply_filters( 'pods_form_ui_field_html5', 0, static::$type ),
				'depends-on' => [ static::$type . '_format_type' => 'number' ],
				'type'       => 'boolean',
			],
			static::$type . '_placeholder'      => [
				'label'   => __( 'HTML Placeholder', 'pods' ),
				'default' => '',
				'type'    => 'text',
				'help'    => [
					__( 'Placeholders can provide instructions or an example of the required data format for a field. Please note: It is not a replacement for labels or description text, and it is less accessible for people using screen readers.', 'pods' ),
					'https://www.w3.org/WAI/tutorials/forms/instructions/#placeholder-text',
				],
			],
		];

		return $options;
	}

	/**
	 * {@inheritdoc}
	 */
	public function schema( $options = null ) {

		$length = (int) pods_v( static::$type . '_max_length', $options, 12, true );

		if ( $length < 1 || 64 < $length ) {
			$length = 64;
		}

		$decimals = $this->get_max_decimals( $options );

		$schema = 'DECIMAL(' . $length . ',' . $decimals . ')';

		return $schema;

	}

	/**
	 * {@inheritdoc}
	 */
	public function prepare( $options = null ) {

		$format = static::$prepare;

		$decimals = $this->get_max_decimals( $options );

		if ( 6 < $decimals ) {
			// %F only allows 6 decimals by default
			$format = '%.' . $decimals . 'F';
		} elseif ( 0 < $decimals ) {
			$format = '%F';
		}

		return $format;

	}

	/**
	 * @todo 2.8 Centralize the usage of this method. See PR #5540.
	 * {@inheritdoc}
	 */
	public function is_empty( $value = null ) {

		$is_empty = false;

		$value = (float) $value;

		if ( empty( $value ) ) {
			$is_empty = true;
		}

		return $is_empty;

	}

	/**
	 * {@inheritdoc}
	 */
	public function display( $value = null, $name = null, $options = null, $pod = null, $id = null ) {
		return $this->format( $value, $name, $options, $pod, $id );
	}

	/**
	 * {@inheritdoc}
	 */
	public function input( $name, $value = null, $options = null, $pod = null, $id = null ) {

		$options         = ( is_array( $options ) || is_object( $options ) ) ? $options : (array) $options;
		$form_field_type = PodsForm::$field_type;
		$is_read_only    = false;

		$value = $this->normalize_value_for_input( $value, $options, '' );

		if ( 'slider' === pods_v( static::$type . '_format_type', $options, 'number' ) ) {
			$field_type = 'slider';
		} else {
			$field_type = static::$type;
		}

		if ( isset( $options['name'] ) && ! pods_permission( $options ) ) {
			if ( pods_v_bool( 'read_only_restricted', $options ) ) {
				$is_read_only = true;
			} else {
				return;
			}
		} elseif ( ! pods_has_permissions( $options ) ) {
			if ( pods_v_bool( 'read_only', $options ) ) {
				$options['readonly'] = true;
			}
		}

		if ( $is_read_only ) {
			$options['readonly'] = true;

			$field_type = 'text';
		}

		// Enforce boolean.
		$options[ static::$type . '_html5' ]       = filter_var( pods_v( static::$type . '_html5', $options, false ), FILTER_VALIDATE_BOOLEAN );
		$options[ static::$type . '_format_soft' ] = filter_var( pods_v( static::$type . '_format_soft', $options, false ), FILTER_VALIDATE_BOOLEAN );

		// Only format the value for non-HTML5 inputs.
		if ( ! $options[ static::$type . '_html5' ] ) {
			// Ensure proper format
			if ( is_array( $value ) ) {
				foreach ( $value as $k => $repeatable_value ) {
					$value[ $k ] = $this->format( $repeatable_value, $name, $options, $pod, $id );
				}
			} else {
				$value = $this->format( $value, $name, $options, $pod, $id );
			}
		}

		if ( ! empty( $options['disable_dfv'] ) ) {
			return pods_view( PODS_DIR . 'ui/fields/number.php', compact( array_keys( get_defined_vars() ) ) );
		}

		$type = pods_v( 'type', $options, static::$type );

		$args = compact( array_keys( get_defined_vars() ) );
		$args = (object) $args;

		$this->render_input_script( $args );
	}

	/**
	 * {@inheritdoc}
	 */
	public function regex( $value = null, $name = null, $options = null, $pod = null, $id = null ) {

		$format_args = $this->get_number_format_args( $options );
		$thousands   = $format_args['thousands'];
		$dot         = $format_args['dot'];

		return '\-*[0-9\\' . implode( '\\', array_filter( [ $dot, $thousands ] ) ) . ']+';
	}

	/**
	 * {@inheritdoc}
	 */
	public function validate( $value, $name = null, $options = null, $fields = null, $pod = null, $id = null, $params = null ) {
		$validate = parent::validate( $value, $name, $options, $fields, $pod, $id, $params );

		$errors = [];

		if ( is_array( $validate ) ) {
			$errors = $validate;
		}

		$format_args = $this->get_number_format_args( $options );
		$thousands   = $format_args['thousands'];
		$dot         = $format_args['dot'];

		$check = str_replace(
			[ $thousands, $dot, html_entity_decode( $thousands, ENT_COMPAT ) ],
			[ '', '.', '' ],
			$value
		);

		$check = trim( $check );

		$check = preg_replace( '/[0-9\.\-\s]/', '', $check );

		$label = pods_v( 'label', $options, ucwords( str_replace( '_', ' ', $name ) ) );

		if ( 0 < strlen( (string) $check ) ) {
			// Translators: %s stands for the input value.
			$errors[] = sprintf( esc_html__( '%s is not numeric', 'pods' ), $label );
		}

		if ( ! empty( $errors ) ) {
			return $errors;
		}

		return $validate;
	}

	/**
	 * {@inheritdoc}
	 */
	public function pre_save( $value, $id = null, $name = null, $options = null, $fields = null, $pod = null, $params = null ) {

		$format_args = $this->get_number_format_args( $options );
		$thousands   = $format_args['thousands'];
		$dot         = $format_args['dot'];
		$decimals    = $format_args['decimals'];

		// Slider only supports `1234.00` format so no need for replacing characters.
		if ( 'slider' !== pods_v( static::$type . '_format_type', $options ) ) {
			// Not a slider so we need to replace format characters.
			$value = str_replace(
				[ $thousands, html_entity_decode( $thousands, ENT_COMPAT ), $dot, html_entity_decode( $dot, ENT_COMPAT ) ],
				[ '', '', '.', '.' ],
				$value
			);

			// HTML5 supports both `1234.00` and `1234,00` formats so let's replace commas as decimals (thousands replaced above).
			if ( 1 === (int) pods_v( static::$type . '_html5', $options, false ) ) {
				$value = str_replace( ',', '.', $value );
			}
		}

		$value = trim( $value );

		$value = preg_replace( '/[^0-9\.\-]/', '', $value );

		if ( $this->is_empty( $value ) && ( ! is_numeric( $value ) || 0.0 !== (float) $value ) ) {
			// Don't enforce a default value here.
			return null;
		}

		$value = number_format( (float) $value, $decimals, '.', '' );

		// Optionally remove trailing decimal zero's.
		if ( pods_v( static::$type . '_format_soft', $options, false ) ) {
			$value = $this->trim_decimals( $value, '.' );
		}

		return $value;
	}

	/**
	 * {@inheritdoc}
	 */
	public function format( $value = null, $name = null, $options = null, $pod = null, $id = null ) {

		if ( $this->is_empty( $value ) && ( ! is_numeric( $value ) || 0.0 !== (float) $value ) ) {
			// Don't enforce a default value here.
			return null;
		}

		$format_args = $this->get_number_format_args( $options );
		$thousands   = $format_args['thousands'];
		$dot         = $format_args['dot'];
		$decimals    = $format_args['decimals'];

		if ( 'i18n' === pods_v( static::$type . '_format', $options ) ) {
			$value = number_format_i18n( (float) $value, $decimals );
		} else {
			$value = number_format( (float) $value, $decimals, $dot, $thousands );
		}

		// Additional output handling for decimals
		$decimals = (int) pods_v( static::$type . '_decimals', $options, 2 );

		if ( $decimals < 1 ) {
			return $value;
		}

		$decimal_handling = pods_v( static::$type . '_decimal_handling', $options, 'none' );

		if ( 'none' === $decimal_handling && (bool) pods_v( static::$type . '_format_soft', $options, false ) ) {
			$decimal_handling = 'remove';
		}

		if ( 'none' !== $decimal_handling ) {
			$format_args = $this->get_number_format_args( $options );
			$dot         = $format_args['dot'];
			$thousands   = $format_args['thousands'];

			$value_parts = explode( $dot, $value );

			$value_number_int = isset( $value_parts[0] ) ? (int) str_replace( $thousands, '', $value_parts[0] ) : 0;
			$is_zero          = 0 === $value_number_int;

			if ( isset( $value_parts[1] ) && 0 === (int) $value_parts[1] ) {
				switch ( $decimal_handling ) {
					case 'remove':
						// Remove decimals.
						$value = $value_parts[0];
						break;

					case 'remove_only_zero':
						// Remove decimals only when number is zero.
						if ( $is_zero ) {
							$value = $value_parts[0];
						}
						break;

					case 'dash':
						// Convert to dash.
						$value = $value_parts[0] . $dot . '-';
						break;

					case 'dash_only_zero':
						// Convert to dash only when number is zero.
						if ( $is_zero ) {
							$value = $value_parts[0] . $dot . '-';
						}
						break;

					case 'dash_whole_zero':
						// Convert to the whole value to dash when number is zero.
						if ( $is_zero ) {
							$value = '-';
						}
						break;
				}
			}
		}

		return $value;
	}

	/**
	 * Trim trailing 0 decimals from numbers.
	 *
	 * @since 2.7.15
	 *
	 * @param string $value
	 * @param string $dot
	 *
	 * @return string
	 */
	public function trim_decimals( $value, $dot ) {
		$parts = explode( $dot, $value );

		if ( isset( $parts[1] ) ) {
			$parts[1] = rtrim( $parts[1], '0' );

			if ( empty( $parts[1] ) ) {
				unset( $parts[1] );
			}
		}

		return implode( $dot, $parts );
	}

	/**
	 * Get the formatting arguments for numbers.
	 *
	 * @since 2.7.0
	 *
	 * @param array $options Field options.
	 *
	 * @return array {
	 * @type string $thousands
	 * @type string $dot
	 * @type int    $decimals
	 *                       }
	 */
	public function get_number_format_args( $options ) {

		$format = pods_v( static::$type . '_format', $options );
		$format = pods_unslash( $format );

		switch ( $format ) {
			case '9.999,99':
				$thousands = '.';
				$dot       = ',';
				break;
			case '9,999.99':
				$thousands = ',';
				$dot       = '.';
				break;
			case '9\'999.99':
				$thousands = '\'';
				$dot       = '.';
				break;
			case '9 999,99':
				$thousands = ' ';
				$dot       = ',';
				break;
			case '9999.99':
				$thousands = '';
				$dot       = '.';
				break;
			case '9999,99':
				$thousands = '';
				$dot       = ',';
				break;
			default:
				global $wp_locale;
				$thousands = $wp_locale->number_format['thousands_sep'];
				$dot       = $wp_locale->number_format['decimal_point'];
				break;
		}

		$decimals = $this->get_max_decimals( $options );

		return [
			'thousands' => $thousands,
			'dot'       => $dot,
			'decimals'  => $decimals,
		];
	}

	/**
	 * Get the max allowed decimals.
	 *
	 * @since 2.7.0
	 *
	 * @param array $options Field options.
	 *
	 * @return int
	 */
	public function get_max_decimals( $options ) {

		$length = (int) pods_v( static::$type . '_max_length', $options, 12, true );

		if ( $length < 1 || 64 < $length ) {
			$length = 64;
		}

		$decimals = (int) pods_v( static::$type . '_decimals', $options, 0 );

		if ( $decimals < 1 ) {
			$decimals = 0;
		} elseif ( 30 < $decimals ) {
			$decimals = 30;
		}

		if ( $length < $decimals ) {
			$decimals = $length;
		}

		return $decimals;
	}
}
