<?php
namespace QuantumCache\Cache;

\defined( 'ABSPATH' ) || exit;

/**
 * AutoFragments
 *
 * Caches expensive pieces of post pages across classic and block themes:
 *  - the_content (classic + many block themes)
 *  - core/post-content (block)
 *  - core/query (query loop)
 *  - common blocks (navigation, latest-posts, archives)
 *  - classic widget sidebars
 *
 * DEBUG:
 *  Define QUANTUMCACHE_DEBUG_MARKERS=true (in wp-config.php) to inject <!--QC-AF ...--> markers
 *  into the HTML so you can verify fragments are executed/cached on a GET MISS.
 */
final class AutoFragments {

	/** Prevent double-wrapping the main post body across the two wrappers */
	private static array $wrapped_body = array();

	/** Track which sidebars we started buffering for */
	private static array $sidebar_buffering = array();

	public static function register(): void {
				\QuantumCache\quantumcache_send_debug_header( 'X-QC-AF-Register', '1' );
		$enabled = (int) get_option( 'quantumcache_auto_fragments', 1 );
		\QuantumCache\quantumcache_send_debug_header( 'X-QC-AF-Enabled', (string) $enabled );
		if ( 1 !== $enabled ) {
			return;
		}

		// Classic themes: run just after core's default (10) so we wrap the final content.
		add_filter( 'the_content', array( __CLASS__, 'wrap_the_content' ), 11 );

		// Block themes: catch post body and the heavy query loop.
		add_filter( 'render_block', array( __CLASS__, 'wrap_post_content_block' ), 9999, 2 );
		add_filter( 'render_block', array( __CLASS__, 'wrap_query_block' ), 9999, 2 );

		// Fallback for list loops that render excerpts.
		add_filter( 'the_excerpt', array( __CLASS__, 'wrap_the_excerpt' ), 9999 );

		// Common blocks worth caching.
		add_filter( 'render_block', array( __CLASS__, 'wrap_common_blocks' ), 9999, 2 );

		// Classic sidebars (widget areas).
		add_action( 'dynamic_sidebar_before', array( __CLASS__, 'sidebar_begin' ), 0, 1 );
		add_action( 'dynamic_sidebar_after', array( __CLASS__, 'sidebar_end' ), 9999, 1 );
	}

	/** Basic guards: anonymous GET front-end HTML */
	private static function can_cache(): bool {
		if ( is_admin() ) {
			return false;
		}

		$req_method = isset( $_SERVER['REQUEST_METHOD'] )
			? strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) )
			: 'GET';

		if ( 'GET' !== $req_method ) {
			return false;
		}

		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return false;
		}

		if ( is_preview() || is_feed() ) {
			return false;
		}

		if ( function_exists( 'is_user_logged_in' ) && is_user_logged_in() ) {
			if ( function_exists( 'quantumcache_allow_logged_in_cache' ) ) {
				if ( ! quantumcache_allow_logged_in_cache() ) {
					return false;
				}
			} else {
				// No policy helper available → default to anon-only.
				return false;
			}
		}

		return true;
	}

	/** Inject <!--QC-AF ...--> markers when quantumcache_debug_markers option = 1 */
	private static function mark( string $name, string $html ): string {
		if ( (bool) get_option( 'quantumcache_debug_markers', false ) ) {
			return "<!--QC-AF {$name} start-->{$html}<!--QC-AF {$name} end-->";
		}
		return $html;
	}

	/** Classic + most block themes route through the_content on single posts */
	public static function wrap_the_content( $html ) {
		if ( ! self::can_cache() || ! is_singular() || ! in_the_loop() || ! is_main_query() || '' === (string) $html ) {
			\QuantumCache\quantumcache_send_debug_header(
				'X-QC-AF-the_content-skip',
				(string) (int) self::can_cache() . ',' .
				(string) (int) is_singular() . ',' .
				(string) (int) in_the_loop() . ',' .
				(string) (int) is_main_query() . ',' .
				( '' === (string) $html ? 'empty' : 'ok' )
			);
			return $html;
		}

		\QuantumCache\quantumcache_send_debug_header( 'X-QC-AF-the_content', 'wrap' );

		$post_id = (int) get_the_ID();
		if ( $post_id <= 0 ) {
			return $html;
		}

		// Shared guard with core/post-content to avoid double-wrap.
		if ( isset( self::$wrapped_body[ $post_id ] ) ) {
			return $html;
		}
		self::$wrapped_body[ $post_id ] = true;

		$ptype = get_post_type( $post_id );
		$ptype = $ptype ? $ptype : 'post';

		if ( ! self::can_cache() || ! is_singular() || ! in_the_loop() || ! is_main_query() || '' === (string) $html ) {
			\QuantumCache\quantumcache_send_debug_header(
				'X-QC-AF-the_content-skip',
				(string) (int) self::can_cache() . ',' .
				(string) (int) is_singular() . ',' .
				(string) (int) in_the_loop() . ',' .
				(string) (int) is_main_query() . ',' .
				( '' === (string) $html ? 'empty' : 'ok' )
			);
			return $html;
		}
		\QuantumCache\quantumcache_send_debug_header( 'X-QC-AF-the_content', 'wrap' );

		return \QuantumCache\quantumcache_fragment(
			'single:the_content',
			array(
				'id'    => $post_id,
				'ptype' => $ptype,
			),
			static function () use ( $html ) {
				return self::mark( 'the_content', (string) $html );
			},
			array(
				'tags'      => array( 'post:' . $post_id, 'post_type:' . $ptype ),
				'ttl'       => 600,
				'stale_ttl' => 86400,
			)
		);
	}

	/** Block theme: core/post-content */
	public static function wrap_post_content_block( string $block_content, array $block ) {
		if ( ! self::can_cache() || ! is_singular() ) {
			return $block_content;
		}

		if ( empty( $block['blockName'] ) || 'core/post-content' !== $block['blockName'] ) {
			return $block_content;
		}

		$post_id = (int) get_queried_object_id();
		if ( $post_id <= 0 ) {
			return $block_content;
		}

		// Shared guard with the_content to avoid double-wrap.
		if ( isset( self::$wrapped_body[ $post_id ] ) ) {
			return $block_content;
		}
		self::$wrapped_body[ $post_id ] = true;

		$ptype = get_post_type( $post_id );
		$ptype = $ptype ? $ptype : 'post';

		return \QuantumCache\quantumcache_fragment(
			'single:post_content_block',
			array(
				'id'    => $post_id,
				'ptype' => $ptype,
			),
			static function () use ( $block_content ) {
				return self::mark( 'post_content_block', $block_content );
			},
			array(
				'tags'      => array( 'post:' . $post_id, 'post_type:' . $ptype ),
				'ttl'       => 600,
				'stale_ttl' => 86400,
			)
		);
	}

	/** Excerpts in archive loops etc. */
	public static function wrap_the_excerpt( $html ) {
		if ( ! self::can_cache() || ! in_the_loop() || ! is_main_query() || '' === (string) $html ) {
			return $html;
		}

		$post_id = (int) get_the_ID();
		if ( $post_id <= 0 ) {
			return $html;
		}

		static $wrapped = array();
		if ( isset( $wrapped[ "excerpt:$post_id" ] ) ) {
			return $html;
		}
		$wrapped[ "excerpt:$post_id" ] = true;

		$ptype = get_post_type( $post_id );
		$ptype = $ptype ? $ptype : 'post';

		return \QuantumCache\quantumcache_fragment(
			'loop:the_excerpt',
			array(
				'id'    => $post_id,
				'ptype' => $ptype,
			),
			static function () use ( $html ) {
				return self::mark( 'the_excerpt', (string) $html );
			},
			array(
				'tags'      => array( 'post:' . $post_id, 'post_type:' . $ptype ),
				'ttl'       => 300,
				'stale_ttl' => 86400,
			)
		);
	}

	/** Block theme: core/query (query loop) */
	public static function wrap_query_block( string $block_content, array $block ) {
		if ( ! self::can_cache() ) {
			return $block_content;
		}

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

		$attrs   = ( isset( $block['attrs'] ) && is_array( $block['attrs'] ) ) ? $block['attrs'] : array();
		$query   = ( isset( $attrs['query'] ) && is_array( $attrs['query'] ) ) ? $attrs['query'] : array();
		$orderby = isset( $query['orderBy'] ) ? (string) $query['orderBy'] : '';

		// Skip highly-volatile shapes.
		if ( 'rand' === strtolower( $orderby ) ) {
			return $block_content;
		}

		$default_paged = get_query_var( 'paged' );
		$default_paged = $default_paged ? (int) $default_paged : 1;

		$page_in = isset( $query['page'] ) ? (int) $query['page'] : $default_paged;
		$page    = max( 1, $page_in );

		$ctx = array(
			'page'     => $page,
			'per_page' => (int) ( $query['perPage'] ?? 10 ),
			'postType' => (string) ( $query['postType'] ?? 'post' ),
			'order'    => (string) ( $query['order'] ?? 'desc' ),
			'orderBy'  => $orderby,
			'taxQuery' => md5( wp_json_encode( $query['taxQuery'] ?? array() ) ),
			'search'   => md5( (string) ( $query['search'] ?? '' ) ),
			'inherit'  => (int) ( $query['inherit'] ?? 1 ),
			'sticky'   => (int) ( $query['sticky'] ?? 0 ),
		);

		$gid     = get_the_ID();
		$post_id = $gid ? (int) $gid : 0;

		$ctx['post'] = $post_id;

		$tags = array( 'post_type:' . $ctx['postType'] );
		if ( $post_id ) {
			$tags[] = 'post:' . $post_id;
			$cats   = get_the_terms( $post_id, 'category' );
			if ( is_array( $cats ) ) {
				foreach ( $cats as $t ) {
					if ( isset( $t->term_id ) ) {
						$tags[] = 'term:category:' . (int) $t->term_id;
					}
				}
			}
		}

		static $wrapped = array();
		$key            = 'core-query:' . md5( wp_json_encode( $ctx ) );

		if ( isset( $wrapped[ $key ] ) ) {
			return $block_content;
		}
		$wrapped[ $key ] = true;

		return \QuantumCache\quantumcache_fragment(
			'block:core-query',
			$ctx,
			static function () use ( $block_content ) {
				return self::mark( 'core_query', $block_content );
			},
			array(
				'ttl'       => 600,
				'stale_ttl' => 86400,
				'tags'      => $tags,
			)
		);
	}

	/** Specific common blocks */
	public static function wrap_common_blocks( string $block_content, array $block ) {
		if ( ! self::can_cache() || empty( $block['blockName'] ) ) {
			return $block_content;
		}

		$name = $block['blockName'];

		if ( 'core/navigation' === $name ) {
			$attrs    = ( isset( $block['attrs'] ) && is_array( $block['attrs'] ) ) ? $block['attrs'] : array();
			$menu_id  = isset( $attrs['ref'] ) ? (int) $attrs['ref'] : 0;
			$menu_loc = isset( $attrs['overlayMenu']['menu'] ) ? (string) $attrs['overlayMenu']['menu'] : '';
			$lang     = function_exists( 'get_locale' ) ? get_locale() : 'en_US';

			return \QuantumCache\quantumcache_fragment(
				'block:navigation',
				array(
					'menu_id'  => $menu_id,
					'menu_loc' => $menu_loc,
					'lang'     => $lang,
				),
				static function () use ( $block_content ) {
					return self::mark( 'navigation', $block_content );
				},
				array(
					'tags'      => array( 'menu', 'nav' ),
					'ttl'       => 1800,
					'stale_ttl' => 86400,
				)
			);
		}

		if ( 'core/latest-posts' === $name ) {
			$attrs = ( isset( $block['attrs'] ) && is_array( $block['attrs'] ) ) ? $block['attrs'] : array();
			$qty   = isset( $attrs['postsToShow'] ) ? (int) $attrs['postsToShow'] : 5;
			$cat   = isset( $attrs['categories'] ) ? (array) $attrs['categories'] : array();

			return \QuantumCache\quantumcache_fragment(
				'block:latest-posts',
				array(
					'count' => $qty,
					'cat'   => implode( ',', array_map( 'intval', $cat ) ),
				),
				static function () use ( $block_content ) {
					return self::mark( 'latest_posts', $block_content );
				},
				array(
					'tags'      => array( 'post_type:post' ),
					'ttl'       => 600,
					'stale_ttl' => 86400,
				)
			);
		}

		if ( 'core/archives' === $name ) {
			return \QuantumCache\quantumcache_fragment(
				'block:archives',
				array(),
				static function () use ( $block_content ) {
					return self::mark( 'archives', $block_content );
				},
				array(
					'tags'      => array( 'post_type:post' ),
					'ttl'       => 1800,
					'stale_ttl' => 86400,
				)
			);
		}

		return $block_content;
	}

	/** Classic widgets: cache the whole sidebar emission */
	public static function sidebar_begin( $index = null ): void {
		if ( ! self::can_cache() ) {
			return;
		}

		$key                             = is_scalar( $index ) ? (string) $index : '_default';
		self::$sidebar_buffering[ $key ] = true;

		ob_start();
	}

	public static function sidebar_end( $index ): void {
		$key = is_scalar( $index ) ? (string) $index : '_default';

		// If we didn't start buffering, do nothing.
		if ( empty( self::$sidebar_buffering[ $key ] ) ) {
			return;
		}

		// We started buffering — always close it, even if can_cache() is now false.
		unset( self::$sidebar_buffering[ $key ] );

		$html = (string) ob_get_clean();
		if ( '' === $html ) {
			return;
		}

		// If we can’t cache now, just output the captured sidebar normally.
		if ( ! self::can_cache() ) {
			echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			return;
		}

		$gid     = get_the_ID();
		$post_id = $gid ? (int) $gid : 0;

		echo wp_kses_post(
			\QuantumCache\quantumcache_fragment(
				'sidebar:' . sanitize_key( (string) $index ),
				array(
					'post' => $post_id,
				),
				static function () use ( $html ) {
					return self::mark( 'sidebar', $html );
				},
				array(
					'ttl'       => 600,
					'stale_ttl' => 86400,
					'tags'      => array_filter(
						array(
							'sidebar',
							( $post_id ? 'post:' . $post_id : '' ),
						)
					),
				)
			)
		);
	}
}
