<?php
/**
 * Product Trait
 *
 * @link       https://personalizewp.com/
 * @since      3.2.0 Merged into PWP
 * @since      2.3.0
 *
 * @package    PersonalizeWP
 * @subpackage PersonalizeWP/Traits
 */

namespace PersonalizeWP\Traits;

/**
 * Product Trait
 *
 * This trait is used by Segments and Scoring Rules to map order retrieval and associated products and categories.
 *
 * @since 2.3.0
 */
trait Product_Trait {

	use Condition_Operator_Trait;

	/**
	 * Cached Product IDs of order to check conditions against
	 *
	 * @since 2.3.0
	 *
	 * @var array
	 */
	private $order_product_ids = null;

	/**
	 * Cached Category IDs of order to check conditions against
	 *
	 * @since 2.3.0
	 *
	 * @var array
	 */
	private $order_category_ids = null;

	/**
	 * Loop through conditions, check if Activity matches - used when deciding if item is to be applied or not.
	 *
	 * Expanded logic from inherited base due to Product/Category sub-array requirements.
	 *
	 * @since 2.3.0
	 *
	 * @param array $args {
	 *     Array of args for matching an activity against a series of conditions
	 *
	 *     @type Activity $activity    Activity object
	 *     @type array    $conditions  Array of conditions to match
	 *     @type string   $operator    Operator to use when multiple conditions are set. Default 'ANY'
	 *     @type mixed    $order       Object indicating the order itself, e.g. WooCommerce WC_Order
	 * }
	 *
	 * @return boolean
	 */
	protected function activity_has_matched_conditions( $args ) {

		$args = wp_parse_args( $args, [ 'operator' => self::$default_operator ] );
		list( 'activity' => $activity, 'conditions' => $all_conditions, 'operator' => $operator ) = $args;
		unset( $args['conditions'] ); // Reusing the main args and passing onto the arguments below.

		foreach ( $all_conditions as $plugin => $conditions ) {
			// Match the plugin type (e.g. 'woocommerce') to activity recorded.
			if ( $plugin !== $activity->object_type ) {
				return false;
			}

			// Conditions are in an associated array form [plugin][type][ids]
			foreach ( $conditions as $type => $type_ids ) {
				foreach ( $type_ids as $match_id ) {

					$match_args = [
						'condition' => $match_id,
						'options'   => $type,
					];
					// Combine with whatever arguments the parent call might have added.
					$match_args = array_merge( $args, $match_args );

					$matched = $this->condition_has_matched_criteria( $match_args );
					switch ( $operator ) {
						case 'ALL':
							// Require all conditions to match, so a single failure causes no match.
							if ( ! $matched ) {
								return false;
							}
							break;

						default:
						case 'ANY':
							// Require any condition to match, so a single success triggers true.
							if ( $matched ) {
								return true;
							}
							break;
					}
				} // Each ID within a type (i.e. 'product', 'product_cat')
			}
		}

		switch ( $operator ) {
			case 'ALL':
				// Required all conditions to match, no failures above must indicate a success.
				return true;

			default:
			case 'ANY':
				// Required any condition to match, no matches above must indicate a failure.
				return false;
		}
	}

	/**
	 * Test condition against Product/Category criteria, matching IDs.
	 *
	 * @since 2.3.0
	 *
	 * @param array $args {
	 *     Array of args to use for matching the condition.
	 *
	 *     @type Activity $activity   Activity object
	 *     @type mixed    $condition  Item ID to match against
	 *     @type mixed    $options    Product/category type to match
	 *     @type mixed    $order      Object indicating the order itself, e.g. WooCommerce WC_Order
	 * }
	 *
	 * @return boolean
	 */
	protected function condition_has_matched_criteria( $args ) {

		list( 'activity' => $activity, 'condition' => $match_id, 'options' => $type, 'order' => $order ) = $args;

		switch ( $type ) {
			case 'product':
				// Populate/cache all the Product IDs in the order.
				if ( is_null( $this->order_product_ids ) ) {
					$this->order_product_ids = $this->get_order_products( $activity->object_type, $order );
				}

				return in_array( $match_id, $this->order_product_ids, true );

			case 'product_cat':
				// Populate/cache all the Category IDs of Products in the order.
				if ( is_null( $this->order_category_ids ) ) {
					$this->order_category_ids = $this->get_order_categories( $activity->object_type, $order );
				}

				return in_array( $match_id, $this->order_category_ids, true );
		}

		// Fail safe
		return false;
	}

	/**
	 * Get the order.
	 *
	 * @since 2.3.0
	 *
	 * @param string $order_type Order type, e.g. WooCommerce
	 * @param int    $order_id   ID of the order
	 *
	 * @return Object|bool Order or false if none exists
	 */
	private function get_order( $order_type, $order_id ) {
		$order = false;

		switch ( $order_type ) {
			case 'woocommerce':
				if ( function_exists( 'wc_get_order' ) ) {
					$order = wc_get_order( $order_id );
				}
				break;
		}

		return $order;
	}

	/**
	 * Get all the products within an order.
	 *
	 * @since 2.3.0
	 *
	 * @param string $order_type Order type, e.g. WooCommerce
	 * @param Object $order      Order
	 *
	 * @return array
	 */
	private function get_order_products( $order_type, $order ) {

		$product_ids = [];

		switch ( $order_type ) {
			case 'woocommerce':
				foreach ( $order->get_items() as $item ) {
					if ( $item->is_type( 'line_item' ) ) {
						$product_ids[] = $item->get_product_id();
					}
				}
				break;
		}

		return array_unique( array_filter( $product_ids ) );
	}

	/**
	 * Get all the categories of products within an order.
	 *
	 * @since 2.3.0
	 *
	 * @param string $order_type Order type, e.g. WooCommerce
	 * @param Object $order      Order
	 *
	 * @return array
	 */
	private function get_order_categories( $order_type, $order ) {

		$category_ids = [];

		switch ( $order_type ) {
			case 'woocommerce':
				foreach ( $order->get_items() as $item ) {
					if ( $item->is_type( 'line_item' ) ) {
						$product_id = $item->get_product_id();
						if ( ! $product_id ) {
							continue;
						}
						$product      = wc_get_product( $product_id );
						$cat_ids      = $product->get_category_ids();
						$category_ids = array_merge( $category_ids, $cat_ids );
					}
				}
				break;
		}

		return array_unique( array_filter( $category_ids ) );
	}
}
