<?php
/**
 * Defines Forms 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.

/**
 * Forms Controller class
 *
 * @extends  WP_REST_Controller
 */
class Forms_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 = 'forms-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;
		$page     = (int) $request['page'];
		$per_page = (int) $request['per_page'];
		$offset   = (int) ( $request['page'] - 1 ) * $per_page;
		$results  = array();

		// Separately search each type of supported form via their API
		$gf_result = $this->search_gravityforms( $request );
		if ( is_array( $gf_result[ self::RESULT_ITEMS ] ) && isset( $gf_result[ self::RESULT_TOTAL ] ) ) {
			$total += (int) $gf_result[ self::RESULT_TOTAL ];

			// Format the results in a form that the JS and processing can use.
			foreach ( $gf_result[ self::RESULT_ITEMS ] as $form ) {
				$results[] = array(
					'value' => 'gravityforms:' . $form['id'],
					'title' => sprintf(
						/* translators: 1: %s form name. */
						__( 'Gravity Form: %s', 'personalizewp' ),
						$form['title']
					),
				);
			}
		}
		$nf_result = $this->search_ninjaforms( $request );
		if ( is_array( $nf_result[ self::RESULT_ITEMS ] ) && isset( $nf_result[ self::RESULT_TOTAL ] ) ) {
			$total += (int) $nf_result[ self::RESULT_TOTAL ];

			// Format the results in a form that the JS and processing can use.
			foreach ( $nf_result[ self::RESULT_ITEMS ] as $form ) {
				$results[] = array(
					'value' => 'ninjaforms:' . $form['id'],
					'title' => sprintf(
						/* translators: 1: %s form name. */
						__( 'Ninja Form: %s', 'personalizewp' ),
						$form['title']
					),
				);
			}
		}
		$ws_result = $this->search_wsform( $request );
		if ( is_array( $ws_result[ self::RESULT_ITEMS ] ) && isset( $ws_result[ self::RESULT_TOTAL ] ) ) {
			$total += (int) $ws_result[ self::RESULT_TOTAL ];

			// Format the results in a form that the JS and processing can use.
			foreach ( $ws_result[ self::RESULT_ITEMS ] as $form ) {
				$results[] = array(
					'value' => 'wsform:' . $form['id'],
					'title' => sprintf(
						/* translators: 1: %s form name. */
						__( 'WS Form: %s', 'personalizewp' ),
						$form['title']
					),
				);
			}
		}

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

		// Slice the results based on pagination
		$results = array_slice( $results, $offset, $per_page );

		$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 GravityForms for a given search request.
	 *
	 * @since 2.0.0
	 *
	 * @param \WP_REST_Request $request Full REST request.
	 * @return array {
	 *     Associative array containing found items and total count for the matching search results.
	 *
	 *     @type array[] $items Array containing the matching form IDs and titles.
	 *     @type int     $total Total count for the matching search results.
	 * }
	 */
	private function search_gravityforms( \WP_REST_Request $request ) {

		$forms = ! defined( 'WP_DEBUG' || ! WP_DEBUG ) ? wp_cache_get( 'gravityforms', 'personalizewp' ) : false;
		if ( false === $forms ) {
			$forms = [];

			// Searching Gravity Forms
			if ( class_exists( '\GFAPI' ) ) {
				$_forms = \GFAPI::get_forms( true, false, 'title', 'ASC' );
				if ( ! empty( $_forms ) ) {
					foreach ( $_forms as $form ) {
						$forms[] = array(
							'id'    => $form['id'],
							'title' => $form['title'],
						);
					}
				}
			}
			// Briefly cache the response during searching
			wp_cache_add( 'gravityforms', $forms, 'personalizewp', 5 * MINUTE_IN_SECONDS );
		}

		if ( ! empty( $request['search'] ) ) {
			$search = strtolower( $request['search'] );
			// Sub-select forms
			$forms = array_filter(
				$forms,
				function ( $form ) use ( $search ) {
					return str_contains( strtolower( $form['title'] ), $search );
				}
			);
		}

		return array(
			self::RESULT_ITEMS => $forms,
			self::RESULT_TOTAL => count( $forms ),
		);
	}

	/**
	 * Searches NinjaForms for a given search request.
	 *
	 * @since 2.1.0
	 *
	 * @param \WP_REST_Request $request Full REST request.
	 * @return array {
	 *     Associative array containing found items and total count for the matching search results.
	 *
	 *     @type array[] $items Array containing the matching form IDs and titles.
	 *     @type int     $total Total count for the matching search results.
	 * }
	 */
	private function search_ninjaforms( \WP_REST_Request $request ) {

		$forms = ! defined( 'WP_DEBUG' || ! WP_DEBUG ) ? wp_cache_get( 'ninjaforms', 'personalizewp' ) : false;
		if ( false === $forms ) {
			$forms = [];

			// Searching Ninja Forms
			if ( class_exists( '\Ninja_Forms' ) ) {
				$_forms = \Ninja_Forms()->form()->get_forms();
				if ( ! empty( $_forms ) ) {
					foreach ( $_forms as $form ) {
						$forms[] = array(
							'id'    => $form->get_id(),
							'title' => $form->get_setting( 'title' ),
						);
					}
				}
			}
			// Briefly cache the response during searching
			wp_cache_add( 'ninjaforms', $forms, 'personalizewp', 5 * MINUTE_IN_SECONDS );
		}

		if ( ! empty( $request['search'] ) ) {
			$search = strtolower( $request['search'] );
			// Sub-select forms
			$forms = array_filter(
				$forms,
				function ( $form ) use ( $search ) {
					return str_contains( strtolower( $form['title'] ), $search );
				}
			);
		}

		return array(
			self::RESULT_ITEMS => $forms,
			self::RESULT_TOTAL => count( $forms ),
		);
	}

	/**
	 * Searches WS Form for a given search request.
	 *
	 * @since 2.2.0
	 *
	 * @param \WP_REST_Request $request Full REST request.
	 * @return array {
	 *     Associative array containing found items and total count for the matching search results.
	 *
	 *     @type array[] $items Array containing the matching form IDs and titles.
	 *     @type int     $total Total count for the matching search results.
	 * }
	 */
	private function search_wsform( \WP_REST_Request $request ) {

		$forms = ! defined( 'WP_DEBUG' || ! WP_DEBUG ) ? wp_cache_get( 'wsform', 'personalizewp' ) : false;
		if ( false === $forms ) {
			$forms = [];

			// Searching Ninja Forms
			if ( class_exists( '\WS_Form' ) ) {
				$_forms = wsf_form_get_all_key_value();
				if ( ! empty( $_forms ) ) {
					foreach ( $_forms as $form_id => $form_label ) {
						$forms[] = array(
							'id'    => $form_id,
							'title' => $form_label,
						);
					}
				}
			}
			// Briefly cache the response during searching
			wp_cache_add( 'wsform', $forms, 'personalizewp', 5 * MINUTE_IN_SECONDS );
		}

		if ( ! empty( $request['search'] ) ) {
			$search = strtolower( $request['search'] );
			// Sub-select forms
			$forms = array_filter(
				$forms,
				function ( $form ) use ( $search ) {
					return str_contains( strtolower( $form['title'] ), $search );
				}
			);
		}

		return array(
			self::RESULT_ITEMS => $forms,
			self::RESULT_TOTAL => count( $forms ),
		);
	}
}
