<?php
/**
 * Ajax request.
 *
 * @package northmill-online-shared
 */

declare(strict_types=1);

namespace Northmill_Online_Shared\Modules\Ajax\Models;

use Northmill_Online_Shared\Abstract_Shared_Base;
use Northmill_Online_Shared\Modules\Loggers\Log_Level_Enum;

defined( 'ABSPATH' ) || exit;

/**
 * Class Request
 */
class Request_Model {

	/**
	 * Ajax request action (without wp_ajax_ prefix).
	 *
	 * @var string
	 */
	protected string $action;

	/**
	 * Requested function (usually a sync), callable.
	 * Has to be publicly accessible.
	 *
	 * @var array
	 */
	protected array $request_function;

	/**
	 * Is the request public? Available when not logged in.
	 *
	 * @var bool $is_public
	 */
	protected bool $is_public = false;

	/**
	 * Request nonce.
	 *
	 * @var string
	 */
	protected string $nonce_name;

	/**
	 * Nonce query arg.
	 *
	 * @var false|string
	 */
	protected false|string $nonce_query_arg;

	/**
	 * Request constructor.
	 *
	 * @param string       $key              Request key.
	 * @param array        $request_function Request function.
	 * @param bool         $is_public        Whether the request is public (available when not logged in).
	 * @param string|null  $nonce_name       Nonce name.
	 * @param false|string $nonce_query_arg  Nonce query arg.
	 */
	public function __construct(
		string $key,
		array $request_function,
		bool $is_public = false,
		?string $nonce_name = null,
		false|string $nonce_query_arg = false
	) {
		$this->action           = Abstract_Shared_Base::base_instance()->package_info->get_service_name_underscored() . '_' . $key;
		$this->request_function = $request_function;
		$this->is_public        = $is_public;
		$this->nonce_query_arg  = $nonce_query_arg;

		if ( empty( $nonce_name ) ) {
			$nonce_name = $this->action . '_nonce';
		}

		$this->nonce_name = $nonce_name;

		add_action(
			'wp_ajax_' . $this->action,
			array( $this, 'ajax_request' )
		);

		if ( $this->is_public ) {
			add_action(
				'wp_ajax_nopriv_' . $this->action,
				array( $this, 'ajax_request' )
			);
		}
	}

	/**
	 * Get AJAX action.
	 *
	 * @return string
	 */
	public function get_action(): string {
		return $this->action;
	}

	/**
	 * Get nonce name.
	 *
	 * @return string
	 */
	public function get_nonce_name(): string {
		return $this->nonce_name;
	}

	/**
	 * Get nonce.
	 *
	 * @return string
	 */
	public function get_nonce(): string {
		return wp_create_nonce( $this->get_nonce_name() );
	}

	/**
	 * Get nonce query arg.
	 *
	 * @param bool $is_for_frontend Whether to get the query arg for frontend.
	 *                              If false, return the query arg as it is set.
	 *                              If true, always returns string with default value if not explicitly set.
	 *
	 * @return string|bool
	 */
	public function get_nonce_query_arg( bool $is_for_frontend = false ): string|bool {
		if ( $is_for_frontend ) {
			return $this->nonce_query_arg ? $this->nonce_query_arg : '_ajax_nonce';
		} else {
			return $this->nonce_query_arg;
		}
	}

	/**
	 * Get data for JS.
	 *
	 * @return array
	 */
	public function get_data_for_js(): array {
		return array(
			'action'          => $this->get_action(),
			'nonce'           => $this->get_nonce(),
			'nonce_query_arg' => $this->get_nonce_query_arg( true ),
		);
	}

	/**
	 * Ajax request.
	 *
	 * @return void
	 */
	public function ajax_request(): void {
		$this->verify_request( $params );
		$response = call_user_func( $this->request_function, $params );
		$this->respond( $response );
	}

	/**
	 * Verify ajax request validity.
	 *
	 * @param array|null $params         API request params.
	 */
	protected function verify_request( ?array &$params = null ): void {
		$this->verify_nonce();
		$this->get_post_data( $params );
	}

	/**
	 * Verify request nonce.
	 */
	private function verify_nonce(): void {
		check_ajax_referer( $this->get_nonce_name(), $this->get_nonce_query_arg() );
	}

	/**
	 * Verify request page data.
	 *
	 * @param null|array $params API params.
	 */
	protected function get_post_data( ?array &$params ): void {}

	/**
	 * Respond to ajax request.
	 *
	 * @param mixed $response Response data.
	 *
	 * @return void
	 */
	protected function respond( mixed $response ): void {
		if (
			! empty( $_SERVER['HTTP_X_REQUESTED_WITH'] ) &&
			strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) === 'xmlhttprequest'
		) {
			Abstract_Shared_Base::base_instance()->logger->log(
				'ajax',
				'Ajax response.',
				array(
					'response' => $response,
				),
				Log_Level_Enum::DEBUG
			);

			wp_send_json_success( $response );
		} else {
			Abstract_Shared_Base::base_instance()->logger->log(
				'ajax',
				'Not an ajax request.',
				array(
					'$_SERVER' => $_SERVER,
				),
				Log_Level_Enum::ERROR
			);

			wp_send_json_error( 'fail' );
		}
	}
}
