<?php
/**
 * Reaction endpoints
 *
 * @package Nisje
 */

namespace Dekode\Nisje\Components\Rest;

defined( 'ABSPATH' ) || die( 'Shame on you' );

/**
 * Reactions Rest Class
 */
class Reaction_Controller extends \WP_REST_Controller {
	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->namespace = nisje_get_rest_namespace();
		$this->rest_base = 'reactions';
		$this->hook_base = $this->rest_base;
	}

	/**
	 * Register routes
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace, '/' . $this->rest_base . '/add', [
				[
					'methods'             => \WP_REST_Server::CREATABLE,
					'callback'            => [ $this, 'add' ],
					'permission_callback' => [ $this, 'add_permissions_check' ],
					'args'                => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ),
				],
				'schema' => [ $this, 'get_public_item_schema' ],
			]
		);

		register_rest_route(
			$this->namespace, '/' . $this->rest_base . '/remove', [
				[
					'methods'             => \WP_REST_Server::CREATABLE,
					'callback'            => [ $this, 'remove' ],
					'permission_callback' => [ $this, 'remove_permissions_check' ],
					'args'                => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ),
				],
				'schema' => [ $this, 'get_public_item_schema' ],
			]
		);
	}

	/**
	 * Check if a given request has access to react.
	 *
	 * @param  WP_REST_Request $request Full details about the request.
	 * @return WP_Error|boolean
	 */
	public function add_permissions_check( $request ) {
		$auth = nisje_validate_rest_authentication( $this->hook_base, 'add' );
		if ( is_wp_error( $auth ) ) {
			return $auth;
		}

		return true;
	}

	/**
	 * Add reaction
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_Error|WP_REST_Response
	 */
	public function add( $request ) {
		$args = new \stdClass();

		$args->object_id        = (int) $request['object_id'];
		$args->user_id          = (int) bp_loggedin_user_id();
		$args->object_type      = $request['object_type'];
		$args->reaction_type_id = (int) $request['reaction_type_id'];

		/**
		 * Filter the query arguments for a request.
		 */
		$args = apply_filters( "nisje_rest_{$this->hook_base}_query", $args, $request );

		$existing_reaction = nisje_get_reaction( $args->object_id, $args->user_id, $args->object_type, $args->reaction_type_id );

		if ( $existing_reaction ) {
			return new \WP_Error(
				'nisje_rest_existing_reaction', esc_html__( 'You have already reacted to this object.', 'nisje' ), [
					'status' => 401,
				]
			);
		}

		$reaction = nisje_add_reaction( $args->object_id, $args->user_id, $args->object_type, $args->reaction_type_id );

		if ( ! $reaction ) {
			return new \WP_Error(
				'nisje_rest_add_reaction', esc_html__( 'Could not react to this object.', 'nisje' ), [
					'status' => 500,
				]
			);
		}

		$schema = $this->get_item_schema();

		/**
		 * Fires after users are invited.
		 */
		do_action( "nisje_rest_after_{$this->hook_base}", $args, $request );

		$request->set_param( 'context', 'view' );
		$response = $this->prepare_item_for_response( $args, $request );
		$response = rest_ensure_response( $response );
		$response->set_status( 201 );
		$response->header( 'Location', rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );

		return $response;
	}

	/**
	 * Check if a given request has access to react.
	 *
	 * @param  WP_REST_Request $request Full details about the request.
	 * @return WP_Error|boolean
	 */
	public function remove_permissions_check( $request ) {
		$auth = nisje_validate_rest_authentication( $this->hook_base, 'remove' );
		if ( is_wp_error( $auth ) ) {
			return $auth;
		}

		return true;
	}

	/**
	 * Remove reaction
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_Error|WP_REST_Response
	 */
	public function remove( $request ) {
		$args = new \stdClass();

		$args->object_id        = (int) $request['object_id'];
		$args->user_id          = (int) bp_loggedin_user_id();
		$args->object_type      = $request['object_type'];
		$args->reaction_type_id = (int) $request['reaction_type_id'];

		/**
		 * Filter the query arguments for a request.
		 */
		$args = apply_filters( "nisje_rest_{$this->hook_base}_query", $args, $request );

		$existing_reaction = nisje_get_reaction( $args->object_id, $args->user_id, $args->object_type, $args->reaction_type_id );

		if ( $existing_reaction && $existing_reaction->user_id === $args->user_id ) {
			$reaction = nisje_remove_reaction( $args->object_id, $args->user_id, $args->object_type, $args->reaction_type_id );
		} else {
			return new \WP_Error(
				'nisje_rest_remove_own_reaction', esc_html__( 'You are not allowed to remove other users reactions.', 'nisje' ), [
					'status' => 401,
				]
			);
		}

		if ( ! $reaction ) {
			return new \WP_Error(
				'nisje_rest_remove_reaction', esc_html__( 'Could not remove reaction from this object.', 'nisje' ), [
					'status' => 401,
				]
			);
		}

		$schema = $this->get_item_schema();

		/**
		 * Fires after users are invited.
		 */
		do_action( "nisje_rest_after_{$this->hook_base}", $args, $request );

		$request->set_param( 'context', 'view' );
		$response = $this->prepare_item_for_response( $args, $request );
		$response = rest_ensure_response( $response );
		$response->set_status( 201 );
		$response->header( 'Location', rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );

		return $response;
	}

	/**
	 * Prepares the return value
	 *
	 * @param stdClass        $item    Object.
	 * @param WP_REST_Request $request The request.
	 * @param boolean         $is_raw  Optional, not used. Defaults to false.
	 * @return WP_REST_Response
	 */
	public function prepare_item_for_response( $item, $request, $is_raw = false ) {
		$schema = $this->get_item_schema();

		$data = [];

		if ( ! empty( $schema['properties']['user_id'] ) ) {
			$data['user_id'] = (int) $item->user_id;
		}

		if ( ! empty( $schema['properties']['object_id'] ) ) {
			$data['object_id'] = (int) $item->object_id;
		}

		if ( ! empty( $schema['properties']['object_type'] ) ) {
			$data['object_type'] = $item->object_type;
		}

		if ( ! empty( $schema['properties']['reaction_type_id'] ) ) {
			$data['reaction_type_id'] = $item->reaction_type_id;
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );

		$data     = $this->filter_response_by_context( $data, $context );
		$response = rest_ensure_response( $data );
		$response->add_links( $this->prepare_links( $item ) );

		/**
		 * Filter value returned from the API.
		 */
		return apply_filters( "nisje_rest_prepare_{$this->hook_base}_value", $response, $item, $request );
	}

	/**
	 * Prepare links for the request.
	 *
	 * @param array $item Item.
	 * @return array Links for the given plugin.
	 */
	protected function prepare_links( $item ) {
		$base = sprintf( '/%s/%s/', $this->namespace, $this->rest_base );

		// Entity meta.
		$links = [
			'member' => [
				'href' => rest_url( '/' . $this->namespace . '/members/' . $item->user_id ),
			],
		];

		return $links;
	}

	/**
	 * Get the plugin schema, conforming to JSON Schema.
	 *
	 * @return array
	 */
	public function get_item_schema() {
		$schema = [
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => $this->hook_base,
			'type'       => 'object',
			'properties' => [
				'user_id'          => [
					'context'     => [ 'view' ],
					'description' => esc_html__( 'A unique alphanumeric ID for user.', 'nisje' ),
					'type'        => 'integer',
					'readonly'    => true,
				],
				'object_id'        => [
					'context'     => [ 'view' ],
					'description' => esc_html__( 'A unique alphanumeric ID for the object.', 'nisje' ),
					'type'        => 'integer',
					'required'    => true,
				],
				'object_type'      => [
					'context'     => [ 'view' ],
					'description' => esc_html__( 'Object type.', 'nisje' ),
					'type'        => 'string',
					'enum'        => [ 'post', 'comment', 'activity' ],
					'required'    => true,
				],
				'reaction_type_id' => [
					'context'     => [ 'view' ],
					'description' => esc_html__( 'A unique alphanumeric ID for the reaction type.', 'nisje' ),
					'type'        => 'integer',
					'required'    => true,
				],
			],
		];

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