<?php


class Tribe__Events__REST__V1__Endpoints__Single_Event
	extends Tribe__Events__REST__V1__Endpoints__Base
	implements Tribe__REST__Endpoints__READ_Endpoint_Interface,
	Tribe__REST__Endpoints__CREATE_Endpoint_Interface,
	Tribe__REST__Endpoints__DELETE_Endpoint_Interface,
    Tribe__REST__Endpoints__UPDATE_Endpoint_Interface,
	Tribe__Documentation__Swagger__Provider_Interface {

	/**
	 * @var Tribe__REST__Main
	 */
	protected $main;

	/**
	 * @var WP_REST_Request
	 */
	protected $serving;
	/**
	 * @var Tribe__Events__REST__Interfaces__Post_Repository
	 */
	protected $post_repository;

	/**
	 * @var Tribe__Events__REST__V1__Validator__Interface
	 */
	protected $validator;

	/**
	 * @var Tribe__Events__REST__V1__Endpoints__Linked_Post_Endpoint_Interface
	 */
	protected $venue_endpoint;

	/**
	 * @var Tribe__Events__REST__V1__Endpoints__Linked_Post_Endpoint_Interface
	 */
	protected $organizer_endpoint;

	/**
	 * Tribe__Events__REST__V1__Endpoints__Single_Event constructor.
	 *
	 * @param Tribe__REST__Messages_Interface                                    $messages
	 * @param Tribe__Events__REST__Interfaces__Post_Repository                   $post_repository
	 * @param Tribe__Events__REST__V1__Validator__Interface                      $validator
	 * @param Tribe__Events__REST__V1__Endpoints__Linked_Post_Endpoint_Interface $venue_endpoint
	 * @param Tribe__Events__REST__V1__Endpoints__Linked_Post_Endpoint_Interface $organizer_endpoint
	 */
	public function __construct(
		Tribe__REST__Messages_Interface $messages,
		Tribe__Events__REST__Interfaces__Post_Repository $post_repository,
		Tribe__Events__REST__V1__Validator__Interface $validator,
		Tribe__Events__REST__V1__Endpoints__Linked_Post_Endpoint_Interface $venue_endpoint,
		Tribe__Events__REST__V1__Endpoints__Linked_Post_Endpoint_Interface $organizer_endpoint
	) {
		parent::__construct( $messages );
		$this->post_repository = $post_repository;
		$this->validator = $validator;
		$this->venue_endpoint = $venue_endpoint;
		$this->organizer_endpoint = $organizer_endpoint;
	}

	/**
	 * @param WP_REST_Request $request
	 *
	 * @return WP_REST_Response|WP_Error An array containing the data on success or a WP_Error instance on failure.
	 */
	public function get( WP_REST_Request $request ) {
		$this->serving = $request;

		$event = get_post( $request['id'] );

		$cap = get_post_type_object( Tribe__Events__Main::POSTTYPE )->cap->read_post;
		if ( ! ( 'publish' === $event->post_status || current_user_can( $cap, $request['id'] ) ) ) {
			$message = $this->messages->get_message( 'event-not-accessible' );

			return new WP_Error( 'event-not-accessible', $message, [ 'status' => 403 ] );
		}

		if ( ! $this->validator->can_access_password_content( $event, $request ) ) {
			$message = $this->messages->get_message( 'event-password-protected' );

			return new WP_Error( 'event-password-protected', $message, [ 'status' => 403 ] );
		}

		$data = $this->post_repository->get_event_data( $request['id'], 'single' );

		/**
		 * Filters the data that will be returned for a single event request.
		 *
		 * @since 4.5.13
		 *
		 * @param array           $data    The retrieved data.
		 * @param WP_REST_Request $request The original request.
		 */
		$data = apply_filters( 'tribe_rest_single_event_data', $data, $request );

		return is_wp_error( $data ) ? $data : new WP_REST_Response( $data );
	}

	/**
	 * Returns an array in the format used by Swagger 2.0.
	 *
	 * While the structure must conform to that used by v2.0 of Swagger the structure can be that of a full document
	 * or that of a document part.
	 * The intelligence lies in the "gatherer" of information rather than in the single "providers" implementing this
	 * interface.
	 *
	 * @link http://swagger.io/
	 *
	 * @return array An array description of a Swagger supported component.
	 */
	public function get_documentation() {
		$get_defaults  = $delete_defaults = [ 'in' => 'query', 'type' => 'string' ];
		$post_args = $this->CREATE_args();

		return [
			'get'    => [
				'parameters' => $this->swaggerize_args( $this->READ_args(), $get_defaults ),
				'responses'  => [
					'200' => [
						'description' => __( 'Returns the data of the event with the specified post ID', 'the-events-calendar' ),
						'content' => [
							'application/json' => [
								'schema' => [
									'$ref' => '#/components/schemas/Event',
								]
							]
						],
					],
					'400' => [
						'description' => __( 'The event post ID is missing.', 'the-events-calendar' ),
					],
					'403' => [
						'description' => __( 'The event with the specified ID is not accessible.', 'the-events-calendar' ),
					],
					'404' => [
						'description' => __( 'An event with the specified ID does not exist.', 'the-events-calendar' ),
					],
				],
			],
			'post'   => [
				'parameters'  => $this->swaggerize_args( $this->READ_args(), [] ),
				'requestBody' => $this->swaggerize_post_args( 'application/x-www-form-urlencoded', $post_args ),
				'responses'  => [
					'200' => [
						'description' => __( 'Returns the data of the updated event', 'the-events-calendar' ),
						'content' => [
							'application/json' => [
								'schema' => [
									'$ref' => '#/components/schemas/Event',
								]
							]
						],
					],
					'201' => [
						'description' => __( 'Returns the data of the created event', 'the-events-calendar' ),
						'content' => [
							'application/json' => [
								'schema' => [
									'$ref' => '#/components/schemas/Event',
								]
							]
						],
					],
					'400' => [
						'description' => __( 'A required parameter is missing or an input parameter is in the wrong format', 'the-events-calendar' ),
					],
					'403' => [
						'description' => __( 'The user is not authorized to create events', 'the-events-calendar' ),
					],
				],
			],
			'delete' => [
				'parameters' => $this->swaggerize_args( $this->DELETE_args(), $delete_defaults ),
				'responses'  => [
					'200' => [
						'description' => __( 'Deletes an event and returns its data', 'the-events-calendar' ),
						'content' => [
							'application/json' => [
								'schema' => [
									'$ref' => '#/components/schemas/Event',
								]
							]
						],
					],
					'400' => [
						'description' => __( 'The event post ID is missing or does not exist.', 'the-events-calendar' ),
					],
					'403' => [
						'description' => __( 'The current user cannot delete the event with the specified ID.', 'the-events-calendar' ),
					],
					'410' => [
						'description' => __( 'The event with the specified ID has been deleted already.', 'the-events-calendar' ),
					],
					'500' => [
						'description' => __( 'The event with the specified ID could not be deleted.', 'the-events-calendar' ),
					],
				],
			],
		];
	}

	/**
	 * Provides the content of the `args` array to register the endpoint support for GET requests.
	 *
	 * @return array
	 */
	public function READ_args() {
		return [
			'id' => [
				'in'                => 'path',
				'type'              => 'integer',
				'description'       => __( 'the event post ID', 'the-events-calendar' ),
				'required'          => true,
				'validate_callback' => [ $this->validator, 'is_event_id' ],
			],
			'password' => [
				'in'                => 'path',
				'type'              => 'string',
				'description'       => __( 'The event password', 'the-events-calendar' ),
				'required'          => false,
				'validate_callback' => [ $this->validator, 'is_string' ],
			],
		];
	}

	/**
	 * Returns the content of the `args` array that should be used to register the endpoint
	 * with the `register_rest_route` function.
	 *
	 * @return array
	 */
	public function CREATE_args() {
		return [
			// Post fields
			'author'             => [
				'required'          => false,
				'validate_callback' => [ $this->validator, 'is_user_id' ],
				'type'              => 'integer',
				'description'       => __( 'The event author ID', 'the-events-calendar' ),
			],
			'date'               => [
				'required'          => false,
				'validate_callback' => [ $this->validator, 'is_time' ],
				'type'              => 'string',
				'description'       => __( 'The event publication date', 'the-events-calendar' ),
			],
			'date_utc'           => [
				'required'          => false,
				'validate_callback' => [ $this->validator, 'is_time' ],
				'type'              => 'string',
				'description'       => __( 'The event publication date (UTC time zone)', 'the-events-calendar' ),
			],
			'title'              => [
				'required'          => true,
				'validate_callback' => [ $this->validator, 'is_string' ],
				'type'              => 'string',
				'description'       => __( 'The event title', 'the-events-calendar' ),
			],
			'description'        => [
				'required'          => false,
				'validate_callback' => [ $this->validator, 'is_string' ],
				'type'              => 'string',
				'description'       => __( 'The event description', 'the-events-calendar' ),
			],
			'slug'               => [
				'required'          => false,
				'validate_callback' => [ $this->validator, 'is_string' ],
				'type'              => 'string',
				'description'       => __( 'The event slug', 'the-events-calendar' ),
			],
			'excerpt'            => [
				'required'          => false,
				'validate_callback' => [ $this->validator, 'is_string' ],
				'type'              => 'string',
				'description'       => __( 'The event excerpt', 'the-events-calendar' ),
			],
			'status'             => [
				'required'          => false,
				'validate_callback' => [ $this->validator, 'is_post_status' ],
				'type'              => 'string',
				'description'       => __( 'The event post status', 'the-events-calendar' ),
			],
			// Event meta fields
			'timezone'           => [
				'required'          => false,
				'validate_callback' => [ $this->validator, 'is_timezone_or_empty' ],
				'type'              => 'string',
				'description'       => __( 'The event time zone', 'the-events-calendar' ),
			],
			'all_day'            => [
				'required'    => false,
				'default'     => false,
				'type'        => 'boolean',
				'description' => __( 'Whether the event lasts the whole day or not', 'the-events-calendar' ),
			],
			'start_date'         => [
				'required'          => true,
				'validate_callback' => [ $this->validator, 'is_time' ],
				'type'              => 'string',
				'description'       => __( 'The event start date and time', 'the-events-calendar' ),
			],
			'end_date'           => [
				'required'          => true,
				'validate_callback' => [ $this->validator, 'is_time' ],
				'type'              => 'string',
				'description'       => __( 'The event end date and time', 'the-events-calendar' ),
			],
			'image'              => [
				'required'          => false,
				'validate_callback' => [ $this->validator, 'is_image' ],
				'type'              => 'string',
				'description'       => __( 'The event featured image ID or URL', 'the-events-calendar' ),
			],
			'cost'               => [
				'required'     => false,
				'swagger_type' => 'string',
				'description'  => __( 'The event cost', 'the-events-calendar' ),
			],
			'website'            => [
				'required'          => false,
				'validate_callback' => [ $this->validator, 'is_url_or_empty' ],
				'swagger_type'      => 'string',
				'default'           => null,
				'description'       => __( 'The event website URL', 'the-events-calendar' ),
			],
			// Event presentation data
			'show_map'           => [
				'required'    => false,
				'type'        => 'boolean',
				'description' => __( 'Whether the event should show a map or not', 'the-events-calendar' ),
			],
			'show_map_link'      => [
				'required'    => false,
				'type'        => 'boolean',
				'description' => __( 'Whether the event should show a map link or not', 'the-events-calendar' ),
			],
			'hide_from_listings' => [
				'required'    => false,
				'type'        => 'boolean',
				'description' => __( 'Whether events should be hidden in the calendar view or not', 'the-events-calendar' ),
			],
			'sticky'             => [
				'required'    => false,
				'type'        => 'boolean',
				'description' => __( 'Whether the event should be sticky in the calendar view or not', 'the-events-calendar' ),
			],
			'featured'           => [
				'required'    => false,
				'type'        => 'boolean',
				'description' => __( 'Whether the event should be featured on the site or not', 'the-events-calendar' ),
			],
			// Taxonomies
			'categories'         => [
				'required'     => false,
				'default'      => null,
				'swagger_type' => 'array',
				'items'        => [ 'type' => 'integer' ],
				'description'  => __( 'The event category ID or name', 'the-events-calendar' ),
			],
			'tags'               => [
				'required'     => false,
				'default'      => null,
				'swagger_type' => 'array',
				'items'        => [ 'type' => 'integer' ],
				'description'  => __( 'The event tag ID or name', 'the-events-calendar' ),
			],
			// Linked Posts
			'venue'              => [
				'required'          => false,
				'default'           => null,
				'validate_callback' => [ $this->validator, 'is_venue_id_or_entry_or_empty' ],
				'swagger_type'      => 'array',
				'items'             => [ 'type' => 'integer' ],
				'description'       => __( 'The event venue IDs or data', 'the-events-calendar' ),
			],
			'organizer'          => [
				'required'          => false,
				'default'           => null,
				'validate_callback' => [ $this->validator, 'is_organizer_id_or_entry_or_empty' ],
				'swagger_type'      => 'array',
				'items'             => [ 'type' => 'integer' ],
				'description'       => __( 'The event organizer IDs or data', 'the-events-calendar' ),
			],
		];
	}

	/**
	 * Handles POST requests on the endpoint.
	 *
	 * @param WP_REST_Request $request
	 * @param bool            $return_id Whether the created post ID should be returned or the full response object.
	 *
	 * @return WP_Error|WP_REST_Response|int An array containing the data on success or a WP_Error instance on failure.
	 */
	public function create( WP_REST_Request $request, $return_id = false ) {
		$this->serving = $request;

		$postarr = $this->prepare_postarr( $request );


		if ( is_wp_error( $postarr ) ) {
			return $postarr;
		}

		$id = Tribe__Events__API::createEvent( array_filter( $postarr ) );

		if ( is_wp_error( $id ) ) {
			return $id;
		}

		if ( $return_id ) {
			return $id;
		}

		$data = $this->post_repository->get_event_data( $id );
		$response = new WP_REST_Response( $data );
		$response->set_status( 201 );

		return $response;
	}

	/**
	 * Whether the current user can create content of the specified type or not.
	 *
	 * @return bool Whether the current user can post or not.
	 */
	public function can_create() {
		$cap = get_post_type_object( Tribe__Events__Main::POSTTYPE )->cap->edit_posts;

		return current_user_can( $cap );
	}

	/**
	 * Returns the content of the `args` array that should be used to register the endpoint
	 * with the `register_rest_route` function.
	 *
	 * @return array
	 */
	public function DELETE_args() {
		return $this->READ_args();
	}

	/**
	 * Handles DELETE requests on the endpoint.
	 *
	 * @param WP_REST_Request $request
	 *
	 * @return WP_Error|WP_REST_Response An array containing the data of the trashed post on
	 *                                   success or a WP_Error instance on failure.
	 */
	public function delete( WP_REST_Request $request ) {
		$event_id = $request['id'];

		$event = get_post( $event_id );

		if ( 'trash' === $event->post_status ) {
			$message = $this->messages->get_message( 'event-is-in-trash' );

			return new WP_Error( 'event-is-in-trash', $message, [ 'status' => 410 ] );
		}

		/**
		 * Filters the event delete operation.
		 *
		 * Returning a non `null` value here will override the default trashing operation.
		 *
		 * @param int|bool        $deleted Whether the event was successfully deleted or not.
		 * @param WP_REST_Request $request The original API request.
		 *
		 * @since 4.6
		 */
		$deleted = apply_filters( 'tribe_events_rest_event_delete', null, $request );
		if ( null === $deleted ) {
			$deleted = wp_trash_post( $event_id );
		}

		if ( false === $deleted ) {
			$message = $this->messages->get_message( 'could-not-delete-event' );

			return new WP_Error( 'could-not-delete-event', $message, [ 'status' => 500 ] );
		}

		$data = $this->post_repository->get_event_data( $event_id );

		return is_wp_error( $data ) ? $data : new WP_REST_Response( $data );
	}

	/**
	 * Whether the current user can delete posts of the type managed by the endpoint or not.
	 *
	 * @since 4.6
	 * @since 6.15.16.1 Add more logic to check if the user can delete the event.
	 *
	 * @param $request WP_REST_Request The request object.
	 *
	 * @return bool
	 */
	public function can_delete( ?WP_REST_Request $request = null ) {
		$id = $request['id'] ?? null;

		if ( ! $id ) {
			return current_user_can( get_post_type_object( Tribe__Events__Main::POSTTYPE )->cap->delete_posts );
		}

		return current_user_can( get_post_type_object( Tribe__Events__Main::POSTTYPE )->cap->delete_post, $id );
	}

	/**
	 * Handles UPDATE requests on the endpoint.
	 *
	 * @param WP_REST_Request $request
	 *
	 * @return WP_Error|WP_REST_Response An array containing the data of the updated post on
	 *                                   success or a WP_Error instance on failure.
	 */
	public function update( WP_REST_Request $request ) {
		$this->serving = $request;

		$postarr = $this->prepare_postarr( $request );

		if ( is_wp_error( $postarr ) ) {
			return $postarr;
		}

		$id = Tribe__Events__API::updateEvent( $request['id'], $postarr );

		if ( is_wp_error( $id ) ) {
			/** @var WP_Error $id */
			return $id;
		}

		if ( false === $id ) {
			$message = $this->messages->get_message( 'could-not-update-event' );

			return new WP_Error( 'could-not-update-event', $message, [ 'status' => 403 ] );
		}

		$data = $this->post_repository->get_event_data( $id );

		$response = new WP_REST_Response( $data );
		$response->set_status( 200 );

		return $response;
	}

	/**
	 * Returns the content of the `args` array that should be used to register the endpoint
	 * with the `register_rest_route` function.
	 *
	 * @return array
	 */
	public function EDIT_args() {
		// when editing the only required argument is the ID ('id')
		$create_args = $this->CREATE_args();
		array_walk( $create_args, [ $this, 'unrequire_arg' ] );

		return array_merge( $this->READ_args(), $create_args );
	}

	/**
	 * Whether the current user can update content of this type or not.
	 *
	 * @since 4.6
	 * @since 6.15.16.1 Add more logic to check if the user can edit the event.
	 *
	 * @param $request WP_REST_Request The request object.
	 *
	 * @return bool Whether the current user can update or not.
	 */
	public function can_edit( ?WP_REST_Request $request = null ) {
		$id = $request['id'] ?? null;

		if ( ! $id ) {
			return current_user_can( get_post_type_object( Tribe__Events__Main::POSTTYPE )->cap->edit_posts );
		}

		return current_user_can( get_post_type_object( Tribe__Events__Main::POSTTYPE )->cap->edit_post, $id );
	}

	/**
	 * Prepares an array with the event data from the specified request.
	 *
	 * @param WP_REST_Request $request
	 *
	 * @return array|WP_Error An array specifying the event data, a `WP_Error` if an error occurred.
	 */
	protected function prepare_postarr( WP_REST_Request $request ) {
		$events_cat            = Tribe__Events__Main::TAXONOMY;

		$post_date     = isset( $request['date'] )
			? Tribe__Date_Utils::build_date_object( $request['date'], $request['timezone'] )->format( 'Y-m-d H:i:s' )
			: false;
		$post_date_gmt = isset( $request['date_utc'] )
			? Tribe__Timezones::localize_date( 'Y-m-d H:i:s', $request['date_utc'], 'UTC' )
			: false;

		// Resolve our dates based on timezone if available.
		$event_start_date = null;
		$event_start_time = null;
		$event_end_date   = null;
		$event_end_time   = null;
		if ( $request['start_date'] ) {
			$start_date_obj   = Tribe__Date_Utils::build_date_object( $request['start_date'], $request['timezone'] );
			$event_start_date = $start_date_obj->format( 'Y-m-d' );
			$event_start_time = $start_date_obj->format( 'H:i:s' );
		}
		if ( $request['end_date'] ) {
			$end_date_obj   = Tribe__Date_Utils::build_date_object( $request['end_date'], $request['timezone'] );
			$event_end_date = $end_date_obj->format( 'Y-m-d' );
			$event_end_time = $end_date_obj->format( 'H:i:s' );
		}
		$postarr = [
			// Post fields
			'post_author'           => $request['author'],
			'post_date'             => $post_date,
			'post_date_gmt'         => $post_date_gmt,
			'post_title'            => $request['title'],
			'post_name'             => $request['slug'],
			'post_content'          => $request['description'],
			'post_excerpt'          => $request['excerpt'],
			'post_status'           => $this->scale_back_post_status( $request['status'], Tribe__Events__Main::POSTTYPE ),
			// Event data
			'EventTimezone'         => $request['timezone'],
			'EventAllDay'           => isset( $request['all_day'] ) ? tribe_is_truthy( $request['all_day'] ) : null,
			'EventStartDate'        => $event_start_date,
			'EventStartTime'        => $event_start_time,
			'EventEndDate'          => $event_end_date,
			'EventEndTime'          => $event_end_time,
			'FeaturedImage'         => tribe_upload_image( $request['image'] ),
			'EventCost'             => $request['cost'],
			'EventCurrencyPosition' => tribe( 'cost-utils' )->parse_currency_position( $request['cost'] ),
			'EventCurrencySymbol'   => tribe( 'cost-utils' )->parse_currency_symbol( $request['cost'] ),
			'EventURL'              => filter_var( $request['website'], FILTER_SANITIZE_URL ),
			// Taxonomies
			'tax_input'             => [],
		];
		// Check if categories is provided (allowing for empty array to remove categories).
		if ( isset( $request['categories'] ) ) {
			$postarr['tax_input'][ $events_cat ] = [];

			if ( ! empty( $request['categories'] ) ) {
				$postarr['tax_input'][ $events_cat ] = Tribe__Terms::translate_terms_to_ids( $request['categories'], $events_cat );
			}
		}

		// Check if tags is provided (allowing for empty array to remove tags).
		if ( isset( $request['tags'] ) ) {
			$postarr['tax_input']['post_tag'] = [];

			if ( ! empty( $request['tags'] ) ) {
				$postarr['tax_input']['post_tag'] = Tribe__Terms::translate_terms_to_ids( $request['tags'], 'post_tag' );
			}
		}

		// If an empty EventTimezone was passed, lets unset it so it can be unset during event meta save
		if ( empty( $postarr['EventTimezone'] ) ) {
			unset( $postarr['EventTimezone'] );
		} else {
			// If we are changing a timezone, we need to ensure clear EventTimezoneAbbr so it gets correctly set.
			$postarr['EventTimezoneAbbr'] = '';
		}

		$venue = $this->venue_endpoint->insert( $request['venue'] );

		if ( is_wp_error( $venue ) ) {
			return $venue;
		}

		$postarr['venue'] = $venue;

		$organizer = $this->organizer_endpoint->insert( $request['organizer'] );

		if ( is_wp_error( $organizer ) ) {
			return $organizer;
		}

		$postarr['organizer'] = $organizer;

		// Event presentation data
		$postarr['EventShowMap']          = tribe_is_truthy( $request['show_map'] );
		$postarr['EventShowMapLink']      = tribe_is_truthy( $request['show_map_link'] );
		if ( tribe_is_truthy( $request['hide_from_listings'] ) ) {
			$postarr['EventHideFromUpcoming'] = 'yes';
		}
		$postarr['EventShowInCalendar']   = tribe_is_truthy( $request['sticky'] );
		$postarr['feature_event']         = tribe_is_truthy( $request['featured'] );

		// If we are scheduling an event and a date has been provided, WP requires an additional argument
		if ( ! empty( $postarr['post_status'] ) && 'future' === $postarr['post_status'] && ! empty( $postarr['post_date'] ) ) {
			$postarr['edit_date'] = true;
		}

		/**
		 * Allow filtering of $postarr data with additional $request arguments.
		 *
		 * @param array           $postarr Post array used for create/update
		 * @param WP_REST_Request $request REST request object
		 *
		 * @since 4.6
		 */
		$postarr = apply_filters( 'tribe_events_rest_event_prepare_postarr', $postarr, $request );

		$postarr = array_filter( $postarr, [ $this->validator, 'is_not_null' ] );

		return $postarr;
	}
}
