<?php

namespace CouponsPlus\App\Offers;

use CouponsPlus\App\Conditions\CardExportableData;
use CouponsPlus\App\Conditions\ItemsSet;
use CouponsPlus\App\Coupon\Abilities\DashboardCard;
use CouponsPlus\App\Coupon\ComponentOptionsRegistry;
use CouponsPlus\App\Offers\Abilities\SchedulableOffer;
use CouponsPlus\App\Offers\OffersRegistrator;
use CouponsPlus\Original\Cache\MemoryCache;
use CouponsPlus\Original\Collections\Collection;
use CouponsPlus\Original\Collections\JSONMapper;
use CouponsPlus\Original\Collections\MappedObject;
use CouponsPlus\Original\Collections\Mapper\Types;
use WC_Cart;
use WC_Coupon;
use CouponsPlus\App\Coupon\Tree\Finders\Sets\OfferItems;
use CouponsPlus\App\Coupon\Tree\Abilities\OffersItemsSetResolvable;

#[\AllowDynamicProperties]
Abstract Class Offer implements DashboardCard
{
    const ITEMS_SET_REQUIRED_ORDER = ''; // one of the below constants
    const ITEMS_SET_ORDER_CHEAPEST_FIRST = 'cheapest_first';
    const ITEMS_SET_ORDER_MOST_EXPENSIVE_FIRST = 'most_expensive_first';
    const ITEMS_SET_ORDER_MIXED = 'mixed';
    const ITEMS_SET_ORDER_NO_SPECIAL_SORTING = 'no_special_sorting';

    protected /*WC_Cart*/ $cart;
    protected /*WC_Coupon*/ $coupon;
    protected /*MappedObject*/ $options;
    protected string $id;

    abstract public static function getOptions() : Collection;

    /**
     * @param OfferItems $offerItems this one is for the ones that implement OffersItemsSetResolvable, I know I should have refactored the offers but i need to be pragmatic so redesigning and breaking stuff is not an option right now
     */
    abstract public function apply(ItemsSet $itemsSet);
    abstract static public function getIconUrl() : string;
    public static function getNotes() : array { return []; }
    public static function getUseCases() : Collection { return new Collection([]); }
    /*abstract*/ public function onOptionsLoaded() {} #overridable iff needed

    public function getType() : string
    {
        return static::TYPE;
    }

    public static function supportsRecursion() : bool
    {
        return is_a(static::class, OffersItemsSetResolvable::class, true);
    }

    public static function getAllOptionsMap(): Collection
    {
        return static::getOptions()->concat([
            '__BASE__' => [
                'isRecursive' => Types::BOOLEAN()->withDefault(false)->meta([
                    'booleanLabels' => [
                        'false' => [
                            'name' => __('Once per order', 'coupons-plus-for-woocommerce'),
                            'description' => __('Apply the offer only once per order.', 'coupons-plus-for-woocommerce'),
                        ],
                        'true' => [
                            'name' => __('Repeatedly', 'coupons-plus-for-woocommerce'),
                            'description' => __('Apply the offer to as many eligible items as possible per order', 'coupons-plus-for-woocommerce'),
                        ],
                    ]
                ]),
            ]
        ]);
    }

    public static function createOptions($data) : MappedObject
    {
        (string) $Offer = OffersRegistrator::get()->all()->get(
            is_string($data)? json_decode($data)->type : $data->type
        );

        (object) $offerJsonMapper = new JSONMapper($Offer::getAllOptionsMap()->asArray());

        return $offerJsonMapper->smartMap($data->options);
    }

    public static function createFromOptions($options, WC_Coupon $coupon) : Offer
    {
        (object) $JSONMapper = new JSONMapper([
            'type' => Types::STRING()->allowed(OffersRegistrator::get()->all()->map(function(string $Offer) : string {
                return $Offer::TYPE;
            })),
            'options' => Types::ANY
        ]);

        (object) $preOptions = $JSONMapper->smartMap($options);

        (string) $Offer = OffersRegistrator::get()->all()->get($preOptions->type);

        (object) $offer = new $Offer($preOptions->options);

        $offer->setCoupon($coupon);
        $offer->setCart(\WC()->cart);

        return $offer;
    }
    
    public function __construct(/*string|array*/ $options)
    {
        $this->options = ComponentOptionsRegistry::getInstance()->getOrCreate(
            static::class,
            $options,
            static::getOptions()->asArray()
        );
        $this->cache = new MemoryCache;

        $this->onOptionsLoaded();
    }

    /**
     * Only on 3.0 and up
     */
    public function setID(string $id)
    {
        $this->id = $id;
    }

    /**
     * Only on 3.0 and up.
     *
     * On 2 and below, this is not used.
     */
    public function getID() : string
    {
        return $this->id ?? '';
    }

    public function needsToBeRescheduled(ItemsSet $itemsSet) : bool
    {
        return $this instanceof SchedulableOffer && $this->canBeScheduled($itemsSet);
    }

    public function setCoupon(\WC_Coupon $coupon)
    {
        $this->coupon = $coupon;
    }

    public function setCart(WC_Cart $cart = null)
    {
        $this->cart = $cart; 
    }

    public function couponIsActive()
    {
        return in_array(
            $this->coupon->get_code($context = 'edit'), 
            $this->cart->get_applied_coupons()
        );
    }

    public static function exportDefault() : MappedObject
    {
        (object) $options = static::createOptions((object) [
            'type' => static::TYPE,
            'options' => []
        ]);

        return $options;
    }

    public function getLoadedOptions() : MappedObject
    {
        return $this->options;   
    }
    
    public function getDataToExport()
    {
        return new CardExportableData($this);
    }
}