<?php
/**
 * Groups Rest Endpoint
 *
 * @package Nisje
 */

namespace Dekode\Nisje\Components\Rest;

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

/**
 * Groups Class
 */
class Groups_Controller extends \WP_REST_Controller {
	/**
	 * Constructor
	 */
	public function __construct() {
		$this->namespace = nisje_get_rest_namespace();
		$this->rest_base = buddypress()->groups->id;
		$this->hook_base = strtolower( buddypress()->groups->id );
	}

	/**
	 * Register routes.
	 */
	public function register_routes() {
		register_rest_route( $this->namespace, '/' . $this->rest_base, [
			[
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => [ $this, 'get_items' ],
				'permission_callback' => [ $this, 'get_items_permissions_check' ],
				'args'                => $this->get_collection_params(),
			],
			[
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => [ $this, 'create_item' ],
				'permission_callback' => [ $this, 'create_item_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 . '/(?P<id>[\d]+)', [
			'args'   => [
				'id' => [
					'description' => esc_html__( 'Unique identifier for the object.', 'nisje' ),
					'type'        => 'integer',
				],
			],
			[
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => [ $this, 'get_item' ],
				'permission_callback' => [ $this, 'get_item_permissions_check' ],
				'args'                => [
					'context' => $this->get_context_param( [
						'default' => 'view',
					] ),
				],
			],
			[
				'methods'             => \WP_REST_Server::EDITABLE,
				'callback'            => [ $this, 'update_item' ],
				'permission_callback' => [ $this, 'update_item_permissions_check' ],
				'args'                => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ),
			],
			[
				'methods'             => \WP_REST_Server::DELETABLE,
				'callback'            => [ $this, 'delete_item' ],
				'permission_callback' => [ $this, 'delete_item_permissions_check' ],
				'args'                => [
					'force' => [
						'default'     => false,
						'description' => esc_html__( 'Required to be true, as resource does not support trashing.', 'nisje' ),
					],
				],
			],
			'schema' => [ $this, 'get_public_item_schema' ],
		] );
	}

	/**
	 * Check if a given request has access to group items.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_Error|bool
	 */
	public function get_items_permissions_check( $request ) {
		$permission = nisje_override_rest_permission_check( $this->hook_base, 'get_items', $request );
		if ( true === $permission ) {
			return true;
		}

		$auth = nisje_validate_rest_authentication( $this->hook_base, 'get_items' );
		if ( is_wp_error( $auth ) ) {
			return $auth;
		}

		// Only bp_moderators and logged in users (viewing their own groups) can see hidden groups.
		if ( ! empty( $request['show_hidden'] ) && ( ! bp_current_user_can( 'bp_moderate' ) && ! ( ! empty( $request['user_id'] ) && bp_loggedin_user_id() === $request['user_id'] ) ) ) {
			return new \WP_Error( 'nisje_rest_user_cannot_view_hidden', esc_html__( 'Sorry, you cannot view hidden groups.', 'nisje' ), [
				'status' => rest_authorization_required_code(),
			] );
		}

		if ( 'edit' === $request['context'] ) {
			return new \WP_Error( 'nisje_rest_forbidden_context', esc_html__( 'Sorry, you cannot view this resource with edit context.', 'nisje' ), [
				'status' => rest_authorization_required_code(),
			] );
		}

		return true;
	}

	/**
	 * Get a collection of groups.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_Error|WP_REST_Response
	 */
	public function get_items( $request ) {
		$args = [
			'type'               => $request['type'],
			'order'              => $request['order'],
			'orderby'            => $request['orderby'],
			'user_id'            => $request['user_id'],
			'include'            => $request['include'],
			'exclude'            => $request['exclude'],
			'search'             => $request['search'],
			'parent_id'          => $request['parent_id'],
			'group_type'         => $request['group_type'],
			'group_type__in'     => $request['group_type__in'],
			'group_type__not_in' => $request['group_type__not_in'],
			'functionality'      => $request['functionality'],
			'show_hidden'        => $request['show_hidden'],
			'per_page'           => $request['per_page'],
			'page'               => $request['page'],
			// Defaults.
			'populate_extras'    => false,
			'update_meta_cache'  => true,
		];

		/**
		 * Filter the query arguments for a request.
		 * Enables adding extra arguments or setting defaults for a group collection request.
		 *
		 * @param array           $args    Key value array of query var to query value.
		 * @param WP_REST_Request $request The request used.
		 */
		$args = apply_filters( 'nisje_rest_groups_query', $args, $request );

		$query_args = $this->prepare_items_query( $args, $request );

		$retval = [];

		$groups = groups_get_groups( $query_args );
		foreach ( $groups['groups'] as $group ) {
			if ( ! $this->check_read_permission( $group ) ) {
				// We should probably update the total as well: $groups['total']--;.
				continue;
			}

			$data     = $this->prepare_item_for_response( $group, $request );
			$retval[] = $this->prepare_response_for_collection( $data );
		}

		$page         = isset( $query_args['page'] ) ? (int) $query_args['page'] : 1;
		$total_groups = isset( $groups['total'] ) ? $groups['total'] : 0;

		if ( $total_groups < 1 ) {
			// Out-of-bounds, run the query again without LIMIT for total count.
			unset( $query_args['page'] );
			$count_query  = groups_get_groups( $query_args );
			$total_groups = isset( $count_query['total'] ) ? $count_query['total'] : 0;
		}

		$max_pages = ceil( $total_groups / (int) $query_args['per_page'] );

		$response = rest_ensure_response( $retval );
		$response->header( 'X-WP-Total', (int) $total_groups );
		$response->header( 'X-WP-TotalPages', (int) $max_pages );

		$request_params = $request->get_query_params();

		$base = add_query_arg( $request_params, rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );

		if ( $page > 1 ) {
			$prev_page = $page - 1;
			if ( $prev_page > $max_pages ) {
				$prev_page = $max_pages;
			}
			$prev_link = add_query_arg( 'page', $prev_page, $base );
			$response->link_header( 'prev', $prev_link );
		}
		if ( $max_pages > $page ) {
			$next_page = $page + 1;
			$next_link = add_query_arg( 'page', $next_page, $base );
			$response->link_header( 'next', $next_link );
		}

		return $response;
	}

	/**
	 * Check if a given request has access to get information about a specific group.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return bool
	 */
	public function get_item_permissions_check( $request ) {
		$permission = nisje_override_rest_permission_check( $this->hook_base, 'get_item', $request );
		if ( true === $permission ) {
			return true;
		}

		$auth = nisje_validate_rest_authentication( $this->hook_base, 'get_item' );
		if ( is_wp_error( $auth ) ) {
			return $auth;
		}

		$group_id = (int) $request['id'];

		$group = nisje_get_group( $group_id );
		if ( is_wp_error( $group ) ) {
			return $group;
		}

		$user_id = bp_loggedin_user_id();

		if ( 'hidden' === $group->status && ( ! bp_current_user_can( 'bp_moderate' ) && ! groups_is_user_member( $user_id, $group->id ) ) && ! groups_check_user_has_invite( $user_id, $group->id ) ) {
			return new \WP_Error( 'nisje_rest_user_no_access', esc_html__( 'Group not found.', 'nisje' ), [
				'status' => 404,
			] );
		}

		if ( 'edit' === $request['context'] && ! groups_is_user_admin( $user_id, $group->id ) ) {
			return new \WP_Error( 'nisje_rest_no_access_to_edit', esc_html__( 'You are not allowed to edit this group.', 'nisje' ), [
				'status' => rest_authorization_required_code(),
			] );
		}

		return true;
	}

	/**
	 * Retrieve a single group.
	 *
	 * @param WP_REST_Request $request The Request.
	 * @return WP_REST_Request|WP_Error Plugin object data on success, WP_Error otherwise.
	 */
	public function get_item( $request ) {
		$group_id = (int) $request['id'];

		$group = nisje_get_group( $group_id );
		if ( is_wp_error( $group ) ) {
			return $group;
		}

		if ( 'public' === $group->status ) {
			// Not really, but you are supposed to get the same data.
			$request->set_param( 'context', 'member' );
		} elseif ( in_array( $group->status, [ 'hidden', 'private' ], true ) ) {
			if ( groups_is_user_member( bp_loggedin_user_id(), $group->id ) ) {
				$request->set_param( 'context', 'member' );
			}
		}

		$data     = $this->prepare_item_for_response( $group, $request );
		$response = rest_ensure_response( $data );

		return $response;
	}

	/**
	 * Check if a given request has access to create a group.
	 *
	 * @param  WP_REST_Request $request Full details about the request.
	 * @return WP_Error|boolean
	 */
	public function create_item_permissions_check( $request ) {
		$permission = nisje_override_rest_permission_check( $this->hook_base, 'create_item', $request );
		if ( true === $permission ) {
			return true;
		}

		$auth = nisje_validate_rest_authentication( $this->hook_base, 'create_item' );
		if ( is_wp_error( $auth ) ) {
			return $auth;
		}

		if ( ! empty( $request['creator_id'] ) && bp_loggedin_user_id() !== $request['creator_id'] ) {
			return new \WP_Error( 'nisje_rest_cannot_create_as_others', esc_html__( 'You are not allowed to create groups as this user.', 'nisje' ), [
				'status' => rest_authorization_required_code(),
			] );
		}

		if ( ! bp_user_can_create_groups() || ! $this->check_create_permission() ) {
			return new \WP_Error( 'nisje_rest_cannot_create', esc_html__( 'Sorry, you are not allowed to create new groups.', 'nisje' ), [
				'status' => rest_authorization_required_code(),
			] );
		}

		if ( ! empty( $request['status'] ) && 'hidden' === $request['status'] && ! current_user_can( 'nisje_create_groups_hidden' ) ) {
			return new \WP_Error( 'nisje_rest_no_access_status', esc_html__( 'You are not allowed to create hidden groups.', 'nisje' ), [
				'status' => rest_authorization_required_code(),
			] );
		}

		return true;
	}

	/**
	 * Create a single Group
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_Error|WP_REST_Response
	 */
	public function create_item( $request ) {
		// We do not want id for new groups. Die.
		if ( ! empty( $request['id'] ) ) {
			return new \WP_Error( 'nisje_rest_id_not_allowed', esc_html__( 'You are not allowed to update non-existing groups. :)', 'nisje' ), [
				'status' => 400,
			] );
		}

		// Prepare Request.
		$group = $this->prepare_item_for_database( $request );
		if ( is_wp_error( $group ) ) {
			return $group;
		}

		$group_args = get_object_vars( $group );
		$group_id   = groups_create_group( $group_args );

		if ( ! $group_id ) {
			return new \WP_Error( 'nisje_rest_cannot_create_group', esc_html__( 'The group could not be inserted.', 'nisje' ), [
				'status' => 500,
			] );
		}

		$group = nisje_get_group( $group_id );
		if ( is_wp_error( $group ) ) {
			return $group;
		}

		// Save group types.
		if ( ! empty( $group_args['group_types'] ) ) {
			bp_groups_set_group_type( $group->id, $group_args['group_types'] );
		}

		if ( isset( $group_args['full_description'] ) ) {
			groups_update_groupmeta( $group->id, 'full_description', $group_args['full_description'] );
		}

		groups_update_groupmeta( $group->id, 'invite_status', $group_args['invite_status'] );

		// Set color. This is just a random number. The logic is in the APP.
		if ( ! isset( $group_args['color'] ) ) {
			groups_update_groupmeta( $group->id, 'color', wp_rand( 1, 8 ) );
		} else {
			groups_update_groupmeta( $group->id, 'color', $group_args['color'] );
		}

		// Set Group News if active.
		if ( isset( $group_args['enable_news'] ) && $group_args['enable_news'] ) {
			groups_update_groupmeta( $group->id, '_nisje_news_enabled', true );
		}

		// Set Group Wiki if active.
		if ( isset( $group_args['enable_wiki'] ) && $group_args['enable_wiki'] ) {
			groups_update_groupmeta( $group->id, '_nisje_wiki_enabled', true );
		}

		// Set Group Event if active.
		if ( isset( $group_args['enable_event'] ) && $group_args['enable_event'] ) {
			groups_update_groupmeta( $group->id, '_nisje_event_enabled', true );
		}

		/**
		 * Hook to set group meta for custom extensions
		 *
		 * @param object          $group      Group
		 * @param WP_REST_Request $request    Request object.
		 * @param array           $group_args Group Arguments.
		 */
		do_action( 'nisje_set_group_meta', $group, $request, $group_args );

		$schema = $this->get_item_schema();

		/**
		 * Fires after a single group is created or updated via the REST API.
		 *
		 * @param object          $group      Group
		 * @param WP_REST_Request $request    Request object.
		 * @param array           $group_args Group Arguments.
		 */
		do_action( 'nisje_rest_insert_group', $group, $request, $group_args );

		$fields_update = $this->update_additional_fields_for_object( $group, $request );

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

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

		$response = $this->prepare_item_for_response( $group, $request );

		$response = rest_ensure_response( $response );

		$response->set_status( 201 );

		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $group_id ) ) );

		return $response;
	}

	/**
	 * Check if a given request has access to update a group.
	 *
	 * @param  WP_REST_Request $request Full details about the request.
	 * @return WP_Error|boolean
	 */
	public function update_item_permissions_check( $request ) {
		$permission = nisje_override_rest_permission_check( $this->hook_base, 'update_item', $request );
		if ( true === $permission ) {
			return true;
		}

		$auth = nisje_validate_rest_authentication( $this->hook_base, 'update_item' );
		if ( is_wp_error( $auth ) ) {
			return $auth;
		}

		$group_id = (int) $request['id'];
		$group    = nisje_get_group( $group_id );
		if ( is_wp_error( $group ) ) {
			return $group;
		}

		if ( ! $this->check_update_permission( $group ) ) {
			return new \WP_Error( 'nisje_rest_cannot_create', esc_html__( 'Sorry, you are not allowed to update groups.', 'nisje' ), [
				'status' => rest_authorization_required_code(),
			] );
		}

		if ( ! groups_is_user_admin( bp_loggedin_user_id(), $group->id ) ) {
			return new \WP_Error( 'nisje_rest_cannot_edit_group', esc_html__( 'You are not allowed to update groups as this user.', 'nisje' ), [
				'status' => rest_authorization_required_code(),
			] );
		}

		if ( ! empty( $request['status'] ) && 'hidden' === $request['status'] && 'hidden' !== $group->status && ! current_user_can( 'nisje_create_groups_hidden' ) ) {
			return new \WP_Error( 'nisje_rest_cannot_wrong_status', esc_html__( 'You are not allowed to change this to a hidden group.', 'nisje' ), [
				'status' => rest_authorization_required_code(),
			] );
		}

		return true;
	}

	/**
	 * Update a single post.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_Error|WP_REST_Response
	 */
	public function update_item( $request ) {
		$group_id = (int) $request['id'];
		$group    = nisje_get_group( $group_id );
		if ( is_wp_error( $group ) ) {
			return $group;
		}

		$group_args = $this->prepare_item_for_database( $request );
		if ( is_wp_error( $group_args ) ) {
			return $group_args;
		}

		if ( isset( $group_args->name ) ) {
			$group->name = $group_args->name;
		}

		if ( isset( $group_args->description ) ) {
			$group->description = $group_args->description;
		}

		if ( isset( $group_args->enable_forum ) ) {
			$group->enable_forum = $group_args->enable_forum;
		}

		if ( isset( $group_args->invite_status ) ) {
			$group->invite_status = $group_args->invite_status;
		}

		if ( isset( $group_args->status ) ) {
			$group->status = $group_args->status;
		}

		// Unsupported.
		$notify_members = false;
		$parent_id      = false;
		$enable_forum   = false;

		// Could have save this value directly with $groups->save but there are some filter in BuddyPress we need to trigger.
		if ( ! groups_edit_base_group_details( $group->id, $group->name, $group->description, $notify_members ) ) {
			return new \WP_Error( 'nisje_rest_group_edit_base_not_updated', esc_html__( 'We could not update the name and description. These values cannot be empty. Please contact the administrator with error code 2001.', 'nisje' ), [
				'status' => 500,
			] );
		}

		if ( ! groups_edit_group_settings( $group->id, $enable_forum, $group->status, $group->invite_status, $parent_id ) ) {
			return new \WP_Error( 'nisje_rest_group_edit_group_settings', esc_html__( 'We could not update the group settings. Please contact the administrator with error code 2002.', 'nisje' ), [
				'status' => 500,
			] );
		}

		// Save group types.
		if ( ! empty( $group_args->group_types ) ) {
			bp_groups_set_group_type( $group->id, $group_args->group_types );
		}

		if ( isset( $group_args->color ) ) {
			groups_update_groupmeta( $group->id, 'color', $group_args->color );
		}

		if ( isset( $group_args->full_description ) ) {
			groups_update_groupmeta( $group->id, 'full_description', $group_args->full_description );
		}

		if ( isset( $group_args->enable_event ) ) {
			groups_update_groupmeta( $group->id, '_nisje_event_enabled', $group_args->enable_event );
		}

		if ( isset( $group_args->enable_news ) ) {
			groups_update_groupmeta( $group->id, '_nisje_news_enabled', $group_args->enable_news );
		}

		if ( isset( $group_args->enable_wiki ) ) {
			groups_update_groupmeta( $group->id, '_nisje_wiki_enabled', $group_args->enable_wiki );
		}

		/**
		 * Hook to update group meta for custom extensions
		 *
		 * @param object          $group      Group
		 * @param WP_REST_Request $request    Request object.
		 * @param array           $group_args Group Arguments.
		 */
		do_action( 'nisje_update_group_meta', $group, $request, $group_args );

		$schema = $this->get_item_schema();

		$group = nisje_get_group( $group->id );

		$fields_update = $this->update_additional_fields_for_object( $group, $request );
		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		/**
		 * Fires after a single group is updated via the REST API.
		 *
		 * @param object          $group      Group
		 * @param WP_REST_Request $request    Request object.
		 * @param array           $group_args Group Arguments.
		 */
		do_action( 'nisje_rest_update_group', $group, $request, $group_args );

		$request->set_param( 'context', 'edit' );
		$response = $this->prepare_item_for_response( $group, $request );

		return rest_ensure_response( $response );
	}

	/**
	 * Check if a given request has access to delete a group.
	 *
	 * @param  WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error
	 */
	public function delete_item_permissions_check( $request ) {
		$permission = nisje_override_rest_permission_check( $this->hook_base, 'delete_item', $request );
		if ( true === $permission ) {
			return true;
		}

		$auth = nisje_validate_rest_authentication( $this->hook_base, 'delete_item' );
		if ( is_wp_error( $auth ) ) {
			return $auth;
		}

		$group_id = (int) $request['id'];
		$group    = nisje_get_group( $group_id );
		if ( is_wp_error( $group ) ) {
			return $group;
		}

		if ( ! $this->check_delete_permission( $group ) ) {
			return new \WP_Error( 'nisje_rest_cannot_delete', esc_html__( 'Sorry, you are not allowed to delete groups.', 'nisje' ), [
				'status' => rest_authorization_required_code(),
			] );
		}

		if ( ! groups_is_user_admin( bp_loggedin_user_id(), $group->id ) ) {
			return new \WP_Error( 'nisje_rest_cannot_delete_admin', esc_html__( 'Only administrators can delete groups.', 'nisje' ), [
				'status' => rest_authorization_required_code(),
			] );
		}

		return true;
	}

	/**
	 * Delete a single group.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function delete_item( $request ) {
		$force = isset( $request['force'] ) ? (bool) $request['force'] : false;

		// We don't support trashing for this type, error out.
		if ( ! $force ) {
			return new \WP_Error( 'nisje_rest_trash_not_supported', esc_html__( 'Groups do not support trashing.', 'nisje' ), [
				'status' => 501,
			] );
		}

		$group_id = (int) $request['id'];
		$group    = nisje_get_group( $group_id );
		if ( is_wp_error( $group ) ) {
			return $group;
		}

		if ( ! groups_delete_group( $group->id ) ) {
			return new \WP_Error( 'nisje_rest_user_cannot_delete', esc_html__( 'The group cannot be deleted.', 'nisje' ), [
				'status' => 500,
			] );
		}

		$request->set_param( 'context', 'edit' );
		$response = $this->prepare_item_for_response( $group, $request );

		/**
		 * Fires after a single group is deleted via the REST API.
		 *
		 * @param object           $group    The deleted group.
		 * @param WP_REST_Response $response The response data.
		 * @param WP_REST_Request  $request  The request sent to the API.
		 */
		do_action( 'nisje_rest_delete_group', $group, $response, $request );

		return $response;
	}

	/**
	 * Determine the allowed query_vars for a get_items() response and prepare.
	 *
	 * @param array           $prepared_args Prepared Arguments.
	 * @param WP_REST_Request $request       Request.
	 * @return array          $query_args
	 */
	protected function prepare_items_query( $prepared_args = [], $request = null ) {
		$valid_vars = array_flip( $this->get_allowed_query_vars() );
		$query_args = [];
		foreach ( $valid_vars as $var => $index ) {
			if ( isset( $prepared_args[ $var ] ) ) {
				/**
				 * Filter the query_vars used in `get_items` for the constructed query.
				 * The dynamic portion of the hook name, $var, refers to the query_var key.
				 *
				 * @param mixed $prepared_args[ $var ] The query_var value.
				 */
				$query_args[ $var ] = apply_filters( "nisje_rest_groups_query_{$var}", $prepared_args[ $var ] );
			}
		}

		if ( empty( $query_args['group_type'] ) && ! empty( $query_args['group_type__in'] ) ) {
			$query_args['group_type'] = $query_args['group_type__in'];
		}

		if ( ! empty( $query_args['functionality'] ) && 'all' !== $query_args['functionality'] ) {
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
			$query_args['meta_query'] = [
				[
					'key'     => '_nisje_' . $query_args['functionality'] . '_enabled',
					'value'   => 1,
					'type'    => 'numeric',
					'compare' => '=',
				],
			];
		}

		$query_args['search_terms'] = $query_args['search'];

		if ( empty( $query_args['parent_id'] ) ) {
			$query_args['parent_id'] = null;
		}

		return $query_args;
	}

	/**
	 * Get all the groups vars that are allowed for the API request.
	 *
	 * @return array
	 */
	protected function get_allowed_query_vars() {
		global $wp;

		/**
		 * Filter the publicly allowed query vars.
		 * Allows adjusting of the default query vars that are made public.
		 *
		 * @param array  Array of allowed \WP_Query query vars.
		 */
		$valid_vars = apply_filters( 'query_vars', $wp->public_query_vars );

		// Define our own in addition to WP's normal vars.
		$rest_valid = [
			'type',
			'order',
			'orderby',
			'user_id',
			'include',
			'exclude',
			'search',
			'parent_id',
			'slug',
			'functionality',
			'group_type',
			'group_type__in',
			'group_type__not_in',
			'meta_query',
			'show_hidden',
			'per_page',
			'page',
			'populate_extras',
			'update_meta_cache',
		];
		$valid_vars = array_merge( $valid_vars, $rest_valid );

		/**
		 * Filter allowed query vars for the REST API.
		 *
		 * This filter allows you to add or remove query vars from the final allowed
		 * list for all requests, including unauthenticated ones. To alter the
		 * vars for editors only, {@see rest_private_query_vars}.
		 *
		 * @param array {
		 *    Array of allowed \WP_Query query vars.
		 *
		 *    @param string $allowed_query_var The query var to allow.
		 * }
		 */
		$valid_vars = apply_filters( 'nisje_rest_group_query_vars', $valid_vars );

		return $valid_vars;
	}

	/**
	 * Prepare a single group for create or update.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_Error|stdClass $prepared_group group object.
	 */
	protected function prepare_item_for_database( $request ) {
		$prepared_group = new \stdClass();

		// ID.
		if ( isset( $request['id'] ) ) {
			$prepared_group->group_id = absint( $request['id'] );
			$prepared_group->id       = absint( $request['id'] );
		}

		$schema = $this->get_item_schema();

		// Creator.
		if ( ! empty( $schema['properties']['creator_id'] ) && ! empty( $request['creator_id'] ) ) {
			$creator_id = (int) $request['creator_id'];
			if ( get_current_user_id() !== $creator_id ) {
				$user_obj = get_userdata( $creator_id );
				if ( ! $user_obj ) {
					return new \WP_Error( 'dekode_rest_invalid_creator_id', esc_html__( 'Invalid author id.', 'nisje' ), [
						'status' => 400,
					] );
				}
			}
			$prepared_group->creator_id = $creator_id;
		} else {
			$prepared_group->creator_id = get_current_user_id();
		}

		// Group name.
		if ( ! empty( $schema['properties']['name'] ) && isset( $request['name'] ) ) {
			if ( is_string( $request['name'] ) ) {
				$prepared_group->name = wp_filter_post_kses( $request['name'] );
			}
		}

		// Group color.
		if ( ! empty( $schema['properties']['color'] ) && isset( $request['color'] ) ) {
			$prepared_group->color = absint( $request['color'] );
		}

		// Group description.
		if ( ! empty( $schema['properties']['description'] ) && isset( $request['description'] ) ) {
			if ( is_string( $request['description'] ) ) {
				$prepared_group->description = wp_filter_post_kses( $request['description'] );
			}
		}

		// Group description.
		if ( ! empty( $schema['properties']['full_description'] ) && isset( $request['full_description'] ) ) {
			if ( is_string( $request['full_description'] ) ) {
				$prepared_group->full_description = wp_filter_post_kses( $request['full_description'] );
			}
		}

		// Group status.
		if ( ! empty( $schema['properties']['status'] ) && isset( $request['status'] ) ) {
			if ( in_array( $request['status'], $schema['properties']['status']['enum'], true ) ) {
				$prepared_group->status = $request['status'];
			}
		}

		// Forum.
		if ( ! empty( $schema['properties']['enable_forum'] ) && isset( $request['enable_forum'] ) ) {
			$prepared_group->enable_forum = (bool) $request['enable_forum'];
		}

		// Event.
		if ( ! empty( $schema['properties']['enable_event'] ) && isset( $request['enable_event'] ) ) {
			$prepared_group->enable_event = (bool) $request['enable_event'];
		}

		// News.
		if ( ! empty( $schema['properties']['enable_news'] ) && isset( $request['enable_news'] ) ) {
			$prepared_group->enable_news = (bool) $request['enable_news'];
		}

		// Wiki.
		if ( ! empty( $schema['properties']['enable_wiki'] ) && isset( $request['enable_wiki'] ) ) {
			$prepared_group->enable_wiki = (bool) $request['enable_wiki'];
		}

		if ( ! empty( $schema['properties']['invite_status'] ) && isset( $request['invite_status'] ) ) {
			$prepared_group->invite_status = $request['invite_status'];
		}

		if ( ! empty( $schema['properties']['group_types'] ) && isset( $request['group_types'] ) ) {
			if ( ! empty( $request['group_types'] ) && is_array( $request['group_types'] ) ) {
				$prepared_group->group_types = $request['group_types'];
			}
		}

		// Post date.
		if ( ! empty( $schema['properties']['date_created'] ) && isset( $request['date_created'] ) ) {
			if ( ! empty( $request['date_created'] ) ) {
				$prepared_group->date_created = rest_get_date_with_gmt( $request['created_date'] );
			}
		}

		/**
		 * Hook to add to prepared group data
		 *
		 * @param object          $schema         Group schema object.
		 * @param stdClass        $prepared_group An object representing a single post prepared for inserting or updating the database.
		 * @param WP_REST_Request $request        Request object.
		 */
		do_action( 'nisje_add_to_prepared_group_data', $schema, $prepared_group, $request );

		/**
		 * Filter a group before it is inserted via the REST API.
		 *
		 * @param stdClass        $prepared_group An object representing a single post prepared for inserting or updating the database.
		 * @param WP_REST_Request $request        Request object.
		 */
		return apply_filters( 'nisje_rest_pre_insert_group', $prepared_group, $request );
	}

	/**
	 * Check if we can read a group.
	 *
	 * Correctly handles groups with the hidden status.
	 *
	 * @param object $group Group object.
	 * @return boolean Can we read it?
	 */
	public function check_read_permission( $group ) {
		return current_user_can( 'nisje_list_groups', $group->id );
	}

	/**
	 * Check if we can create a group.
	 *
	 * @return boolean Can we create it?.
	 */
	protected function check_create_permission() {
		return current_user_can( 'nisje_create_groups' );
	}

	/**
	 * Check if we can update a group.
	 *
	 * @param object $group Group object.
	 * @return boolean Can we create it?.
	 */
	protected function check_update_permission( $group ) {
		return current_user_can( 'nisje_update_groups', $group->id );
	}


	/**
	 * Check if we can delete a group.
	 *
	 * @param object $group Group object.
	 * @return boolean Can we delete it?
	 */
	protected function check_delete_permission( $group ) {
		return current_user_can( 'nisje_delete_groups', $group->id );
	}

	/**
	 * Prepares group data for return as an object.
	 *
	 * @param stdClass        $item    Group data.
	 * @param WP_REST_Request $request 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    = [];
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';

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

		if ( ! empty( $schema['properties']['name'] ) ) {
			// Direct: $item->name.
			$data['name'] = bp_get_group_name( $item );
		}

		// If this is the 'edit' or 'member' context, fill in more details--similar to "populate_extras".
		if ( 'list' !== $context ) {
			if ( ! empty( $schema['properties']['creator_id'] ) ) {
				// Direct: $item->creator_id.
				$data['creator_id'] = (int) bp_get_group_creator_id( $item );
			}

			if ( ! empty( $schema['properties']['slug'] ) ) {
				// Direct: $item->slug.
				$data['slug'] = bp_get_group_slug( $item );
			}

			if ( ! empty( $schema['properties']['link'] ) ) {
				$data['link'] = trailingslashit( bp_get_groups_directory_permalink() . $item->id );
			}

			if ( ! empty( $schema['properties']['description'] ) ) {
				$data['description'] = [
					'raw'      => $item->description,
					'rendered' => bp_get_group_description( $item ),
				];
			}

			if ( ! empty( $schema['properties']['full_description'] ) ) {
				$full_description = groups_get_groupmeta( $item->id, 'full_description', true );

				$data['full_description'] = [
					'raw'      => $full_description,
					'rendered' => $full_description,
				];
			}

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

			if ( ! empty( $schema['properties']['status'] ) ) {
				$data['status'] = bp_get_group_status( $item );
			}

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

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

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

			if ( ! empty( $schema['properties']['last_activity'] ) ) {
				$data['last_activity'] = nisje_prepare_date_response( groups_get_groupmeta( $item->id, 'last_activity' ) );
			}

			if ( ! empty( $schema['properties']['enable_forum'] ) ) {
				$data['enable_forum'] = bp_group_is_forum_enabled( $item );
			}

			if ( ! empty( $schema['properties']['enable_news'] ) ) {
				$data['enable_news'] = (bool) groups_get_groupmeta( $item->id, '_nisje_news_enabled', true );
			}

			if ( ! empty( $schema['properties']['enable_event'] ) ) {
				$data['enable_event'] = (bool) groups_get_groupmeta( $item->id, '_nisje_event_enabled', true );
			}

			if ( ! empty( $schema['properties']['enable_wiki'] ) ) {
				$data['enable_wiki'] = (bool) groups_get_groupmeta( $item->id, '_nisje_wiki_enabled', true );
			}

			if ( ! empty( $schema['properties']['group_types'] ) ) {
				$data['group_types'] = bp_groups_get_group_type( $item->id, false );
			}

			$user_id = bp_loggedin_user_id();

			if ( ! empty( $schema['properties']['is_member'] ) ) {
				$data['is_member'] = (bool) groups_is_user_member( $user_id, $item->id );
			}

			if ( ! empty( $schema['properties']['is_admin'] ) ) {
				$data['is_admin'] = (bool) groups_is_user_admin( $user_id, $item->id );
			}

			if ( ! empty( $schema['properties']['is_mod'] ) ) {
				$data['is_mod'] = (bool) groups_is_user_mod( $user_id, $item->id );
			}

			if ( ! empty( $schema['properties']['is_banned'] ) ) {
				$data['is_banned'] = (bool) groups_is_user_banned( $user_id, $item->id );
			}

			if ( ! empty( $schema['properties']['is_invited'] ) ) {
				$data['is_invited'] = (bool) groups_check_user_has_invite( $user_id, $item->id );
			}

			if ( ! empty( $schema['properties']['is_pending'] ) ) {
				$data['is_pending'] = (bool) groups_check_for_membership_request( $user_id, $item->id );
			}

			if ( ! empty( $schema['properties']['user_list'] ) ) {
				$user_list = [];

				$members_query = new \BP_Group_Member_Query( [
					'group_id'   => $item->id,
					'per_page'   => 3,
					'max'        => 3,
					'type'       => 'random',
					'group_role' => [ 'admin', 'mod', 'member' ],
				] );

				// Structure the return value as expected by the template functions.
				$members = [
					'members' => array_values( $members_query->results ),
					'total'   => $members_query->total_users,
				];

				if ( isset( $members['members'] ) ) {
					foreach ( (array) $members['members'] as $user ) {
						$user_list[] = [
							'ID'          => (int) $user->ID,
							'displayname' => $user->display_name,
							'avatar_urls' => nisje_get_user_avatar( $user->ID ),
						];
					}
				}

				$data['user_list'] = $user_list;
			}

			// If this is the 'edit' or 'member' context, fill in more details--similar to "populate_extras".
			if ( in_array( $context, [ 'edit', 'member' ], true ) ) {
				if ( ! empty( $schema['properties']['invite_status'] ) ) {
					$data['invite_status'] = groups_get_groupmeta( $item->id, 'invite_status', true );
					if ( is_array( $data['invite_status'] ) ) {
						$data['invite_status'] = array_shift( $data['invite_status'] );
					}
				}

				$data['mods']   = [];
				$data['admins'] = [];

				// Add admins and moderators to their respective arrays.
				$admin_mods = groups_get_group_members( [
					'group_id'   => $item->id,
					'group_role' => [ 'admin', 'mod' ],
				] );

				foreach ( (array) $admin_mods['members'] as $user ) {
					$user_object = [
						'ID'          => (int) $user->ID,
						'displayname' => $user->display_name,
						'avatar_urls' => nisje_get_user_avatar( $user->ID ),
					];

					if ( ! empty( $user->is_admin ) ) {
						$data['admins'][] = $user_object;
					} else {
						$data['mods'][] = $user_object;
					}
				}

				// Set user-specific data.
				$data['is_mod'] = (bool) groups_is_user_mod( $user_id, $item->id );

				// If this is a private or hidden group, does the current user have access?
				if ( ( 'private' === $item->status ) || ( 'hidden' === $item->status ) ) {

					// Assume user does not have access to hidden/private groups.
					$data['user_has_access'] = false;

					// Group members or community moderators have access.
					if ( ( $item->is_member && is_user_logged_in() ) ) {
						$data['user_has_access'] = true;
					}
				} else {
					$data['user_has_access'] = true;
				}
			}
		}

		/**
		 * Filter group data object.
		 *
		 * @param object $data Group prepared data object.
		 * @param object $schema Group schema object.
		 * @param array $item Object.
		 */
		$data = apply_filters( 'nisje_filter_group_data_object', $data, $schema, $item );

		$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 an activity value returned from the API.
		 *
		 * @param array           $response
		 * @param WP_REST_Request $request Request used to generate the response.
		 */
		return apply_filters( 'nisje_rest_prepare_groups_value', $response, $item, $request );
	}

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

		// Entity meta.
		$links = [
			'self'       => [
				'href' => rest_url( $base . $item->id ),
			],
			'collection' => [
				'href' => rest_url( $base ),
			],
		];

		return $links;
	}

	/**
	 * Get the plugin schema, conforming to JSON Schema.
	 *
	 * @return array
	 */
	public function get_item_schema() {
		$allowed_invite_statuses = (array) apply_filters( 'groups_allowed_invite_status', [ 'members', 'mods', 'admins' ] );
		// Use the first element in array as default value.
		if ( ! empty( $allowed_invite_statuses ) ) {
			$default_invite_status = $allowed_invite_statuses[0];
		} else {
			// Cannot be empty. Fix filter f*** up!
			$allowed_invite_statuses = [ 'members', 'mods', 'admins' ];
			$default_invite_status   = 'members';
		}

		$schema = [
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => $this->hook_base,
			'type'       => 'object',
			'properties' => [
				'id'                 => [
					'context'     => [ 'view', 'edit', 'member', 'list' ],
					'description' => esc_html__( 'A unique alphanumeric ID for the object.', 'nisje' ),
					'readonly'    => true,
					'type'        => 'integer',
				],
				'creator_id'         => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'The ID for the creator of the object.', 'nisje' ),
					'type'        => 'integer',
				],
				'name'               => [
					'context'     => [ 'view', 'edit', 'member', 'list' ],
					'description' => esc_html__( 'Name of the group.', 'nisje' ),
					'type'        => 'string',
					'required'    => true,
				],
				'slug'               => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'The URL-friendly slug for the group.', 'nisje' ),
					'type'        => 'string',
					'arg_options' => [
						'sanitize_callback' => [ $this, 'sanitize_slug' ],
					],
				],
				'link'               => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'The permalink to this object on the site.', 'nisje' ),
					'format'      => 'url',
					'type'        => 'string',
					'readonly'    => true,
				],
				'description'        => [
					'description' => esc_html__( 'The description of the group used in the group listings. A short description of the group.', 'nisje' ),
					'type'        => 'object',
					'arg_options' => [
						'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
						'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
					],
					'context'     => [ 'view', 'edit', 'member' ],
					'required'    => true,
					'properties'  => [
						'raw'      => [
							'description' => esc_html__( 'Description of the group, as it exists in the database.', 'nisje' ),
							'type'        => 'string',
							'context'     => [ 'edit', 'member' ],
						],
						'rendered' => [
							'description' => esc_html__( 'HTML description of the group transformed for display.', 'nisje' ),
							'type'        => 'string',
							'context'     => [ 'view', 'edit', 'member' ],
							'readonly'    => true,
						],
					],
				],
				'full_description'   => [
					'description' => esc_html__( 'Full description of the group.', 'nisje' ),
					'type'        => 'object',
					'arg_options' => [
						'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
						'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
					],
					'context'     => [ 'view', 'edit', 'member' ],
					'properties'  => [
						'raw'      => [
							'description' => esc_html__( 'Full description of the group., as it exists in the database.', 'nisje' ),
							'type'        => 'string',
							'context'     => [ 'edit', 'member' ],
						],
						'rendered' => [
							'description' => esc_html__( 'HTML full description of the group transformed for display.', 'nisje' ),
							'type'        => 'string',
							'context'     => [ 'view', 'edit', 'member' ],
							'readonly'    => true,
						],
					],
				],
				'parent_id'          => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'The ID of the parent group.', 'nisje' ),
					'type'        => 'integer',
				],
				'status'             => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'The status of the group.', 'nisje' ),
					'type'        => 'string',
					'enum'        => [ 'public', 'private', 'hidden' ],
				],
				'date_created'       => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'The date the object was published, in the site timezone.', 'nisje' ),
					'type'        => 'string',
					'format'      => 'date-time',
				],
				'last_activity'      => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'Last activity in group', 'nisje' ),
					'type'        => 'string',
					'format'      => 'date-time',
					'readonly'    => true,
				],
				'total_member_count' => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'Total group members', 'nisje' ),
					'type'        => 'integer',
					'readonly'    => true,
				],
				'group_types'        => [
					'context'           => [ 'view', 'edit', 'member' ],
					'description'       => esc_html__( 'Group Types.', 'nisje' ),
					'type'              => 'array',
					'items'             => [
						'type' => 'string',
					],
					'sanitize_callback' => [ $this, 'sanitize_group_types' ],
					'validate_callback' => [ $this, 'validate_group_types' ],
				],
				'invite_status'      => [
					'context'     => [ 'edit', 'member' ],
					'description' => esc_html__( 'Invite status for the group.', 'nisje' ),
					'type'        => 'string',
					'default'     => $default_invite_status,
					'enum'        => $allowed_invite_statuses,
				],
				'enable_forum'       => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'Whether the group has a forum or not.', 'nisje' ),
					'type'        => 'boolean',
				],
				'enable_event'       => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'Whether the group has a events or not.', 'nisje' ),
					'type'        => 'boolean',
				],
				'enable_news'        => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'Whether the group has a news or not.', 'nisje' ),
					'type'        => 'boolean',
				],
				'enable_wiki'        => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'Whether the group has a wiki or not.', 'nisje' ),
					'type'        => 'boolean',
				],
				'color'              => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'Color ID.', 'nisje' ),
					'type'        => 'integer',
				],
				'admins'             => [
					'context'     => [ 'edit', 'member' ],
					'description' => esc_html__( 'Group administrators.', 'nisje' ),
					'type'        => 'array',
					'items'       => [
						'type' => 'string',
					],
					'readonly'    => true,
				],
				'mods'               => [
					'context'     => [ 'edit', 'member' ],
					'description' => esc_html__( 'Group modificators.', 'nisje' ),
					'type'        => 'array',
					'items'       => [
						'type' => 'string',
					],
					'readonly'    => true,
				],
				'is_mod'             => [
					'context'     => [ 'edit', 'member' ],
					'description' => esc_html__( 'Whether current user is moderator or not.', 'nisje' ),
					'type'        => 'boolean',
					'readonly'    => true,
				],
				'is_admin'           => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'Whether current user is administrator or not.', 'nisje' ),
					'type'        => 'boolean',
					'readonly'    => true,
				],
				'is_mod'             => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'Whether current user is moderator or not.', 'nisje' ),
					'type'        => 'boolean',
					'readonly'    => true,
				],
				'is_member'          => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'Whether current user is member or not.', 'nisje' ),
					'type'        => 'boolean',
					'readonly'    => true,
				],
				'is_invited'         => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'Whether current user is invited or not.', 'nisje' ),
					'type'        => 'boolean',
					'readonly'    => true,
				],
				'is_banned'          => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'Whether current user is banned or not.', 'nisje' ),
					'type'        => 'boolean',
					'readonly'    => true,
				],
				'is_pending'         => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'Whether current user is pending or not.', 'nisje' ),
					'type'        => 'boolean',
					'readonly'    => true,
				],
				'user_has_access'    => [
					'context'     => [ 'edit', 'member' ],
					'description' => esc_html__( 'Whether current user has access.', 'nisje' ),
					'type'        => 'boolean',
					'readonly'    => true,
				],
				'user_list'          => [
					'context'     => [ 'view', 'edit', 'member' ],
					'description' => esc_html__( 'User listing.', 'nisje' ),
					'type'        => 'array',
					'items'       => [
						'type' => 'string',
					],
					'readonly'    => true,
				],
			],
		];

		/**
		 * Filter group schema object.
		 *
		 * @param object $schema
		 */
		$schema = apply_filters( 'nisje_filter_group_schema_object', $schema );

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

	/**
	 * Get the query params for collections of plugins.
	 *
	 * @return array
	 */
	public function get_collection_params() {
		$params = parent::get_collection_params();

		$params['context']['default'] = 'view';

		// @TODO Make this extendable.
		$registered_functionality = [
			'all',
			'wiki',
			'news',
			'event',
		];

		$params['type'] = [
			'description'       => esc_html__( 'Shorthand for certain orderby/order combinations', 'nisje' ),
			'type'              => 'string',
			'default'           => 'active',
			'enum'              => [ 'active', 'newest', 'alphabetical', 'random', 'popular' ],
			'validate_callback' => 'rest_validate_request_arg',
		];

		$params['order'] = [
			'description'       => esc_html__( 'Order sort attribute ascending or descending.', 'nisje' ),
			'type'              => 'string',
			'default'           => 'DESC',
			'enum'              => [ 'ASC', 'DESC' ],
			'validate_callback' => 'rest_validate_request_arg',
		];

		$params['orderby'] = [
			'description'       => esc_html__( 'Order groups by which attribute.', 'nisje' ),
			'type'              => 'string',
			'default'           => 'date_created',
			'enum'              => [ 'date_created', 'last_activity', 'total_member_count', 'name', 'random' ],
			'validate_callback' => 'rest_validate_request_arg',
		];

		$params['user_id'] = [
			'description'       => esc_html__( 'Pass a user_id to limit to only groups that this user is a member of.', 'nisje' ),
			'type'              => 'integer',
			'default'           => 0,
			'sanitize_callback' => 'absint',
			'validate_callback' => 'rest_validate_request_arg',
		];

		$params['include'] = [
			'description'       => esc_html__( 'Ensure result set includes specific IDs.', 'nisje' ),
			'type'              => 'array',
			'default'           => [],
			'sanitize_callback' => 'wp_parse_id_list',
		];

		$params['exclude'] = [
			'description'       => esc_html__( 'Ensure result set excludes specific IDs.', 'nisje' ),
			'type'              => 'array',
			'default'           => [],
			'sanitize_callback' => 'wp_parse_id_list',
		];

		$params['search'] = [
			'description'       => esc_html__( 'Limit result set to items that match this search query.', 'nisje' ),
			'default'           => '',
			'type'              => 'string',
			'sanitize_callback' => 'sanitize_text_field',
			'validate_callback' => 'rest_validate_request_arg',
		];

		$params['parent_id'] = [
			'description'       => esc_html__( 'Get groups that are children of the specified group(s).', 'nisje' ),
			'type'              => 'array',
			'default'           => [],
			'sanitize_callback' => 'wp_parse_id_list',
		];

		$params['group_type'] = [
			'description'       => esc_html__( 'Limit results set to groups of a certain type.', 'nisje' ),
			'default'           => '',
			'type'              => 'string',
			'sanitize_callback' => 'sanitize_text_field',
			'validate_callback' => 'rest_validate_request_arg',
		];

		$params['group_type__in'] = [
			'description'       => esc_html__( 'Limit results set to groups of certain types. Comma separated string of types.', 'nisje' ),
			'default'           => '',
			'type'              => 'string',
			'sanitize_callback' => [ $this, 'sanitize_group_types' ],
			'validate_callback' => [ $this, 'validate_group_types' ],
		];

		$params['group_type__not_in'] = [
			'description'       => esc_html__( 'Exclude groups of certain types. Comma separated string of types.', 'nisje' ),
			'default'           => '',
			'type'              => 'string',
			'sanitize_callback' => [ $this, 'sanitize_group_types' ],
			'validate_callback' => [ $this, 'validate_group_types' ],
		];

		$params['show_hidden'] = [
			'description'       => esc_html__( 'Whether results should include hidden groups.', 'nisje' ),
			'default'           => false,
			'type'              => 'boolean',
			'validate_callback' => 'rest_validate_request_arg',
		];

		$params['functionality'] = [
			'description'       => esc_html__( 'Limit results set to groups with certain fucntionality. Comma separated string of functionality.', 'nisje' ),
			'type'              => 'string',
			'default'           => 'all',
			'enum'              => $registered_functionality,
			'validate_callback' => 'rest_validate_request_arg',
		];

		$params['per_page'] = [
			'description'       => esc_html__( 'Maximum number of results returned per result set.', 'nisje' ),
			'default'           => 20,
			'type'              => 'integer',
			'sanitize_callback' => 'absint',
			'validate_callback' => 'rest_validate_request_arg',
		];

		$params['page'] = [
			'description'       => esc_html__( 'Offset the result set by a specific number of pages of results.', 'nisje' ),
			'default'           => 1,
			'type'              => 'integer',
			'sanitize_callback' => 'absint',
			'validate_callback' => 'rest_validate_request_arg',
		];

		return $params;
	}

	/**
	 * Clean up group_type__in input.
	 *
	 * @param string $value Comma-separated list of group types.
	 * @return array|null
	 */
	public function sanitize_group_types( $value ) {
		if ( ! empty( $value ) ) {
			$types = explode( ',', $value );

			$registered_types = bp_groups_get_group_types();

			$valid_types = array_intersect( $types, $registered_types );

			if ( ! empty( $valid_types ) ) {
				return $valid_types;
			} else {
				return null;
			}
		}

		return $value;
	}

	/**
	 * Validate group_type__in input.
	 *
	 * @param mixed           $value   Group Type Value.
	 * @param WP_REST_Request $request Rest Request.
	 * @param string          $param   Parameter.
	 * @return WP_Error|boolean
	 */
	public function validate_group_types( $value, $request, $param ) {
		if ( ! empty( $value ) ) {
			$types = explode( ',', $value );

			$registered_types = bp_groups_get_group_types();
			foreach ( $types as $type ) {
				if ( ! in_array( $type, $registered_types, true ) ) {
					// translators: 1: user provied group type 2: List of registered group types.
					return new \WP_Error( 'nisje_rest_group_invalid_group_type', sprintf( esc_html__( 'The group type you provided, %1$s, is not one of %2$s.', 'nisje' ), $type, implode( ', ', $registered_types ) ) );
				}
			}
		}
		return true;
	}
}
