<?php


namespace Jet_Form_Builder\Blocks;

use Jet_Form_Builder\Blocks\Types\Base;
use Jet_Form_Builder\Form_Manager;
use JFB_Modules\Post_Type\Module as Post_Type_Module;

// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}

/**
 * Class Helper
 *
 * @package Jet_Form_Builder\Blocks
 */
class Block_Helper {

	const PREVIEW_NONCE_FIELD  = 'jfb_preview_nonce';
	const PREVIEW_NONCE_ACTION = 'jfb-preview-form';

	/**
	 * @since 3.1.1
	 *
	 * @param $parsed_block
	 *
	 * @return string
	 * @throws \Exception
	 */
	public static function resolve_name( $parsed_block ): string {
		$name = is_string( $parsed_block ) ? $parsed_block : $parsed_block['blockName'] ?? '';

		// is has 'jet-forms/' namespace in 'blockName' property
		if ( ! is_string( $name ) || ! self::is_field( $name ) ) {
			throw new \Exception( 'empty_or_not_field' );
		}

		if ( ! \WP_Block_Type_Registry::get_instance()->is_registered( $name ) ) {
			throw new \Exception( 'not_block_type' );
		}

		return self::delete_namespace( $name );
	}

	/**
	 * @param $value
	 * @param $blocks
	 *
	 * @return array
	 */
	public static function find_block_by_name( $value, $blocks ): array {
		return self::find_block_by_attr( $value, 'name', $blocks );
	}

	/**
	 * @param $value
	 * @param $attr_name
	 * @param $blocks
	 *
	 * @return array
	 */
	public static function find_block_by_attr( $value, $attr_name, $blocks ): array {
		return self::find_block(
			function ( $block ) use ( $value, $attr_name ) {
				if ( empty( $block['blockName'] ) || ! self::is_field( $block['blockName'] ) ) {
					return false;
				}
				return ( ( $block['attrs'][ $attr_name ] ?? false ) === $value );
			},
			$blocks
		);
	}

	public static function find_by_block_name( array $blocks, string $block_name ): array {
		return self::find_block(
			function ( $block ) use ( $block_name ) {
				return ( ( $block['blockName'] ?? false ) === $block_name );
			},
			$blocks
		);
	}

	public static function get_form_field_names( $blocks ): array {
		$names = array();

		self::find_block(
			function ( $block ) use ( &$names ) {
				if ( empty( $block['blockName'] ) || ! self::is_field( $block['blockName'] ) ) {
					return false;
				}
				if ( ! empty( $block['attrs']['name'] ) ) {
					$names[ $block['attrs']['name'] ] = 1;
				}

				return false;
			},
			$blocks
		);

		return $names;
	}

	public static function find_block( $callback, $blocks ): array {
		if ( ! is_callable( $callback ) ) {
			return array();
		}
		foreach ( $blocks as $block ) {
			if ( ! isset( $block['blockName'] ) && ! isset( $block['attrs'] ) ) {
				continue;
			}
			if ( call_user_func( $callback, $block ) ) {
				return $block;
			}

			if ( 0 < count( $block['innerBlocks'] ?? array() ) ) {
				$find = self::find_block( $callback, $block['innerBlocks'] );

				if ( $find ) {
					return $find;
				}
			}
		}

		return array();
	}


	/**
	 * @param $blocks
	 * @param string $scope
	 *
	 * @return array
	 */
	public static function filter_blocks_by_namespace( $blocks, string $scope = Form_Manager::NAMESPACE_FIELDS ): array {
		$fields    = array();
		$generator = self::generate_blocks_in_space( $blocks, $scope );

		foreach ( $generator as $block ) {
			$fields[] = $block;
		}

		return $fields;
	}

	/**
	 * @since 3.1.0
	 *
	 * @param array $blocks
	 * @param string $scope
	 *
	 * @return \Generator
	 */
	public static function generate_blocks_in_space(
		array $blocks,
		string $scope = Form_Manager::NAMESPACE_FIELDS
	): \Generator {
		return self::generate_blocks(
			static function ( $block ) use ( $scope ) {
				return ( false !== stripos( $block['blockName'], $scope ) );
			},
			$blocks
		);
	}

	/**
	 * @param $callback
	 * @param array $source
	 *
	 * @return \Generator
	 * @since 3.1.0
	 */
	public static function generate_blocks( $callback, array $source ): \Generator {
		foreach ( $source as $index => $block ) {
			if ( ! isset( $block['blockName'] ) ) {
				continue;
			}
			if ( call_user_func( $callback, $block ) ) {
				yield $block;
			}

			if ( ! empty( $block['innerBlocks'] ) ) {
				yield from self::generate_blocks( $callback, $block['innerBlocks'] );
			}
		}
	}

	public static function is_valid_form_post( $post_id, bool $allow_preview = false ): bool {
		$post = get_post( $post_id );

		if ( ! is_a( $post, \WP_Post::class ) ) {
			return false;
		}

		if (
			Post_Type_Module::SLUG === $post->post_type
			&& 'publish' === $post->post_status
		) {
			return true;
		}

		return $allow_preview && self::is_valid_preview_form_post( $post );
	}

	public static function is_valid_preview_form_post( $post_id ): bool {
		$post = get_post( $post_id );

		if ( ! is_a( $post, \WP_Post::class ) ) {
			return false;
		}

		$nonce = self::get_preview_nonce();

		if ( ! $nonce || ! wp_verify_nonce( $nonce, self::PREVIEW_NONCE_ACTION ) ) {
			return false;
		}

		if ( Post_Type_Module::SLUG === $post->post_type ) {
			return current_user_can( 'edit_jet_fb_form', $post->ID );
		}

		if ( 'revision' !== $post->post_type ) {
			return false;
		}

		$parent_form_id = (int) $post->post_parent;
		$parent_form    = $parent_form_id ? get_post( $parent_form_id ) : null;

		if ( ! $parent_form || Post_Type_Module::SLUG !== $parent_form->post_type ) {
			return false;
		}

		return current_user_can( 'edit_jet_fb_form', $parent_form_id );
	}

	public static function get_preview_nonce(): string {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( empty( $_REQUEST[ self::PREVIEW_NONCE_FIELD ] ) ) {
			return '';
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		return sanitize_text_field( wp_unslash( $_REQUEST[ self::PREVIEW_NONCE_FIELD ] ) );
	}

	public static function get_blocks_by_post(
		$post_id,
		bool $require_form_post = true,
		bool $allow_preview = false
	): array {
		$post = get_post( $post_id );

		if ( ! is_a( $post, \WP_Post::class ) ) {
			return array();
		}

		if ( $require_form_post && ! self::is_valid_form_post( $post, $allow_preview ) ) {
			return array();
		}

		return array_map(
			function ( $block ) {
				self::walk_by_reusable( $block );

				return $block;
			},
			parse_blocks( $post->post_content )
		);
	}

	private static function walk_by_reusable( array &$block ) {
		if ( ! empty( $block['innerBlocks'] ) ) {
			foreach ( $block['innerBlocks'] as &$current ) {
				self::walk_by_reusable( $current );
			}

			return;
		}

		if ( 'core/block' !== $block['blockName'] ) {
			return;
		}

		$reusable_id          = $block['attrs']['ref'] ?? 0;
		$block['innerBlocks'] = self::get_blocks_by_post( $reusable_id, false );
	}

	public static function delete_namespace( $block ): string {
		if ( is_array( $block ) ) {
			$block = $block['blockName'] ?? '';
		}

		if ( stripos( $block, '/' ) === false ) {
			return $block;
		}

		return explode( '/', $block )[1] ?? '';
	}

	public static function is_field( string $block_name ): bool {
		return ( stripos( $block_name, Form_Manager::NAMESPACE_FIELDS ) !== false );
	}

	public static function render_with_context( $block, $context ) {
		return ( new \WP_Block( $block, $context ) )->render();
	}

	public static function pref( string $block_name ): string {
		return Form_Manager::NAMESPACE_FIELDS . self::delete_namespace( $block_name );
	}

	public static function get_attrs_from_block( array $block, array $attrs_list ): array {
		$source = $block['attrs'] ?? $block;
		$attrs  = array();

		foreach ( $attrs_list as $attr_name ) {
			if ( ! isset( $source[ $attr_name ] ) ) {
				continue;
			}
			$attrs[ $attr_name ] = $source[ $attr_name ];
		}

		return $attrs;
	}

	/**
	 * @param $names
	 *
	 * @return array|string
	 */
	public static function get_block_names( $names ) {
		if ( ! is_array( $names ) ) {
			$block = jet_form_builder()->blocks->get_field_by_name( $names );

			return $block->get_name();
		}

		return array_map(
			array( static::class, 'get_block_names' ),
			$names
		);
	}

	public static function current_block(): array {
		return is_array( \WP_Block_Supports::$block_to_render )
			? \WP_Block_Supports::$block_to_render
			: array();
	}


	/**
	 * @return \WP_Block_Type|null
	 */
	public static function current_block_type() {
		$name = self::current_block()['blockName'] ?? '';

		return \WP_Block_Type_Registry::get_instance()->get_registered( $name );
	}

}
