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

namespace NMFS\Sync\Items\Campaigns\Models;

use WC_Coupon;

defined( 'ABSPATH' ) || exit;

/**
 * Class Discounter_Type_0_Model
 */
class Discounter_Type_0_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;

	/**
	 * X - Y
	 *
	 * @var int
	 */
	protected int $diff;

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

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

	/**
	 * Construct model.
	 *
	 * @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->diff            = $this->x - $this->y;
		$this->priority        = $priority;
		$this->threshold_total = $threshold_total;
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_name(): string {
		return __( 'Buy X amount and get Y amount', '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 ( $this->diff < 0 ) {
			return false;
		}

		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 the products applicable for the discount.
	 *
	 * @param array $products The products in the cart.
	 *
	 * @return array The applicable products.
	 */
	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 the discounted products based on the maximum allowed discount.
	 *
	 * @param Product_Model[] $products               The products in the cart.
	 * @param float           $maximum_allowed_discount The maximum allowed discount.
	 *
	 * @return Product_Model[] The discounted products.
	 */
	protected function inner_get_discounted_products( array $products, float $maximum_allowed_discount ): array {
		if ( count( $products ) < $this->x ) {
			return array();
		}

		array_splice( $products, - $this->diff, $this->diff ); // Remove x most expensive products.
		$least_expensive = array_splice( $products, 0, $this->y );
		if ( ! empty( $least_expensive ) ) {
			foreach ( $least_expensive as $i ) {
				$maximum_allowed_discount -= $i->get_price();
				if ( $maximum_allowed_discount >= 0 ) {
					$i->set_discount( $i->get_price() );
				}
			}
		}

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

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