<?php
/**
 * The Form class file.
 *
 * @package hcaptcha-wp
 */

namespace HCaptcha\EssentialBlocks;

use HCaptcha\Helpers\API;
use HCaptcha\Helpers\HCaptcha;
use HCaptcha\Helpers\Request;
use HCaptcha\Helpers\Utils;
use WP_Block;

/**
 * Class Form.
 */
class Form {

	/**
	 * Nonce action.
	 */
	private const ACTION = 'hcaptcha_essential_blocks';

	/**
	 * Nonce name.
	 */
	private const NONCE = 'hcaptcha_essential_blocks_nonce';

	/**
	 * Script handle.
	 */
	private const HANDLE = 'hcaptcha-essential-blocks';

	/**
	 * Form constructor.
	 */
	public function __construct() {
		$this->init_hooks();
	}

	/**
	 * Init hooks.
	 *
	 * @return void
	 */
	protected function init_hooks(): void {
		// Disable recaptcha compatibility; otherwise, the Essential Blocks script fails.
		$settings = hcaptcha()->settings();

		if ( ! $settings ) {
			// @codeCoverageIgnoreStart
			return;
			// @codeCoverageIgnoreEnd
		}

		$settings->set( 'recaptcha_compat_off', [ 'on' ] );

		add_action( 'wp_ajax_eb_form_submit', [ $this, 'verify' ], 9 );
		add_action( 'wp_ajax_nopriv_eb_form_submit', [ $this, 'verify' ], 9 );

		if ( ! Request::is_frontend() ) {
			return;
		}

		add_filter( 'render_block', [ $this, 'add_hcaptcha' ], 10, 3 );
		add_action( 'wp_head', [ $this, 'print_inline_styles' ] );
		add_action( 'wp_print_footer_scripts', [ $this, 'enqueue_scripts' ], 9 );
		add_filter( 'script_loader_tag', [ $this, 'add_type_module' ], 10, 3 );
	}

	/**
	 * Add hcaptcha to an Essential Blocks form.
	 *
	 * @param string|mixed $block_content The block content.
	 * @param array        $block         The full block, including name and attributes.
	 * @param WP_Block     $instance      The block instance.
	 *
	 * @return string
	 * @noinspection PhpUnusedParameterInspection
	 */
	public function add_hcaptcha( $block_content, array $block, WP_Block $instance ): string {
		if ( 'essential-blocks/form' !== $block['blockName'] ) {
			return (string) $block_content;
		}

		$form_id = $this->get_form_id_from_block_content( (string) $block_content );

		$search = '<div class="eb-form-submit';
		$args   = [
			'action' => self::ACTION,
			'name'   => self::NONCE,
			'id'     => [
				'source'  => HCaptcha::get_class_source( __CLASS__ ),
				'form_id' => $form_id,
			],
		];

		return str_replace( $search, HCaptcha::form( $args ) . "\n" . $search, $block_content );
	}

	/**
	 * Get form id from block content.
	 *
	 * @param string $block_content Block content.
	 *
	 * @return string
	 * @noinspection NestedTernaryOperatorInspection
	 */
	private function get_form_id_from_block_content( string $block_content ): string {
		if (
			preg_match( '/<button\b(?=[^>]*\beb-form-submit-button\b)[^>]*>/i', $block_content, $button ) &&
			preg_match( '/\bdata-id=(?:"([^"]+)"|\'([^\']+)\'|([^\s>]+))/i', $button[0], $m )
		) {
			return $m[1] ?: ( $m[2] ?: $m[3] );
		}

		if ( preg_match( '/<form id="(.+?)"/', $block_content, $m ) ) {
			return $m[1];
		}

		return '0';
	}

	/**
	 * Verify the hCaptcha.
	 *
	 * @return void
	 */
	public function verify(): void {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing
		$form_data_str = isset( $_POST['form_data'] ) ? sanitize_text_field( wp_unslash( $_POST['form_data'] ) ) : '';
		$form_data     = Utils::json_decode_arr( $form_data_str );

		$error_message = API::verify( $this->get_entry( $form_data, $this->get_form_id() ) );

		if ( null !== $error_message ) {
			wp_send_json_error( $error_message );
		}
	}

	/**
	 * Get entry.
	 *
	 * @param array  $form_data Form data.
	 * @param string $form_id   Form id.
	 *
	 * @return array
	 */
	private function get_entry( array $form_data, string $form_id ): array {
		return [
			'nonce_name'         => self::NONCE,
			'nonce_action'       => self::ACTION,
			'h-captcha-response' => $form_data['h-captcha-response'] ?? '',
			'post_data'          => $form_data,
			'data'               => $this->get_data( $form_data ),
			'expected_id'        => $this->get_expected_id( $form_id ),
		];
	}

	/**
	 * Get expected hCaptcha widget id.
	 *
	 * @param string $form_id Form id.
	 *
	 * @return array
	 */
	private function get_expected_id( string $form_id ): array {
		return [
			'source'  => HCaptcha::get_class_source( __CLASS__ ),
			'form_id' => $form_id,
		];
	}

	/**
	 * Get form id.
	 *
	 * @return string
	 */
	private function get_form_id(): string {
		return (string) Request::filter_input( INPUT_POST, 'form_id' );
	}

	/**
	 * Get data for anti-spam checks.
	 *
	 * @param array $form_data Form data.
	 *
	 * @return array
	 */
	private function get_data( array $form_data ): array {
		$data          = [];
		$excluded_keys = [
			'hcap_',
			'hcaptcha-',
			'hcaptcha_',
			'h-captcha-response',
			'_wp_http_referer',
		];

		foreach ( $form_data as $key => $value ) {
			$key_str = (string) $key;

			foreach ( $excluded_keys as $excluded_key ) {
				if ( 0 === strpos( $key_str, $excluded_key ) ) {
					continue 2;
				}
			}

			$data[ $key ] = $value;
		}

		return $data;
	}

	/**
	 * Print inline styles.
	 *
	 * @return void
	 * @noinspection CssUnusedSymbol
	 */
	public function print_inline_styles(): void {
		/* language=CSS */
		$css = '
	.wp-block-essential-blocks-form .h-captcha {
		margin: 15px 0 0 0;
	}
';

		HCaptcha::css_display( $css );
	}

	/**
	 * Enqueue scripts.
	 *
	 * @return void
	 */
	public function enqueue_scripts(): void {
		$min = hcap_min_suffix();

		wp_enqueue_script(
			self::HANDLE,
			HCAPTCHA_URL . "/assets/js/hcaptcha-essential-blocks$min.js",
			[],
			HCAPTCHA_VERSION,
			true
		);
	}

	/**
	 * Add the type="module" attribute to the script tag.
	 *
	 * @param string|mixed $tag    Script tag.
	 * @param string       $handle Script handle.
	 * @param string       $src    Script source.
	 *
	 * @return string
	 * @noinspection PhpUnusedParameterInspection
	 */
	public function add_type_module( $tag, string $handle, string $src ): string {
		$tag = (string) $tag;

		if ( self::HANDLE !== $handle ) {
			return $tag;
		}

		return HCaptcha::add_type_module( $tag );
	}
}
