<?php
/**
 * Discounter type 1 model.
 * Buy X amount and get Y discount.
 *
 * @package flo-sync
 *
 * @phpcs:disable Squiz.Commenting.FunctionComment.MissingParamTag
 */

namespace NMFS\Sync\Items\Campaigns\Models;

use Automattic\WooCommerce\Utilities\NumberUtil;
use WC_Coupon;

defined( 'ABSPATH' ) || exit;

/**
 * Class Discounter_Type_1_Model
 */
class Discounter_Type_1_Model implements Discounter_Interface {

	/**
	 * WooCommerce coupon for this discount
	 *
	 * @var WC_Coupon
	 */
	protected WC_Coupon $coupon;

	/**
	 * All skus valid for this discount
	 *
	 * @var string[]
	 */
	protected array $skus;

	/**
	 * Value X (amount needed to buy)
	 *
	 * @var int
	 */
	protected int $x;

	/**
	 * Value Y (amount with discount if X is fulfilled)
	 *
	 * @var int
	 */
	protected int $y;

	/**
	 * Priority
	 *
	 * @var int
	 */
	protected int $priority;

	/**
	 * Cart sum needed to activate this discount
	 *
	 * @var int
	 */
	protected int $threshold_total;


	/**
	 * Constructor.
	 *
	 * @param WC_Coupon $coupon          WooCommerce coupon for this discount.
	 * @param string[]  $skus            All skus valid for this discount.
	 * @param int       $x               Value X (amount needed to buy).
	 * @param int       $y               Value Y (amount with discount if X is fulfilled).
	 * @param int       $priority        Priority.
	 * @param int       $threshold_total Cart sum needed to activate this discount.
	 */
	public function __construct( WC_Coupon $coupon, array $skus, int $x, int $y, int $priority, int $threshold_total ) {
		$this->coupon          = $coupon;
		$this->skus            = $skus;
		$this->x               = $x;
		$this->y               = $y;
		$this->priority        = $priority;
		$this->threshold_total = $threshold_total;
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_name(): string {
		return __( 'Buy X amount and get Y discount', 'flo-sync' );
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_coupon(): WC_Coupon {
		return $this->coupon;
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_priority(): int {
		return $this->priority;
	}

	/**
	 * {@inheritDoc}
	 */
	public function is_applicable( float $cart_total, array $products ): bool {
		if ( $cart_total < $this->threshold_total ) {
			return false;
		}

		$applicable_products = $this->applicable_products( $products );

		if ( count( $applicable_products ) < $this->x ) {
			return false;
		}

		foreach ( $applicable_products as $applicable_product ) {
			$applicable_product->add_use();
		}

		return true;
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_discounted_products( array $products, float $cart_total ): array {
		return $this->inner_get_discounted_products(
			$this->applicable_products( $products ),
			$cart_total
		);
	}

	/**
	 * Get applicable products.
	 *
	 * @param Product_Model[] $products Products to get discount for.
	 *
	 * @return array
	 */
	protected function applicable_products( array $products ): array {
		$skus = $this->skus;

		return array_filter(
			$products,
			function ( $product ) use ( $skus ) {
				if ( $product->get_uses() > 0 ) {
					return false;
				}

				return in_array( $product->get_sku(), $skus, true );
			}
		);
	}

	/**
	 * Get discounted products.
	 *
	 * @param Product_Model[] $products                 Products to get discount for.
	 * @param float           $maximum_allowed_discount Maximum allowed discount.
	 *
	 * @return Product_Model[]
	 */
	protected function inner_get_discounted_products( array $products, float $maximum_allowed_discount ): array {
		if ( count( $products ) < $this->x ) {
			return array();
		}

		$discounted_products = array_splice( $products, - $this->x, $this->x ); // Remove x most expensive products.
		if ( ! empty( $discounted_products ) ) {
			$maximum_allowed_discount -= $this->y;
			if ( $maximum_allowed_discount >= 0 ) {
				$discount = NumberUtil::round( $this->y / $this->x, wc_get_rounding_precision() );
				foreach ( $discounted_products as $i ) {
					$i->set_discount( $discount );
				}
			}
		}

		if ( empty( $products ) ) {
			return array_merge(
				$discounted_products
			);
		}

		return array_merge(
			$discounted_products,
			$this->inner_get_discounted_products( $products, $maximum_allowed_discount )
		);
	}
}
