<?php
/**
 * This class contain all utils methods
 *
 * @package YITH\DynamicPricingAndDiscounts\Classes
 */

defined( 'ABSPATH' ) || exit;

/**
 * The Utils class
 */
class YWDPD_Utils {

	/**
	 * Build the array with the information to show in the quantity table
	 *
	 * @param WC_Product           $product The product.
	 * @param YWDPD_Quantity_Table $rule    The rule.
	 * @param string               $layout  The table layout.
	 *
	 * @return  array
	 * @since  3.0.0
	 * @author YITH <plugins@yithemes.com>
	 */
	public static function build_quantity_table_args( $product, $rule, $layout ) {

		if ( 'variable' === $product->get_type() ) {
			$variations_price = $product->get_variation_prices( false );
			$variations_price = $variations_price['price'];
			$min_variation_id = ! $variations_price ? 0 : current( array_keys( $variations_price, min( $variations_price ), true ) );
			$max_variation_id = ! $variations_price ? 0 : current( array_keys( $variations_price, max( $variations_price ), true ) );
			/**
			 * APPLY_FILTERS: ywdpd_change_base_price
			 *
			 * Change the base product price.
			 *
			 * @param float      $price   The price.
			 * @param WC_Product $product The product.
			 *
			 * @return float
			 */
			$default_price = apply_filters( 'ywdpd_change_base_price', current( $variations_price ), wc_get_product( $min_variation_id ) );
			/**
			 * APPLY_FILTERS: ywdpd_change_base_price
			 *
			 * Change the base product price.
			 *
			 * @param float      $price   The price.
			 * @param WC_Product $product The product.
			 *
			 * @return float
			 */
			$default_max_price = apply_filters( 'ywdpd_change_base_price', end( $variations_price ), wc_get_product( $max_variation_id ) );
		} else {
			/**
			 * APPLY_FILTERS: ywdpd_change_base_price
			 *
			 * Change the base product price.
			 *
			 * @param float      $price   The price.
			 * @param WC_Product $product The product.
			 *
			 * @return float
			 */
			$default_price = apply_filters( 'ywdpd_change_base_price', $product->get_price(), $product );

			$default_max_price = false;
		}
		$qty_type             = $rule->get_qty_type();
		$rules                = 'range' === $qty_type ? $rule->get_rules() : $rule->get_fixed_rules();
		$heading_type         = YITH_WC_Dynamic_Options::get_table_type_heading();
		$show_discount_column = YITH_WC_Dynamic_Options::can_show_discount_column();
		$table_heading        = YITH_WC_Dynamic_Options::get_quantity_columns_table_title();
		$formatted_prices     = array();
		$unformatted_prices   = array();

		if ( 'horizontal' === $layout && 'classic' === $rule->get_table_style() ) {

			$results = array(
				'quantity_row' => array(),
				'price_row'    => array(),
			);

			$heading = array(
				'quantity_row' => $table_heading['quantity'],
				'price_row'    => $table_heading['price'],
			);

			if ( $show_discount_column ) {
				$results['discount_row'] = array();
				$heading['discount_row'] = isset( $table_heading['discount'] ) ? $table_heading['discount'] : __( '% Discount', 'ywdpd' );
			}
		} else {
			$results = array();
			$heading = array(
				$table_heading['quantity'],
				$table_heading['price'],
			);
			if ( $show_discount_column ) {
				$heading[] = isset( $table_heading['discount'] ) ? $table_heading['discount'] : __( '% Discount', 'ywdpd' );
			}
		}
		// Build the array to show in the table.
		foreach ( $rules as $qty_rule ) {
			if ( 'range' === $qty_type ) {
				$min_qty = $qty_rule['min_quantity'];
				$max_qty = empty( $qty_rule['max_quantity'] ) ? '*' : $qty_rule['max_quantity'];
			} else {
				$min_qty      = $qty_rule['units'];
				$max_qty      = $min_qty;
				$heading_type = 'min';
			}

			$qty_html = $min_qty;
			if ( 'all' === $heading_type && $min_qty !== $max_qty ) {

				$qty_html .= '*' === $max_qty ? '+' : ' - ' . $max_qty;
			} elseif ( 'max' === $heading_type ) {
				/* translators: %s is the quantity */
				$qty_html = '*' === $max_qty ? $min_qty . '+' : sprintf( esc_html_x( 'Up to %s', 'Up to 5', 'ywdpd' ), $max_qty );
			} else {
				$qty_html = $min_qty !== $max_qty ? $qty_html . '+' : $qty_html;
			}
			/**
			 * APPLY_FILTERS: ywdpd_quantity_table_header_html
			 *
			 * Change the table header.
			 *
			 * @param string $qty_html the header.
			 * @param int    $min_qty  the min qty.
			 * @param int    $max_qty  the max qty.
			 * @param array  $qty_rule the quantity rule.
			 *
			 * @return string
			 */
			$qty_html           = apply_filters( 'ywdpd_quantity_table_header_html', $qty_html, $min_qty, $max_qty, $qty_rule );
			$key                = $min_qty . '-' . $max_qty;
			$formatted_qty_info = $qty_html;

			$price = $rule->get_discount_amount( $default_price, $qty_rule );
			/**
			 * APPLY_FILTERS: ywdpd_price_to_display
			 *
			 * Change price used for display
			 *
			 * @param float      $price_to_display the price.
			 * @param WC_Product $product          the product.
			 * @param float      $price            the base price.
			 *
			 * @return float
			 */
			$price_to_display = apply_filters( 'ywdpd_price_to_display', wc_get_price_to_display( $product, array( 'price' => $price ) ), $product, $price );
			$percentage_info  = self::get_discount_percentage( $price, $default_price );

			if ( $default_max_price ) {
				$max_price = $rule->get_discount_amount( $default_max_price, $qty_rule );
				/**
				 * APPLY_FILTERS: ywdpd_price_to_display
				 *
				 * Change price used for display
				 *
				 * @param float      $price_to_display the price.
				 * @param WC_Product $product          the product.
				 * @param float      $price            the base price.
				 *
				 * @return float
				 */
				$max_price_to_display = apply_filters( 'ywdpd_price_to_display', wc_get_price_to_display( $product, array( 'price' => $max_price ) ), $product, $max_price );

				if ( $price_to_display !== $max_price_to_display ) {
					$price_to_display_format = wc_format_price_range( $price_to_display, $max_price_to_display );
					$max_percentage_info     = self::get_discount_percentage( $max_price, $default_max_price );
					if ( $percentage_info !== $max_percentage_info ) {
						$percentage_info = self::get_discount_percentage_from_to_html( $percentage_info, $max_percentage_info );
					} else {
						$percentage_info = self::get_discount_percentage_html( $percentage_info );
					}
				} else {
					$price_to_display_format = wc_price( $price_to_display );
					$percentage_info         = self::get_discount_percentage_html( $percentage_info );
				}
			} else {
				$new_price_html = '';
				$price_to_display_format = wc_price( $price_to_display );
				$percentage_info         = self::get_discount_percentage_html( $percentage_info );
				/**
				 * APPLY_FILTERS: ywdpd_use_regular_price_html
				 *
				 * Force to show the regular price instead of sale price
				 *
				 * @param bool       $can_use_regular_price Is possible use the regular price.
				 * @param WC_Product $product               the product.
				 *
				 * @return bool
				 */
				if ( apply_filters( 'ywdpd_use_regular_price_html', $default_price === $price, $product ) ) {
					if ( $product->is_on_sale() ) {
						$new_price_html = self::get_product_new_price_html( $product->get_regular_price(), $price, $product );
					}
				} else {
					$new_price_html = self::get_product_new_price_html( $default_price, $price, $product );
				}
				$formatted_prices[ $key ]   = $new_price_html;
				$unformatted_prices[ $key ] = $price_to_display;
			}

			if ( 'horizontal' === $layout && 'classic' === $rule->get_table_style()) {

				$results['quantity_row'][ $key ] = $formatted_qty_info;
				$results['price_row'][ $key ]    = $price_to_display_format;
				if ( $show_discount_column ) {
					$results['discount_row'][ $key ] = $percentage_info;
				}
			} else {

				$tmp = array(
					'quantity_row' => $formatted_qty_info,
					'price_row'    => $price_to_display_format,
				);
				if ( $show_discount_column ) {
					$tmp['discount_row'] = $percentage_info;
				}

				$results[ $key ] = $tmp;
			}
		}

		$allowed_items = 'fixed' === $rule->get_qty_type() ? $rule->get_fixed_rules() : array();

		return array(
			'heading'            => $heading,
			'results'            => $results,
			'formatted_prices'   => $formatted_prices,
			'unformatted_prices' => $unformatted_prices,
			'allowed_units'      => $allowed_items,
		);
	}

	/**
	 * Get the discount percentage
	 *
	 * @param float $discounted_price The discounted price.
	 * @param float $old_price        The original price.
	 *
	 * @return float
	 * @since  3.0.0
	 */
	public static function get_discount_percentage( $discounted_price, $old_price ) {
		if ( ! empty( $old_price ) && $old_price > 0 ) {
			$diff = floatval( $old_price ) - floatval( $discounted_price );
			/**
			 * APPLY_FILTERS: ywdpd_price_decimals_for_percentage
			 *
			 * Change the decimals rounding in percentage discount calculation.
			 *
			 * @param int $decimals the number of decimals to round (default from wc_get_price_decimals function).
			 */
			$percentage = round( $diff / $old_price, apply_filters( 'ywdpd_price_decimals_for_percentage', wc_get_price_decimals() ) ) * 100;

		} else {
			$percentage = 0;
		}

		/**
		 * APPLY_FILTERS: ywdpd_get_discount_percentage
		 *
		 * Change the percentage discount.
		 *
		 * @param float $percentage       the percentage discount.
		 * @param float $discounted_price the discounted price.
		 * @param float $old_price        the old price.
		 *
		 * @return float
		 */
		return apply_filters( 'ywdpd_get_discount_percentage', $percentage, $discounted_price, $old_price );
	}

	/**
	 * Return the formatted percentage
	 *
	 * @param float $percentage The percentage.
	 *
	 * @return string
	 * @since  3.0.0
	 */
	public static function get_discount_percentage_html( $percentage ) {
		if ( $percentage > 0 ) {

			if ( fmod( $percentage, 1 ) > 0 ) {
				$percentage_discount_format = number_format( $percentage, 2, '.', '' ) . '%';
			} else {
				$percentage_discount_format = $percentage . '%';
			}
		} else {
			$percentage_discount_format = '-';
		}
		/**
		 * APPLY_FILTERS: ywdpd_percentual_discount
		 *
		 * Change the percentage discount html.
		 *
		 * @param string $percentage_discount_format the percentage discount html.
		 * @param float  $percentage                 the percentage value.
		 *
		 * @return string
		 */
		$percentage_discount_format = apply_filters( 'ywdpd_percentual_discount', $percentage_discount_format, $percentage );

		return $percentage_discount_format;
	}

	/**
	 * Return the formatted percentage range
	 *
	 * @param float $percentage_from The minimum percentage.
	 * @param float $percentage_to   The maximum percentage.
	 *
	 * @return string
	 * @since  3.0.0
	 */
	public static function get_discount_percentage_from_to_html( $percentage_from, $percentage_to ) {
		$percentage_discount_format = self::get_discount_percentage_html( $percentage_from ) . '-' . self::get_discount_percentage_html( $percentage_to );

		/**
		 * APPLY_FILTERS: yith_ywdpd_utils_get_discount_percentage_from_to_html
		 *
		 * Change the percentage discount range html.
		 *
		 * @param string $percentage_discount_format the percentage discount html.
		 * @param float  $percentage_from            the min percentage value.
		 * @param float  $percentage_to              the max percentage value.
		 *
		 * @return string
		 */
		return apply_filters( 'yith_ywdpd_utils_get_discount_percentage_from_to_html', $percentage_discount_format, $percentage_from, $percentage_to );
	}

	/**
	 * Return the new price html
	 *
	 * @param float      $old_price The old price.
	 * @param float      $new_price The new price.
	 * @param WC_Product $product   The product.
	 *
	 * @return string
	 * @since  3.0.0
	 */
	public static function get_product_new_price_html( $old_price, $new_price, $product ) {
		$new_price_to_display       = wc_get_price_to_display(
			$product,
			array(
				'qty'   => 1,
				'price' => $new_price,
			)
		);
		$new_price_html             = wc_price( $new_price_to_display );
		$old_price_html             = '';
		$percentage_discount_format = '';
		if ( $old_price > $new_price ) {

			$old_price_to_display       = wc_get_price_to_display(
				$product,
				array(
					'qty'   => 1,
					'price' => $old_price,
				)
			);
			$old_price_html             = wc_price( $old_price_to_display );
			$percentage_discount        = self::get_discount_percentage( $new_price, $old_price );
			$percentage_discount_format = self::get_discount_percentage_html( $percentage_discount );
		}

		$price_html = self::get_formatted_price_html( $old_price_html, $new_price_html, $percentage_discount_format, $product ) . $product->get_price_suffix( $new_price );

		/**
		 * APPLY_FILTERS: ywdpd_get_new_price_html
		 *
		 * Change the price html.
		 *
		 * @param string     $price_html the price html.
		 * @param float      $old_price  the old price.
		 * @param float      $new_price  the new price.
		 * @param WC_Product $product    the product.
		 *
		 * @return float
		 */
		return apply_filters( 'ywdpd_get_new_price_html', $price_html, $old_price, $new_price, $product );
	}

	/**
	 * Return the price with the current format
	 *
	 * @param string     $default_price_html    The default price.
	 * @param string     $discounted_price_html The discounted price.
	 * @param string     $percentage_html       The discount percentage.
	 * @param WC_Product $product               The product.
	 *
	 * @return string
	 * @since  3.0.0
	 */
	public static function get_formatted_price_html( $default_price_html, $discounted_price_html, $percentage_html, $product ) {
		$price_html = YITH_WC_Dynamic_Options::get_price_format();
		$price_html = str_replace( '%original_price%', $default_price_html, $price_html );
		$price_html = str_replace( '%discounted_price%', $discounted_price_html, $price_html );
		$price_html = str_replace( '%percentual_discount%', $percentage_html, $price_html );

		return $price_html;
	}

	/**
	 * Check taxonomy.
	 *
	 * @param array  $list          List.
	 * @param string $item          Product id.
	 * @param string $taxonomy_name Taxonomy.
	 * @param bool   $in            Bool.
	 *
	 * @return bool
	 * @since  3.0.0
	 */
	public static function check_taxonomy( $list, $item, $taxonomy_name, $in = true ) {

		yith_ywdpd_deprecated_function( 'YITH_WC_Dynamic_Pricing_Helper::check_taxonomy', '3.0', 'create a specific YWDPD_Price_Rule object and check with YWDPD_Price_Rule::is_valid_to_apply($product)' );

		$excluded = false;
		$product  = wc_get_product( $item );
		$item     = $product->get_parent_id() ? $product->get_parent_id() : $item;

		$get_by       = is_numeric( $list[0] ) ? 'ids' : 'slugs';
		$list_of_item = wc_get_product_terms( $item, $taxonomy_name, array( 'fields' => $get_by ) );
		$intersect    = array_intersect( $list_of_item, $list );
		if ( ! empty( $intersect ) ) {
			$excluded = true;
		}

		return $in ? $excluded : ! $excluded;
	}

	/**
	 * Return an array with all items is involved in a particular rule.
	 *
	 * @param YWDPD_Price_Rule $price_rule The price rule.
	 *
	 * @return array
	 * @since  3.0.0
	 */
	public static function get_involved_items_by_rule( $price_rule ) {

		if ( $price_rule->is_enabled_apply_adjustment_to() ) {
			$items = self::get_involved_adjustment_items( $price_rule );
		} else {
			$items = self::get_involved_items( $price_rule );
		}

		return $items;
	}

	/**
	 * Return an array with all item valid for the adjustment.
	 *
	 * @param YWDPD_Price_Rule $price_rule The price rule.
	 *
	 * @since  3.0.0
	 */
	public static function get_involved_adjustment_items( $price_rule ) {
		$items          = array(
			'type'     => '',
			'item_ids' => array(),
		);
		$adjustment_for = $price_rule->get_rule_apply_adjustment_discount_for();
		if ( 'all_products' === $adjustment_for ) {
			$args = array(
				'limit'   => 4,
				'status'  => 'publish',
				'type'    => array( 'simple', 'variable' ),
				'orderby' => 'rand',
				'return'  => 'ids',
			);

			$is_exclude_active = $price_rule->is_exclude_adjustment_to_enabled();
			if ( $is_exclude_active ) {
				$exclude_for = $price_rule->get_exclude_apply_adjustment_rule_for();

				switch ( $exclude_for ) {
					case 'specific_products':
						$product_to_exclude = $price_rule->get_apply_adjustment_products_list_excluded();
						break;
					case 'specific_categories':
						$category_ids = $price_rule->get_apply_adjustment_categories_list_excluded();
						if ( is_array( $category_ids ) && count( $category_ids ) > 0 ) {
							$tax_query = WC()->query->get_tax_query(
								array(
									array(
										'taxonomy' => 'product_cat',
										'terms'    => array_values( $category_ids ),
										'operator' => 'IN',
									),
								)
							);

							$product_to_exclude = get_posts(
								array(
									'numberposts' => - 1,
									'post_type'   => array( 'product' ),
									'post_status' => 'publish',
									'tax_query'   => $tax_query, // phpcs:ignore
									'fields'      => 'ids',
								)
							);
						}
						break;
					case 'specific_tag':
						$tag_ids = $price_rule->get_apply_adjustment_tags_list_excluded();
						if ( is_array( $tag_ids ) && count( $tag_ids ) > 0 ) {
							$tax_query = WC()->query->get_tax_query(
								array(
									array(
										'taxonomy' => 'product_tag',
										'terms'    => array_values( $tag_ids ),
										'operator' => 'IN',
									),
								)
							);

							$product_to_exclude = get_posts(
								array(
									'numberposts' => - 1,
									'post_type'   => array( 'product' ),
									'post_status' => 'publish',
									'tax_query'   => $tax_query, // phpcs:ignore
									'fields'      => 'ids',
								)
							);
						}
						break;
					default:
						/**
						 * APPLY_FILTERS: ywdpd_get_product_ids_involved_adjustment_to_exclude
						 *
						 * Add product ids to exclude.
						 *
						 * @param array            $product_ids the array.
						 * @param YWDPD_Price_Rule $price_rule  the price rule.
						 * @param string           $exclude_for the exclude condition.
						 *
						 * @return array
						 */
						$product_to_exclude = apply_filters( 'ywdpd_get_product_ids_involved_adjustment_to_exclude', array(), $price_rule, $exclude_for );

						break;
				}

				if ( is_array( $product_to_exclude ) && count( $product_to_exclude ) > 0 ) {
					$args['exclude'] = $product_to_exclude;
				}
			}
			$items['type']     = 'product_ids';
			$items['item_ids'] = wc_get_products( $args );
		} else {

			switch ( $adjustment_for ) {
				case 'specific_products':
					$items['type']     = 'product_ids';
					$items['item_ids'] = $price_rule->get_apply_adjustment_products_list();
					break;
				case 'specific_categories':
					$items['type']     = 'product_cat';
					$items['item_ids'] = $price_rule->get_apply_adjustment_categories_list();
					break;
				case 'specific_tag':
					$items['type']     = 'product_tag';
					$items['item_ids'] = $price_rule->get_apply_adjustment_tags_list();
					break;
				default:
					/**
					 * APPLY_FILTERS: ywdpd_involved_adjustment_items
					 *
					 * Add product id to include.
					 *
					 * @param array            $product_ids    the array.
					 * @param YWDPD_Price_Rule $price_rule     the price rule.
					 * @param string           $adjustment_for the condition.
					 *
					 * @return array
					 */
					$items = apply_filters( 'ywdpd_involved_adjustment_items', array(), $price_rule, $adjustment_for );
					break;
			}
		}

		return $items;
	}

	/**
	 * Return an array with all item valid for the apply.
	 *
	 * @param YWDPD_Price_Rule $price_rule The price rule.
	 *
	 * @since  3.0.0
	 */
	public static function get_involved_items( $price_rule ) {
		$items    = array(
			'type'     => '',
			'item_ids' => array(),
		);
		$rule_for = $price_rule->get_rule_for();
		if ( 'all_products' === $rule_for ) {
			$args = array(
				'limit'   => 4,
				'status'  => 'publish',
				'type'    => array( 'simple', 'variable' ),
				'orderby' => 'rand',
				'return'  => 'ids',
			);

			$is_exclude_active = $price_rule->is_exclude_from_apply_enabled();
			if ( $is_exclude_active ) {
				$exclude_for = $price_rule->get_exclude_rule_for();

				switch ( $exclude_for ) {
					case 'specific_products':
						$product_to_exclude = $price_rule->get_exclude_rule_for_products_list();
						break;
					case 'specific_categories':
						$category_ids = $price_rule->get_exclude_rule_for_categories_list();
						if ( is_array( $category_ids ) && count( $category_ids ) > 0 ) {
							$tax_query = WC()->query->get_tax_query(
								array(
									array(
										'taxonomy' => 'product_cat',
										'terms'    => array_values( $category_ids ),
										'operator' => 'IN',
									),
								)
							);

							$product_to_exclude = get_posts(
								array(
									'numberposts' => - 1,
									'post_type'   => array( 'product' ),
									'post_status' => 'publish',
									'tax_query'   => $tax_query, // phpcs:ignore
									'fields'      => 'ids',
								)
							);
						}
						break;
					case 'specific_tag':
						$tag_ids = $price_rule->get_exclude_rule_for_tags_list();
						if ( is_array( $tag_ids ) && count( $tag_ids ) > 0 ) {
							$tax_query = WC()->query->get_tax_query(
								array(
									array(
										'taxonomy' => 'product_tag',
										'terms'    => array_values( $tag_ids ),
										'operator' => 'IN',
									),
								)
							);

							$product_to_exclude = get_posts(
								array(
									'numberposts' => - 1,
									'post_type'   => array( 'product' ),
									'post_status' => 'publish',
									'tax_query'   => $tax_query, // phpcs:ignore
									'fields'      => 'ids',
								)
							);
						}
						break;
					default:
						/**
						 * APPLY_FILTERS: ywdpd_get_product_ids_involved_to_exclude
						 *
						 * Add product ids to exclude.
						 *
						 * @param array            $product_ids the array.
						 * @param YWDPD_Price_Rule $price_rule  the price rule.
						 * @param string           $exclude_for the exclude condition.
						 *
						 * @return array
						 */
						$product_to_exclude = apply_filters( 'ywdpd_get_product_ids_involved_to_exclude', array(), $price_rule, $exclude_for );
						break;
				}

				if ( is_array( $product_to_exclude ) && count( $product_to_exclude ) > 0 ) {
					$args['exclude'] = $product_to_exclude;
				}
			}
			$items['type']     = 'product_ids';
			$items['item_ids'] = wc_get_products( $args );
		} else {

			switch ( $rule_for ) {
				case 'specific_products':
					$items['type']     = 'product_ids';
					$items['item_ids'] = $price_rule->get_rule_for_product_list();
					break;
				case 'specific_categories':
					$items['type']     = 'product_cat';
					$items['item_ids'] = $price_rule->get_rule_for_categories_list();
					break;
				case 'specific_tag':
					$items['type']     = 'product_tag';
					$items['item_ids'] = $price_rule->get_rule_for_tags_list();
					break;
				default:
					/**
					 * APPLY_FILTERS: ywdpd_involved_items
					 *
					 * Add product id to include.
					 *
					 * @param array            $product_ids the array.
					 * @param YWDPD_Price_Rule $price_rule  the price rule.
					 * @param string           $rule_for    the condition.
					 *
					 * @return array
					 */
					$items = apply_filters( 'ywdpd_involved_items', $items, $price_rule, $rule_for );
					break;
			}
		}

		return $items;
	}
}
