<?php
/**
 * Intercept
 *
 * @package   WP Grid Builder
 * @author    Loïc Blascos
 * @copyright 2019-2024 Loïc Blascos
 */

namespace WP_Grid_Builder\Includes\FrontEnd;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Intercept request to filter custom content
 *
 * @class WP_Grid_Builder\FrontEnd\Intercept
 * @since 1.5.1
 */
final class Intercept {

	/**
	 * Requested data
	 *
	 * @since 1.5.1
	 * @access protected
	 *
	 * @var array
	 */
	protected $request = [];

	/**
	 * Action type
	 *
	 * @since 1.5.1
	 * @access protected
	 *
	 * @var string
	 */
	protected $action = '';

	/**
	 * Content identifier
	 *
	 * @since 1.5.1
	 * @access protected
	 *
	 * @var string
	 */
	protected $content = '';

	/**
	 * Content interceptor
	 *
	 * @since 1.5.1
	 * @access protected
	 *
	 * @var string
	 */
	protected $interceptor = 'wpgb-content';

	/**
	 * Next query identifier
	 *
	 * @since 1.5.1
	 * @access protected
	 *
	 * @var string
	 */
	protected $next_query_id = '';

	/**
	 * Output buffering start level
	 *
	 * @since 1.8.1
	 * @access protected
	 *
	 * @var integer
	 */
	protected $ob_start_level = 0;

	/**
	 * Constructor
	 *
	 * @since 1.5.1
	 * @access public
	 */
	public function __construct() {

		add_filter( 'wp_grid_builder/async/intercept', [ $this, 'intercept' ], 10, 3 );
		add_filter( 'wp_grid_builder/facet/render_args', [ $this, 'content_name' ] );
		add_filter( 'wp_grid_builder/facet/transient_name', [ $this, 'transient_name' ] );
		add_filter( 'render_block_core/shortcode', [ $this, 'prerender_query_shortcode' ] );

		add_shortcode( 'wpgb_query', [ $this, 'next_query_id' ] );
		add_action( 'pre_get_posts', [ $this, 'check_query_id' ], PHP_INT_MAX - 9 );

		if ( wpgb_get_option( 'filter_custom_content' ) ) {

			add_action( 'loop_no_results', [ $this, 'inject' ] );
			add_action( 'loop_start', [ $this, 'inject' ] );

		}
	}

	/**
	 * Maybe intercept request
	 *
	 * @since 1.5.1
	 * @access public
	 *
	 * @param boolean $intercept Whether to intercept requested.
	 * @param string  $action    Requested action.
	 * @param array   $request   Requested data.
	 * @return boolean
	 */
	public function intercept( $intercept, $action, $request ) {

		if ( empty( $request['id'] ) || 0 !== stripos( $request['id'], $this->interceptor ) ) {
			return $intercept;
		}

		$this->action  = $action;
		$this->request = $request;
		$this->content = $request['id'];

		do_action( 'wp_grid_builder/async/' . $action, $this->request );
		add_action( 'shutdown', [ $this, 'filter' ], -1 );

		ob_start();
		$this->ob_start_level = ob_get_level();

		return true;

	}

	/**
	 * Format content/grid name for consistency
	 * Prevent duplication of jS instances if name is inconsistent.
	 *
	 * @since 1.5.1
	 * @access public
	 *
	 * @param array $args Facet paramters.
	 * @return array
	 */
	public function content_name( $args ) {

		if ( 0 === stripos( trim( $args['grid'] ), $this->interceptor ) ) {
			$args['grid'] = strtolower( trim( $args['grid'] ) );
		}

		return $args;

	}

	/**
	 * Change transient name to correctly cache content depending on the page
	 *
	 * @since 1.5.1
	 * @access public
	 *
	 * @param string $transient Facet transient name.
	 * @return string
	 */
	public function transient_name( $transient ) {

		if ( false !== stripos( $transient, 'g' . $this->interceptor ) ) {
			$transient .= md5( preg_replace( '/\?.*/', '', get_pagenum_link() ) );
		}

		return $transient;

	}

	/**
	 * Prerender shortcode block to correctly detect custom queries in the block editor.
	 * Shortcodes are rendered after all blocks. So we have to render shortcodes when rendering each block.
	 *
	 * @since 1.6.8
	 * @access public
	 *
	 * @param string $block_content The block content about to be appended.
	 * @return string
	 */
	public function prerender_query_shortcode( $block_content ) {

		if ( preg_match( '/\[wpgb_query.*?\]/m', $block_content ) ) {
			$block_content = do_shortcode( $block_content );
		}

		return $block_content;

	}

	/**
	 * Setup query ID for the next WP_Query
	 *
	 * @since 1.5.1
	 * @access public
	 *
	 * @param array $atts Shortcode attributes.
	 */
	public function next_query_id( $atts ) {

		$this->next_query_id = 'wpgb-content';

		if ( empty( $atts['id'] ) ) {
			return;
		}

		$query_id = trim( $atts['id'] );

		if ( 0 === stripos( $query_id, $this->interceptor ) ) {
			$this->next_query_id = strtolower( $atts['id'] );
		}
	}

	/**
	 * Check if it is a filterable query from page content
	 *
	 * @since 1.5.1
	 * @access public
	 *
	 * @param object $query Holds WP query object.
	 */
	public function check_query_id( $query ) {

		$this->set_query_id( $query );

		$query_id   = $query->get( 'wp_grid_builder' );
		$is_archive = $this->is_archive( $query_id );

		if ( ! $is_archive && ! $this->is_custom( $query_id ) ) {

			$this->unset_query_id( $query );
			return;

		}

		$query->set( 'wpgb_inject', $is_archive ? $this->interceptor : $query_id );

	}

	/**
	 * Add query ID setup from shortcode [wpgb_query]
	 *
	 * @since 1.5.1
	 * @access public
	 *
	 * @param object $query Holds WP query object.
	 */
	public function set_query_id( $query ) {

		if ( empty( $this->next_query_id ) ) {
			return;
		}

		$query->set( 'wp_grid_builder', $this->next_query_id );
		$this->next_query_id = '';

	}

	/**
	 * Unset query ID that does not need to be filtered
	 *
	 * @since 1.5.1
	 * @access public
	 *
	 * @param object $query Holds WP query object.
	 */
	public function unset_query_id( $query ) {

		if ( ! wpgb_doing_ajax() || empty( $this->request ) ) {
			return;
		}

		// Optimize query that does not need to be filtered.
		if ( $query->get( 'wp_grid_builder' ) ) {

			$query->set( 'posts_per_page', 1 );
			$query->set( 'no_found_rows', 1 );
			$query->set( 'meta_query', [] );
			$query->set( 'tax_query', [] );
			$query->set( 'post__in', [] );

		}

		// Unset query to prevent conflicts with multiple content.
		$query->set( 'wp_grid_builder', false );

	}

	/**
	 * Whether it is an archive query
	 *
	 * @since 1.5.1
	 * @access public
	 *
	 * @param boolean|string $query_id Query identifier.
	 */
	public function is_archive( $query_id ) {

		return true === $query_id || 'true' === $query_id;

	}

	/**
	 * Whether it is a custom query
	 *
	 * @since 1.5.1
	 * @access public
	 *
	 * @param boolean|string $query_id Query identifier.
	 */
	public function is_custom( $query_id ) {

		$query_id = strtolower( trim( $query_id ) );

		return (
			( ! empty( $this->request ) && $query_id === $this->content ) ||
			( empty( $this->request ) && 0 === strpos( $query_id, $this->interceptor ) )
		);
	}

	/**
	 * Inject hidden placeholder in the page
	 *
	 * @since 1.5.1
	 * @access public
	 *
	 * @param object $query Holds WP query object.
	 */
	public function inject( $query ) {

		if ( ! $query->get( 'wpgb_inject' ) ) {
			return;
		}

		// We only inject in 3rd party content from the body.
		if ( ! did_action( 'wp_head' ) || doing_action( 'wp_head' ) || doing_action( 'wp_grid_builder/layout/do_loop' ) ) {
			return;
		}

		echo '<div class="' . sanitize_html_class( strtolower( $query->get( 'wpgb_inject' ) ) ) . '" hidden></div>';

		$query->set( 'wpgb_inject', false );

	}

	/**
	 * Clean all output buffers
	 *
	 * @since 1.8.1
	 * @access public
	 */
	public function clean_output() {

		$levels = ob_get_level();

		for ( $i = 0; $i < $levels; $i++ ) {
			ob_end_clean();
		}
	}

	/**
	 * Fetch page content before to shutdown
	 *
	 * @since 1.5.1
	 * @access public
	 */
	public function fetch() {

		$content = '';

		while ( ob_get_level() > $this->ob_start_level ) {
			ob_end_flush();
		}

		preg_match( '/<body.*\/body>/s', ob_get_clean(), $body );
		$this->clean_output();

		if ( ! empty( $body ) ) {
			$content = trim( current( $body ) );
		}

		return $content;

	}

	/**
	 * Filter page content
	 *
	 * @since 1.5.1
	 * @access public
	 */
	public function filter() {

		switch ( $this->action ) {
			case 'render':
				$this->render();
				break;
			case 'refresh':
				$this->refresh();
				break;
			case 'search':
				$this->search();
				break;
		}

		wp_die();

	}

	/**
	 * Render facets on first load
	 *
	 * @since 1.5.1
	 * @access protected
	 */
	protected function render() {

		$this->clean_output();

		wp_send_json(
			apply_filters(
				'wp_grid_builder/async/render_response',
				[
					'facets' => wpgb_refresh_facets( $this->request ),
				],
				$this->request
			)
		);
	}

	/**
	 * Refresh facets and content
	 *
	 * @since 1.5.1
	 * @access protected
	 */
	protected function refresh() {

		wp_send_json(
			apply_filters(
				'wp_grid_builder/async/refresh_response',
				[
					'facets' => wpgb_refresh_facets( $this->request ),
					'posts'  => $this->fetch(),
				],
				$this->request
			)
		);
	}

	/**
	 * Search for facet choices
	 *
	 * @since 1.5.1
	 * @access protected
	 */
	protected function search() {

		$this->clean_output();

		wp_send_json(
			apply_filters(
				'wp_grid_builder/async/search_response',
				wpgb_search_facet_choices( $this->request ),
				$this->request
			)
		);
	}
}
