<?php
/**
 * Discounter type Set model.
 * Article Set.
 *
 * @package flo-sync
 *
 * @phpcs:disable Squiz.Commenting.FunctionComment.MissingParamTag
 */

namespace NMFS\Sync\Items\Campaigns\Models;

use WC_Coupon;

defined( 'ABSPATH' ) || exit;

/**
 * Class Discounter_Type_Set_Model
 */
class Discounter_Type_Set_Model implements Discounter_Interface {


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

	/**
	 * List of articles with quantity, sku and price per unit
	 *
	 * @var array
	 */
	protected array $values;

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

	/**
	 * Discounter_Type_Set_Model constructor.
	 *
	 * @param WC_Coupon $coupon  WooCommerce coupon for this discount.
	 * @param array     $values List of articles with quantity, sku and price per unit.
	 * @param int       $priority Priority.
	 */
	public function __construct( WC_Coupon $coupon, array $values, int $priority ) {
		$this->coupon   = $coupon;
		$this->values   = $values;
		$this->priority = $priority;
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_name(): string {
		return __( 'Article Set', '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 ( empty( $this->values ) ) {
			return false;
		}

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

		$counter = array();
		foreach ( $this->values as $v ) {
			$counter[ $v->sku ] = array(
				'needed'  => $v->quantity, // Needed quantity for be valid.
				'current' => 0, // Counter.
			);
		}

		foreach ( $applicable_products as $applicable_product ) {
			++$counter[ $applicable_product->get_sku() ]['current'];
		}

		foreach ( $counter as $c ) {
			if ( $c['current'] < $c['needed'] ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_discounted_products( array $products, float $cart_total ): array {
		return $this->internal_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 {
		$values = $this->values;

		return array_filter(
			$products,
			function ( $product ) use ( $values ) {
				if ( $product->get_uses() > 0 ) {
					return false;
				}
				foreach ( $values as $v ) {
					if ( (string) $v->sku !== $product->get_sku() ) {
						continue;
					}

					return true;
				}

				return false;
			}
		);
	}

	/**
	 * 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 internal_get_discounted_products( array $products, float $maximum_allowed_discount ): array {
		if ( count( $products ) < 1 ) {
			return array();
		}

		if ( ! $this->is_applicable( $maximum_allowed_discount, $products ) ) {
			return $products;
		}

		$products = array_values( $products );

		$discounted_products = array();

		foreach ( $this->values as $v ) {
			for ( $i = 0; $i < $v->quantity; $i++ ) {
				$key = null;

				foreach ( $products as $k => $p ) {
					if ( $p->get_sku() === (string) $v->sku ) {
						$key = $k;
						break;
					}
				}

				if ( is_null( $key ) ) {
					// We don't have the correct quantity of some product, abort.
					return array_merge(
						$discounted_products,
						$products
					);
				}

				$discounted_product = array_splice( $products, $key, 1 )[0];
				$discounted_product->set_discount( $discounted_product->get_price() - $v->price_per_unit );
				$discounted_products[] = $discounted_product;
			}
		}

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

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