<?php
/**
 * Endpoints to retrieve information about profile field groups
 *
 * @package Nisje
 */

namespace Dekode\Nisje\Components\Rest;

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

/**
 * Xprofile Groups Rest Class
 */
class XProfile_Groups_Controller extends \WP_REST_Controller {

	/**
	 * Constructor
	 */
	public function __construct() {
		$this->namespace = nisje_get_rest_namespace();
		$this->rest_base = buddypress()->profile->id . '/groups';
		$this->hook_base = strtolower( buddypress()->profile->id . '_groups' );
	}

	/**
	 * 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(),
				],
				'schema' => [ $this, 'get_public_item_schema' ],
			]
		);
	}

	/**
	 * Permission Check.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_Error|bool
	 */
	public function get_items_permissions_check( $request ) {
		$auth = nisje_validate_rest_authentication( $this->hook_base, 'get_items' );
		if ( is_wp_error( $auth ) ) {
			return $auth;
		}

		return true;
	}

	/**
	 * Retrieve xprofile field groups.
	 *
	 * @param WP_REST_Request $request Rest Request.
	 * @return WP_REST_Request List of object data.
	 */
	public function get_items( $request ) {
		$args = [
			'user_id'                => $request['user_id'],
			'member_type'            => $request['member_type'],
			'hide_empty_groups'      => $request['hide_empty_groups'],
			'hide_empty_fields'      => $request['hide_empty_fields'],
			'fetch_fields'           => $request['fetch_fields'],
			'fetch_field_data'       => $request['fetch_field_data'],
			'fetch_visibility_level' => $request['fetch_visibility_level'],
			'exclude_groups'         => $request['exclude_groups'],
			'exclude_fields'         => $request['exclude_fields'],
			'update_meta_cache'      => $request['update_meta_cache'],
		];

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

		$retval       = [];
		$field_groups = bp_xprofile_get_groups( $args );
		foreach ( $field_groups as $item ) {
			$data     = $this->prepare_item_for_response( $item, $request );
			$retval[] = $this->prepare_response_for_collection( $data );
		}

		$response = rest_ensure_response( $retval );

		return $response;
	}

	/**
	 * Prepares xProfile groups for return as an object.
	 *
	 * @param stdClass        $item    Item data.
	 * @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']['id'] ) ) {
			$data['id'] = (int) $item->id;
		}

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

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

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

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

		if ( ! empty( $request['fetch_fields'] ) && isset( $item->fields ) && is_array( $item->fields ) ) {
			$data['fields'] = [];
			foreach ( $item->fields as $field ) {
				$can_edit = bp_xprofile_get_meta( $field->id, 'field', 'member_can_edit' );
				$can_edit = ( ! $can_edit || 'yes' === $can_edit ) ? true : false;

				$field_data = [
					'id'                => (int) $field->id,
					'group_id'          => (int) $field->group_id,
					'parent_id'         => (int) $field->parent_id,
					'type'              => $field->type,
					'name'              => $field->name,
					'description'       => $field->description,
					'is_required'       => (bool) $field->is_required,
					'can_delete'        => (bool) $field->can_delete,
					'can_edit'          => $can_edit,
					'field_order'       => (int) $field->field_order,
					'option_order'      => (int) $field->option_order,
					'order_by'          => $field->order_by,
					'is_default_option' => (bool) $field->is_default_option,
				];

				if ( ! empty( $request['fetch_visibility_level'] ) ) {
					$field_data['visibility_level'] = $field->visibility_level;
				}

				if ( ! empty( $request['fetch_field_data'] ) ) {
					if ( isset( $field->data->id ) ) {
						$field_data['data']['id'] = $field->data->id;
					}

					$field_data['data']['value'] = maybe_unserialize( $field->data->value );
				}

				$field_data['options'] = $field->get_children( true );

				$data['fields'][] = $field_data;
			}
		}

		$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 );
	}

	/**
	 * Check if a given request has access to get information about a specific field.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return bool
	 */
	public function get_item_permissions_check( $request ) {
		return $this->get_items_permissions_check( $request );
	}

	/**
	 * Prepare links for the request.
	 *
	 * @param array $item Xprofile group.
	 * @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;
	}

	/**
	 * Clean up member_type input.
	 *
	 * @param string $value Comma-separated list of group types.
	 * @return array|null
	 */
	public function sanitize_member_types( $value ) {
		if ( ! empty( $value ) ) {
			$types            = explode( ',', $value );
			$registered_types = bp_get_member_types();
			// Add the special value.
			$registered_types[] = 'any';
			$valid_types        = array_intersect( $types, $registered_types );

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

	/**
	 * Validate member_type input.
	 *
	 * @param  mixed           $value   Value.
	 * @param  WP_REST_Request $request The Request.
	 * @param  string          $param   Param.
	 *
	 * @return WP_Error|boolean
	 */
	public function validate_member_types( $value, $request, $param ) {
		if ( ! empty( $value ) ) {
			$types            = explode( ',', $value );
			$registered_types = bp_get_member_types();
			// Add the special value.
			$registered_types[] = 'any';
			foreach ( $types as $type ) {
				if ( ! in_array( $type, $registered_types, true ) ) {
					// translators: %1$s: Member type %2$s: Registered member types.
					return new \WP_Error( 'rest_invalid_group_type', sprintf( esc_html__( 'The member type you provided, %1$s, is not one of %2$s.', 'nisje' ), $type, implode( ', ', $registered_types ) ) );
				}
			}
		}
		return true;
	}

	/**
	 * Get the extended profile group 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' => [
				'id'          => [
					'context'     => [ 'view', 'edit' ],
					'description' => esc_html__( 'A unique alphanumeric ID for the object.', 'nisje' ),
					'readonly'    => true,
					'type'        => 'integer',
				],
				'name'        => [
					'context'     => [ 'view', 'edit' ],
					'description' => esc_html__( 'The name of the profile field group.', 'nisje' ),
					'type'        => 'string',
				],
				'description' => [
					'context'     => [ 'view', 'edit' ],
					'description' => esc_html__( 'The description of the profile field group.', 'nisje' ),
					'type'        => 'string',
				],
				'group_order' => [
					'context'     => [ 'view', 'edit' ],
					'description' => esc_html__( 'The order of the group.', 'nisje' ),
					'type'        => 'integer',
				],
				'can_delete'  => [
					'context'     => [ 'view', 'edit' ],
					'description' => esc_html__( 'Whether the profile field group can be deleted or not.', 'nisje' ),
					'type'        => 'boolean',
				],
				'fields'      => [
					'context'     => [ 'view', 'edit' ],
					'description' => esc_html__( 'The fields associated with this field group.', 'nisje' ),
					'type'        => 'array',
					'items'       => [
						'type' => 'string',
					],
				],
			],
		];

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

	/**
	 * Get the query params for xprofile field groups.
	 *
	 * @return array
	 */
	public function get_collection_params() {
		$params                       = parent::get_collection_params();
		$params['context']['default'] = 'view';

		$params['user_id'] = [
			'description'       => esc_html__( 'Required if you want to load a specific user\'s data.', 'nisje' ),
			'default'           => bp_loggedin_user_id(),
			'type'              => 'integer',
			'sanitize_callback' => 'absint',
		];

		$params['member_type'] = [
			'description'       => esc_html__( 'Limit fields by those restricted to a given member type, or array of member types.', 'nisje' ),
			'default'           => false,
			'type'              => 'array',
			'sanitize_callback' => [ $this, 'sanitize_member_types' ],
			'validate_callback' => [ $this, 'validate_member_types' ],
		];

		$params['hide_empty_groups'] = [
			'description' => esc_html__( 'True to hide field groups where the user has not provided data.', 'nisje' ),
			'default'     => false,
			'type'        => 'boolean',
		];

		$params['hide_empty_fields'] = [
			'description' => esc_html__( 'True to hide fields where the user has not provided data.', 'nisje' ),
			'default'     => false,
			'type'        => 'boolean',
		];

		$params['fetch_fields'] = [
			'description' => esc_html__( 'Whether to fetch the fields for each group.', 'nisje' ),
			'default'     => false,
			'type'        => 'boolean',
		];

		$params['fetch_field_data'] = [
			'description' => esc_html__( 'Whether to fetch data for each field. Requires a $user_id.', 'nisje' ),
			'default'     => false,
			'type'        => 'boolean',
		];

		$params['fetch_visibility_level'] = [
			'description' => esc_html__( 'Whether to fetch the visibility level for each field.', 'nisje' ),
			'default'     => false,
			'type'        => 'boolean',
		];

		$params['exclude_groups'] = [
			'description' => esc_html__( 'Ensure result set excludes specific profile field groups.', 'nisje' ),
			'default'     => [],
			'type'        => 'array',
			'items'       => [
				'type' => 'integer',
			],
		];

		$params['exclude_fields'] = [
			'description' => esc_html__( 'Ensure result set excludes specific profile fields.', 'nisje' ),
			'default'     => [],
			'type'        => 'array',
			'items'       => [
				'type' => 'integer',
			],
		];

		$params['update_meta_cache'] = [
			'description' => esc_html__( 'Whether to pre-fetch xprofilemeta for all retrieved groups, fields, and data.', 'nisje' ),
			'default'     => true,
			'type'        => 'boolean',
		];

		return $params;
	}
}
