<?php
/**
 * Defines Products Product/Category Searching API
 *
 * @link https://personalizewp.com/
 * @since 3.2.0
 *
 * @package PersonalizeWP
 * @subpackage PersonalizeWP/Admin/REST
 */

namespace PersonalizeWP\Admin\REST;

use PersonalizeWP\Admin\Admin;

defined( 'ABSPATH' ) || exit; // Exit if accessed directly.

/**
 * Products Controller class
 *
 * @extends  WP_REST_Controller
 */
class Products_Controller extends \WP_REST_Controller {

	/**
	 * Field containing the items in the search result.
	 */
	const RESULT_ITEMS = 'items';

	/**
	 * Field containing the total count in the search result.
	 */
	const RESULT_TOTAL = 'total';

	/**
	 * Endpoint namespace.
	 *
	 * @since 2.0.0
	 *
	 * @var string
	 */
	protected $namespace;

	/**
	 * Route base.
	 *
	 * @since 2.0.0
	 *
	 * @var string
	 */
	protected $rest_base = 'products-search';

	/**
	 * Initialize the class and set its properties.
	 *
	 * @param string $ns Namespace
	 */
	public function __construct( $ns ) {
		$this->namespace = $ns;
	}

	/**
	 * Registers the routes for the search controller.
	 *
	 * @since 2.0.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {
		register_rest_route(
			"{$this->namespace}v1",
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => \WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permission_check' ),
					'args'                => $this->get_collection_params(),
				),
			)
		);
	}

	/**
	 * Checks if a given request has access to search content.
	 *
	 * @since 2.0.0
	 *
	 * @param \WP_REST_Request $request Full details about the request.
	 * @return true|\WP_Error True if the request has search access, WP_Error object otherwise.
	 */
	public function get_items_permission_check( $request ) {

		// Restrict endpoint to only PWP admin users.
		if ( current_user_can( Admin::CAP ) ) {
			return true;
		}

		// This is an allow-list approach.
		return new \WP_Error( 'rest_forbidden', esc_html__( 'You can not view private data.', 'personalizewp' ), array( 'status' => 401 ) );
	}

	/**
	 * Retrieves a collection of search results.
	 *
	 * @since 2.0.0
	 *
	 * @param \WP_REST_Request $request Full details about the request.
	 * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {

		$total     = 0;
		$max_pages = 0;
		$page      = (int) $request['page'];
		$per_page  = (int) $request['per_page'];
		$results   = array();
		$subtype   = $request['subtype'] ?? 'products,categories'; // Default is both

		$subtypes = array_map( 'trim', explode( ',', $subtype ) );

		// Search products if subtype includes 'products'
		if ( in_array( 'products', $subtypes, true ) ) {
			$wc_products = $this->search_wc_products( $request );
			if ( ! $wc_products->is_error() ) {
				$headers   = $wc_products->get_headers();
				$total    += (int) $headers['X-WP-Total'];
				$max_pages = max( $max_pages, (int) $headers['X-WP-TotalPages'] );
				$data      = $wc_products->get_data();

				// Format the results in a form that the JS and processing can use.
				foreach ( $data as $product ) {
					$results[] = array(
						'value' => sprintf(
							'woocommerce:product:%1$d',
							$product['id']
						),
						'title' => $product['title'],
						'url'   => '', // $product['url'],
					);
				}
			}
		}

		// Search categories if subtype includes 'categories'
		if ( in_array( 'categories', $subtypes, true ) ) {
			$wc_cats = $this->search_wc_cats( $request );
			if ( ! $wc_cats->is_error() ) {
				$headers   = $wc_cats->get_headers();
				$total    += (int) $headers['X-WP-Total'];
				$max_pages = max( $max_pages, (int) $headers['X-WP-TotalPages'] );
				$data      = $wc_cats->get_data();

				// Format the results in a form that the JS and processing can use.
				foreach ( $data as $cat ) {
					$results[] = array(
						'value' => sprintf(
							'woocommerce:%1$s:%2$d',
							$cat['type'],
							$cat['id']
						),
						'title' => $cat['title'],
						'url'   => '', // $cat['url'],
					);
				}
			}
		}

		if ( 0 === $max_pages ) {
			$max_pages = (int) ceil( $total / $per_page );
		}
		if ( $page > $max_pages && $total > 0 ) {
			return new \WP_Error(
				'rest_search_invalid_page_number',
				__( 'The page number requested is larger than the number of pages available.', 'personalizewp' ),
				array( 'status' => 400 )
			);
		}

		$response = rest_ensure_response( $results );
		$response->header( 'X-WP-Total', $total );
		$response->header( 'X-WP-TotalPages', $max_pages );

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

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

		return $response;
	}

	/**
	 * Searches WooCommerce Products via the WP Search route
	 *
	 * @since 2.0.0
	 *
	 * @param \WP_REST_Request $request Full REST request.
	 * @return WP_REST_Response Response data for the request.
	 */
	private function search_wc_products( \WP_REST_Request $request ) {

		$request_products = new \WP_REST_Request( 'GET', '/wp/v2/search' );
		$params           = $request->get_params();
		// Add specified request parameters into the request.
		if ( ! empty( $params ) ) {
			foreach ( $params as $param_name => $param_value ) {
				$request_products->set_param( $param_name, $param_value );
			}
		}
		$request_products->set_param( 'type', 'post' );
		$request_products->set_param( 'subtype', 'product' );

		return rest_do_request( $request_products );
	}

	/**
	 * Searches WooCommerce Categories via the WP Search route
	 *
	 * @since 2.0.0
	 *
	 * @param \WP_REST_Request $request Full REST request.
	 * @return WP_REST_Response Response data for the request.
	 */
	private function search_wc_cats( \WP_REST_Request $request ) {

		$request_cats = new \WP_REST_Request( 'GET', '/wp/v2/search' );
		$params       = $request->get_params();
		// Add specified request parameters into the request.
		if ( ! empty( $params ) ) {
			foreach ( $params as $param_name => $param_value ) {
				$request_cats->set_param( $param_name, $param_value );
			}
		}
		$request_cats->set_param( 'type', 'term' );
		$request_cats->set_param( 'subtype', 'product_cat,product_tag' );

		return rest_do_request( $request_cats );
	}

	/**
	 * Retrieves the query params for the search collection.
	 *
	 * @since 3.0.0
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params() {
		$params = parent::get_collection_params();

		$params['subtype'] = array(
			'description' => __( 'Limit results to specific subtypes.', 'personalizewp' ),
			'type'        => 'string',
			'default'     => 'products,categories',
		);

		return $params;
	}
}
