<?php

namespace CouponsPlus\App\Coupon\Tree\Cart;

use WC_Cart;

use Exception;
use RuntimeException;
use CouponsPlus\App\Conditions\CartItem;

use CouponsPlus\App\Conditions\ItemsSet;

use function WC;

/**
 * Modifies the contents of the cart AND throws an exception after modifying it.
 * Client code can the catch this and act accordingly
 * (usually just gracefully terminate execution of the current grove because cart modifications will trigger a recalculation with a new grove instance)
 */
class CartModifier
{
    /**
     * Null because in the admin area, the cart is not available. and we don't want to throw an error.
     */
    protected ?WC_Cart $cart = null;
    protected bool $sendModifiedSignal;

    public function __construct(bool $sendModifiedSignal = true)
    {
        $this->cart = WC()->cart;
        $this->sendModifiedSignal = $sendModifiedSignal;
    }

    /**
     * @param callable(WC_Cart $cart): void $modifier
     * @throws CartModifiedException
     */
    public function modify(callable $modifier)
    {
        $modifier($this->cart);

        if ($this->sendModifiedSignal) {
            throw new CartModifiedException();
        }
    }

    /**
     * Adds an item to the cart from an existing item in the cart.
     *
     * @throws CartModifiedException
     */
    public function addFromCart(CartItem $itemToAdd, int $quantity, string $componentId): void
    {
        $this->modify(function () use ($quantity, $componentId, $itemToAdd) {
            // here we need to keep track of the items added by us
            try {
                AutoAddedItems::registerItems($itemToAdd, $quantity, $componentId);

                $product = $itemToAdd->getProduct();
                $productId = $itemToAdd->getState('product_id') ?: ($product->get_parent_id() ?: $product->get_id());
                $variationId = $itemToAdd->getState('variation_id') ?: ($product->is_type('variation') ? $product->get_id() : 0);
                $variation = $itemToAdd->getState('variation') ?: ($product->is_type('variation') ? $product->get_variation_attributes() : []);

                if (
                    !$this->cart->add_to_cart(
                        (int) $productId,
                        $quantity,
                        (int) $variationId,
                        (array) $variation
                    )
                ) {
                    throw new RuntimeException;
                }
            } catch (Exception $e) {
                // couldn't add to the cart it seems in which case lets deregister the items we tried to add
                // now there might be cases when the item was partially added so we need to deregister only what was actually added
                // how to know that though?
                // let's just keep it like this for now
                AutoAddedItems::deregisterItems($itemToAdd, $quantity, $componentId);
            }
        });
    }

    public function removeFromCart(CartItem $itemToRemove, string $componentId): void
    {
        $this->modify(function () use ($componentId, $itemToRemove) {
            try {
                AutoAddedItems::deregisterItems($itemToRemove, $itemToRemove->getState('quantity'), $componentId);

                $this->cart->set_quantity(
                    $itemToRemove->getState('key'),
                    $this->cart->get_cart_item($itemToRemove->getState('key'))['quantity'] - $itemToRemove->getState('quantity'),
                    false
                );
            } catch (Exception $e) {
            }
        });
    }

}