<?php

declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\BlocksSharedState;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use Automattic\WooCommerce\Internal\ProductFilters\Params;
use WP_Block;

/**
 * ProductFilters class.
 */
class ProductFilters extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-filters';

	/**
	 * Register the context.
	 *
	 * @return string[]
	 */
	protected function get_block_type_uses_context() {
		return array( 'postId', 'query', 'queryId', 'forcePageReload' );
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = array() ) {
		parent::enqueue_data( $attributes );

		if ( is_admin() ) {
			$this->asset_data_registry->add( 'globalStylesColors', wp_get_global_styles( array( 'color' ) ) );
		}

		BlocksSharedState::load_store_config( 'I acknowledge that using private APIs means my theme or plugin will inevitably break in the next version of WooCommerce' );

		// Classic themes do not support client-side navigation on product
		// archive pages, so disable it globally for the Interactivity Router.
		$is_product_archive = is_shop() || is_product_taxonomy() || ( is_search() && 'product' === get_post_type() );
		if ( ! wp_is_block_theme() && $is_product_archive ) {
			wp_interactivity_config( 'core/router', array( 'clientNavigationDisabled' => true ) );
		}
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! $block instanceof WP_Block ) {
			return $content;
		}

		wp_enqueue_script( 'wc-settings' );

		$query_id      = $block->context['queryId'] ?? 0;
		$filter_params = $this->get_filter_params( $query_id );

		wp_interactivity_config( $this->get_full_block_name(), [ 'canonicalUrl' => $this->get_canonical_url_no_pagination( $filter_params ) ] );

		/**
		 * Filter hook to modify the selected filter items.
		 *
		 * @since 9.7.0
		 */
		$active_filters = apply_filters( 'woocommerce_blocks_product_filters_selected_items', array(), $filter_params );

		usort(
			$active_filters,
			function ( $a, $b ) {
				return strnatcmp( $a['activeLabel'], $b['activeLabel'] );
			}
		);

		$block_context         = array_merge(
			$block->context,
			array(
				'filterParams'  => $filter_params,
				'activeFilters' => $active_filters,
			),
		);
		$inner_blocks          = array_reduce(
			$block->parsed_block['innerBlocks'],
			function ( $carry, $parsed_block ) use ( $block_context ) {
				$carry .= ( new \WP_Block( $parsed_block, $block_context ) )->render();
				return $carry;
			},
			''
		);
		$interactivity_context = array(
			'params'          => $filter_params,
			'activeFilters'   => $active_filters,
			// Null when not a descendant of a Product Collection block, so the
			// frontend can fall back to the global interactivity config.
			'forcePageReload' => isset( $block->context['forcePageReload'] ) ? (bool) $block->context['forcePageReload'] : null,
		);

		$show_filter_drawer = ! isset( $attributes['showFilterDrawer'] ) || false !== $attributes['showFilterDrawer'];
		$wrapper_classes    = array( 'wc-block-product-filters' );
		if ( ! $show_filter_drawer ) {
			$wrapper_classes[] = 'is-filter-drawer-disabled';
		}

		$wrapper_attributes = array(
			'class'                         => implode( ' ', $wrapper_classes ),
			'data-wp-interactive'           => $this->get_full_block_name(),
			'data-wp-init--colors'          => 'callbacks.initColors',
			'data-wp-watch--active-filters' => 'callbacks.syncActiveFiltersWithServer',
			'data-wp-context'               => (string) wp_json_encode( $interactivity_context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
			'style'                         => $this->get_css_variables( $attributes ),
		);

		if ( $show_filter_drawer ) {
			$wrapper_attributes['data-wp-watch--scrolling']         = 'callbacks.scrollLimit';
			$wrapper_attributes['data-wp-on--keyup']                = 'actions.closeOverlayOnEscape';
			$wrapper_attributes['data-wp-class--is-overlay-opened'] = 'context.isOverlayOpened';
		}

		// TODO: Remove this conditional once the fix is released in WP. https://github.com/woocommerce/gutenberg/pull/4.
		if ( ! isset( $block->context['productCollectionLocation'] ) ) {
			$wrapper_attributes['data-wp-router-region'] = $this->generate_navigation_id( $block );
		}

		ob_start();
		?>
		<div <?php echo get_block_wrapper_attributes( $wrapper_attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<?php if ( $show_filter_drawer ) : ?>
				<button
					type="button"
					class="wc-block-product-filters__open-overlay"
					data-wp-on--click="actions.openOverlay"
				>
					<?php echo $this->get_svg_icon( 'filter-icon-2' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
					<span><?php echo esc_html__( 'Filter products', 'woocommerce' ); ?></span>
				</button>
				<div class="wc-block-product-filters__overlay">
					<div class="wc-block-product-filters__overlay-wrapper">
						<div
							class="wc-block-product-filters__overlay-dialog"
							role="dialog"
							aria-label="<?php echo esc_html__( 'Product Filters', 'woocommerce' ); ?>"
						>
							<header class="wc-block-product-filters__overlay-header">
								<button
									type="button"
									class="wc-block-product-filters__close-overlay"
									data-wp-on--click="actions.closeOverlay"
								>
									<span><?php echo esc_html__( 'Close', 'woocommerce' ); ?></span>
									<?php echo $this->get_svg_icon( 'close' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
								</button>
							</header>
							<div class="wc-block-product-filters__overlay-content">
								<?php echo $inner_blocks; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
							</div>
							<footer
								class="wc-block-product-filters__overlay-footer"
							>
								<button
									type="button"
									class="wc-block-product-filters__apply wp-element-button"
									data-wp-interactive="<?php echo esc_attr( $this->get_full_block_name() ); ?>"
									data-wp-on--click="actions.closeOverlay"
								>
									<span><?php echo esc_html__( 'Apply', 'woocommerce' ); ?></span>
								</button>
							</footer>
						</div>
					</div>
				</div>
			<?php else : ?>
				<div class="wc-block-product-filters__content">
					<?php echo $inner_blocks; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
				</div>
			<?php endif; ?>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * Get SVG icon markup for a given icon name.
	 *
	 * @param string $name The name of the icon to retrieve.
	 * @return string SVG markup for the icon, or empty string if icon not found.
	 */
	private function get_svg_icon( string $name ) {
		$icons = array(
			'close'         => '<path d="M12 13.0607L15.7123 16.773L16.773 15.7123L13.0607 12L16.773 8.28772L15.7123 7.22706L12 10.9394L8.28771 7.22705L7.22705 8.28771L10.9394 12L7.22706 15.7123L8.28772 16.773L12 13.0607Z" fill="currentColor"/>',
			'filter-icon-2' => '<path d="M10 17.5H14V16H10V17.5ZM6 6V7.5H18V6H6ZM8 12.5H16V11H8V12.5Z" fill="currentColor"/>',
		);

		if ( ! isset( $icons[ $name ] ) ) {
			return '';
		}

		return sprintf(
			'<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">%s</svg>',
			$icons[ $name ]
		);
	}

	/**
	 * Get CSS custom properties from block attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return string CSS custom properties string.
	 */
	private function get_css_variables( $attributes ) {
		$styles = array();

		$bg = StyleAttributesUtils::get_background_color_class_and_style( $attributes );
		if ( ! empty( $bg['value'] ) ) {
			$styles[] = sprintf( '--wc-product-filters-background-color: %s', $bg['value'] );
		}

		$text = StyleAttributesUtils::get_text_color_class_and_style( $attributes );
		if ( ! empty( $text['value'] ) ) {
			$styles[] = sprintf( '--wc-product-filters-text-color: %s', $text['value'] );
		}

		$block_gap = $attributes['style']['spacing']['blockGap'] ?? '';
		if ( $block_gap ) {
			$styles[] = sprintf( '--wc-product-filter-block-spacing: %s', StyleAttributesUtils::get_spacing_value( $block_gap ) );
		}

		return $styles ? implode( ';', $styles ) . ';' : '';
	}

	/**
	 * Generate a unique navigation ID for the block.
	 *
	 * @param mixed $block - Block instance.
	 * @return string - Unique navigation ID.
	 */
	private function generate_navigation_id( $block ) {
		return sprintf(
			'wc-product-filters-%s',
			md5(
				wp_json_encode(
					array(
						'attrs'       => $block->parsed_block['attrs'] ?? array(),
						'innerBlocks' => $block->parsed_block['innerBlocks'] ?? array(),
					)
				)
			)
		);
	}

	/**
	 * Parse the filter parameters from the URL.
	 * For now we only get the global query params from the URL. In the future,
	 * we should get the query params based on $query_id.
	 *
	 * @param int $query_id Query ID.
	 * @return array Parsed filter params.
	 */
	private function get_filter_params( $query_id ) {
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';

		$parsed_url = wp_parse_url( esc_url_raw( $request_uri ) );

		if ( empty( $parsed_url['query'] ) ) {
			return array();
		}

		parse_str( $parsed_url['query'], $url_query_params );

		$filter_param_keys = wc_get_container()->get( Params::class )->get_param_keys();

		return array_filter(
			$url_query_params,
			function ( $key ) use ( $filter_param_keys ) {
				return in_array( $key, $filter_param_keys, true );
			},
			ARRAY_FILTER_USE_KEY
		);
	}

	/**
	 * Disable the style handle for this block type. We use block.json to load the style.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}

	/**
	 * Disable the editor style handle for this block type. We use block.json to load the style.
	 *
	 * @return null
	 */
	protected function get_block_type_editor_style() {
		return null;
	}

	/**
	 * Disable the script handle for this block type. We use block.json to load the script.
	 *
	 * @param string|null $key The key of the script to get.
	 * @return null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}

	/**
	 * Get the canonical URL without pagination.
	 *
	 * @param array $filter_params Filter parameters.
	 * @return string Canonical URL without pagination.
	 */
	private function get_canonical_url_no_pagination( $filter_params ) {
		$canonical_url_no_pagination = is_singular() ? get_permalink() : get_pagenum_link( 1 );
		$decoded_url                 = html_entity_decode( $canonical_url_no_pagination, ENT_QUOTES, get_bloginfo( 'charset' ) );
		$parsed_url                  = wp_parse_url( $decoded_url );

		// If there are active filters, $parsed_url['query'] is empty for page or post but not empty for archives.
		if ( empty( $filter_params ) || empty( $parsed_url['query'] ) ) {
			return $decoded_url;
		}

		foreach ( array_keys( $filter_params ) as $key ) {
			$parsed_url['query'] = remove_query_arg( $key, $parsed_url['query'] );
		}

		$url = '';

		if ( isset( $parsed_url['scheme'] ) ) {
			$url .= $parsed_url['scheme'] . '://';
		}

		if ( isset( $parsed_url['host'] ) ) {
			$url .= $parsed_url['host'];
		}

		if ( isset( $parsed_url['port'] ) ) {
			$url .= ':' . $parsed_url['port'];
		}

		if ( isset( $parsed_url['path'] ) ) {
			$url .= $parsed_url['path'];
		}

		if ( ! empty( $parsed_url['query'] ) ) {
			$url .= '?' . $parsed_url['query'];
		}

		if ( isset( $parsed_url['fragment'] ) ) {
			$url .= '#' . $parsed_url['fragment'];
		}

		return $url;
	}
}
