<?php

namespace CouponsPlus\App\Offers;

use WC_Coupon;
use WC_Discounts;
use CouponsPlus\Original\Collections\Collection;

#[\AllowDynamicProperties]
Class CouponItemDiscountAmount extends CouponDiscountAmount
{
    protected /*string*/ $cartItemKey;
    /**
     * @var string
     */
    protected $uniqueKey;
    protected float $sumOfAllDiscounts = 0.0;

    /**
     * @override
     */
    public function __construct(WC_Coupon  $coupon)
    {
        parent::__construct($coupon);

        // here register this object in the discount state
        DiscountState::getInstance()->registerDiscount($this);
    }

    public function addCartItemKey(string $cartItemKey)
    {
        $this->cartItemKey = $cartItemKey;
    }

    public function registerEvents(): void
    {
        /**
         * Coupons+ 3 uses a custom discount type and as such we have to register a custom filter for the self::getDiscountedAmount()
         * to trigger.
         */
        if ($this->coupon->isTree()) {
            add_filter(
                'woocommerce_coupon_get_items_to_apply',
                [$this, 'addValidItemsToWCDiscounts'],
                10,
                3
            );

            add_filter(
                'woocommerce_coupon_get_apply_quantity',
                [$this, 'addValidQuantityToWCDiscounts'],
                10,
                4
            );
        }

        add_filter(
            'woocommerce_coupon_get_discount_amount',
            [$this, 'getDiscountedAmount'],
            $priority = 10,
            $numberOfArguments = 5
        );
    }

    public function unregisterEvents(): void
    {
        if ($this->coupon->isTree()) {
            // potentially we should uh, refactor the args that are repeated in  registerevents() so that the removal dont break if
            // we change them when registering the filter in the method above
            remove_filter(
                'woocommerce_coupon_get_items_to_apply',
                [$this, 'addValidItemsToWCDiscounts'],
                10
            );

            add_filter(
                'woocommerce_coupon_get_apply_quantity',
                [$this, 'addValidQuantityToWCDiscounts'],
                10,
                4
            );
        }

        remove_filter(
            'woocommerce_coupon_get_discount_amount',
            [$this, 'getDiscountedAmount'],
            $priority = 10
        );
    }

    public function addValidQuantityToWCDiscounts(int $apply_quantity, $item, WC_Coupon $coupon, WC_Discounts $WCDiscounts)
    {
        if (!$this->isOurCoupon($coupon)) {
            return $apply_quantity;
        }

        return $this->numberOfItemsAppliedTo;
    }
    /**
     * Coupons+ 3 and up
     */
    public function addValidItemsToWCDiscounts(array $itemsToDiscountUsuallyEmpty, WC_Coupon $coupon, WC_Discounts $wcDiscounts) : array
    {
        if (!$this->isOurCoupon($coupon)) {
            return $itemsToDiscountUsuallyEmpty;
        }

        (object) $cartItems = Collection::create($wcDiscounts->get_items());
        (object) $itemsToDiscount = Collection::create($itemsToDiscountUsuallyEmpty);

        if (!$itemsToDiscount->hasKey($this->cartItemKey)) {
            /**
             * It has to be from $wcDiscounts->get_items() as the format is expected a custom stdClass instead of the array returned by WC_Cart->get_cart_item()
             */
            $itemsToDiscount->set($this->cartItemKey, $cartItems->get($this->cartItemKey));
        }

        return $itemsToDiscount->asArray();
    }

    public function getDiscountedAmount(float $sumOfAllDiscounts, float $discountingAmount, /*array|null*/ $cartItemArray, bool $single, \WC_Coupon $coupon) /* : float*/
    {
        if (DiscountState::getInstance()->hasHardLimitBeenReached($this->uniqueKey)) {
            return $sumOfAllDiscounts;
        }

        // Handle both cart items (arrays) and order items (WC_Order_Item_Product objects)
        $itemKey = $this->extractItemKey($cartItemArray);
        (boolean) $itsTheItemWeWant = $this->cartItemKey === $itemKey;
        (boolean) $itsTheCouponWeWant = $coupon->get_code($context = 'edit') == $this->classicCoupon->get_code($context = 'edit');

        $discountState = DiscountState::getInstance();

        if ($itsTheItemWeWant && $itsTheCouponWeWant) {
            $sumOfPreviousDiscounts = $discountState->getSumOfDiscounts($this->uniqueKey);

            $intendedDiscountIsLargerThanTheHardLimit = bccomp(
                $sumOfPreviousDiscounts + $this->amount, $this->maximumAmount, 2
                ) > 0;

            /** @noinspection NotOptimalIfConditionsInspection */
            if ($this->hasHardLimit() && $intendedDiscountIsLargerThanTheHardLimit) {
                $discountState->setHardLimitReached($this->uniqueKey);

                $amountToDiscountOnThisCall = $this->maximumAmount - $sumOfPreviousDiscounts;

                $sumOfAllDiscounts += $amountToDiscountOnThisCall;
            } else {
                $amountToDiscountOnThisCall = $this->amount;

                $sumOfAllDiscounts += $amountToDiscountOnThisCall;
                $discountState->addToSumOfDiscounts($this->uniqueKey, $this->amount);
            }

            // here register the amount we've discounted for this item by KEY
            DiscountState::getInstance()->addToSumOfCartItemDiscounts($this->cartItemKey, $amountToDiscountOnThisCall);
        }

        // here register the total discount amount we've given so far
        $this->sumOfAllDiscounts += $sumOfAllDiscounts;


        return $sumOfAllDiscounts;
    }

    public function setHardLimit(float $maximumAmount)
    {
        parent::setHardLimit($maximumAmount);
    }

    public function setUniqueKey(string $uniqueKey)
    {
        $this->uniqueKey = $uniqueKey;
    }

    public function getCartItemKey() : string
    {
        return $this->cartItemKey;
    }

    public function getUniqueKey(): string
    {
        return $this->uniqueKey;
    }


    public function getSumOfAllDiscounts(): float
    {
        return $this->sumOfAllDiscounts;
    }

    public function getSumOfDiscountsForThisCartItem() : float
    {
        return DiscountState::getInstance()->getSumOfCartItemDiscounts($this->cartItemKey);
    }

    /**
     * Extracts the item key from either a cart item array or an order item object.
     * Cart items use 'key' index, while order items use get_id().
     *
     * @param array|\WC_Order_Item_Product|null $item
     * @return string|null
     */
    protected function extractItemKey($item): ?string
    {
        if ($item === null) {
            return null;
        }

        // Order item context (WC_Order_Item_Product)
        if ($item instanceof \WC_Order_Item_Product) {
            return (string) $item->get_id();
        }

        // Cart item context (array with 'key')
        if (is_array($item) && isset($item['key'])) {
            return $item['key'];
        }

        return null;
    }
}