<?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Enums\ProductStatus;

/**
 * RelatedProducts class.
 */
class RelatedProducts extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'related-products';

	/**
	 * The Block with its attributes before it gets rendered
	 *
	 * @var array
	 */
	protected $parsed_block;

	/**
	 * Initialize this block type.
	 *
	 * - Hook into WP lifecycle.
	 * - Register the block with WordPress.
	 * - Hook into pre_render_block to update the query.
	 */
	protected function initialize() {
		parent::initialize();
		add_filter(
			'pre_render_block',
			array( $this, 'update_query' ),
			10,
			2
		);

		add_filter(
			'render_block',
			array( $this, 'render_block' ),
			10,
			2
		);
	}

	/**
	 * It isn't necessary register block assets because it is a server side block.
	 */
	protected function register_block_type_assets() {
		return null;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}

	/**
	 * Update the query for the product query block.
	 *
	 * @param string|null $pre_render   The pre-rendered content. Default null.
	 * @param array       $parsed_block The block being rendered.
	 */
	public function update_query( $pre_render, $parsed_block ) {
		if ( 'core/query' !== $parsed_block['blockName'] ) {
			return $pre_render;
		}

		$this->parsed_block = $parsed_block;

		if ( ProductQuery::is_woocommerce_variation( $parsed_block ) && 'woocommerce/related-products' === $parsed_block['attrs']['namespace'] ) {
			// Set this so that our product filters can detect if it's a PHP template.
			add_filter(
				'query_loop_block_query_vars',
				array( $this, 'build_query' ),
				10,
				2
			);
		}

		return $pre_render;
	}

	/**
	 * Return a custom query based on attributes, filters and global WP_Query.
	 *
	 * @param WP_Query $query The WordPress Query.
	 * @param WP_Block $block The block being rendered.
	 * @return array
	 */
	public function build_query( $query, $block = null ) {
		$parsed_block = $this->parsed_block;
		if ( ! $this->is_related_products_block( $parsed_block, $block ) ) {
			return $query;
		}

		$related_products_ids = $this->get_related_products_ids( $query['posts_per_page'] );
		if ( count( $related_products_ids ) < 1 ) {
			return array();
		}

		return array(
			'post_type'      => 'product',
			'post__in'       => $related_products_ids,
			'post_status'    => ProductStatus::PUBLISH,
			'posts_per_page' => $query['posts_per_page'],
		);
	}

	/**
	 * If there are no related products, return an empty string.
	 *
	 * @param string $content The block content.
	 * @param array  $block The block.
	 *
	 * @return string The block content.
	 */
	public function render_block( string $content, array $block ) {
		if ( ! $this->is_related_products_block( $block ) ) {
			return $content;
		}

		// If there are no related products, render nothing.
		$related_products_ids = $this->get_related_products_ids();
		if ( count( $related_products_ids ) < 1 ) {
			return '';
		}

		return $content;
	}

	/**
	 * Determines whether the block is a related products block.
	 *
	 * @param array $parsed_block The parsed block.
	 * @param array $rendered_block The rendered block.
	 *
	 * @return bool Whether the block is a related products block.
	 */
	private function is_related_products_block( $parsed_block, $rendered_block = null ) {
		$is_product_collection_block = $rendered_block->context['query']['isProductCollectionBlock'] ?? false;
		if ( ! $is_product_collection_block && 'woocommerce/related-products' === ( $parsed_block['attrs']['namespace'] ?? null ) && ProductQuery::is_woocommerce_variation( $parsed_block ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Get related products ids.
	 * The logic is copied from the core function woocommerce_related_products. https://github.com/woocommerce/woocommerce/blob/ca49caabcba84ce9f60a03c6d3534ec14b350b80/plugins/woocommerce/includes/wc-template-functions.php/#L2039-L2074
	 *
	 * @param number $product_per_page Products per page.
	 * @return int[] Products ids.
	 */
	private function get_related_products_ids( $product_per_page = 5 ): array {
		global $post;

		$product = wc_get_product( $post->ID );

		if ( ! $product instanceof \WC_Product ) {
			return array();
		}

		$related_products_ids = wc_get_related_products( $product->get_id(), $product_per_page, $product->get_upsell_ids() );
		if ( ! empty( $related_products_ids ) ) {
			// Optimization: reduce the number of SQLs needed to populate product objects.
			_prime_post_caches( $related_products_ids );

			$related_products = array_filter( array_map( 'wc_get_product', $related_products_ids ), 'wc_products_array_filter_visible' );
			$related_products = wc_products_array_orderby( $related_products, 'rand', 'desc' );
			/** @var \WC_Product[] $related_products */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort

			// Optimization: reduce the number of SQLs needed to fetch images when rendering.
			_prime_post_caches( array_filter( array_map( fn( $product ) => (int) $product->get_image_id(), $related_products ) ) );

			$related_products_ids = array_map( fn( $product ) => $product->get_id(), $related_products );
		}

		return $related_products_ids;
	}
}
