<?php
/**
 * Please see weepie-framework.php for more details.
 */

namespace WpieFw\Rest;

use WpieFw\Wpie\WpieGlobals;
use WpieFw\Helpers\WpieAjaxHelper;
use WpieFw\Helpers\WpieMiscHelper;
use WpieFw\Exceptions\WpieExceptionLogger;
use WpieFw\Exceptions\WpieExceptionInterface;
use WpieFw\Exceptions\WpieInvalidArgumentException;
use WpieFw\Exceptions\WpieUnexpectedValueException;

if( ! defined( 'ABSPATH' ) ) exit;

/**
 * WpieRestApi Class
 *
 * @since 2.0.14
 */
final class WpieRestApi
{
	/**
	 * @var WpieGlobals
	 */
	private $globals;

	/**
	 * @var string
	 */
	private $namespace = '';

	/**
	 * @var array
	 */
	private $routes = [];

	/**
	 * Constructor
	 *
	 * @access public
	 * @param WpieGlobals $globals
	 */
	public function __construct( WpieGlobals $globals )
	{
		$this->globals = $globals;
		// TODO make api version configurable
		$this->namespace = $this->globals->nameSpace . '/v1';
	}

	/**
	 * Add rest routes
	 *
	 * Plugins can add shortcodes with the hook {$nameSpace}_register_rest_routes
	 *
	 * @access public
	 * @uses register_rest_route()
	 */
	public function init()
	{
		if( !WpieAjaxHelper::doingRestRequest() ) {
			return;
		}

		try {
			/**
			 * Let plugin modules register rest routes
			 *
			 * @param array $routes
			 *
			 * @since 2.0.14
			 * @return array
			 */
			$this->routes = apply_filters( $this->globals->nameSpace . '_register_rest_routes', $this->routes );
		} catch( WpieExceptionInterface $e ) {
			throw $e;
		}

		if( is_array( $this->routes ) && 0 < count( $this->routes ) ) {
			add_action( 'rest_api_init', [ $this, 'registerRoutes' ] );
		}
	}

	/**
	 * Callback for the rest_api_init hook
	 *
	 * @uses register_rest_route()
	 * @uses self::processRestRequest()
	 * @since 2.0.14
	 */
	public function registerRoutes()
	{
		foreach ( $this->routes as $route ) {
			$args = [
				'methods' => 'GET',
				'callback' => null
			];

			if( !isset( $route['action'] ) ) {
				throw new WpieInvalidArgumentException( 'Cannot register route, no action found' );
			}
			if( !isset( $route['callback'] ) ) {
				throw new WpieInvalidArgumentException( 'Cannot register route, no callback found' );
			}

			$action = $route['action'];

			if( 0 !== strpos( $route['action'], '/' ) ) {
				$action = "/$action";
				$route['action'] = $action;
			}

			if( isset( $route['method'] ) ) {
				$args['methods'] = $route['method'];
			}

			if( isset( $route['callback'] ) && is_callable( $route['callback'] ) ) {
				$callback = $route['callback'];

				$args['callback'] = function( $request ) use( $action, $callback ) {
					return $this->processRestRequest( $action, $callback, $request );
				};
			}

			register_rest_route( $this->namespace, $action, $args );
		}
	}

	/**
	 * Process the REST request
	 *
	 * @param string $action
	 * @param callable $callback
	 * @param \WP_REST_Request $request
	 *
	 * @uses check_ajax_referer()
	 * @since 2.0.14
	 */
	private function processRestRequest( string $action, $callback, \WP_REST_Request $request )
	{
		$state = 1;
		$r = [
			'state' => (string)$state,
			'out' => '',
			'errors' => []
		];

		try {
			if( false === check_ajax_referer( 'wp_rest', false, false ) ) {
				throw new WpieUnexpectedValueException( sprintf( 'failed verifying rest nonce for route %s', $request->route ) );
			}

			// TODO add validation hooks like WpieHooks::processAjaxRequest()

			// Call the function implemented by the plugin
			$return = call_user_func( $callback, $request );

			if( null === $return ) {
				return $return;
			}

			if( is_array( $return ) && isset( $return['content'] ) ) {
				return $return['content'];
			}

			$allowFalse = ( is_array( $return ) && isset( $return['allow_false'] ) );

			$state = WpieAjaxHelper::isValidData( $return, $allowFalse ); // 1 or 0

			if(  0 === $state && isset( $return['errors'] ) ) {
				$r['errors'] = array_merge( $r['errors'],  [ $return['errors'] ] );
			} elseif(  0 === $state && isset( $return['msg'] ) ) {
				$r['errors'] = array_merge( $r['errors'],  [ $return['msg'] ] );
			} elseif( 1 === $state ) {
				$r['out'] = $return;
			}

			if( WpieAjaxHelper::hasWpError( $return ) ) {
				$r['errors'] = array_merge( $r['errors'],  WpieAjaxHelper::getWpErrors( $return ) );
			}
		} catch( WpieExceptionInterface $e ) {
			WpieExceptionLogger::log( $e );

			if( WpieMiscHelper::isDebugModeLoggedIn() ) {
				/** @var WpieExceptionInterface $e */
				$r['errors'][] = $e->getMessage();
			}
		}

		$r['state'] = (string)$state;
		return $r;
	}
}