<?php

namespace CouponsPlus\App\Offers;

use CouponsPlus\App\Conditions\ItemsSet;
use CouponsPlus\App\Offers\BuiltIn\BuyXGetY;
use CouponsPlus\App\Coupon\CartComponentsSet;
use CouponsPlus\App\Offers\Abilities\Offers;
use CouponsPlus\App\Offers\Offer;
use CouponsPlus\App\Offers\OffersScheduler;
use CouponsPlus\App\Conditions\PendingCartItem;
use CouponsPlus\Original\Collections\Collection;
use WC_Coupon;
use CouponsPlus\App\Coupon\Tree\Cart\CartModifier;
use CouponsPlus\App\Coupon\Tree\Abilities\OfferData;
use CouponsPlus\App\Coupon\Tree\Finders\Sets\OffersDataSet;
use CouponsPlus\App\Coupon\Tree\Cart\CartModifiedException;
use CouponsPlus\App\Coupon\Tree\Finders\GroveOffersDataFinder;

#[\AllowDynamicProperties]
Class OffersSet extends CartComponentsSet implements Offers
{
    protected OffersDataSet $offersDataSet;

    /**
     * @param OffersDataSet $offersDataSet
     */
    public function __construct(array $items)
    {
        parent::__construct($items);
        // here lets set a default empty offers data set so that it doesnt fail when we just instanciate thiswitgh an empty array
        $this->offersDataSet = new OffersDataSet([]);
    }


    public static function createFromData(array $offersData, WC_Coupon $coupon) : OffersSet
    {
        return new static(Collection::create($offersData)->map(function($offerData) use ($coupon) : Offer {
            return Offer::createFromOptions($offerData, $coupon);
        })->asArray());
    }

    /**
     * Not designed well (afterthought), but i need to be pragmatic and this needs to do for now
     */
    public function setOfferData(OffersDataSet $offersDataSet): void
    {
        $this->offersDataSet = $offersDataSet;
    }

    public function apply(OffersScheduler $offersScheduler, bool $pendingItemsAlreadyAdded = false)
    {
        /** @var Offer $offer */
        foreach ($this->items->asArray() as $offer) {
            if ($offer->needsToBeRescheduled($this->getItemsSet()) && $offersScheduler->offerHasBeenApplied($offer, $this->getItemsSet())) {
                continue;
            }

            if ($offer->needsToBeRescheduled($this->getItemsSet())) {
                $offersScheduler->schedule($offer, $this->getItemsSet());
            } else {
                // Add pending items to the cart only if not already handled at collection level
                if (!$pendingItemsAlreadyAdded) {
                    $this->addPendingCartItemsToTheCart($this->offersDataSet->getItemsSet(), $offer->getID());
                }
                // if the previous method added any items to the cart
                // execution will finish here and a new instance of the grove engine will be created and in such case
                // we might now have items to add to the cart so all items from $this->offersDataSet->getItemsSet()
                // will be "clean" (all in the cart)

                // we're not finding items anymore so let's reset the items used to find the offers so that
                // they dont conficlt with the offers upon offer application:
                OffersSetCollection::resetRegisteredItemsToUse();

                $offer->apply(
                    /*$offer instanceof BuyXGetY? $this->offersDataSet->getOfferItems()->getItemsUsedToMakeTheOfferEligibleOnly() : */
                    $this->offersDataSet->getItemsSet()
                    /*$this->getItemsSet()*/
                );
            }
        }
    }

    /**
     * Adds pending cart items to the cart with proper shut-off/resume handling.
     *
     * @param Collection<PendingCartItem> $pendingItems Items must have offerIdForAutoAdd property set
     * @throws CartModifiedException
     */
    public static function addPendingItemsToCart(Collection $pendingItems): void
    {
        (array) $itemsToAddToCart = $pendingItems->getValues()->asArray();

        /** @var PendingCartItem $pendingCartItem */
        foreach ($itemsToAddToCart as $index => $pendingCartItem) {
            if (array_key_last($itemsToAddToCart) !== $index) {
                GroveOffersDataFinder::shutOff();
                (object) $cartModifier = new CartModifier(false);
            } else {
                GroveOffersDataFinder::resume();
                $cartModifier = new CartModifier(true);
            }

            (string) $offerId = $pendingCartItem->offerIdForAutoAdd ?? '';

            $cartModifier->addFromCart(
                $pendingCartItem,
                $pendingCartItem->getQuantity(),
                $offerId
            );
        }
    }

    /**
     * @throws CartModifiedException
     */
    protected function addPendingCartItemsToTheCart(ItemsSet $itemsSet, string $offerId): void
    {
        (object) $pendingItems = $itemsSet->getPendingItemsToAddToCartJoined();

        // Set offerIdForAutoAdd on each pending item
        $pendingItems->forEvery(function (PendingCartItem $pendingCartItem) use ($offerId): void {
            $pendingCartItem->offerIdForAutoAdd = $offerId;
        });

        static::addPendingItemsToCart($pendingItems);
    }

    public function getItemsSet() : ItemsSet
    {
        return $this->offersDataSet->getItemsSet();
    }

    public function canBeApplied() : bool
    {
        return $this->offersDataSet->hasAnyItems();
    }

    protected function getInitialItemsCollection(array $items) : Collection
    {
        return (new Collection($items))->filter(function($offer) {
            return $offer instanceof Offer;
        });
    }

    public function getIds(): Collection
    {
        return $this->items->map(function(Offer $offer) {
            return $offer->getId();
        });
    }
}