<?php
/**
 * This class manage the exclusion rules
 *
 * @package  YITH\DynamicPricing\And\Discounts\Classes
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * The class that manage the exclusions
 */
class YITH_WC_Dynamic_Exclusion_Manager {

	use YWDPD_Singleton_Trait;

	/**
	 * Store if the exclusion table is empty
	 *
	 * @var bool
	 */
	protected $list_is_empty;

	/**
	 * The array that store if a product is excluded or not
	 *
	 * @var array
	 */
	protected $products_excluded;
	/**
	 * The array with all exclusion rules.
	 *
	 * @var array
	 */
	protected $exclude_rules;

	/**
	 * YITH_WC_Dynamic_Exclusion_Manager constructor.
	 */
	private function __construct() {
		add_action( 'init', array( $this, 'load_exclude_rules' ), 20 );
		add_filter( 'ywdpd_add_cart_item_in_clone', array( $this, 'exclude_item_from_cart' ), 100, 2 );
		add_filter( 'ywdpd_can_calculate_dynamic_price', array( $this, 'exclude_product' ), 100, 2 );
		add_filter( 'ywdpd_rule_is_valid_for_product', array( $this, 'exclude_product' ), 100, 2 );
		add_filter( 'ywdpd_show_quantity_table', array( $this, 'exclude_product' ), 100, 2 );
		add_filter( 'ywdpd_show_notices', array( $this, 'exclude_product' ), 100, 2 );

	}

	/**
	 * Load all exclusion rule
	 *
	 * @author YITH <plugins@yithemes.com>
	 * @since  3.0.0
	 */
	public function load_exclude_rules() {
		if ( empty( $this->exclude_rules ) ) {
			$this->exclude_rules = ywdpd_get_price_rules_by_type( 'exclude_items' );
		}
		$this->list_is_empty     = 0 === count( $this->exclude_rules ) && ! YWDPD_Exclusion_List_Data_Store::has_items();
		$this->products_excluded = array(
			'products' => array(),
			'category' => array(),
			'tag'      => array(),
		);
	}

	/**
	 * Check if the product is in exclusion
	 *
	 * @param WC_Product $product The product to check.
	 *
	 * @since  3.0.0
	 */
	public function is_in_exclusion_rule( $product ) {
		$is_in_exclusion = false;

		if ( ! $this->list_is_empty ) {

			/**
			 * The exclusion rule
			 *
			 * @var YWDPD_Exclude_Items $exclude_rule
			 */

			foreach ( $this->exclude_rules as $exclude_rule ) {

				if ( $exclude_rule->is_valid() && $exclude_rule->is_valid_to_apply( $product ) ) {
					$is_in_exclusion = true;
					break;
				}
			}
			if ( ! $is_in_exclusion ) {

				$is_in_exclusion = self::is_product_tag_in_exlusion_list( $product ) || self::is_product_cat_in_exlusion_list( $product ) || self::is_product_in_exclusion_list( $product );
			}
		}

		/**
		 * APPLY_FILTERS: ywdpd_get_is_in_exclusion_list
		 *
		 * The product is in the exclusion list or not.
		 *
		 * @param bool       $is_in_exclusion the coupon label.
		 * @param WC_Product $product         The product.
		 *
		 * @return string
		 */
		return apply_filters( 'ywdpd_get_is_in_exclusion_list', $is_in_exclusion, $product );
	}

	/**
	 * Check if the product is in exclusion list.
	 *
	 * @param WC_Product $product The product.
	 *
	 * @return bool.
	 * @since  3.0.0
	 */
	public function is_product_in_exclusion_list( $product ) {
		$product_ids = absint( $product->get_id() );
		$parent_id   = 'variation' === $product->get_type() ? $product->get_parent_id() : false;
		if ( isset( $this->products_excluded['products'][ $product_ids ] ) ) {
			return $this->products_excluded['products'][ $product_ids ];
		}
		/**
		 * APPLY_FILTERS: ywdpd_get_exclusion_product_ids
		 *
		 * The product ids that are in exclusion list.
		 *
		 * @param array $product_ids the ids
		 *
		 * @return array
		 */
		$product_ids_to_check = apply_filters( 'ywdpd_get_exclusion_product_ids', YWDPD_Exclusion_List_Data_Store::get_terms_by_type( 'product' ) );

		$product_ids_to_check = array_map( 'absint', $product_ids_to_check );
		$is_exclusion         = false;

		if ( in_array( $product_ids, $product_ids_to_check, true ) ) {
			$is_exclusion = true;
		}

		if ( ! $is_exclusion && $parent_id && in_array( $parent_id, $product_ids_to_check, true ) ) {
			$is_exclusion = true;
		}

		$this->products_excluded['products'][ $product_ids ] = $is_exclusion;

		return $is_exclusion;
	}

	/**
	 * Check if the product tag is in exclusion list
	 *
	 * @param WC_Product $product The product.
	 *
	 * @return bool
	 * @since  3.0.0
	 */
	public function is_product_tag_in_exlusion_list( $product ) {
		$product_id = 'variation' === $product->get_type() ? $product->get_parent_id() : $product->get_id();
		if ( isset( $this->products_excluded['tag'][ $product_id ] ) ) {
			return $this->products_excluded['tag'][ $product_id ];
		}
		$product_tag_ids = YWDPD_Taxonomy::get_product_tag_ids( $product );
		/**
		 * APPLY_FILTERS: ywdpd_get_exclusion_tag_ids
		 *
		 * The product tag ids that are in exclusion list.
		 *
		 * @param array $product_tag_ids the ids
		 *
		 * @return array
		 */
		$product_tag_ids_to_check                      = apply_filters( 'ywdpd_get_exclusion_tag_ids', YWDPD_Exclusion_List_Data_Store::get_terms_by_type( 'product_tag' ) );
		$intersect                                     = array_intersect( $product_tag_ids, $product_tag_ids_to_check );
		$this->products_excluded['tag'][ $product_id ] = count( $intersect ) > 0;

		return $this->products_excluded['tag'][ $product_id ];
	}

	/**
	 * Check if the product category is in exclusion list
	 *
	 * @param WC_Product $product The product.
	 *
	 * @return bool
	 * @since  3.0.0
	 */
	public function is_product_cat_in_exlusion_list( $product ) {
		$product_id = 'variation' === $product->get_type() ? $product->get_parent_id() : $product->get_id();

		if ( isset( $this->products_excluded['category'][ $product_id ] ) ) {
			return $this->products_excluded['category'][ $product_id ];
		}
		$product_cat_ids = YWDPD_Taxonomy::get_product_category_ids( $product );
		/**
		 * APPLY_FILTERS: ywdpd_get_exclusion_category_ids
		 *
		 * The product category ids that are in exclusion list.
		 *
		 * @param array $product_category_ids the ids
		 *
		 * @return array
		 */
		$product_cat_ids_to_check = apply_filters( 'ywdpd_get_exclusion_category_ids', YWDPD_Exclusion_List_Data_Store::get_terms_by_type( 'product_cat' ) );

		$intersect = array_intersect( $product_cat_ids, $product_cat_ids_to_check );

		$this->products_excluded['category'][ $product_id ] = count( $intersect ) > 0;

		return $this->products_excluded['category'][ $product_id ];
	}

	/**
	 * Check if the current cart item should be get for the clone cart.
	 *
	 * @param bool  $add_item  Add the item or not in the cart process.
	 * @param array $cart_item The cart item.
	 *
	 * @return bool
	 * @since  3.0.0
	 */
	public function exclude_item_from_cart( $add_item, $cart_item ) {

		return ( isset( $cart_item['data'] ) && ! $this->is_in_exclusion_rule( $cart_item['data'] ) );
	}

	/**
	 * Check if possible the current product get the dynamic price.
	 *
	 * @param bool       $can_calculate Can calculate the dynamic price for the product.
	 * @param WC_Product $product       The product.
	 *
	 * @return bool
	 * @since  3.0.0
	 */
	public function exclude_product( $can_calculate, $product ) {
		return ! $this->is_in_exclusion_rule( $product );
	}
}
