<?php
/**
 * FE Admin Event Input Fields controller.
 *
 * @since 1.4 Event authorization restriction
 */

declare( strict_types=1 );
namespace FeData\FastEvents\RestAPI\Public;

defined( 'ABSPATH' ) || exit;

use FeData\FastEvents\Helpers\AuthHelper;
use FeData\FastEvents\Utilities\JsonUtil;
use FeData\FastEvents\Utilities\UniqIds;
use FeData\FastEvents\Utilities\WebhookUtil;
use stdClass;
use WP_Error;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

/**
 * FE Admin Events Input Fields class.
 */
class InputFieldsController extends WP_REST_Controller {

	private const CREATE = 0;
	private const UPDATE = 1;
	private const DELETE = 2;

	/**
	 * New input field id.
	 *
	 * @since 1.0
	 *
	 * @var string
	 */
	private string $new_id;

	/**
	 * Event id.
	 *
	 * @since 1.0
	 *
	 * @var int
	 */
	private int $event_id;


	/**
	 * Admin Event Input Fields constructor.
	 *
	 * @param AuthHelper $auth The auth helper instance.
	 *
	 * @since 1.0
	 */
	public function __construct( private readonly AuthHelper $auth ) {
		$this->namespace = 'fast-events/v1';
		$this->rest_base = 'admin/events';
	}


	/**
	 * Register the routes for the objects of the controller.
	 *
	 * @since 1.0
	 * @since 2.0 Refactored for a new permission system
	 */
	public function register_routes(): void {

		// Get input fields.
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)/input-fields',
			array(
				'args'   => array(
					'id' => array(
						'description'       => __( 'The unique id of the event', 'fast-events' ),
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => function () {
						return $this->fast_events_admin_permissions( $this->auth::INPUT_FIELDS_READ );
					},
					'args'                => $this->get_collection_params(),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_item' ),
					'permission_callback' => function () {
						return $this->fast_events_admin_permissions( $this->auth::INPUT_FIELDS_CHANGE );
					},
					'args'                => $this->get_endpoint_args_for_item_schema(),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)/input-fields/(?P<field>v[0-9,a-f]{5,6})',
			array(
				'args'   => array(
					'id'    => array(
						'description'       => __( 'The unique id of the event', 'fast-events' ),
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
					),
					'field' => array(
						'description'       => __( 'The unique id of the input field', 'fast-events' ),
						'type'              => 'string',
						'required'          => true,
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => function () {
						return $this->fast_events_admin_permissions( $this->auth::INPUT_FIELDS_READ );
					},
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_item' ),
					'permission_callback' => function () {
						return $this->fast_events_admin_permissions( $this->auth::INPUT_FIELDS_CHANGE );
					},
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_item' ),
					'permission_callback' => function () {
						return $this->fast_events_admin_permissions( $this->auth::INPUT_FIELDS_CHANGE );
					},
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)/input-fields/(?P<field>v[0-9,a-f]{5,6})/reorder',
			array(
				'args'   => array(
					'id'    => array(
						'description'       => __( 'The unique id of the event', 'fast-events' ),
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
					),
					'field' => array(
						'description'       => __( 'The unique id of the input field', 'fast-events' ),
						'type'              => 'string',
						'required'          => true,
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'reorder_item' ),
					'permission_callback' => function () {
						return $this->fast_events_admin_permissions( $this->auth::TICKET_TYPES_CHANGE );
					},
					'args'                => array(
						'old_index' => array(
							'required'          => true,
							'type'              => 'integer',
							'sanitize_callback' => 'absint',
						),
						'new_index' => array(
							'required'          => true,
							'type'              => 'integer',
							'sanitize_callback' => 'absint',
						),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}


	/**
	 * Get the authorization for the Events Input Fields endpoints.
	 *
	 * @param int $action The action on the controller.
	 * @return WP_Error|bool
	 *
	 * @since 1.0
	 * @since 2.0 Refactored for a new permission system
	 */
	public function fast_events_admin_permissions( int $action ): WP_Error|bool {
		return $this->auth->authorize( $action );
	}


	/**
	 * Get all input fields of the event.
	 *
	 * @param WP_REST_Request $request Request instance.
	 * @return WP_REST_Response|WP_Error|array
	 *
	 * @since 1.0
	 */
	public function get_items( $request ): WP_Error|WP_REST_Response|array {

		$result = $this->get_event_data( $request );
		if ( is_wp_error( $result ) ) {
			return $result;
		}

		$input_fields = array();

		foreach ( $result as $item ) {
			$input_fields[] = $this->prepare_response_for_collection(
				$this->prepare_item_for_response( $item, $request )
			);
		}

		$response = rest_ensure_response( $input_fields );

		// Generate the totals headers.
		$response->header( 'X-WP-Total', count( $input_fields ) );
		$response->header( 'X-WP-TotalPages', 1 );

		return $response;
	}


	/**
	 * Retrieve input_fields.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return array|WP_Error Response object on success, or WP_Error object on failure.
	 *
	 * @since 1.0
	 * @since 1.4.0 Event authorization restriction
	 * @since 2.1.0 Added min, max and is_personalised
	 */
	private function get_event_data( WP_REST_Request $request ): WP_Error|array {
		global $wpdb;

		// Check the event authorization.
		$resp = $this->auth->check_event_authorization( absint( $request['id'] ) );
		if ( is_wp_error( $resp ) ) {
			return $resp;
		}

		// Look up the event.
		$event = $wpdb->get_row( $wpdb->prepare( "SELECT id, input_fields FROM {$wpdb->prefix}fe_events WHERE id = %d", $request['id'] ) );
		if ( empty( $event ) ) {
			return new WP_Error( 'not_found', __( 'Event not found', 'fast-events' ), array( 'status' => 404 ) );
		}

		$inp = JsonUtil::get_input_fields( $event->input_fields );

		// Save event_id.
		$this->event_id = (int) $event->id;

		// Add all fields to the structure.
		$input_fields = array();
		if ( isset( $inp->fields ) ) {
			foreach ( $inp->fields as $field ) {
				$tmp['id']              = $field->id;
				$tmp['name']            = $field->name;
				$tmp['type']            = $field->type;
				$tmp['value']           = $field->value;
				$tmp['min']             = $field->min ?? '';
				$tmp['max']             = $field->max ?? '';
				$tmp['is_required']     = 1 === (int) ( $field->required ?? 0 );
				$tmp['is_personalised'] = 1 === (int) ( $field->personalise ?? 0 );
				$input_fields[]         = $tmp;
			}
		}

		return $input_fields;
	}


	/**
	 * Retrieve a single input_field.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error|array Response object on success, or WP_Error object on failure.
	 *
	 * @since 1.0
	 */
	public function get_item( $request ): WP_Error|WP_REST_Response|array {

		// Look up the input fields.
		$result = $this->get_event_data( $request );
		if ( is_wp_error( $result ) ) {
			return $result;
		}

		// Does this id exist?
		$key = array_search( $request['field'], array_column( $result, 'id' ), true );
		if ( false === $key ) {
			return new WP_Error( 'not_found', __( 'Input field not found', 'fast-events' ), array( 'status' => 404 ) );
		}

		return rest_ensure_response( $this->prepare_item_for_response( $result[ $key ], $request ) );
	}


	/**
	 * Create a new input field.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error|bool|array Response object on success, or WP_Error object on failure.
	 *
	 * @since 1.0
	 * @since 2.1.0 Added min, max and is_personalised
	 */
	public function create_item( $request ): WP_Error|WP_REST_Response|bool|array {

		// Look up the input fields.
		$result = $this->get_event_data( $request );
		if ( is_wp_error( $result ) ) {
			return $result;
		}

		$input_field = array();

		// Is a name provided?
		if ( isset( $request['name'] ) ) {
			$name = $request['name'];

			// 'Name' and 'Email' are not allowed as name field.
			if ( 'name' === strtolower( $name ) || 'email' === strtolower( $name ) ) {
				return new WP_Error( 'invalid_name', __( '"Name" and "Email" is not allowed as input field name', 'fast-events' ), array( 'status' => 400 ) );
			}

			if ( in_array( $name, array_column( $result, 'name' ), true ) ) {
				return new WP_Error( 'invalid_key', __( 'Input field name already exists', 'fast-events' ), array( 'status' => 400 ) );
			}
			$input_field['name'] = $name;
		} else {
			return new WP_Error( 'no_name', __( 'Name field is missing', 'fast-events' ), array( 'status' => 400 ) );
		}

		// Is the type provided?
		if ( isset( $request['type'] ) ) {
			$input_field['type'] = $request['type'];
		}

		// Is the value provided?
		if ( isset( $request['value'] ) ) {
			$input_field['value'] = $request['value'];
		}

		// Is the minimum value/length provided?
		if ( isset( $request['min'] ) ) {
			$input_field['min'] = $request['min'];
		}

		// Is the maximum value/length provided?
		if ( isset( $request['max'] ) ) {
			$input_field['max'] = $request['max'];
		}

		// Is the required flag provided?
		if ( isset( $request['is_required'] ) ) {
			$input_field['is_required'] = $request['is_required'];
		}

		// Is the personalised flag provided?
		if ( isset( $request['is_personalised'] ) ) {
			$input_field['is_personalised'] = $request['is_personalised'];
		}

		// Update event.
		$resp = $this->update_event_data( $input_field );
		if ( is_wp_error( $resp ) ) {
			return $resp;
		}
		$input_field['id'] = $this->new_id;

		$request->set_param( 'context', 'view' );

		return rest_ensure_response( $this->prepare_item_for_response( $input_field, $request ) );
	}


	/**
	 * Update a single input field.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error|bool|array Response object on success, or WP_Error object on failure.
	 *
	 * @since 1.0
	 * @since 2.1.0 Added min, max and is_personalised
	 */
	public function update_item( $request ): WP_Error|WP_REST_Response|bool|array {

		// Look up the input fields.
		$result = $this->get_event_data( $request );
		if ( is_wp_error( $result ) ) {
			return $result;
		}

		// Does this key exist?
		$key = array_search( $request['field'], array_column( $result, 'id' ), true );
		if ( false === $key ) {
			return new WP_Error( 'not_found', __( 'Input field not found', 'fast-events' ), array( 'status' => 404 ) );
		}

		// Did the name change?
		if ( isset( $request['name'] ) ) {
			$name = $request['name'];

			// 'Name' and 'Email' are not allowed as name field.
			if ( 'name' === strtolower( $name ) || 'email' === strtolower( $name ) ) {
				return new WP_Error( 'invalid_name', __( '"Name" and "Email" is not allowed as input field name', 'fast-events' ), array( 'status' => 400 ) );
			}

			$idx = array_search( $name, array_column( $result, 'name' ), true );
			if ( false !== $idx && $idx !== $key ) {
				return new WP_Error( 'invalid_key', __( 'Input field name already exists', 'fast-events' ), array( 'status' => 400 ) );
			}
			$result[ $key ]['name'] = $name;
		}

		// Did the type change?
		if ( isset( $request['type'] ) ) {
			$result[ $key ]['type'] = $request['type'];
		}

		// Did the value change?
		if ( isset( $request['value'] ) ) {
			$result[ $key ]['value'] = $request['value'];
		}

		// Did the minimum value/length change?
		if ( isset( $request['min'] ) ) {
			$result[ $key ]['min'] = $request['min'];
		}

		// Did the maximum value/length change?
		if ( isset( $request['max'] ) ) {
			$result[ $key ]['max'] = $request['max'];
		}

		// Did the required flag change?
		if ( isset( $request['is_required'] ) ) {
			$result[ $key ]['is_required'] = $request['is_required'];
		}

		// Did the personalised flag change?
		if ( isset( $request['is_personalised'] ) ) {
			$result[ $key ]['is_personalised'] = $request['is_personalised'];
		}

		// Update event.
		$resp = $this->update_event_data( $result[ $key ], self::UPDATE );
		if ( is_wp_error( $resp ) ) {
			return $resp;
		}

		$request->set_param( 'context', 'edit' );

		return rest_ensure_response( $this->prepare_item_for_response( $result[ $key ], $request ) );
	}


	/**
	 * Reorder a single input field.
	 * The frontend (FE Admin App) dragged the item to another position
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error|bool|array|stdClass Response object on success, or WP_Error object on failure.
	 *
	 * @since 2.0
	 * @since 2.3.0 Use fast_events_json_encode.
	 */
	public function reorder_item( WP_REST_Request $request ): WP_Error|WP_REST_Response|bool|array|stdClass {
		global $wpdb;

		// Start the transaction.
		$wpdb->query( 'START TRANSACTION' );

		// Look up the event.
		$event = $wpdb->get_row( $wpdb->prepare( "SELECT input_fields FROM {$wpdb->prefix}fe_events WHERE id = %d FOR UPDATE", $request['id'] ) );
		if ( empty( $event ) ) {
			$wpdb->query( 'ROLLBACK' );

			return new WP_Error( 'not_found', __( 'Event not found', 'fast-events' ), array( 'status' => 404 ) );
		}

		$inp = JsonUtil::get_input_fields( $event->input_fields );

		// Does this id exist?
		if ( ! in_array( $request['field'], array_column( $inp->fields, 'id' ), true ) ) {
			$wpdb->query( 'ROLLBACK' );

			return new WP_Error( 'not_found', __( 'Input field not found', 'fast-events' ), array( 'status' => 404 ) );
		}

		// Are the indexes in range?
		if ( $request['old_index'] > count( $inp->fields ) || $request['new_index'] > count( $inp->fields ) ) {
			$wpdb->query( 'ROLLBACK' );

			return new WP_Error( 'invalid_index', __( 'Index out of range', 'fast-events' ), array( 'status' => 400 ) );
		}

		// Reorder element.
		$moved_item = array_splice( $inp->fields, $request['old_index'], 1 );
		array_splice( $inp->fields, $request['new_index'], 0, $moved_item );

		// Update event.
		$result = $wpdb->update(
			$wpdb->prefix . 'fe_events',
			array( 'input_fields' => JsonUtil::fast_events_json_encode( $inp ) ),
			array( 'id' => $request['id'] )
		);

		// Analyse the update.
		if ( false === $result ) {
			$error = $wpdb->last_error;
			$wpdb->query( 'ROLLBACK' );

			return new WP_Error( 'failed', __( 'Rearrange input field failed: ', 'fast-events' ) . $error, array( 'status' => 400 ) );
		}
		if ( 0 === $result ) {
			$wpdb->query( 'ROLLBACK' );

			return new WP_Error( 'nothing_to_update', __( 'Rearrange input field: nothing to update', 'fast-events' ), array( 'status' => 400 ) );
		}

		$wpdb->query( 'COMMIT' );

		return rest_ensure_response( array( 'result' => true ) );
	}


	/**
	 * Delete a single input field.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error|bool|array Response object on success, or WP_Error object on failure.
	 *
	 * @since 1.0
	 */
	public function delete_item( $request ): WP_Error|WP_REST_Response|bool|array {

		// Look up the input fields.
		$result = $this->get_event_data( $request );
		if ( is_wp_error( $result ) ) {
			return $result;
		}

		// Does this input field exist?
		$key = array_search( $request['field'], array_column( $result, 'id' ), true );
		if ( false === $key ) {
			return new WP_Error( 'not_found', __( 'Input field not found', 'fast-events' ), array( 'status' => 404 ) );
		}
		$old_data = $result[ $key ];

		// Delete the input field and update the event.
		$resp = $this->update_event_data( $result[ $key ], self::DELETE );
		if ( is_wp_error( $resp ) ) {
			return $resp;
		}

		$request->set_param( 'context', 'edit' );

		$previous = $this->prepare_item_for_response( $old_data, $request );

		$response = new WP_REST_Response();
		$response->set_data(
			array(
				'deleted'  => true,
				'previous' => $previous->get_data(),
			)
		);

		return $response;
	}


	/**
	 * Update event data.
	 *
	 * @param array $input_field Input field object.
	 * @param int   $method      Type of action (create, update, or delete).
	 * @return bool|WP_Error True, or WP_Error object on failure.
	 *
	 * @since 1.0
	 * @since 1.4.0 Event authorization restriction
	 * @since 1.6.0 - Replaced unset with array_splice eliminating json_encode side effects
	 *              - ROLLBACK after errors
	 * @since 2.1.0 Added min, max and is_personalised
	 * @since 2.3.0 Use fast_events_json_encode.
	 */
	private function update_event_data( array $input_field, int $method = self::CREATE ): WP_Error|bool {
		global $wpdb;

		// Check the event authorization.
		$resp = $this->auth->check_event_authorization( absint( $this->event_id ) );
		if ( is_wp_error( $resp ) ) {
			return $resp;
		}

		// Start the transaction.
		$wpdb->query( 'START TRANSACTION' );

		// Look up the event.
		$event = $wpdb->get_row( $wpdb->prepare( "SELECT input_fields, webhooks_needed FROM {$wpdb->prefix}fe_events WHERE id = %d FOR UPDATE", $this->event_id ) );
		if ( empty( $event ) ) {
			$wpdb->query( 'ROLLBACK' );

			return new WP_Error( 'not_found', __( 'Event not found', 'fast-events' ), array( 'status' => 404 ) );
		}

		$inp = JsonUtil::get_input_fields( $event->input_fields );

		switch ( $method ) {
			case self::DELETE:
				// Does this id exist?
				$key = array_search( $input_field['id'], array_column( $inp->fields, 'id' ), true );
				if ( false === $key ) {
					$wpdb->query( 'ROLLBACK' );

					return new WP_Error( 'not_found', __( 'Input field not found', 'fast-events' ), array( 'status' => 404 ) );
				}

				// Remove row.
				array_splice( $inp->fields, $key, 1 );
				break;

			case self::UPDATE:
				// Does this id exist?
				$key = array_search( $input_field['id'], array_column( $inp->fields, 'id' ), true );
				if ( false === $key ) {
					$wpdb->query( 'ROLLBACK' );

					return new WP_Error( 'not_found', __( 'Input field not found', 'fast-events' ), array( 'status' => 404 ) );
				}

				// Update row.
				$inp->fields[ $key ]->name        = $input_field['name'];
				$inp->fields[ $key ]->type        = $input_field['type'];
				$inp->fields[ $key ]->value       = $input_field['value'];
				$inp->fields[ $key ]->min         = $input_field['min'];
				$inp->fields[ $key ]->max         = $input_field['max'];
				$inp->fields[ $key ]->required    = true === ( $input_field['is_required'] ?? 1 ) ? 1 : 0;
				$inp->fields[ $key ]->personalise = true === ( $input_field['is_personalised'] ?? 1 ) ? 1 : 0;

				// Check for errors.
				$result = $this->check_input( $inp->fields[ $key ] );
				if ( is_wp_error( $result ) ) {
					return $result;
				}
				break;

			case self::CREATE:
				// Does this new name exist?
				$key = in_array( $input_field['name'], array_column( $inp->fields, 'name' ), true );
				if ( false !== $key ) {
					$wpdb->query( 'ROLLBACK' );

					return new WP_Error( 'not_found', __( 'Input field name already exists', 'fast-events' ), array( 'status' => 400 ) );
				}

				// Create a new row.
				$tmp              = new stdClass();
				$tmp->id          = 'v' . ( count( $inp->fields ) + count( $inp->tickets ) ) . UniqIds::uniq_field();
				$this->new_id     = $tmp->id;
				$tmp->name        = $input_field['name'];
				$tmp->type        = $input_field['type'];
				$tmp->value       = $input_field['value'] ?? '';
				$tmp->min         = $input_field['min'] ?? '';
				$tmp->max         = $input_field['max'] ?? '';
				$tmp->required    = true === ( $input_field['is_required'] ?? 0 ) ? 1 : 0;
				$tmp->personalise = true === ( $input_field['is_personalised'] ?? 0 ) ? 1 : 0;
				$inp->fields[]    = $tmp;

				// Check for errors.
				$result = $this->check_input( $tmp );
				if ( is_wp_error( $result ) ) {
					return $result;
				}
				break;
		}

		// Update event.
		$result = $wpdb->update( $wpdb->prefix . 'fe_events', array( 'input_fields' => JsonUtil::fast_events_json_encode( $inp ) ), array( 'id' => $this->event_id ) );

		// Analyse the update.
		if ( false === $result ) {
			$error = $wpdb->last_error;
			$wpdb->query( 'ROLLBACK' );

			return new WP_Error( 'failed', __( 'Input fields update failed: ', 'fast-events' ) . $error, array( 'status' => 400 ) );
		}
		if ( 0 === $result ) {
			$wpdb->query( 'ROLLBACK' );

			return new WP_Error( 'nothing_to_update', __( 'Input fields update: nothing to update', 'fast-events' ), array( 'status' => 400 ) );
		}

		$wpdb->query( 'COMMIT' );

		// Fire webhooks if needed.
		if ( 1 === (int) $event->webhooks_needed ) {
			WebhookUtil::create_webhook( 'event.updated', $this->event_id );
		}

		return true;
	}


	/**
	 * Check the min/max values or length of the input type.
	 *
	 * @param stdClass $inp Input field object.
	 * @return WP_Error|bool.
	 *
	 * @since 2.1.0
	 */
	private function check_input( stdClass $inp ): WP_Error|bool {
		switch ( $inp->type ) {
			case 'text':
			case 'password':
				if ( ( ! empty( $inp->min ) && ! is_numeric( $inp->min ) ) || ( ! empty( $inp->max ) && ! is_numeric( $inp->max ) ) ) {
					return new WP_Error( 'format_error', __( 'Non-numeric minimum or maximum length', 'fast-events' ), array( 'staus' => 400 ) );
				}
				$min = intval( $inp->min );
				$max = intval( $inp->max );
				if ( $min < 0 || $max > 50 || $min > $max ) {
					return new WP_Error( 'range_error', __( 'Minimum and maximum length must be between 0 and 50', 'fast-events' ), array( 'staus' => 400 ) );
				}
				break;
			case 'number':
				if ( ( ! empty( $inp->min ) && ! is_numeric( $inp->min ) ) || ( ! empty( $inp->max ) && ! is_numeric( $inp->max ) ) ) {
					return new WP_Error( 'format_error', __( 'Non-numeric minimum or maximum value', 'fast-events' ), array( 'staus' => 400 ) );
				}
				$min = intval( $inp->min );
				$max = intval( $inp->max );
				if ( $min > $max ) {
					return new WP_Error( 'range_error', __( 'Minimum value cannot be greater than the maximum value', 'fast-events' ), array( 'staus' => 400 ) );
				}
				break;
			case 'time':
				$format = 'H:i';
				if ( ! empty( $inp->min ) ) {
					$from = date_create_from_format( $format, $inp->min );
					if ( false === $from ) {
						return new WP_Error( 'invalid_date', __( 'Invalid minimum time (HH:MM)', 'fast-events' ), array( 'staus' => 400 ) );
					}
				}
				if ( ! empty( $inp->max ) ) {
					$to = date_create_from_format( $format, $inp->max );
					if ( false === $to ) {
						return new WP_Error( 'invalid_date', __( 'Invalid maximum time (HH:MM)', 'fast-events' ), array( 'staus' => 400 ) );
					}
				}
				if ( ! empty( $inp->min ) && ! empty( $inp->max ) && $inp->min > $inp->max ) {
					return new WP_Error( 'invalid_date', __( 'Minimum time cannot be greater than the maximum time', 'fast-events' ), array( 'staus' => 400 ) );
				}
				break;
			case 'date':
				$format = 'Y-m-d';
				if ( ! empty( $inp->min ) ) {
					$from = date_create_from_format( $format, $inp->min );
					if ( false === $from ) {
						return new WP_Error( 'invalid_date', __( 'Invalid minimum date (YYYY-MM-DD)', 'fast-events' ), array( 'staus' => 400 ) );
					}
				}
				if ( ! empty( $inp->max ) ) {
					$to = date_create_from_format( $format, $inp->max );
					if ( false === $to ) {
						return new WP_Error( 'invalid_date', __( 'Invalid maximum date (YYYY-MM-DD)', 'fast-events' ), array( 'staus' => 400 ) );
					}
				}
				if ( ! empty( $inp->min ) && ! empty( $inp->max ) && $inp->min > $inp->max ) {
					return new WP_Error( 'invalid_date', __( 'Minimum date cannot be greater than the maximum date', 'fast-events' ), array( 'staus' => 400 ) );
				}
				break;
			default:
				break;
		}

		return true;
	}


	/**
	 * Prepares a single input field output for response.
	 *
	 * @param mixed           $item Input field object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 *
	 * @since 1.0
	 * @since 2.1.0 Added min, max and is_personalised
	 */
	public function prepare_item_for_response( $item, $request ): WP_REST_Response {

		$data   = array();
		$fields = $this->get_fields_for_response( $request );

		if ( rest_is_field_included( 'id', $fields ) ) {
			$data['id'] = $item['id'];
		}

		if ( rest_is_field_included( 'name', $fields ) ) {
			$data['name'] = $item['name'];
		}

		if ( rest_is_field_included( 'type', $fields ) ) {
			$data['type'] = $item['type'];
		}

		if ( rest_is_field_included( 'value', $fields ) ) {
			$data['value'] = $item['value'];
		}

		if ( rest_is_field_included( 'min', $fields ) ) {
			$data['min'] = $item['min'];
		}

		if ( rest_is_field_included( 'max', $fields ) ) {
			$data['max'] = $item['max'];
		}

		if ( rest_is_field_included( 'is_required', $fields ) ) {
			$data['is_required'] = $item['is_required'];
		}

		if ( rest_is_field_included( 'is_personalised', $fields ) ) {
			$data['is_personalised'] = $item['is_personalised'];
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'embed';

		$data = $this->add_additional_fields_to_object( $data, $request );
		$data = $this->filter_response_by_context( $data, $context );

		// Wrap the data in a response object.
		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$response->add_links( $this->prepare_links( $item ) );
		}

		return $response;
	}


	/**
	 * Prepares links for the input_fields request.
	 *
	 * @param array $field Input field object.
	 * @return array Links for the given order.
	 *
	 * @since 1.0
	 */
	protected function prepare_links( array $field ): array {
		return array(
			'self'       => array(
				'href' => rest_url( sprintf( '%s/%s/%d/input-fields/%s', $this->namespace, $this->rest_base, $this->event_id, $field['id'] ) ),
			),
			'collection' => array(
				'href' => rest_url( sprintf( '%s/%s/%d/input-fields', $this->namespace, $this->rest_base, $this->event_id ) ),
			),
		);
	}


	/**
	 * Return the input fields schema.
	 *
	 * @return array
	 *
	 * @since 1.0
	 * @since 2.1.0 Added min, max and is_personalised.
	 */
	public function get_item_schema(): array {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$this->schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'input-fields',
			'type'       => 'object',
			'properties' => array(
				'id'              => array(
					'description' => __( 'The unique id of the input field.', 'fast-events' ),
					'context'     => array( 'view', 'embed' ),
					'type'        => 'string',
					'readonly'    => true,
				),
				'name'            => array(
					'description' => __( 'The name of the input field.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'string',
					'required'    => true,
					'minLength'   => 1,
					'maxLength'   => 10,
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
				'type'            => array(
					'description' => __( 'The type of input field.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'string',
					'enum'        => array( 'text', 'number', 'select', 'password', 'date', 'time' ),
					'default'     => 'text',
				),
				'value'           => array(
					'description' => __( 'Default value of the input field.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'string',
					'default'     => '',
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
				'min'             => array(
					'description' => __( 'The minimum value or length.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'string',
					'default'     => '',
					'maxLength'   => 10,
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
				'max'             => array(
					'description' => __( 'The maximum value or length.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'string',
					'default'     => '',
					'maxLength'   => 10,
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
				'is_required'     => array(
					'description' => __( 'Is input required for this field?', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'boolean',
					'default'     => true,
				),
				'is_personalised' => array(
					'description' => __( 'Is this field for the personalisation of a ticket?', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'boolean',
					'default'     => true,
				),
			),
		);

		return $this->add_additional_fields_schema( $this->schema );
	}
}
