<?php
/**
 * "Current product" taxonomy filter rules helper.
 *
 * Converts current_category and current_tag filter rules into concrete
 * WP_Query tax_query clauses based on the current product's own terms.
 *
 * RELATION SEMANTICS
 * ------------------
 * Given Current Product A with categories { Run, Cloth }:
 *
 *  any
 *      Show products that share any category with the current product.
 *      => IN { Run, Cloth }
 *      Example result: G (cloth, gym), H (run, gym)
 *
 *  none
 *      Show products that share NO category with the current product.
 *      => NOT IN { Run, Cloth }
 *      Example result: B (summer), C/E (gym), D (uncategorized)
 *
 *  in [Run, Gym]
 *      effective = intersect( current, value ) = { Run, Cloth } & { Run, Gym } = { Run }
 *      Show products IN { Run }.
 *      Gym is excluded because the current product is not in Gym.
 *      Example result: H
 *
 *  not_in [Run, Gym]
 *      remaining = current - value = { Run, Cloth } - { Run, Gym } = { Cloth }
 *      Show products IN { Cloth }  (the current product's categories that are NOT in the list).
 *      Example result: G
 *
 * The current product itself is always excluded upstream (ContextResolver adds
 * it to post__not_in), so no extra handling is needed here.
 *
 * @package PRECO
 */

namespace PRECO\Includes\Frontend\Engine\Filters\Query;

defined( 'ABSPATH' ) || exit;

/**
 * "Current product" taxonomy filter rules helper.
 */
class CurrentTaxRuleHelper {

	/**
	 * Resolve and add a current-product taxonomy filter clause.
	 *
	 * Passes $enforce_empty and $tax_query by reference so the caller can detect
	 * when the rule makes the whole result set impossible and short-circuit.
	 *
	 * @param array            $tax_query     WP_Query tax_query array (by reference).
	 * @param bool             $enforce_empty Caller's enforce_empty flag (by reference).
	 * @param string           $taxonomy      WP taxonomy slug, e.g. 'product_cat'.
	 * @param string           $relation      Rule relation: any|none|in|not_in.
	 * @param mixed            $value         Admin-stored value (array of slugs or null).
	 * @param \WC_Product|null $product       Current product object from context.
	 * @return void
	 */
	public function add_rule(
		array &$tax_query,
		bool &$enforce_empty,
		string $taxonomy,
		string $relation,
		$value,
		$product
	): void {
		if ( ! ( $product instanceof \WC_Product ) ) {
			// No current product in context – cannot resolve current terms.
			$enforce_empty = true;
			return;
		}

		$current_slugs = $this->get_product_term_slugs( $product, $taxonomy );

		switch ( $relation ) {

			// any: products sharing >= 1 category with the current product.
			case 'any':
				if ( empty( $current_slugs ) ) {
					$enforce_empty = true;
					return;
				}
				$tax_query[] = array(
					'taxonomy' => $taxonomy,
					'field'    => 'slug',
					'terms'    => $current_slugs,
					'operator' => 'IN',
				);
				break;

			// none: products sharing NO category with the current product.
			case 'none':
				if ( empty( $current_slugs ) ) {
					// Product has no terms -> every product qualifies -> no constraint needed.
					return;
				}
				$tax_query[] = array(
					'taxonomy' => $taxonomy,
					'field'    => 'slug',
					'terms'    => $current_slugs,
					'operator' => 'NOT IN',
				);
				break;

			// in [Cat1, Cat2]: products in the intersection of current categories
			// and admin-selected categories.
			// effective = intersect( current, value )
			// Example: current={Run,Cloth}, value=[Run,Gym] -> effective={Run} -> IN {Run}.
			case 'in':
				$value_slugs = $this->coerce_slugs( $value );
				if ( empty( $value_slugs ) ) {
					$enforce_empty = true;
					return;
				}
				$effective = array_values( array_intersect( $current_slugs, $value_slugs ) );
				if ( empty( $effective ) ) {
					// No overlap -> "Gym is totally excluded" per spec -> empty result.
					$enforce_empty = true;
					return;
				}
				$tax_query[] = array(
					'taxonomy' => $taxonomy,
					'field'    => 'slug',
					'terms'    => $effective,
					'operator' => 'IN',
				);
				break;

			// not_in [Cat1, Cat2]: products in the current product's categories
			// that are NOT in the admin-selected list.
			// remaining = current - value  (set difference)
			// Example: current={Run,Cloth}, value=[Run,Gym] -> remaining={Cloth} -> IN {Cloth}
			// Note: this is IN {remaining}, NOT "NOT IN {intersection}".
			// "NOT IN {Run}" would return everything outside Run - far too broad.
			case 'not_in':
				$value_slugs = $this->coerce_slugs( $value );
				$effective   = array_values( array_diff( $current_slugs, $value_slugs ) );
				if ( empty( $effective ) ) {
					// All of the current product's categories were excluded -> no candidates.
					$enforce_empty = true;
					return;
				}
				$tax_query[] = array(
					'taxonomy' => $taxonomy,
					'field'    => 'slug',
					'terms'    => $effective,
					'operator' => 'IN',
				);
				break;
		}
	}

	/**
	 * Add a current-product attribute filter clause.
	 *
	 * The admin stores only the attribute key slug (e.g. 'color') as $value.
	 * The full WP taxonomy is resolved as 'pa_{key}'.
	 *
	 * RELATION SEMANTICS
	 * ------------------
	 *  in  (Is)
	 *      Current product has color: {red, blue}
	 *      => Show products IN {red, blue} for pa_color
	 *      i.e. products that share at least one color value with the current product.
	 *
	 *  not_in  (Is Not)
	 *      Current product has color: {red, blue}
	 *      => Show products NOT IN {red, blue} for pa_color
	 *         AND the product must have at least one color (EXISTS).
	 *      i.e. products with a different color, not products with no color at all.
	 *
	 * @param array            $tax_query     WP_Query tax_query (by reference).
	 * @param bool             $enforce_empty Enforce-empty flag (by reference).
	 * @param string           $relation      'in' or 'not_in'.
	 * @param mixed            $value         Attribute key slug stored by admin (e.g. 'color').
	 * @param \WC_Product|null $product       Current product.
	 * @return void
	 */
	public function add_attribute_rule(
		array &$tax_query,
		bool &$enforce_empty,
		string $relation,
		$value,
		$product
	): void {
		if ( ! ( $product instanceof \WC_Product ) ) {
			$enforce_empty = true;
			return;
		}

		$attr_key = is_string( $value ) ? sanitize_title( trim( $value ) ) : '';
		if ( '' === $attr_key ) {
			$enforce_empty = true;
			return;
		}

		$taxonomy = 'pa_' . $attr_key;
		if ( ! \taxonomy_exists( $taxonomy ) ) {
			$enforce_empty = true;
			return;
		}

		$current_slugs = $this->get_product_term_slugs( $product, $taxonomy );

		if ( 'in' === $relation ) {
			if ( empty( $current_slugs ) ) {
				// Current product has no values for this attribute -> nothing to match.
				$enforce_empty = true;
				return;
			}
			$tax_query[] = array(
				'taxonomy' => $taxonomy,
				'field'    => 'slug',
				'terms'    => $current_slugs,
				'operator' => 'IN',
			);
			return;
		}

		if ( 'not_in' === $relation ) {
			if ( empty( $current_slugs ) ) {
				// No current values -> just require the attribute exists on candidates.
				$tax_query[] = array(
					'taxonomy' => $taxonomy,
					'operator' => 'EXISTS',
				);
				return;
			}
			// Products NOT IN the current product's attribute values,
			// but that DO have at least one value for this attribute.
			$tax_query[] = array(
				'relation' => 'AND',
				array(
					'taxonomy' => $taxonomy,
					'field'    => 'slug',
					'terms'    => $current_slugs,
					'operator' => 'NOT IN',
				),
				array(
					'taxonomy' => $taxonomy,
					'operator' => 'EXISTS',
				),
			);
		}
	}

	// =========================================================================
	// PRIVATE HELPERS
	// =========================================================================

	/**
	 * Get all term slugs for a product under a given taxonomy.
	 *
	 * @param \WC_Product $product  WooCommerce product.
	 * @param string      $taxonomy WP taxonomy slug.
	 * @return string[]
	 */
	private function get_product_term_slugs( \WC_Product $product, string $taxonomy ): array {
		$terms = \get_the_terms( $product->get_id(), $taxonomy );
		if ( ! is_array( $terms ) ) {
			return array();
		}
		$slugs = array();
		foreach ( $terms as $term ) {
			if ( ! empty( $term->slug ) ) {
				$slugs[] = (string) $term->slug;
			}
		}
		return array_values( array_unique( array_filter( $slugs ) ) );
	}

	/**
	 * Normalise a stored rule value to a clean array of sanitized slugs.
	 *
	 * Handles:
	 *  - Array of slugs (normal case from the React admin)
	 *  - Legacy comma-separated string
	 *
	 * @param mixed $value Raw stored value.
	 * @return string[]
	 */
	private function coerce_slugs( $value ): array {
		if ( is_string( $value ) && '' !== trim( $value ) ) {
			$value = array_values(
				array_filter( array_map( 'trim', explode( ',', $value ) ) )
			);
		}
		return is_array( $value )
			? array_values( array_filter( array_map( 'sanitize_title', $value ) ) )
			: array();
	}
}
