<?php
/**
 * Magento 2 Google Analytics 4 (GA4) GTM
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Anowave license that is
 * available through the world-wide-web at this URL:
 * https://www.anowave.com/license-agreement/
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade this extension to newer
 * version in the future.
 *
 * @category 	Anowave
 * @package 	Anowave_Ec
 * @copyright 	Copyright (c) 2026 Anowave (https://www.anowave.com/)
 * @license  	https://www.anowave.com/license-agreement/
 */
namespace Anowave\Ec\Helper;

use Anowave\Ec\Context\CurrentContext;
use Anowave\Package\Helper\Package;
use Anowave\Ec\Helper\Utils;
use Anowave\Ec\Helper\Constants;
use Anowave\Ec\Model\Config\Backend\Key;
use Anowave\Ec\Model\System\Config\Source\Tax;
use Anowave\Ec\Model\System\Config\Source\TaxItem;
use Anowave\Ec\Model\System\Config\Source\Currency;
use Anowave\Ec\Model\Cookie\PrivateData;
use Anowave\Ec\Model\Cookie\Directive;
use Anowave\Ec\Model\Facebook\ConversionsApiFactory;
use Anowave\Ec\Helper\Bridge;
use Anowave\Ec\Helper\Json;
use Anowave\Ec\Helper\Datalayer;
use Anowave\Ec\Model\Nonce;
use Anowave\Ec\Helper\Attributes;
use Magento\Framework\Filesystem;
use Anowave\Ec\Helper\Stock\Proxy as StockProxy;
use Anowave\Ec\Model\ResourceModel\Consent\CollectionFactory as ConsentCollectionFactory;
use Magento\Checkout\Model\Session;
use Magento\Framework\App\Helper\Context;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\Data\CategoryInterface;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Session\SessionManagerInterface;
use Magento\Customer\Model\SessionFactory;
use Magento\Framework\UrlInterface;
use Magento\Catalog\Model\CategoryRepository;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Catalog\Model\Layer\Resolver;
use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Customer\Model\GroupRegistry;
use Magento\Framework\App\ProductMetadataInterface;
use Magento\Review\Model\ReviewFactory;
use Magento\Review\Model\Rating;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Eav\Model\Config as EavConfig;
use Magento\Sales\Model\Order\Config as OrderConfig;
use Magento\Framework\App\Http\Context as HttpContext;
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection as OptionCollection;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory;
use Magento\Sales\Model\ResourceModel\Order\CollectionFactory as OrderCollectionFactory;

class Data extends Package
{
	/**
	 * Variant delimiter
	 *
	 * @var string
	 */
	const VARIANT_DELIMITER = '-';
	
	/**
	 * Variant attributes delimiter
	 *
	 * @var string
	 */
	const VARIANT_DELIMITER_ATT = ':';
	
	/**
	 * Package name
	 * @var string
	 */
	protected $package = 'MAGE2-GTMGA4';
	
	/**
	 * Config path 
	 * @var string
	 */
	protected $config = 'ec/ec4/license';
	
	/**
	 * Order products array 
	 * 
	 * @var array
	 */
	private $_orders = []; 
	
	/**
	 * Brand map (lazy load)
	 * 
	 * @var array
	 */
	private $_brandMap = [];

	/**
	 * Attribute cache (avoids N+1 loads)
	 *
	 * @var array
	 */
	private $attributeCache = [];

	/**
	 * Cached JSON key file content
	 *
	 * @var string|null
	 */
	private $jsonKeyContentCache = null;
	
	/**
	 * @var ProductRepositoryInterface
	 */
	protected $productRepository = null;
	
	/**
	 * @var CategoryRepository
	 */
	protected $categoryRepository;
	
	protected Session $checkoutSession;
	/**
	 * Customer session
	 * 
	 * @var \Magento\Customer\Model\Session $session
	 */
	protected $session = null;

	protected SessionManagerInterface $sessionManagerInterface;
	
	/**
	 * Group registry 
	 * 
	 * @var GroupRegistry
	 */
	protected $groupRegistry = null;
	
	/**
	 * Order config
	 *
	 * @var OrderConfig
	 */
	protected $orderConfig = null;
	
	/**
	 * @var HttpContext
	 */
	protected $httpContext = null;

	/**
	 * @var Magento\Customer\Model\Customer
	 */
	protected $customer = null;

	/**
	 * @var OptionCollection
	 */
	protected $optionCollection;
	
	/**
	 * @var EavConfig
	 */
	protected $eavConfig;
	
	/**
	 * @var \Magento\Framework\Event\ManagerInterface
	 */
	protected $eventManager = null;
	
	/**
	 * @var Datalayer
	 */
	protected $dataLayer = null;
	
	/**
	 * @var \Magento\Framework\App\Request\Http
	 */
	protected $request;
	
	/**
	 * @var StoreManagerInterface
	 */
	protected $storeManager = null;
	
	/**
	 * @var ProductMetadataInterface
	 */
	protected $productMetadata;

	/**
	 * @var CustomerRepositoryInterface
	 */
	protected $customerRepositoryInterface;
	
	/**
	 * @var AttributeFactory
	 */
	protected $attribute;
	
	/**
	 * @var Attributes
	 */
	protected $attributes;
	
	/**
	 * @var Bridge
	 */
	protected $bridge;
	
	/**
	 * @var PrivateData
	 */
	protected PrivateData $privateData;
	
	/**
	 * @var Directive;
	 */
	protected Directive $directive;
	
	/**
	 * @var Json
	 */
	protected $jsonHelper;
	
	protected $stock;
	
	/**
	 * @var \Magento\Sales\Model\ResourceModel\Order\CollectionFactory
	 */
	protected $salesOrderCollection;
	
	/**
	 * @var UrlInterface
	 */
	protected $urlInt;
	
	/**
	 * @var Cart\Proxy
	 */
	protected $cart;
	
	/**
	 * @var Resolver
	 */
	protected $layerResolver;
	
	/**
	 * @var \Anowave\Ec\Model\Facebook\ConversionsApi
	 */
	protected $facebook_conversions_api;
	
	/**
	 * @var ConversionsApiFactory
	 */
	protected $facebookConversionsApiFactory;
	
	/**
	 * @var ReviewFactory 
	 */
	protected $reviewFactory;
	
	/**
	 * @var Rating 
	 */
	protected $ratingFactory;
	
	/**
	 * @var ProductCollectionFactory
	 */
	protected $productCollectionFactory;

	/**
	 * @var ConsentCollectionFactory
	 */
	protected $consentCollection;
	
	/**
	 * @var Nonce
	 */
	protected $nonceProvider;

	/**
	 * @var Filesystem $filesystem
	 */
	protected $filesystem;

	/**
	 * @var Utils
	 */
	protected $utils;

	protected $currentContext;

	protected $scopeConfig;

	/**
	 * Check if returning customer
	 * 
	 * @var boolean
	 */
	private $returnCustomer = false;
	
	/**
	 * Current currency
	 * 
	 * @var string
	 */
	private $currency = null;

	/**
	 * All root child categories
	 */
	private $rootChildren = [];

	/**
	 * Quote item reviews
	 */
	private $reviews = [];

	/**
	 * Convert values to fixed currency
	 */
	private $convert_currency = null;

	/**
	 * Optimize category access
	 */
	private $optimize_category_access = null;

	public function __construct
	(
		Context $context, 
		ProductRepositoryInterface\Proxy $productRepository,
		CategoryRepository\Proxy $categoryRepository,
		Session $checkoutSession,
		SessionFactory $sessionFactory,
		SessionManagerInterface $sessionManagerInterface,
		GroupRegistry $groupRegistry,
		OrderConfig $orderConfig,
		HttpContext $httpContext,
		OptionCollection $optionCollection,
		EavConfig $eavConfig,
		Datalayer $dataLayer,
		StoreManagerInterface $storeManager,
		ProductMetadataInterface $productMetadata,
		CustomerRepositoryInterface $customerRepositoryInterface,
		Attributes\Proxy $attributes,
		AttributeFactory $attribute,
		Bridge $bridge,
		PrivateData $privateData,
		Directive $directive,
		Json $jsonHelper,
		OrderCollectionFactory\Proxy $salesOrderCollection,
		StockProxy $stock,
	    UrlInterface $urlInt,
	    Resolver $layerResolver,
	    ConversionsApiFactory $facebookConversionsApiFactory,
	    ReviewFactory\Proxy $reviewFactory,
	    Rating\Proxy $ratingFactory,
		ConsentCollectionFactory $consentCollection,
	    ProductCollectionFactory $productCollectionFactory,
	    Nonce $nonceProvider,
		Filesystem $filesystem,
		Utils $utils,
		CurrentContext $currentContext
	)
	{
		parent::__construct($context);

		$this->currentContext = $currentContext;
		$this->request = $context->getRequest();
		$this->productRepository = $productRepository;
		$this->categoryRepository = $categoryRepository;
		$this->groupRegistry = $groupRegistry;

		$this->checkoutSession = $checkoutSession;

		$this->session = $sessionFactory->create();
		$this->sessionManagerInterface = $sessionManagerInterface;
		$this->orderConfig = $orderConfig;
		$this->httpContext = $httpContext;
		$this->optionCollection = $optionCollection;
		$this->optionCollection->setPositionOrder('asc')->setStoreFilter(0);
		$this->scopeConfig = $context->getScopeConfig();
		$this->eventManager = $context->getEventManager();
		$this->dataLayer = $dataLayer;
		$this->eavConfig = $eavConfig;
		$this->storeManager = $storeManager;
		$this->productMetadata = $productMetadata;
		$this->customerRepositoryInterface = $customerRepositoryInterface;
		$this->attributes = $attributes;
		$this->attribute = $attribute;
		$this->bridge = $bridge;
		$this->privateData = $privateData;
		$this->directive = $directive;
		$this->jsonHelper = $jsonHelper;
		$this->stock = $stock;
		$this->salesOrderCollection = $salesOrderCollection;
		$this->urlInt = $urlInt;
		$this->layerResolver = $layerResolver;
		$this->facebookConversionsApiFactory = $facebookConversionsApiFactory;
		$this->reviewFactory = $reviewFactory;
		$this->ratingFactory = $ratingFactory;
		$this->consentCollection = $consentCollection;
		$this->nonceProvider = $nonceProvider;
		$this->filesystem = $filesystem;
		$this->utils = $utils;
		$this->productCollectionFactory = $productCollectionFactory;

		
		$this->convert_currency = $this->getCurrencyConvert() !== Currency::AUTO ? $this->getCurrencyConvert() : null;
	}	

	/**
	 * Get order revenue
	 * 
	 * @param OrderInterface $order
	 * @return number|number|NULL
	 */
	public function getRevenue(OrderInterface $order)
	{
		if (\Anowave\Ec\Model\System\Config\Source\AdWords\Strategy::POAS === $this->getGoogleAdsStrategy())
		{
			$revenue = $this->getRevenuePoas($order);
		}
		else 
		{
			$revenue = $order->getGrandTotal();

			switch ($this->getRevenueCalculation())
			{
				case Tax::INCL_TAX: $revenue = ($order->getGrandTotal());
					break;
				case Tax::EXCL_TAX: $revenue = ($order->getSubtotal());
					break;
				case Tax::INCL_TAX_EXCL_SHIPPING: $revenue = ($order->getGrandTotal() - $order->getShippingAmount());
					break;
				case Tax::EXCL_TAX_INCL_SHIPPING: $revenue = ($order->getGrandTotal() - $order->getTaxAmount());
					break;
			}
		}
		
		/**
		 * By default return incl. tax
		 */
		return $this->getAmount($revenue);
	}

	/**
	 * Get POAS profit 
	 * 
	 * @param OrderInterface $order
	 * 
	 * @return float
	 */
	public function getRevenuePoas(OrderInterface $order) : float
	{
		/**
		 * Get revenue
		 */
		$revenue = $order->getSubtotal();

		try 
		{
			$value = 0;

			$poas_attribute = $this->getGoogleAdsPoasAttribute();

			foreach($order->getAllVisibleItems() as $item)
			{
				$cost = (float) $item->getProduct()->getData($poas_attribute);

				if ($cost > 0)
				{
					$value += ($cost * (int) $item->getQtyOrdered());
				}
			}
		}
		catch (\Exception $e){}

		/**
		 * Avoid negative revenue due to wrong values in cost attribute
		 */
		if ($revenue >= $value)
		{
			$revenue -= $value;
		}

		/**
		 * By default return incl. tax
		 */
		return $this->getAmount($revenue);

	}

	/**
	 * Get amount
	 * 
	 * @param mixed $amount
	 * 
	 * @return float
	 */
	public function getAmount($amount) : float
	{
		if (null !== $this->convert_currency)
		{
			$amount = (float) $this->getAmountConvert((float) $amount, $this->getCurrentStoreCurrency(), $this->convert_currency);
		}

		return (float) $amount;
	}


	/** Get converted amount
	 * @param float $amount
	 * @param string|null $from
	 * @param string|null $to
	 * 
	 * @return [type]
	 */
	public function getAmountConvert(float $amount, ?string $from = null, ?string $to = null) : float
	{
		if (!$from || !$to)
		{
			return (float) $amount;
		}

		if ($from !== $to)
		{
			return (float) $this->utils->convertPrice((float) $amount, $from, $to);
		}
		
		return (float) $amount;
	}
	

	/**
	 * Get item price 
	 * 
	 * @param \Magento\Sales\Model\Order\Item $item
	 * @return number|NULL
	 */
	public function getRevenueProduct(\Magento\Sales\Model\Order\Item $item)
	{
		$revenue = $item->getPrice();

		switch ((int) $this->getConfig('ec/tax/revenue_product'))
		{
			case TaxItem::INCL_TAX: 
				
				$revenue = $item->getPriceInclTax();

				break;

			case TaxItem::EXCL_TAX: 
				
				$revenue = $item->getPrice();

				break;
		}

		return $this->getAmount($revenue);
	}
	
	/**
	 * Get AdWords ecomm_totalvalue
	 *
	 * @param OrderInterface $order
	 * @return number|number|NULL
	 */
	public function getRevenueAdWords(OrderInterface $order)
	{
		switch ((int) $this->getConfig('ec/tax/ecomm_totalvalue'))
		{
			case Tax::INCL_TAX:                   return ($order->getGrandTotal());
			case Tax::EXCL_TAX:                   return ($order->getSubtotal());
			case Tax::INCL_TAX_EXCL_SHIPPING:     return ($order->getGrandTotal() - $order->getShippingAmount());
			case Tax::EXCL_TAX_INCL_SHIPPING:     return ($order->getGrandTotal() - $order->getTaxAmount());
		}
		
		/**
		 * By default return incl. tax
		 */
		return $order->getGrandTotal();
	}

	/**
	 * Get checkout push 
	 * 
	 * @param unknown $block
	 */
	public function getCheckoutPush()
	{
		/**
		 * Get data 
		 * 
		 * @var StdClass $data
		 */
		$data = $this->getCheckoutProducts();
		
		/**
		 * Update total value
		 */
		$data->google_tag_params->ecomm_totalvalue = (float) $this->checkoutSession->getQuote()->getGrandTotal();
		
		/**
		 * Build checkout payload 
		 * 
		 * @var array $checkout
		 */

		switch($this->getValueRenderTaxType())
		{
			case \Anowave\Ec\Model\System\Config\Source\TaxItem::INCL_TAX: $total = $this->getAmount((float) $this->checkoutSession->getQuote()->getGrandTotal());
				break;
			case \Anowave\Ec\Model\System\Config\Source\TaxItem::EXCL_TAX: $total = $this->getAmount((float) $this->checkoutSession->getQuote()->getSubtotal());
				break;
		}

		

		$checkout = 
		[
		    'payload' =>
		    [
		        'event' => Constants::EVENT_BEGIN_CHECKOUT,
		        'ecommerce' =>
		        [
					'currency' 	=> $this->getCurrency(),
		            'items' 	=> $data->products,
					'value' 	=> $total
		        ],
		        'value' => $total
		    ],
		    'google_tag_params' => $data->google_tag_params,
		    'total'	            => $total
		];
		
		/**
		 * Create transport object
		 *
		 * @var \Magento\Framework\DataObject $transport
		 */
		$transport = new \Magento\Framework\DataObject
		(
		    [
		        'checkout' => $checkout
		    ]
	    );
		
		/**
		 * Notify others
		 */
		$this->eventManager->dispatch('ec_get_checkout', ['transport' => $transport]);
		
		
		/**
		 * Return
		 */
		return $this->getJsonHelper()->encode
		(
			$transport->getCheckout(),JSON_PRETTY_PRINT
		);
	}
	
	/**
	 * Get multicheckout push 
	 * 
	 * @param string $step
	 * @param \Anowave\Ec\Block\Track $block
	 */
	public function getMultiCheckoutPush($step)
	{
	    /**
	     * Get data
	     *
	     * @var StdClass $data
	     */
	    $data = $this->getCheckoutProducts();
	    
	    /**
	     * Update total value
	     */
	    $data->google_tag_params->ecomm_totalvalue = (float) $this->checkoutSession->getQuote()->getGrandTotal();
	    
	    /**
	     * 
	     * @var integer $currentStep
	     */
	    $current = Constants::MULTI_CHECKOUT_STEP_LOGIN;
	    
	    switch ($step)
	    {
	        case 'login':
	        case 'register':
	            $current = Constants::MULTI_CHECKOUT_STEP_LOGIN;
	            break;
	        case 'addresses':
	            $current = Constants::MULTI_CHECKOUT_STEP_ADDRESSES;
	            break;
	        case 'shipping': 
	            $current = Constants::MULTI_CHECKOUT_STEP_SHIPPING;
	              break;
	        case 'billing': 
	            $current = Constants::MULTI_CHECKOUT_STEP_BILLING;
	            break;
	        case 'overview': 
	            $current = Constants::MULTI_CHECKOUT_STEP_OVERVIEW;
	            break;
	    }

	    /**
	     * Return
	     */
	    return $this->getJsonHelper()->encode
	    (
	        [
	            'payload' =>
	            [
	                'event' => Constants::EVENT_BEGIN_CHECKOUT,
	                'currency' => $this->getStore()->getCurrentCurrencyCode(),
	                'ecommerce' =>
	                [
	                    'items' => $data->products
	                ],
	                'type' => 'multicheckout',
	                'step' => $current
	            ],
	            'google_tag_params' => $data->google_tag_params,
	            'total'	=> (float) $this->checkoutSession->getQuote()->getGrandTotal()
	            
	        ],
	        JSON_PRETTY_PRINT
        );
	}
	
	/**
	 * Get cart push 
	 */
	public function getCartPush()
	{
		/**
		 * Get data
		 *
		 * @var StdClass $data
		 */
		$data = $this->getCheckoutProducts();

		/**
		 * Update total value
		 */
		$data->google_tag_params->ecomm_totalvalue = (float) $this->checkoutSession->getQuote()->getGrandTotal();
		
		/**
		 * Return
		 */
		return $this->getJsonHelper()->encode($data);
	}
	
	/**
	 * Get checkout products 
	 * 
	 * @param unknown $block
	 * @param \Magento\Framework\Registry $registry
	 */
	public function getCheckoutProducts()
	{
		/**
		 * Products 
		 * 
		 * @var array $products
		 */
		$products = [];
		
		/**
		 * AdWords Dynamic Remarketing parameters 
		 * 
		 * @var StdClass $google_tag_params
		 */
		$google_tag_params = (object)
		[
			'ecomm_prodid' 			=> [],
			'ecomm_pvalue' 			=> [],
			'ecomm_pname' 			=> [],
			'ecomm_totalvalue' 		=> 0
		];

		/**
		 * Batch preload products with category_ids to avoid N+1
		 */
		$quoteItems = $this->checkoutSession->getQuote()->getAllVisibleItems();
		$productIds = [];

		foreach ($quoteItems as $item) {
			if ($item->getProduct()) {
				$productIds[] = $item->getProduct()->getId();
			}
		}

		$preloadedProducts = [];

		if ($productIds) {
			$collection = $this->productCollectionFactory->create()
				->addFieldToFilter('entity_id', ['in' => $productIds])
				->addAttributeToSelect(['category_ids']);

			foreach ($collection as $product) {
				$preloadedProducts[$product->getId()] = $product;
			}
		}

		foreach ($quoteItems as $item)
		{
			/**
			 * Get all product categories
			 */
			$categories = $this->getCurrentStoreProductCategories($item->getProduct());

			if (!$categories)
			{
				$preloaded = $preloadedProducts[$item->getProduct()->getId()] ?? null;

				$categories = $this->getCurrentStoreProductCategories($preloaded);
			}
			
			/**
			 * Cases when product does not exist in any category
			 */
			if (!$categories)
			{
				$categories[] = $this->getStoreRootDefaultCategoryId();
			}
			
			try 
			{
    			/**
    			 * Load last category 
    			 */
    			$category = $this->getCategoryObject(end($categories));
			
			}
			catch (\Exception $e)
			{
			    $category = $this->categoryRepository->get
			    (
			        $this->getStoreRootDefaultCategoryId()
		        );
			}
			
			/**
			 * Variant
			 * 
			 * @var array $variant
			 */
			$variant = [];
			
			/**
			 * items[] array node 
			 * 
			 * @var array $node
			 */

			$node = $this->assignCategory($category,
			[
				'item_id' 		     	=> 		  $this->getIdentifierItem($item),
				'item_id_numeric' 	 	=>  (int) $item->getProduct()->getId(),
				'item_name' 		 	=> 		  $item->getName(),
				'price' 	         	=> (float)$this->getPriceItem($item),
				'quantity' 	         	=> (int)  $item->getQty(),
				'item_list_name'     	=>        $this->getCategoryList($category),
				'item_list_id'       	=>        $this->getCategoryList($category),
				'item_brand'		 	=> 		  $this->getBrand($item->getProduct()),
				'item_reviews_count'  	=>        $this->getReviewsCount($item->getProduct()),
				'item_rating_summary' 	=>        $this->getRatingSummary($item->getProduct()),
				'category'            	=>        $this->getCategory($category),
				'currency'            	=>        $this->getCurrency(),
				'remarketingFacebookId' => 		  $this->getFacebookRemarketingId($item->getProduct()),
				'remarketingAdwordsId' 	=> 		  $this->getAdwordsRemarketingId($item->getProduct()),
				$this->getStockDimensionIndex(true) => $this->getStock($item->getProduct())
			]);
	

			/**
			 * Get custom attributes array 
			 * 
			 * @var array $custom_attributes
			 */
			$node += $this->getDataLayerAttributesArray($item->getProduct()->getSku());
			
			/**
			 * Build data 
			 * 
			 * @var \Magento\Framework\DataObject $data
			 */
			$data = new \Magento\Framework\DataObject($node);
			
			/**
			 * AdWords Dynamic Remarketing 
			 * 
			 * @var \Magento\Framework\DataObject $ecomm
			 */
			$ecomm = new \Magento\Framework\DataObject(array
			(
				'id' 		=> 		 	$this->getAdwordsEcommProdId($item),
				'name' 		=> 		 	$data->getName(),
				'price' 	=> (float)  $data->getPrice(),
			));
			
			if (\Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE == $item->getProduct()->getTypeId())
			{
				$variant = [];

				/**
				 * Get buy request 
				 * 
				 * @var []
				 */
				$buyRequest = $item->getProductOptionByCode('info_buyRequest');
				
				/**
				 * Check if buy request is set
				 */
				if ($buyRequest)
				{
					/**
					 * Get info buy request
					 *
					 * @var \Magento\Framework\DataObject
					 */
					$info = new \Magento\Framework\DataObject($buyRequest);
				}
				else 
				{
					/**
					 * Try to obtain buy request as custom option
					 * 
					 * @var []
					 */
					$buyRequest = $item->getProduct()->getCustomOption('info_buyRequest');
					
					if (isset($buyRequest['value']))
					{
						if (false === $value = @unserialize($buyRequest['value']))
						{
							$value = @json_decode($buyRequest['value'], true);
						}
						
						if ($value)
						{
							$info = new \Magento\Framework\DataObject($value);
						}
					}
					else
					{
						$info = new \Magento\Framework\DataObject([]);
					}
				}

				if (isset($info) && $info->getSuperAttribute())
				{
					/**
					 * Construct variant
					 */
					foreach ((array) $info->getSuperAttribute() as $id => $option)
					{
						$attribute = $this->getCachedAttribute((int) $id);

						if ($attribute->usesSource())
						{
							$name = $this->getAttributeLabel($attribute);
							$text = $attribute->getSource()->getOptionText($option);

							if ($this->useDefaultValues())
							{
								/**
								 * Get current store
								 *
								 * @var int
								 */
								$currentStore = $attribute->getSource()->getAttribute()->getStoreId();

								/**
								 * Change default store
								 */
								$attribute->getSource()->getAttribute()->setStoreId(0);

								/**
								 * Get text
								 *
								 * @var string
								 */
								$text = $attribute->getSource()->getOptionText($option);

								/**
								 * Restore store
								 */
								$attribute->getSource()->getAttribute()->setStoreId($currentStore);
							}

							$variant[] = join(self::VARIANT_DELIMITER_ATT, array($name, $text));

						}
					}
				}
			
				if (!$this->useSimples())
				{
					$data->setId
					(
						$this->getIdentifier($item->getProduct())
					);
						
					$data->setName
					(
						$item->getProduct()->getName()
					);
					
					$ecomm->setId
					(
						$this->getAdwordsEcommProdId
						(
							$item->getProduct()
						)
					);
					
					$ecomm->setName
					(
						$item->getProduct()->getName()
					);
				}
				
				
				/**
				 * Load configurable
				 */
				$configurable = $this->productRepository->getById
				(
					$item->getProductId()
				);

				if (!$this->useSimples())
				{
					$data->setId
					(
						$this->getIdentifier($configurable)
					);
					
					$data->setName
					(
						$configurable->getName()
					);
					
					$ecomm->setId
					(
						$this->getAdwordsEcommProdId($configurable)
					);
					
					$ecomm->setName
					(
						$configurable->getName()
					);
				}
				else 
				{
					if ($option = $item->getOptionByCode('simple_product')) 
					{
						$data->setId
						(
							$this->getAdwordsEcommProdId($option->getProduct())
						);
						
						$data->setName
						(
							$option->getProduct()->getName()
						);
						
						$ecomm->setId
						(
							$this->getAdwordsEcommProdId($option->getProduct())
						);
						
						$ecomm->setName
						(
							$option->getProduct()->getName()
						);
					}
				}
				
				$data->setItemBrand
				(
					$this->getBrand($configurable)
				);
				
				/**
				 * Push variant to data
				 *
				 * @var array
				 */
				$data->setItemVariant(join(self::VARIANT_DELIMITER, $variant));
				
				/**
				 * Push variant id into dataLayer[] object
				 */
				if($item->getHasChildren())
				{
				    $child = $item->getOptionByCode('simple_product');
				    
				    if ($child && $child->getId())
				    {
				        $data->setVariantId
				        (
				            $this->getIdentifier($child->getProduct())
			            );
				    }
				}
			}

			/**
			 * Custom and additional options tracking
			 */
			try
			{
			    $variant = [];
			    
			    if ($data->getItemVariant())
			    {
			        $variant[] = $data->getItemVariant();
			    }
			    
				/**
				 * Get order options
				 * 
				 * @var array $options
				 */
				$options = $item->getProduct()->getTypeInstance(true)->getOrderOptions($item->getProduct());
				
				if (isset($options['options']))
				{
					$selection = [];
					
					foreach ($options['options'] as $option)
					{
						$selection[] = join(self::VARIANT_DELIMITER_ATT, [$option['label'], $option['value']]);
					}
					
					if ($selection)
					{
					    $variant[] = (string) $data->getVariant() . join(self::VARIANT_DELIMITER_ATT, $selection);
						
					    /**
					     * Update variant
					     */
						$data->setItemVariant(join(static::VARIANT_DELIMITER, $variant));
						
						/**
						 * Add custom option explicitly
						 */
						$data->setItemCustomOption($selection);
					}
				}
				
				/**
				 * Get additional options
				 */
				$options = $item->getProduct()->getCustomOptions();

				if ($options && isset($options['additional_options']))
				{
					if ($options['additional_options']->getValue())
					{
						$additional_options = json_decode($options['additional_options']->getValue());
						
						$selection = [];
						
						foreach ($additional_options as $option)
						{
						    $selection[] = join(self::VARIANT_DELIMITER_ATT, [$option->label, join(chr(32), array_filter((array) $option->value))]);
						}
						
						if ($selection)
						{
						    $variant[] = (string) $data->getVariant() . join(self::VARIANT_DELIMITER_ATT, $selection);

						    /**
						     * Update variant
						     */
						    $data->setItemVariant(join(static::VARIANT_DELIMITER, $variant));
						    
						    /**
						     * Add custom option explicitly
						     */
						    $data->setItemCustomOption($selection);
						}
					}
				}
			}
			catch (\Exception $e){}
			

			/**
			 * Add product
			 */
			$products[] = $data->getData();
			
			/**
			 * AdWords Dynamic Remarketing
			 */
			$google_tag_params->ecomm_prodid[] = $ecomm->getId();
			$google_tag_params->ecomm_pvalue[] = $ecomm->getPrice();
			$google_tag_params->ecomm_pname[]  = $ecomm->getName();
		}

		/**
		 * Create transport object
		 *
		 * @var \Magento\Framework\DataObject $transport
		 */
		$transport = new \Magento\Framework\DataObject
		(
			[
				'attributes' => $this->attributes->getAttributes()
			]
		);
		
		/**
		 * Notify others
		 */
		$this->eventManager->dispatch('ec_get_checkout_attributes', ['transport' => $transport]);
		
		/**
		 * Get response
		 */
		$attributes = $transport->getAttributes();
		
		foreach ($products as &$product)
		{
			foreach ($attributes as $key => $value)
			{
				$product[$key] = $value;
			}
		}
		
		unset($product);
		
		/**
		 * Notify other plugins for checkout products
		 */
		$transport = new \Magento\Framework\DataObject
		(
		    [
		        'products' => $products
		    ]
	    );
		
		/**
		 * Notify others
		 */
		$this->eventManager->dispatch('ec_get_checkout_products', ['transport' => $transport]);
		
		$products = $transport->getProducts();
		
		return (object)
		[
			'products' 			=> $products,
			'google_tag_params' => $google_tag_params
		];
	}
	
	/**
	 * Get loaded product collection from product list block 
	 *  
	 * @param \Magento\Catalog\Block\Product\ListProduct $list
	 */
	protected function getLoadedCollection(\Magento\Catalog\Block\Product\ListProduct $list)
	{
		$collection = $list->getLoadedProductCollection();
		
		/**
		 * Get toolbar
		 */
		$toolbar = $list->getToolbarBlock();
		
		if ($toolbar)
		{
			$orders = $list->getAvailableOrders();
			
			if ($orders) 
			{
				$toolbar->setAvailableOrders($orders);
			}
			
			$sort = $list->getSortBy();
			
			if ($sort) 
			{
				$toolbar->setDefaultOrder($sort);
			}
			
			$dir = $list->getDefaultDirection();
			
			if ($dir) 
			{
				$toolbar->setDefaultDirection($dir);
			}
			
			$modes = $list->getModes();
			
			if ($modes)
			{
				$toolbar->setModes($modes);
			}
			
			$collection->setCurPage($toolbar->getCurrentPage());

			$limit = (int) $toolbar->getLimit();
			
			if ($limit) 
			{
				$collection->setPageSize($limit);
			}
			
			if ($toolbar->getCurrentOrder()) 
			{
				$collection->setOrder($toolbar->getCurrentOrder(), $toolbar->getCurrentDirection());
			}
		}
		
		return $collection;
	}
	
	/**
	 * Get detail push 
	 * 
	 * @param \Magento\Framework\View\Element\Template $block
	 * @return StdClass|boolean
	 */
	public function getDetailPushForward($block)
	{
		$info = $block->getLayout()->getBlock('product.info');
		
		if ($info)
		{
		    /**
		     * @var Magento\Catalog\Model\Category $category
		     */
			$category = $this->currentContext->getCurrentCategory()->get();

			/**
			 * Pick category from layer resolver
			 */
			if (!$category->getId())
			{
			    $category = $this->layerResolver->get()->getCurrentCategory();
			}

			/**
			 * Check if category isn't root category
			 */
			if ($category)
			{
			    if ((int) $category->getId() === (int) $this->getStoreRootDefaultCategoryId())
			    {
			        $category = null;
			    }
			}
			
			/**
			 * Pick category from list of categories product is assigned to
			 */
			if (!$category)
			{
			    $categories = $this->getCurrentStoreProductCategories($info->getProduct());
				
				if (!$categories && $info->getProduct()->getCategoryIds())
				{
				    $categories = $info->getProduct()->getCategoryIds();
				}

				/**
				 * Cases when product does not exist in any category
				 */
				if (!$categories)
				{
					$categories[] = $this->getStoreRootDefaultCategoryId();
				}
				
				try 
				{
    				$category = $this->getCategoryObject(end($categories));
				}
				catch (\Exception $e) 
				{
				    $categories = $info->getProduct()->getCategoryIds();
				    
				    if (!$categories)
				    {
				        $categories = [$this->getStoreRootDefaultCategoryId()];
				    }
				    
				    $category = $this->getCategoryObject(end($categories));
				}
			}
			
			/**
			 * Create transport object
			 *
			 * @var \Magento\Framework\DataObject $transport
			 */
			$transport = new \Magento\Framework\DataObject
			(
				[
					'attributes' => $this->attributes->getAttributes(),
				    'product'    => $info->getProduct()
				]
			);

			/**
			 * Notify others
			 */
			$this->eventManager->dispatch('ec_get_detail_attributes', ['transport' => $transport]);
			
			/**
			 * Get response
			 */
			$attributes = $transport->getAttributes();
			
			/**
			 * Value parameter 
			 * 
			 * @var integer $value
			 */
			$value = 0;
			
			/**
			 * Items array 
			 * 
			 * @var array $items
			 */
			$items = [];
			
			/**
			 * Build item 
			 * 
			 * @var array $item
			 */
			$item = array_merge
			(
			    [
			        'item_id' 							=> $this->getIdentifier($info->getProduct()),
			        'item_name' 						=> $info->getProduct()->getName(),
			        'price' 							=> $this->getPrice($info->getProduct()),
			        'item_brand'						=> $this->getBrand
			        (
			            $info->getProduct()
		            ),
			        'item_reviews_count'                => $this->getReviewsCount($info->getProduct()),
			        'item_rating_summary'               => $this->getRatingSummary($info->getProduct()),
					'google_business_vertical'			=> $this->getGoogleBusinessVertical(),
					'remarketingFacebookId'     		=> $this->getFacebookRemarketingId($info->getProduct()),
				    'remarketingAdwordsId' 	    		=> $this->getAdwordsRemarketingId($info->getProduct()),
			        $this->getStockDimensionIndex(true)	=> $this->getStock($info->getProduct()),
			        'quantity' 							=> 1,
			        'index'                             => 0
			    ],
			    $attributes
		    );
			
			$item += $this->getDataLayerAttributesArray($info->getProduct()->getSku());
			
			$items[] = $this->assignCategory($category, $item);
			
			foreach ($items as $item)
			{
			    $value += (float) $item['price'];
			}
			
			$data = 
			[
				'ecommerce' => 
				[
					'currency'  => $this->getCurrency(),
				    'value'     => $value,
				    'items'     => $items,
				],
			    'event' => Constants::EVENT_VIEW_ITEM
			];

			if (\Magento\GroupedProduct\Model\Product\Type\Grouped::TYPE_CODE == $info->getProduct()->getTypeId())
			{
				if (!$this->useGroupViewEvent())
				{
					$data['event'] = Constants::EVENT_VIEW_ITEM_GROUP;
				}
			}
			
			/**
			 * There is discrepancy between Google's specification with regards to whether 'list' parameter should be used in 'detail' JSON (
			 * 
			 * @see https://developers.google.com/tag-manager/enhanced-ecommerce#details
			 * @see https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce#product-detail-view 
			 * 
			 * @todo Pending feedback from Google
			 */
			if (1 === (int) $this->getConfig('ec/options/use_detail_list'))
			{
			    $data['ecommerce']['item_list_id'] 	 = $this->getCategoryList($category);
				$data['ecommerce']['item_list_name'] = $this->getCategoryList($category);
			}
			
			
			$data['currentStore'] = $this->getStoreName();
			
			
			/**
			 * Persist data in dataLayer
			 */
			$this->dataLayer->merge($data);
			
			/**
			 * Prepare Related & Upsells impressions
			 */
			$related = [];
			
			/**
			 * Related
			 */
			try 
			{
				$list = $block->getLayout()->getBlock('catalog.product.related');
				
				if ($list)
				{
					/**
					 * Set default position
					 *
					 * @var integer $position
					 */
					$position = 1;

					/**
					 * Push data
					 *
					 * @var []
					 */
					
					foreach ($this->bridge->getLoadedItems($list) as $product)
					{
						/**
						 * Create transport object
						 *
						 * @var \Magento\Framework\DataObject $transport
						 */
						$transport = new \Magento\Framework\DataObject
						(
							[
								'attributes' => $this->attributes->getAttributes(),
							    'product'    => $product
							]
						);
						
						/**
						 * Notify others
						 */
						$this->eventManager->dispatch('ec_get_impression_related_attributes', ['transport' => $transport]);
						
						/**
						 * Get response
						 */
						$attributes = $transport->getAttributes();
						
						$entity = array_merge
						(
							[
								'item_list_id' 		=> Constants::LIST_RELATED,
								'item_list_name' 	=> Constants::LIST_RELATED,
								'item_category'		=> Constants::LIST_RELATED,
								'item_id'			=> $this->getIdentifier($product),
								'item_name'			=> $product->getName(),
								'item_brand'		=> $this->getBrand
								(
									$product
								),
								'price'			=> $this->getPrice($product),
								'quantity'		=> 1,
								'index'			=> $position++
							], 
							$attributes
						);
						
						$related[] = $entity;
					}
				}
			}
			catch (\Exception $e){}
			
			$upsells = [];
			
			try 
			{
				$list = $block->getLayout()->getBlock('product.info.upsell');
				
				if ($list)
				{
					/**
					 * Set default position
					 *
					 * @var integer $position
					 */
					$position = 1;
					
					/**
					 * Push data
					 *
					 * @var []
					 */

					foreach ($this->bridge->getLoadedItems($list) as $product)
					{
						/**
						 * Create transport object
						 *
						 * @var \Magento\Framework\DataObject $transport
						 */
						$transport = new \Magento\Framework\DataObject
						(
							[
								'attributes' => $this->attributes->getAttributes(),
							    'product'    => $product
							]
						);
						
						/**
						 * Notify others
						 */
						$this->eventManager->dispatch('ec_get_impression_upsell_attributes', ['transport' => $transport]);
						
						/**
						 * Get response
						 */
						$attributes = $transport->getAttributes();
						
						$entity = array_merge
						(
							[
								'item_list_id' 		=> Constants::LIST_UP_SELL,
								'item_list_name' 	=> Constants::LIST_UP_SELL,
								'item_category'		=> Constants::LIST_UP_SELL,
								'item_id'			=> $this->getIdentifier($product),
								'item_name'			=> $product->getName(),
								'item_brand'		=> $this->getBrand
								(
									$product
								),
								'price'			=> $this->getPrice($product),
								'index'		=> $position++
							], 
							$attributes
						);
						
						$upsells[] = $entity;
					}
				}
				
				
			}
			catch (\Exception $e){}
			
			/**
			 * Create transport object
			 *
			 * @var \Magento\Framework\DataObject $transport
			 */
			$transport = new \Magento\Framework\DataObject
			(
				[
					'response' => $data,
				    'product'  => $info->getProduct()
				]
			);
			
			/**
			 * Notify others
			 */
			$this->eventManager->dispatch('ec_get_detail_data_after', ['transport' => $transport]);
			
			/**
			 * Get response
			 */
			$data = $transport->getResponse();
			
			/**
			 * Get product
			 */
			$product = $info->getProduct();
			
			/**
			 * Child items (configurable)
			 * 
			 * @var array $children
			 */
			$children = [];
			
			$google_tag_params = 
			[
			    'ecomm_pagetype' 	=> 		   'product',
			    'ecomm_category'	=> 		   $this->escape($this->getCategory($category)),
			    'ecomm_prodid'		=> 		   $this->escape
			    (
			        $this->getAdwordsEcommProdId($product)
		        ),
			    'ecomm_totalvalue'	=> (float) $this->getPrice($product)
			];
			
			if(\Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE === $product->getTypeId())
			{
			    if ($this->useSimples() && $this->useVariantSku())
			    {
			        if (null !== $child = $this->getFirstChild($product))
			        {
			            $google_tag_params['ecomm_prodid'] = $this->escape
			            (
			                $this->getAdwordsEcommProdId($child)
		                );
			        } 
			    }
			}

			if ($this->useAddRelated() && $related)
			{
				$data['related'] = $related;
			}

			if ($this->useAddUpsells() && $upsells)
			{
				$data['upsells'] = $upsells;
			}

	
			$detail = 
			[
				'payload' 			=> $this->getJsonHelper()->encode($data),
			    'fbq'				=> $this->getJsonHelper()->encode($this->getFacebookViewContentTrack($product, $category)),
				'google_tag_params' => $google_tag_params,
				'group'             => $this->getDetailGroup($info, $category)
			];

			return (object) $detail;
		}
		
		return false;
	}

	/**
	 * Get grouped products
	 * 
	 * @param unknown $block
	 * @param unknown $category
	 * @return string
	 */
	public function getDetailGroup(\Magento\Catalog\Block\Product\View $block, \Magento\Catalog\Model\Category $category)
	{
		$group = [];
		
		if (\Magento\GroupedProduct\Model\Product\Type\Grouped::TYPE_CODE == $block->getProduct()->getTypeId())
		{
			foreach ($block->getProduct()->getTypeInstance(true)->getAssociatedProducts($block->getProduct()) as $product)
			{
				$group[] = 
				[
					'id'								=> 	  (int) $product->getId(),
					'item_id' 							=> 			$this->getIdentifier($product),
					'item_name' 						=> 			$product->getName(),
					'price' 							=> 			$this->getPrice($product),
					'item_brand'						=> 			$this->getBrand($product),
					'item_reviews_count'                => 			$this->getReviewsCount($product),
					'item_rating_summary'               => 			$this->getRatingSummary($product),
					'category'							=> 			$this->getCategory($category),
					$this->getStockDimensionIndex(true)	=> (string) $this->getStock($product),
					'quantity' 							=> 			1,
					'index'                             => 			0
				];
			}
		}
		
		return $this->getJsonHelper()->encode($group);
	}
	
	/**
	 * Get purchase payload collection
	 * 
	 * @param \Magento\Framework\View\Element\Template $block
	 * @return string
	 */
	public function getPurchasePayloadCollection($block) : string
	{
	    $payload = [];
	    
	    foreach ($this->getOrders($block) as $order)
	    {
	        $payload[] = $this->getPurchasePayload($order);
	    }
	    
	    return $this->getJsonHelper()->encode($payload);
	}
	
	/**
	 * Get purchase payload 
	 * 
	 * @param \Magento\Sales\Model\Order $order
	 * @return string
	 */
	public function getPurchasePayload($order)
	{	
		$products = [];
		$response = [];
		
		if ($order->getIsVirtual())
		{
		    $address = $order->getBillingAddress();
		}
		else
		{
		    $address = $order->getShippingAddress();
		}

		$response =
		[
		    'currency' => $this->getCurrency(),
		    'ecommerce' =>
		    [
		        'purchase' =>
		        [
		            'transaction_id'  => 		 $order->getIncrementId(),
		            'value'           => (float) $this->getRevenue($order),
		            'tax'             => (float) $this->getAmount($order->getTaxAmount()),
		            'shipping'        => (float) $this->getAmount($order->getShippingAmount()),
		            'currency'        => 		 $this->getCurrency(),
		            'coupon'          => strtoupper((string) $order->getCouponCode()),
					'affiliation'     => (string) $this->getStore()->getName(),
		            'items'           => [],
					
		        ],
		        'currency' => $this->getCurrency()
		    ],
		    'facebook' =>
		    [
		        'revenue' 	=> $this->getAmount((float) $order->getGrandTotal()),
		        'subtotal' 	=> $this->getAmount((float) $order->getSubtotal())
		    ],
			'tiktok' =>
		    [
		        'revenue' 	=> $this->getAmount((float) $order->getGrandTotal()),
		        'subtotal' 	=> $this->getAmount((float) $order->getSubtotal())
		    ],
		    'payment' =>
		    [
		        'method' => $order->getPayment()->getMethodInstance()->getTitle()
		    ],
		    'shipping' =>
		    [
		        'method' => $order->getShippingDescription()
		    ],
		    'event' => Constants::EVENT_PURCHASE
		];

		if ($this->getAddPoasProfit())
		{
			$response['ecommerce']['purchase']['profit'] = $this->getRevenuePoas($order);
		}
		
		/**
		 * GA4
		 */
		
		foreach ($response['ecommerce']['purchase'] as $key => $entity)
		{
		    $response['ecommerce'][$key] = $entity;
		}
		
		if ($this->supportAutomatedDiscounts())
		{
		    $response['ecommerce']['aw_merchant_id']     = $this->getGoogleAdsAwMerchantId();
		    $response['ecommerce']['aw_feed_country']    = $this->getGoogleAdsAwFeedCountry();
		    $response['ecommerce']['aw_feed_language']   = $this->getGoogleAdsAwFeedLanguage();
		    
		    $discount = abs($order->getDiscountAmount());
		    
		    if ($discount)
		    {
		        $response['ecommerce']['discount'] = $this->getAmount($discount);
		    }
		}
		
		if ($order->getCouponCode())
		{
		    $response['ecommerce']['purchase']['discount'] = abs($this->getAmount((float) $order->getDiscountAmount()));
		}
		
		foreach ($order->getAllVisibleItems() as $item)
		{
		    $variant = [];
		    
			$category = $this->currentContext->getCurrentCategory()->get();
		
		    if (!$category || !$category->getId())
		    {
		        /**
		         * Get all product categories
		         */
		        $categories = $this->getCurrentStoreProductCategories($item->getProduct());
		        
		        /**
		         * Cases when product does not exist in any category
		         */
		        if (!$categories)
		        {
		            $categories[] = $this->getStoreRootDefaultCategoryId();
		        }
		        
		        try
		        {
		            /**
		             * Load last category
		             */
		            $category = $this->getCategoryObject(end($categories));
		            
		        }
		        catch (\Exception $e)
		        {
		            $category = $this->categoryRepository->get
		            (
		                $this->getStoreRootDefaultCategoryId()
	                );
		        }
		    }
		    
		    
		    $node = $this->assignCategory($category,
	        [
	            'item_id' 		    					=> 		 	$this->getIdentifier($item->getProduct()),
				'item_id_numeric' 		    			=>    (int) $item->getProduct()->getId(),
	            'item_name' 							=> 		 	$item->getName(),
	            'affiliation'       					=>          $this->getStoreName(),
	            'price' 	        					=> (float) 	$this->getRevenueProduct($item),
	            'quantity' 	        					=> (int) 	$item->getQtyOrdered(),
	            'category'	        					=> 		 	$this->getCategory($category),
	            'item_list_id'      					=>          $this->getCategoryList($category),
	            'item_list_name'    					=>          $this->getCategoryList($category),
	            'item_brand'							=> 		 	$this->getBrand($item->getProduct()),
	            'item_reviews_count'  					=>          $this->getReviewsCount($item->getProduct()),
	            'item_rating_summary' 					=>          $this->getRatingSummary($item->getProduct()),
				'google_business_vertical' 				=> 		    $this->getGoogleBusinessVertical(),
				'remarketingFacebookId' 				=> 		    $this->getFacebookRemarketingId($item->getProduct()),
				'remarketingAdwordsId' 					=> 		    $this->getAdwordsRemarketingId($item->getProduct()),
	            $this->getStockDimensionIndex(true) 	=>          $this->getStock($item->getProduct())
	        ]);

			if ($this->supportAutomatedDiscounts())
			{
				$node['discount'] = $this->getDiscount($item);
			}
		    
		    /**
		     * Add custom attributes
		     *
		     * @var array $custom_attributes
		     */
		    $node += $this->getDataLayerAttributesArray($item->getProduct()->getSku());

		    $data = new \Magento\Framework\DataObject($node);
		    
		    if (\Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE == $item->getProduct()->getTypeId())
		    {
		        $variant = [];
		        
		        /**
		         * Get buy request
		         *
		         * @var []
		         */
		        $buyRequest = $item->getProductOptionByCode('info_buyRequest');
		        
		        /**
		         * Check if buy request is set
		         */
		        if ($buyRequest)
		        {
		            /**
		             * Get info buy request
		             *
		             * @var \Magento\Framework\DataObject
		             */
		            $info = new \Magento\Framework\DataObject($buyRequest);
		        }
		        else
		        {
		            /**
		             * Try to obtain buy request as custom option
		             *
		             * @var []
		             */
		            $buyRequest = $item->getProduct()->getCustomOption('info_buyRequest');
		            
		            if (isset($buyRequest['value']))
		            {
		                $value = unserialize($buyRequest['value']);
		                
		                $info = new \Magento\Framework\DataObject($value);
		            }
		            else
		            {
		                $info = new \Magento\Framework\DataObject([]);
		            }
		        }
		        
		        /**
		         * Construct variant
		         */
		        foreach ($info->getSuperAttribute() as $id => $option)
		        {
		            /**
		             * Load attribute (cached)
		             *
		             * @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute
		             */
		            $attribute = $this->getCachedAttribute((int) $id);
		            
		            if ($attribute->usesSource())
		            {
		                $name = $this->getAttributeLabel($attribute);
		                $text = $attribute->getSource()->getOptionText($option);
		                
		                if ($this->useDefaultValues())
		                {
		                    /**
		                     * Get current store
		                     *
		                     * @var int
		                     */
		                    $currentStore = $attribute->getSource()->getAttribute()->getStoreId();
		                    
		                    /**
		                     * Change default store
		                     */
		                    $attribute->getSource()->getAttribute()->setStoreId(0);
		                    
		                    /**
		                     * Get text
		                     *
		                     * @var string
		                     */
		                    $text = $attribute->getSource()->getOptionText($option);
		                    
		                    /**
		                     * Restore store
		                     */
		                    $attribute->getSource()->getAttribute()->setStoreId($currentStore);
		                }
		                
		                $variant[] = join(self::VARIANT_DELIMITER_ATT, array($name, $text));
		            }
		        }
		        
		        
		        if (!$this->useSimples())
		        {
		            $data->setId
		            (
		                $this->getIdentifier($item->getProduct())
	                );
		            
		            $data->setName
		            (
		                $item->getProduct()->getName()
	                );
		            
		            if($item->getHasChildren())
		            {
		                if ($item->getChildrenItems())
		                {
    		                foreach($item->getChildrenItems() as $child)
    		                {
    		                    $data->setVariantId
    		                    (
    		                        $this->getIdentifier($child)
    		                    );
    		                }
		                }
		                else 
		                {
		                    $child = $item->getOptionByCode('simple_product');
		                    
		                    if ($child && $child->getId())
		                    {
		                        $data->setVariantId
		                        (
		                            $this->getIdentifier($child->getProduct())
	                            );
		                    }
		                }
		            }
		        }
		        else
		        {
		            if($item->getHasChildren())
		            {
		                if ($item->getChildrenItems())
		                {
    		                foreach($item->getChildrenItems() as $child)
    		                {
    		                    $data->setItemId
    		                    (
    		                        $this->getIdentifier($child)
		                        );
    		                    
    		                    $data->setName
    		                    (
    		                        $child->getName()
    	                        );
    		                    
    		                    $data->setVariantId
    		                    (
    		                        $this->getIdentifier($child)
    		                    );
    		                }
		                }
		                else 
		                {
		                    $child = $item->getOptionByCode('simple_product');
		                    
		                    if ($child && $child->getId())
		                    {
		                        $data->setItemId
		                        (
		                            $this->getIdentifier($child)
	                            );
		                        
		                        $data->setName
		                        (
		                            $child->getProduct()->getName()
	                            );
		                        
		                        $data->setVariantId
		                        (
		                            $this->getIdentifier($child->getProduct())
	                            );
		                    }
		                }
		            }
		        }
		        
		        /**
		         * Push variant to data
		         *
		         * @var array
		         */
		        $data->setItemVariant(join(self::VARIANT_DELIMITER, $variant));
		    }
		    
		    if (\Magento\Bundle\Model\Product\Type::TYPE_CODE == $item->getProduct()->getTypeId())
		    {
		        $bundles = [];
		        
		        /**
		         * Get bundle 
		         * 
		         * @var \Magento\Catalog\Model\Product $bundle
		         */
		        $bundle = $item->getProduct();
		        
		        
		        /**
		         * Get buy request 
		         * 
		         * @var array
		         */
		        $buyRequest = $item->getProductOptionByCode('info_buyRequest');
		          
		        if ($buyRequest && isset($buyRequest['bundle_option']))
		        {
		            $options = [];
		            
		            foreach ($buyRequest['bundle_option'] as $option => $selections)
		            {
		                $collection = $bundle->getTypeInstance(true)->getSelectionsCollection($option,$bundle);
		                
		                foreach ($collection as $id => $entity)
		                {
		                    if (in_array($id, (array) $selections))
		                    {
		                        $bundles[] = 
		                        [
		                            'ids'  => $entity->getSku(),
		                            'name' => $entity->getName()
		                        ];
		                    }
		                }
		            }  
		        }

		        if ($bundles)
		        {
		            $response['ecommerce']['purchase']['bundles'] = $bundles;
		            
		            $data->setBundles($bundles);
		        }
		    }

		    /**
		     * Track custom options
		     */
		    
		    try
		    {
		        /**
		         * Custom options tracking
		         *
		         * @var array $options
		         */
		        $options = $item->getProductOptions();
		        
		        if ($options && is_array($options))
		        {
		            if (isset($options['options']))
		            {
		                $selection = [];
		                
		                foreach ($options['options'] as $option)
		                {
		                    $selection[] = join(self::VARIANT_DELIMITER_ATT, [$option['label'], $option['value']]);
		                }
		                
		                if ($selection)
		                {
		                    $variant = (string) $data->getVariant();
		                    
		                    $variant .= join(self::VARIANT_DELIMITER, $selection);
		                    
		                    $data->setItemVariant($variant);
		                }
		            }
		        }
		        
		        /**
		         * Additional options tracking (added via additional_options[])
		         */
		        if (isset($options['additional_options']))
		        {
		            $additional_options = $options['additional_options'];
		            
		            $selection = [];
		            
		            foreach ($additional_options as $option)
		            {
		                $selection[] = join(self::VARIANT_DELIMITER_ATT, [$option['label'], $option['value']]);
		            }
		            
		            if ($selection)
		            {
		                $variant = (string) $data->getVariant();
		                
		                $variant .= join(self::VARIANT_DELIMITER, $selection);
		                
		                $data->setItemVariant($variant);
		            }
		        }
		    }
		    catch (\Exception $e){}
		    
		    /**
		     * Track product specific coupon
		     */
		    
		    if (null !== $item->getAppliedRuleIds())
		    {
		        /**
		         * Get applied rules
		         *
		         * @var array $rules
		         */
		        $rules = explode(chr(44), (string) $item->getAppliedRuleIds());
		        
		        /**
		         * Add coupon parameter if any applied rules.
		         *
		         * By default Magento 2.x does not support multiple coupon codes applied at once so order coupon code should match product coupon code
		         *
		         * @todo Implement multiple coupon codes supported by 3rd parties
		         */
		        if ($rules)
		        {
		            $data->setCoupon(strtoupper((string) $order->getCouponCode()));
		        }
		    }
		    
		    /**
		     * Create transport object
		     *
		     * @var \Magento\Framework\DataObject $transport
		     */
		    $transport = new \Magento\Framework\DataObject
		    (
		        [
		            'product' 	 => $data,
		            'quote_item' => $item
		        ]
	        );
		    
		    /**
		     * Notify others
		     */
		    $this->eventManager->dispatch('ec_order_products_product_get_after', ['transport' => $transport]);
		    
		    /**
		     * Get product
		     */
		    $data = $transport->getProduct();
		    
		    /**
		     * Add product
		     */
		    $products[] = $data->getData();
		}
		
		/**
		 * Create transport object
		 *
		 * @var \Magento\Framework\DataObject $transport
		 */
		$transport = new \Magento\Framework\DataObject
		(
		    [
		        'products' => $products
		    ]
	    );
		
		/**
		 * Notify others
		 */
		$this->eventManager->dispatch('ec_order_products_get_after', ['transport' => $transport]);
		
		/**
		 * Get products
		 */
		$products = $transport->getProducts();
		
		/**
		 * Set products
		 */
		$response['ecommerce']['purchase']['items'] = $products;
		
		/**
		 * Set items array
		 */
		$response['ecommerce']['items'] = $products;
		
		/**
		 * Create transport object
		 *
		 * @var \Magento\Framework\DataObject $transport
		 */
		$transport = new \Magento\Framework\DataObject
		(
		    [
		        'attributes' => $this->attributes->getAttributes(),
		        'products'   => $products
		    ]
	    );
		
		/**
		 * Notify others
		 */
		$this->eventManager->dispatch('ec_get_purchase_attributes', ['transport' => $transport]);
		
		/**
		 * Get response
		 */
		$attributes = $transport->getAttributes();
		
		foreach ($response['ecommerce']['purchase']['items'] as &$product)
		{
		    foreach ($attributes as $key => $value)
		    {
		        $product[$key] = $value;
		    }
		}
		
		unset($product);
		
		$response['currentStore'] = $this->getStoreName();
		
		
		/**
		 * Create transport object
		 *
		 * @var \Magento\Framework\DataObject $transport
		 */
		$transport = new \Magento\Framework\DataObject
		(
		    [
		        'response' => $response, 
		        'order'    => $order
		    ]
	    );
		
		/**
		 * Notify others
		 */
		$this->eventManager->dispatch('ec_get_purchase_push_after', ['transport' => $transport]);
		
		/**
		 * Get response
		 */
		$response = $transport->getResponse();
		
		return $response;
	}
	
	/**
	 * Get purchase google tag params 
	 * 
	 * @param \Magento\Framework\View\Element\Template $block
	 * @return StdClass
	 */
	public function getPurchaseGoogleTagParams($block)
	{
		$google_tag_params = (object) 
		[
			'ecomm_prodid' 			=> [],
			'ecomm_pvalue' 			=> [],
			'ecomm_pname' 			=> [],
			'ecomm_totalvalue' 		=> 0
		];
		
		foreach ($this->getOrders($block) as $order)
		{
			foreach ($order->getAllVisibleItems() as $item)
			{
				$data = new \Magento\Framework\DataObject(array
				(
					'id'  			=> $this->escape($item->getSku()),
					'name' 			=> $this->escape($item->getName()),
					'price' 		=> $item->getPrice(),
					'ecomm_prodid' 	=> $this->getAdwordsEcommProdId($item)
				));
				
				/**
				 * Change values if configurable
				 */
				if (\Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE == $item->getProduct()->getTypeId())
				{
					if (!$this->useSimples())
					{
						$data->setId
						(
							$this->escape($item->getProduct()->getSku())
						);
						
						$data->setName
						(
							$this->escape($item->getProduct()->getName())
						);
						
						$data->setEcommProdid
						(
							$this->escape
							(
								$this->getAdwordsEcommProdId
								(
									$item->getProduct()
								)
							)
						);
					}
					else 
					{
						if($item->getHasChildren()) 
						{
						    if ($item->getChildrenItems())
						    {
    							foreach($item->getChildrenItems() as $child) 
    							{
    								$data->setEcommProdid
    								(
    									$this->escape
    									(
    										$this->getAdwordsEcommProdId
    										(
    											$child
    										)
    									)
    								);
    							}
						    }
						    else 
						    {
						        $child = $item->getOptionByCode('simple_product');
						        
						        if ($child && $child->getId())
						        {
						            $data->setEcommProdid
						            (
						                $this->escape
						                (
						                    $this->getAdwordsEcommProdId
						                    (
						                        $child
					                        )
					                    )
					                );
						        }
						    }
						}
					}
				}
				
				$google_tag_params->ecomm_prodid[] 		= 		  $data->getEcommProdid();
				$google_tag_params->ecomm_pvalue[] 		= (float) $data->getPrice();
				$google_tag_params->ecomm_pname[] 		= 		  $data->getName();
			}
			
			/**
			 * Set total value
			 */
			$google_tag_params->ecomm_totalvalue += (float) $this->getRevenueAdWords($order);
		}
		
		return $google_tag_params;
	}
	
	/**
	 * Get orders
	 * 
	 * @param \Magento\Framework\View\Element\Template $block
	 */
	public function getOrders($block)
	{
		return $this->getOrdersCollection
		(
			(array) $block->getOrderIds()
		);
	}
	
	/**
	 * Get orders collection 
	 * 
	 * @param array $order_ids
	 * @return array
	 */
	public function getOrdersCollection(array $order_ids = [])
	{
		if (!$this->_orders)
		{
			if (!$order_ids)
			{
				$this->_orders = [];
			}
			else 			
			{
				$collection = $this->getSalesOrderCollection()->create();
				
				/**
				 * Filter applicable order ids
				 */
				$collection->addFieldToFilter('entity_id', ['in' => $order_ids]);
				
				foreach ($collection as $order)
				{
					if ($order->getPayment())
					{
						/**
						 * Get filter-out method 
						 * 
						 * @var [] $filter
						 */
						$filter = $this->getOrderFilterOutMethods();
						
						/**
						 * Get order payment method 
						 * 
						 * @var string $method
						 */
						$method = $order->getPayment()->getMethod();
						
						if (!in_array($method, $filter))
						{
							$this->_orders[] = $order;
						}
					}
					else 
					{
						$this->_orders[] = $order;
					}	
				}
			}
		}
		
		return $this->_orders;
	}

	/**
	 * Get visitor push
	 * 
	 * @param Magento\Framework\View\Element\AbstractBlock|null $block
	 * 
	 * @return [type]
	 */
	public function getVisitorPush(?\Magento\Framework\View\Element\AbstractBlock $block = null)
	{
		/**
		 * Get customer group ($this->orderConfig->getVisibleOnFrontStatuses())
		 */
		$type = $this->getPageType();
		
		/**
		 * Default payload
		 */
		$data = array
		(
			'pageType' 					=> $type,
			'pageName'					=> $this->utils->getPageName(),
			'websiteCountry'			=> $this->getCountryByWebsite(),
			'websiteLanguage'  			=> $this->getLanguageByWebsite(),
			'visitorLoginState' 		=> $this->isLogged() ? __('Logged in') : __('Logged out'),
			'visitorLifetimeValue' 		=> 0,
			'visitorExistingCustomer' 	=> __('No'),
			'clientStatus'				=> __('Not client')
		);

		if (Constants::PAGETYPE_CATEGORY === $type)
		{
			if (isset($_SERVER['REQUEST_URI']))
			{
				foreach(array_values(array_filter(explode(chr(47), $_SERVER['REQUEST_URI']))) as $level => $value)
				{
					switch($level)
					{
						case Constants::LEVEL_TOP_CATEGORY:

							$data['page_top_category'] = trim(htmlspecialchars($value, ENT_QUOTES, 'UTF-8'));

							break;
						case Constants::LEVEL_MID_CATEGORY:

							$data['page_mid_category'] = trim(htmlspecialchars($value, ENT_QUOTES, 'UTF-8'));

							break;

						default:

							$data['page_' . $level . '_category'] = trim(htmlspecialchars($value, ENT_QUOTES, 'UTF-8'));

							break;
					}
				}
			}
		}
		
		if ($this->isLogged())
		{
			$data['user_id'] = $data['visitorId'] = (int) $this->getCustomer()->getId();

			$data['hashed_email'] = hash('sha256', $this->getCustomer()->getEmail());
			
			/**
			 * Get statuses 
			 * 
			 * @var array $statuses
			 */
			$statuses = [\Magento\Sales\Model\Order::STATE_COMPLETE,\Magento\Sales\Model\Order::STATE_CLOSED,\Magento\Sales\Model\Order::STATE_PROCESSING];
			
			/**
			 * Get customer order(s)
			 * 
			 * @var \Magento\Sales\Model\ResourceModel\Collection\AbstractCollection
			 */
			$orders = $this->salesOrderCollection->create()->addFieldToSelect('*')->addFieldToFilter('customer_id', $this->getCustomer()->getId())->addFieldToFilter('status',['in' => $statuses])->setOrder('created_at','desc');
			
			$total = 0;
			
			foreach ($orders as $order)
			{
				$total += $order->getGrandTotal();
			}
	
			$data['visitorLifetimeValue'] = $total;
			
			if ($total > 0)
			{
				 $data['visitorExistingCustomer'] = __('Yes');

				 $data['clientStatus'] = __('Client');
				
				/**
				 * Returning customer 
				 * 
				 * @var \Anowave\Ec\Helper\Data $returnCustomer
				 */
				$this->returnCustomer = true;
			}
			
			$group = $this->groupRegistry->retrieve
			(
				$this->getCustomer()->getGroupId()
			);

			/**
			 * Push visitor group
			 */
			$data['visitorType'] = $group->getCustomerGroupCode();
			
			/**
			 * Push number of orders
			 */
			$data['visitorLifetimeOrders'] = (int) $orders->getSize();
		}
		else 
		{
			$group = $this->groupRegistry->retrieve(0);
			
			$data['visitorType'] = $group->getCustomerGroupCode();
		}

		if ($this->isLogged())
		{
			$admin_id = $this->utils->getAdminId($this->isLogged());

			if ($admin_id)
			{
				$data['adminId'] = $admin_id;
			}
		}

		$data['currentStore'] = $this->getStoreName();
		
		/**
		 * Create transport object
		 *
		 * @var \Magento\Framework\DataObject $transport
		 */
		$transport = new \Magento\Framework\DataObject
		(
		    [
		        'visitor' => $data
		    ]
		);
		
		/**
		 * Notify others
		 */
		$this->getEventManager()->dispatch('ec_get_visitor_data', ['transport' => $transport]);
		
		$data = $transport->getVisitor();
		
		return $this->getJsonHelper()->encode($data);
	}

	/**
	 * Get Facebook Pixel Product View content 
	 * 
	 * @param \Magento\Catalog\Model\Product $product
	 * @param \Magento\Catalog\Model\Category $category
	 * @return []
	 */
	public function getFacebookViewContentTrack(\Magento\Catalog\Model\Product $product, \Magento\Catalog\Model\Category $category)
	{
		return 
		[
			'content_type' 		=> 'product',
			'content_name' 		=> $product->getName(),
			'content_category' 	=> $this->getCategory($category),
			'content_ids' 		=> $this->getFacebookRemarketingId($product),
			'currency' 			=> $this->getCurrency(),
			'value' 			=> $this->getPrice($product)
		];
	}

	/**
	 * Use Facebook Pixel tracking
	 */
	public function facebook()
	{
		return 1 === (int) $this->getConfig('ec/facebook/active');
	}
	
	/**
	 * Get facebook pixel tracking code 
	 * 
	 * @return string
	 */
	public function getFacebookPixelCode() : string
	{
		if ($this->facebook())
		{
			return (string) $this->getConfig('ec/facebook/facebook_pixel_code');
		}
		else 
		{
			return '';
		}
	}

	/**
	 * Get facebook pixel trigger event
	 * 
	 * @return string
	 */
	public function getFacebookCookieTriggerEvent() : string 
	{
	    $event = (string) $this->getConfig('ec/facebook/facebook_consent_trigger_event');
	    
	    if ($event)
	    {
	        return $event;
	    }
	        
	    return Constants::COOKIE_CONSENT_GRANTED_EVENT;
	}

	/**
	 * Get plain pixel code
	 * 
	 * @param string $pixel
	 * 
	 * @return [type]
	 */
	private function getPlainCode(string $pixel = '')
	{
		/**
	     * Plain snippet code
	     *
	     * @var string $plain
	     */
	    $plain = '';
	    
	    /**
	     * Create DOMDocument instance
	     * 
	     * @var \DOMDocument $dom
	     */
	    $dom = new \Anowave\Ec\Model\Dom('1.0','utf-8');
	    
	    if (!$pixel)
	    {
	        return $plain;
	    }
	    
	    /**
	     * Load Facebook Pixel code
	     */
	    @$dom->loadHTML($pixel);
	   
	    foreach($dom->getElementsByTagName('script') as $script)
	    {
	        $plain = trim($script->textContent);
	    }
	    
	    return $plain;
	}
	
	/**
	 * Get Facebook Pixel code
	 * 
	 * @return string
	 */
	public function getFacebookPixelCodePlain() : string
	{
		return $this->getPlainCode
		(
			$this->getFacebookPixelCode()
		);
	}

	/**
	 * Get Facebook Pixel code
	 * 
	 * @return string
	 */
	public function getTikTokPixelCodePlain() : string
	{
		return $this->getPlainCode
		(
			$this->getTikTokPixel()
		);
	}

	
	/**
	 * Get Facebook Pixel Advanced Matching Parameters 
	 * 
	 * @return string
	 */
	public function getFacebookAdvancedMatchingParameters()
	{
		/**
		 * Default parameters 
		 * 
		 * @var array $params
		 */
		$params = [];
		
		if ($this->facebook() && $this->isLogged())
		{
			$params['em'] = md5($this->getCustomer()->getEmail());
			$params['fn'] = $this->getCustomer()->getFirstname();
			$params['ln'] = $this->getCustomer()->getLastname();
			
			switch ((int) $this->getCustomer()->getGender())
			{
				case 1: $params['ge'] = 'm';
					break;
				case 2: $params['ge'] = 'f';
					break;
				default: 
					$params['ge'] = null;
					break;
			}
			
			if ($this->getCustomer()->getDob())
			{
				$params['db'] = $this->getCustomer()->getDob();
			}
		}
		
		return $params;
	}
	
	/**
	 * Get customer
	 */
	public function getCustomer()
	{ 
		if (!$this->customer)
		{
			if ($this->currentContext->getCurrentCustomer()->get() > 0)
			{
				$this->customer = $this->customerRepositoryInterface->getById
				(
					$this->currentContext->getCurrentCustomer()->get()
				);
			}
			else if($this->session->getCustomerId())
			{
			    $this->customer = $this->session->getCustomer();
			}
		}
	
		return $this->customer;
	}
	
	/**
	 * Get customer email 
	 * 
	 * @param \Magento\Sales\Model\Order $order
	 * @return string
	 */
	public function getCustomerEmail(\Magento\Sales\Model\Order $order) : string
	{
	    return $order->getCustomerEmail();
	}
	
	/**
	 * Get current visitor id
	 * 
	 * @return number
	 */
	public function getVisitorId()
	{
		if ($this->isLogged())
		{
			return (int) $this->getCustomer()->getId();
		}
		else 
		{
			return 0;
		}
	}
	
	/**
	 * Get Super Attributes
	 */
	public function getSuper()
	{
		$super = [];
		
		if ($this->getCurrentProduct()->getId())
		{
			$product = $this->getCurrentProduct();
			
			if (\Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE == $product->getTypeId())
			{
				$attributes = $product->getTypeInstance(true)->getConfigurableAttributes($product);
								 	
			 	foreach($attributes as $attribute)
			 	{
			 		$object = $attribute->getProductAttribute();
			 		
			 		$super[] = array
			 		(
			 			'id' 				=> $object->getAttributeId(),
			 			'label' 			=> $this->getAttributeLabel($object),
			 			'code'				=> $object->getAttributeCode(),
			 			'options'			=> $this->getAttributeOptions($attribute)
			 		);
			 	}
			}
		}

		return $super;
	}
	
	/**
	 * Get configurable simple products 
	 * 
	 * @return string
	 */
	public function getConfigurableSimples() : array
	{
		$simples = [];
		
		if ($this->getCurrentProduct()->getId())
		{
			/**
			 * Get current product 
			 * 
			 * @var Ambiguous $product
			 */
			$product = $this->getCurrentProduct();
			
			if (\Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE == $product->getTypeId())
			{
			    $attributes = $product->getTypeInstance()->getConfigurableAttributes($product);
			    
				foreach ($product->getTypeInstance()->getUsedProducts($product) as $simple)
				{
					$simples[$simple->getId()] = 
					[
						'id' 		 	=> $this->getIdentifier($simple),
						'parent'	 	=> $this->getIdentifier($product),
						'name' 		 	=> $simple->getName(),
						'parent_name' 	=> $product->getName(),
						'price'		 	=> $simple->getPrice(),
					    'price_tier' 	=> $simple->getTierPrice()
					];
					
					$configurations = [];
					
					foreach ($attributes as $attribute)
					{
						$label = $simple->getAttributeText
						(
							$attribute->getProductAttribute()->getAttributeCode()
						);

						$title = $attribute->getProductAttribute()->getFrontendLabel();

					    $configurations[] = 
					    [
					        'value' 	=> $attribute->getAttributeId(),
					        'label' 	=> $label,
							'title' 	=> $title,
							'variant' 	=> join(static::VARIANT_DELIMITER_ATT, 
							[
								$title,
								$label
							])
					    ];
					}
					
					$simples[$simple->getId()]['configurations'] = $configurations;
				}
			}
		}

		return $simples;
	}
	
	/**
	 * Get bundle items
	 */
	public function getBundle() : array
	{
		$bundles = [];
		$options = [];
		
		if ($this->getCurrentProduct()->getId())
		{
			$product = $this->getCurrentProduct();

			if (\Magento\Bundle\Model\Product\Type::TYPE_CODE === $product->getTypeId())
			{
				foreach ($product->getTypeInstance(true)->getSelectionsCollection($product->getTypeInstance(true)->getOptionsIds($product),$product) as $bundle) 
				{
					$bundles[$bundle->getOptionId()][$bundle->getId()] = 
					[
						'id' 		=> $this->getIdentifier($bundle),
						'name' 		=> $bundle->getName(),
						'price'		=> $bundle->getPrice(),
						'quantity' 	=> $bundle->getSelectionQty(),
					];
				}

				foreach ($product->getTypeInstance(true)->getOptionsCollection($product) as $option) 
				{
					$options[$option->getOptionId()] = 
					[
						'option_title' => $option->getDefaultTitle(),
						'option_type'  => $option->getType()
						
					];
				}
			}
		}
		
		return 		
		[
			'bundles' => $bundles,
			'options' => $options
		];
	}
	/**
	 * Get attribute by ID with in-memory cache (avoids N+1 loads)
	 *
	 * @param int $attributeId
	 * @return \Magento\Catalog\Model\ResourceModel\Eav\Attribute
	 */
	public function getCachedAttribute(int $attributeId): \Magento\Catalog\Model\ResourceModel\Eav\Attribute
	{
		if (!isset($this->attributeCache[$attributeId])) {
			$this->attributeCache[$attributeId] = $this->attribute->create()->load($attributeId);
		}

		return $this->attributeCache[$attributeId];
	}

	/**
	 * Get attribute label 
	 * 
	 * @param \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute
	 */
	public function getAttributeLabel(\Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute)
	{
		return ($this->useDefaultValues() ? $attribute->getFrontendLabel() : $attribute->getStoreLabel());
	}
	
	/**
	 * Get attribute options
	 * 
	 * @param Object $attribute
	 */
	protected function getAttributeOptions($attribute)
	{
		$options = [];
		
		foreach ($attribute->getOptions() as $option)
		{
			$options[] = $option;
		}
			
		if ($this->useDefaultValues())
		{
			try 
			{
				foreach ($options as &$option)
				{
					$this->optionCollection->clear();
					$this->optionCollection->getSelect()->reset('where');
					$this->optionCollection->getSelect()->where('main_table.option_id IN (?)',[$option['value_index']]);
					$this->optionCollection->getSelect()->group('main_table.option_id');
					
					/**
					 * Set admin label
					 *
					 * @var string
					*/
					$option['admin_label'] = $this->optionCollection->getFirstitem()->getValue();
				}
				
				unset($option);
			}
			catch (\Exception $e)
			{
				return [];
			}
		}
			
		return $options;
	}
	
	/**
	 * Get quote item price 
	 * 
	 * @param \Magento\Quote\Model\Quote\Item $item
	 * @return float
	 */
	public function getPriceItem(\Magento\Quote\Model\Quote\Item $item) : float
	{
	    /**
	     * Default price 
	     * 
	     * @var integer $price
	     */
	    $price = 0;
	    
	    /**
	     * Get tax display type configuration
	     *
	     * @var int $display
	     */
	    $tax = $this->getPriceRenderTaxType();
	    
	    switch ($tax)
	    {
	        case TaxItem::INCL_TAX: $price = (float) $item->getPriceInclTax(); break;
	        case TaxItem::EXCL_TAX: $price = (float) $item->getPrice(); break;
	    }

	    return $this->getAmount((float) $price);
	}
	
	/**
	 * Get final price of product 
	 * 
	 * @param \Magento\Catalog\Model\Product $product
	 */
	public function getPrice(\Magento\Catalog\Model\Product $product)
	{
	    /**
	     * Get tax display type configuration
	     * 
	     * @var int $display
	     */
	    $tax = $this->getPriceRenderTaxType();
	    
		/**
		 * Get final price
		 *
		 * @var float
		 */
	    
		switch ($product->getTypeId())
		{
			case \Magento\GroupedProduct\Model\Product\Type\Grouped::TYPE_CODE:
				
			    $products = $product->getTypeInstance()->getAssociatedProducts($product);
			    
			    if ($products)
			    {
			        $minimal = $this->renderPrice($products[0], $tax);
			        			        
			        foreach ($products as $entity)
			        {
			            $price = $this->renderPrice($entity, $tax);

			            if ($price < $minimal)
			            {
			                $minimal = $price;
			            }
			        }
			        
			        $price = round($minimal,2, PHP_ROUND_HALF_UP);
			    }
			    else
			    {
			        $price = 0;
			    }

			    break;
				
			case \Magento\Bundle\Model\Product\Type::TYPE_CODE:
				
			    switch ($tax)
			    {
			        case TaxItem::INCL_TAX: $price = (float) $product->getPriceInfo()->getPrice(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE)->getMinimalPrice()->getValue(); break;
			        case TaxItem::EXCL_TAX: $price = (float) $product->getPriceInfo()->getPrice(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE)->getMinimalPrice()->getBaseAmount(); break;
			    }

				break;
				
			default: 
			    
			    $price = $this->renderPrice($product, $tax);

				break;
				
		}
		
		/**
		 * Allow others to modify price
		 */
		$this->eventManager->dispatch('catalog_product_get_final_price', ['product' => $product, 'qty' => 1]);

		/**
		 * Convert price to desired currency
		 */
		$price = $this->getAmount((float) $price);

		return round($price,2);
	}
	
	/**
	 * Render price 
	 * 
	 * @param \Magento\Catalog\Model\AbstractModel $entity
	 * @param int $tax
	 * @return float
	 */
	private function renderPrice(\Magento\Catalog\Model\AbstractModel $entity, int $tax = 0) : float
	{
	    switch ($tax)
	    {
	        case TaxItem::INCL_TAX: return $entity->getPriceInfo()->getPrice(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE)->getAmount()->getValue();
	        case TaxItem::EXCL_TAX: return $entity->getPriceInfo()->getPrice(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE)->getAmount()->getBaseAmount();
	    }
	    
	    return $entity->getPriceInfo()->getPrice(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE)->getAmount()->getValue();
	}
	
	/**
	 * Get stock status 
	 * 
	 * @param \Magento\Catalog\Model\Product $product
	 */
	public function getStock(\Magento\Catalog\Model\Product $product)
	{
		return $this->stock->isSaleable($product) ?  __('In stock') : __('Out of stock');
	}
	
	/**
	 * Assign category 
	 * 
	 * @param \Magento\Catalog\Model\Category $category
	 * @param array $item
	 * @return array
	 */
	public function assignCategory(\Magento\Catalog\Model\Category $category, array $item = []) : array
	{
	    if ($category)
	    {
	        $indexes = explode(Constants::CATEGORY_SEPARATOR, $this->getCategory($category));
	        
	        if ($indexes)
	        {
	            $item['item_category'] = array_shift($indexes);
	        }
	        
	        $index = 2;
	        
	        while($indexes)
	        {
	            /**
	             * Add category index
	             */
	            $item["item_category{$index}"] = array_shift($indexes);
	            
	            /**
	             * Increment index
	             */
	            $index++;
	        }
	    }

	    return $item;
	}

	public function getOptimizeCategoryAccess() : int 
	{
		if (is_null($this->optimize_category_access))
		{
			$this->optimize_category_access = (int) $this->getConfig('ec/selectors/optimize_category_access');
		}
		
		return $this->optimize_category_access;
	}
	/**
	 * Get category object 
	 * 
	 * @param int $id
	 * 
	 * @return \Magento\Catalog\Model\Category
	 */
	public function getCategoryObject(int $id = 0) : \Magento\Catalog\Model\Category
	{
		if ($this->getOptimizeCategoryAccess())
		{
			$tree = $this->utils->getTree();

			if (array_key_exists($id, $tree))
			{
				return $tree[$id];
			}
		}

		return $this->categoryRepository->get($id);
	}
	
	/**
	 * Get category 
	 * 
	 * @param \Magento\Catalog\Model\Category $category
	 */
	public function getCategory(\Magento\Catalog\Model\Category $category)
	{
		if (0 !== (int) $this->getConfig('ec/options/use_segments'))
		{
			return $this->getCategorySegments($category);
		}
		
		return $category->getName();
	}
	
	/**
	 * Get detail list (correlates with category)
	 * 
	 * @param \Magento\Catalog\Model\Product $product
	 * @param \Magento\Catalog\Model\Category $category
	 * 
	 * @return string
	 */
	public function getCategoryDetailList(\Magento\Catalog\Model\Product $product, \Magento\Catalog\Model\Category $category)
	{
		return $category->getName();
	}
	
	/**
	 * Get category list name
	 * 
	 * @param \Magento\Catalog\Model\Category $category
	 */
	public function getCategoryList(\Magento\Catalog\Model\Category $category)
	{
		return $category->getName();
	}
	
	/**
	 * Retrieve category and it's parents separated by chr(47)
	 *
	 * @param Mage_Catalog_Model_Category $category
	 * @return string
	 */
	public function getCategorySegments(\Magento\Catalog\Model\Category $category)
	{
		if ($category->getSegments())
		{
			return $category->getSegments();
		}
		
		$segments = [];
	
		foreach ($category->getParentCategories() as $parent)
		{
			$segments[] = $parent->getName();
		}
	
		if (!$segments)
		{
			$segments[] = $category->getName();
		}
	
		return trim(join(chr(47), $segments));
	}

	public function getQuoteReviews() : array
	{
		if (!$this->reviews)
		{
			$this->reviews = $this->utils->getReviews($this->checkoutSession->getQuote());
		}

		return $this->reviews;
	}
	
	/**
	 * Count product reviews 
	 * 
	 * @param \Magento\Catalog\Model\Product $product
	 * @return int
	 */
	public function getReviewsCount(\Magento\Catalog\Model\Product $product) : int
	{
		$reviews = $this->getQuoteReviews();

		if (array_key_exists($product->getId(), $reviews))
		{
			return $reviews[$product->getId()];
		}
		else 
		{
			$this->reviews[$product->getId()] = (int) $this->reviewFactory->create()->getResourceCollection()->addStoreFilter($this->getStore()->getId())->addStatusFilter(\Magento\Review\Model\Review::STATUS_APPROVED)->addEntityFilter('product', $product->getId())->getSize();

			return $this->reviews[$product->getId()];
		}

		return 0;
	}
	
	
	/**
	 * Get rating summary 
	 * 
	 * @return float
	 */
	public function getRatingSummary(\Magento\Catalog\Model\Product $product) : float
	{
		return $this->utils->getRating($product);
	}

	/**
	 * Get item discount 
	 * 
	 * @param \Magento\Sales\Model\Order\Item $item
	 * 
	 * @return float
	 */
	public function getDiscount(\Magento\Sales\Model\Order\Item $item) : float
	{
		return (float) $item->getDiscountAmount();
	}
	
	/**
	 * Get product brand 
	 * 
	 * @param \Magento\Catalog\Model\Product $product
	 */
	public function getBrand(\Magento\Catalog\Model\Product $product)
	{
		if (array_key_exists((int) $product->getId(), $this->_brandMap))
		{
			return $this->_brandMap[$product->getId()];
		}

		switch ($product->getTypeId())
		{
			case \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE:
			case \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL: 
			case \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE:
			case \Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE:
				
				$attributes = array_filter([$this->getConfig('ec/options/use_brand_attribute')]);
				
				if (!$attributes)
				{
					$attributes = ['manufacturer'];
				}
				
				foreach ($attributes as $code)
				{
					$attribute = $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $code);
					
					if ($attribute->getId() && $attribute->usesSource())
					{
						$brand = $product->getAttributeText($code);
						
						if (is_array($brand))
						{
						    $brand = join($this->getBrandDelimueter(), $brand);
						}
						else
						{
						    $brand = (string) $brand;
						}

						if (!$brand)
						{
							/**
							 * Get value 
							 * 
							 * @var string $value
							 */
							$value = (int) $product->getResource()->getAttributeRawValue($product->getId(), $code, $this->getStore()->getId());
							
							if ($value > 0)
							{
								/**
								 * Get text representation
								 */
								$brand = $attribute->getSource()->getOptionText($value);
							}
						}
						
						$this->_brandMap[(int) $product->getId()] = $brand;
						
						return $brand;
					}
					else 
					{
						/**
						 * Static brands
						 */
						
						$brand = $product->getResource()->getAttributeRawValue($product->getId(), $code, $this->getStore()->getId());
						
						if ($brand)
						{
							$this->_brandMap[(int) $product->getId()] = $brand;
							
							return $brand;
						}
					}
				}
				break;
		}
		
		/**
		 * Return empty brand
		 */
		return '';
	}
	
	/**
	 * Get Facebook value key
	 */
	public function getFacebookValueKey() : string
	{
		$key = $this->getConfig('ec/facebook/facebook_value');
		
		if (!in_array($key, array('revenue','subtotal')))
		{
			$key = \Anowave\Ec\Model\System\Config\Source\Value::KEY_REVENUE;
		}
		
		return $key;
	}

	/**
	 * Get Facebook value key
	 */
	public function getTikTokValueKey() : string
	{
		$key = $this->getConfig('ec/tiktok/tiktok_value');
		
		if (!in_array($key, array('revenue','subtotal')))
		{
			$key = \Anowave\Ec\Model\System\Config\Source\Value::KEY_REVENUE;
		}
		
		return $key;
	}
	
	/**
	 * Get current store
	 */
	public function getStore()
	{
		return $this->storeManager->getStore();
	}
	
	/**
	 * Set store name
	 */
	public function getStoreName()
	{
		return $this->getStore()->getName();
	}
	
	/**
	 * Get current currency 
	 * 
	 * @return string
	 */
	public function getCurrency() : string
	{
		if ($this->convert_currency)
		{
			return $this->convert_currency;
		}

		return $this->getCurrentStoreCurrency();
	}

	

	/**
	 * Get transactions currency 
	 * 
	 * @return string
	 */
	public function getCurrencyConvert() : string 
	{
		return (string) $this->getConfig(Constants::CONFIG_PATH_CURRENCY_CONVERT);
	}
	
	/**
	 * Get current store currency 
	 * 
	 * @return string
	 */
	public function getCurrentStoreCurrency() : string
	{
		if (!$this->currency)
		{
			$this->currency = $this->getStore()->getCurrentCurrencyCode();
		}

		return $this->currency;
	}

	/**
	 * Lazy load GTM snippet 
	 * 
	 * @return bool
	 */
	public function useLazyload() : bool
	{
		/**
		 * Lazy load cannot happen if cookie directive is enabled
		 */
		if ($this->supportCookieDirective())
		{
			return false;
		}
		
		return 1 === (int) $this->getConfig('ec/general/lazyload');
	}

	/**
	 * Get body snippet
	 * 
	 * @return String
	 */
	public function getBodySnippet()
	{
		return $this->getConfig('ec/general/code_body');
	}
	
	/**
	 * Get head snippet
	 * 
	 * @return string
	 */
	public function getHeadSnippet() : string
	{
		return $this->utils->alterSnippet
		(
			(string) $this->getConfig('ec/general/code_head'), $this->generateNonce(), $this->supportCookieDirective(), $this->useLazyload()
		);
	}
	
	/**
	 * Get nonce 
	 * 
	 * @return string
	 */
	public function generateNonce() : string 
	{
	    return $this->nonceProvider->generateNonce();
	}
	
	/**
	 * Get Google Optimize Page Hiding Snippet
	 * 
	 * @return mixed
	 */
	public function getGoogleOptimizePageHidingSnippet()
	{
		return $this->getConfig('ec/optimize/use_optimize_page_hiding_snippet');
	}

	/**
	 * Check if Google Ads API is enabled
	 * 
	 * @return bool
	 */
	public function isGoogleAdsApiEnabled(): bool
	{
		return 1 === (int) $this->getConfig('ec/adwords/google_ads_api');
	}
	
	/**
	 * Get Google Ads Customer Account Id
	 * 
	 * @return string
	 */
	public function getGoogleAdsApiCustomerId() : string
	{
	    return (string) $this->getConfig('ec/adwords/customer_id');
	}

	/**
	 * Get Google Ads Customer Manager Account Id
	 * 
	 * @return string
	 */
	public function getGoogleAdsApiCustomerManagerId() : string
	{
	    return (string) $this->getConfig('ec/adwords/customer_manager_id');
	}

	/**
	 * Get Google Ads Conversion Action Id
	 * 
	 * @return string
	 */
	public function getGoogleAdsApiConversionActionId() : string
	{
	    return (string) $this->getConfig('ec/adwords/conversion_action_id');
	}

	/**
	 * Get Aw Merchant Id 
	 * 
	 * @return string
	 */
	public function getGoogleAdsApiDeveloperToken() : string
	{
	    return (string) $this->getConfig('ec/adwords/developer_token');
	}

	/**
	 * Get Aw Merchant Id 
	 * 
	 * @return string
	 */
	public function getGoogleAdsAwMerchantId() : string
	{
	    return (string) $this->getConfig('ec/adwords/aw_merchant_id');
	}
	
	/**
	 * Get Aw Feed Country
	 *
	 * @return string
	 */
	public function getGoogleAdsAwFeedCountry() : string
	{
	    return (string) $this->getConfig('ec/adwords/aw_feed_country');
	}
	
	/**
	 * Get Aw Feed Language
	 *
	 * @return string
	 */
	public function getGoogleAdsAwFeedLanguage() : string
	{
	    return (string) $this->getConfig('ec/adwords/aw_feed_language');
	}
	
	/**
	 * Get Google Ads Dynamic Remarketing trigger segment event
	 *
	 * @return string
	 */
	public function getGoogleAdsRemarketingTriggerEvent() : string
	{
	    $event = (string) $this->getConfig('ec/adwords/remarketing_consent_trigger_event');
	    
	    if ($event)
	    {
	        return $event;
	    }
	    
	    return Constants::COOKIE_CONSENT_GRANTED_EVENT;
	}
	
	/**
	 * Check for standalone optimize implementation
	 * 
	 * @return boolean
	 */
	public function getGoogleOptimizeIsStandalone()
	{
		/**
		 * Check Optimize Container Id
		 */
		if ('' === $this->getGoogleOptimizeContainerId())
		{
			return false;
		}
		
		/**
		 * Check Google Analytics Id
		 */
		if ('' === $this->getGoogleOptimizeAnalyticsId())
		{
			return false;
		}
		
		return \Anowave\Ec\Model\System\Config\Source\Optimize\Implementation::I_STANDALONE === (int) $this->getConfig('ec/optimize/implementation');
	}
	
	/**
	 * Check for assisted optimize implementation
	 * 
	 * @return boolean
	 */
	public function getGoogleOptimizeIsAssisted()
	{
		return \Anowave\Ec\Model\System\Config\Source\Optimize\Implementation::I_ASSISTED === (int) $this->getConfig('ec/optimize/implementation');
	}
	
	/**
	 * Get Google Optimize Container ID
	 *
	 * @return mixed
	 */
	public function getGoogleOptimizeContainerId()
	{
		return trim((string) $this->getConfig('ec/optimize/use_optimize_container_id'));
	}
	
	/**
	 * Google Google Optimize Universal Analytics ID
	 * 
	 * @return string
	 */
	public function getGoogleOptimizeAnalyticsId()
	{
		return trim((string) $this->getConfig('ec/general/account'));
	}
	

	/**
	 * Dequeue event
	 * 
	 * @param string $event
	 * 
	 * @return string | bool
	 */
	public function dequeueEvent(string $event = '')
	{
		$payload = $this->session->getData($event);

		if ($payload)
		{
			$this->session->unsetData($event);
			
			return $payload;
		}

		return false;
	}

	/**
	 * Get Facebook Events 
	 * 
	 * @return array
	 */
	public function getFacebookEvents()
	{
		$events = [];
		
		/**
		 * Get complete registration event 
		 */
		if (false != $event = $this->getFacebookCompleteRegistrationEvent())
		{
			$events['CompleteRegistration'] = $event;
		}
		
		return array_filter($events);
	}
	
	/**
	 * Check if contact form has been submitted
	 *
	 * @return JSON|boolean
	 */
	public function getFacebookCompleteRegistrationEvent()
	{
		$event = $this->session->getFacebookCompleteRegistrationEvent();
		
		if ($event)
		{
			$this->session->unsetData('facebook_complete_registration_event');
			
			return $event;
		}
		
		return false;
	}
	
	public function getStoreRootDefaultCategoryId()
	{
		$roots = $this->getAllStoreRootCategories();
		
		if ($roots)
		{
			return (int) reset($roots);
			
		}
		return null;
	}
	
	
	/**
	 * Get root category id
	 *
	 * @param unknown $store
	 * @throws \Exception
	 */
	public function getStoreRootCategoryId($store)
	{
		if (is_int($store))
		{
			$store = $this->storeManager->getStore($store);
		}
		
		if (is_string($store))
		{
			foreach ($this->storeManager->getStores() as $model)
			{
				if ($model->getCode() == $store)
				{
					$store = $model;
					
					break;
				}
			}
		}
		
		if ($store instanceof \Magento\Store\Model\Store)
		{
			return $store->getRootCategoryId();
		}
		
		throw new \Exception("Store $store does not exist anymore");
	}
	
	/**
	 * Get page type 
	 * 
	 * @return string
	 */
	public function getPageType() : string
	{
	    /**
	     * Set default type 
	     * 
	     * @var string $type
	     */
	    $type = Constants::PAGETYPE_OTHER;
	    
	    /**
	     * Check if current page is homepage
	     */
	    if ($this->getIsHomePage())
	    {
	        $type = Constants::PAGETYPE_HOME;
	    }
		
	    /**
	     * Check if current page is category page
	     */
	    if ($this->getCurrentCategory()->getId() || 'category' === $this->request->getControllerName())
	    {
	        $type = Constants::PAGETYPE_CATEGORY;
	    }

	    /**
	     * Check if current page is product page 
	     */
	    if ($this->getCurrentProduct()->getId() || 'product' === $this->request->getControllerName())
	    {
	        $type = Constants::PAGETYPE_PRODUCT;
	    }

	    /**
	     * Check if current page is search results
	     */
	    if ('result' === $this->request->getControllerName())
	    {
	        $type = Constants::PAGETYPE_SEARCH_RESULTS;
	    }
	    
	    if ('cart' === $this->request->getControllerName())
	    {
	        $type = Constants::PAGETYPE_CART;
	    }
	    
	    if ('checkout' === $this->request->getModuleName() && ('index' === $this->request->getControllerName() && 'index' === $this->request->getActionName()))
	    {
	        $type = Constants::PAGETYPE_CHECKOUT;
	    }

		if ('paypal' === $this->request->getModuleName() && ('express' === $this->request->getControllerName() && 'review' === $this->request->getActionName()))
	    {
	        $type = Constants::PAGETYPE_CHECKOUT;
	    }

		if ('hyva_checkout' === $this->request->getModuleName() && ('index' === $this->request->getControllerName() && 'index' === $this->request->getActionName()))
		{
			$type = Constants::PAGETYPE_CHECKOUT;
		}
	    
	    if ('onepage' === $this->request->getControllerName() && 'success' === $this->request->getActionName())
	    {
	        $type = Constants::PAGETYPE_PURCHASE;
	    }

	    if (function_exists('wp'))
	    {
	        return Constants::PAGETYPE_WORDPRESS;
	    }
	    
	    return $type;
	}
	
	/**
	 * Check if homepage 
	 * 
	 * @return bool
	 */
	public function getIsHomePage() : bool
	{
	    return $this->urlInt->getUrl('') === $this->urlInt->getUrl('*/*/*', ['_current'=>true, '_use_rewrite'=>true]);
	}
	
	
	/**
	 * Get an associative array of [store_id => root_category_id] values for all stores
	 * 
	 * @return array
	 */
	public function getAllStoreRootCategories()
	{
		$roots = [];
		
		foreach ($this->storeManager->getStores() as $store)
		{
			if ($store->getId() == $this->getStore()->getId())
			{
				$roots[$store->getId()] = $store->getRootCategoryId();
			}
		}
		
		return $roots;
	}

	/**
	 * Check if module is active
	 * 
	 * @return boolean
	 */
	public function isActive() : bool
	{
	    if ($this->isDisabledByIp())
	    {
	        return false;
	    }

		return 0 !== (int) $this->getConfig('ec/general/active');
	}
	
	
	/**
	 * Check if customer is logged in
	 */
	public function isLogged()
	{
	    /**
	     * Simulate non logged user for cache warmers
	     */
	    if ($this->isAuthDisabledByIp())
	    {
	        return false;
	    }
	    
	    if ($this->httpContext->getValue(\Magento\Customer\Model\Context::CONTEXT_AUTH))
	    {
	        return true;
	    }
	    else if($this->session->isLoggedIn())
	    {
	        return true;
	    }
	    
	    return false;
	}
	
	/**
	 * Disable by IP 
	 * 
	 * @return bool
	 */
	public function isDisabledByIp() : bool
	{
	    return $this->isIpMatch('ec/general/disable_by_ip');
	}
	
	/**
	 * Disable non-id by IP
	 *
	 * @return bool
	 */
	public function isAuthDisabledByIp() : bool
	{
	    return $this->isIpMatch('ec/general/disable_by_ip_auth');
	}
	
	/**
	 * Check if config IP list matches current IP 
	 * 
	 * @param string $config
	 * @return bool
	 */
	public function isIpMatch($config) : bool
	{
	    $ips = (string) $this->getConfig($config);
	    
	    if ($ips)
	    {
	        if (!(php_sapi_name() == 'cli'))
	        {
	            $ips = array_filter(explode(PHP_EOL, trim($ips)));
	            $ips = array_map(function($ip)
	            {
	                return trim($ip);
	                
	            }, $ips);
	            
	            if ($ips && $_SERVER)
	            {
	                if (in_array($_SERVER['REMOTE_ADDR'], $ips))
	                {
	                    return true;
	                }
	            }
	            
	        }
	    }
	    
	    return false;
	}
	
	/**
	 * Check if AdWords Conversion Tracking is active and can be triggered (with consent check)
	 * 
	 * @param bool $consent
	 * @return boolean
	 */
	public function isAdwordsConversionTrackingActive($consent = false)
	{
		return 1 === (int) $this->getConfig('ec/adwords/conversion');
		
		/**
		 * Check for consent
		 */
		if ($consent && $this->supportCookieDirective())
		{
			if ($this->isCookieConsentAccepted())
			{
				return $active;
			}

			return false;
		}

		return $active;
	}
	
	/**
	 * Check if Google Customer Reviews is enabled
	 * 
	 * @return boolean
	 */
	public function isCustomerReviewsActive() : bool
	{
	    return 1 === (int) $this->getConfig('ec/customer_reviews/enable');
	}
	
	/**
	 * Add GTIN to customer reviews 
	 * 
	 * @return bool
	 */
	public function isCustomerReviewsGTIN() : bool
	{
	    return 1 === (int) $this->getConfig('ec/customer_reviews/use_gtin');
	}
	
	/**
	 * Get GTIN attribute
	 *
	 * @return string
	 */
	public function getCustomerReviewsGTINAttribute()
	{
	    $attribute = (string) $this->getConfig('ec/customer_reviews/gtin');
	    
	    return $attribute ? $attribute : null;
	}

	/**
	 * Check if using GTAG implementation 
	 * 
	 * @return boolean
	 */
	public function useAdwordsConversionTrackingStandard() : bool
	{
		return \Anowave\Ec\Model\System\Config\Source\AdWords\Implementation::I_STANDARD === (int) $this->getConfig('ec/adwords/gtag');
	}
	
	/**
	 * Check if using GTAG implementation 
	 * 
	 * @return boolean
	 */
	public function useAdwordsConversionTrackingGtag() : bool
	{
		return \Anowave\Ec\Model\System\Config\Source\AdWords\Implementation::I_GTAG === (int) $this->getConfig('ec/adwords/gtag');
	}

	/**
	 * Check if using Google Ads API implementation 
	 * 
	 * @return boolean
	 */
	public function useAdwordsGoogleAdsApiAdjustment() : bool
	{
		return 1 == (int) $this->getConfig('ec/adwords/allow_enhanced_conversions_adjustments');
	}
	
	public function usePrivateFallback()
	{
		return 1 === (int) $this->getConfig('ec/options/use_private_fallback');
	}
	
	/**
	 * Remove confirmation
	 * 
	 * @return string
	 */
	public function useRemoveConfirm()
	{
		return $this->getUseRemoveConfirm();
	}

	/**
	 * Add related[] key to view_item
	 * 
	 * @return bool
	 */
	public function useAddRelated() : bool
	{
		return 1 === (int) $this->getConfig('ec/options/add_related');
	}

	/**
	 * Add upsells[] key to view_item
	 * 
	 * @return bool
	 */
	public function useAddUpsells() : bool
	{
		return 1 === (int) $this->getConfig('ec/options/add_upsells');
	}

	 /**
     * Enable auto tagging
     * 
     * @return bool
     */
    public function useAutoTagging() : bool
    {
        return 1 === (int) $this->getConfig('ec/cookie/auto_tag');
    }

	/**
     * Get cookie blocking strategy 
     * 
     * @return int
     */
    public function getCookieBlockingStrategy() : int
    {
        return 1 === (int) $this->getConfig('ec/cookie/strategy');
    }
	
	
    /**
     * Get logo margin
     * 
     * @return int
     */
    public function getLogoMargin(array $logos = []) : int
    {
        return $logos ? 100 : 0;
    }

	/**
     * Set asses mode 
     * 
     * @return bool
     */
    public function setAssesMode() : bool
    {
        return 1 === (int) $this->getConfig('ec/cookie/asses');
    }

    /**
     * Get logos
     * 
     * @return array
     */
    public function getLogos() : array
    {
        $logos = [];

        $logos['img'] = $this->getConfig('ec/cookie/logo_img');
        $logos['svg'] = $this->getConfig('ec/cookie/logo_svg');

        return array_filter($logos);
        
    }

	/**
	 * Get localStorage flag
	 * 
	 * @return string
	 */
	public function useLocalStorage()
	{
		return $this->getJsonHelper()->encode($this->getUseLocalStorage());
	}
	
	/**
	 * Use measurement protocol to track admin/offline orders
	 * @return bool
	 */
	public function useMeasurementProtocol() : bool
	{
	    return 1 === (int) $this->helper->getConfig('ec/gmp/use_measurement_protocol');
	}
	
	/**
	 * Enable server-side tracking for orders on success page
	 * 
	 * @return bool
	 */
	public function useMeasurementProtocolOnly() : bool
	{
	    return \Anowave\Ec\Model\System\Config\Source\Server::TRACK_SERVER_SIDE_ENABLED_ON_SUCCESS === (int) $this->getConfig('ec/gmp/use_measurement_protocol_only');
	}
	
	
	/**
	 * Enable server-side tracking for orders when they get placed
	 * 
	 * @return bool
	 */
	public function useMeasurementProtocolOnlyPlaced() : bool
	{
	    return \Anowave\Ec\Model\System\Config\Source\Server::TRACK_SERVER_SIDE_ENABLED_ON_PLACED === (int) $this->getConfig('ec/gmp/use_measurement_protocol_only');
	}
	
	/**
	 * Use only measurement protocol for tracking transactions
	 *
	 * @return bool
	 */
	public function useMeasurementProtocolOnlyKeepEvent() : bool
	{
	    return 1 === (int) $this->getConfig('ec/gmp/use_measurement_protocol_only_keep_event');
	}
	
	/**
	 * Use only measurement protocol for tracking transactions
	 *
	 * @return bool
	 */
	public function useMeasurementProtocolOnlyKeepEnhancedConversionData() : bool
	{
	    return 1 === (int) $this->getConfig('ec/gmp/use_measurement_protocol_only_keep_enhanced_conversion_data');
	}
	
	/**
	 * Use only measurement protocol for tracking cancelled orders
	 *
	 * @return bool
	 */
	public function useMeasurementProtocolCancel() : bool
	{
	    return 1 === (int) $this->getConfig('ec/gmp/use_measurement_protocol_cancel');
	}
	
	/**
	 * Use only measurement protocol for tracking refunds
	 *
	 * @return bool
	 */
	public function useMeasurementProtocolRefund() : bool
	{
	    return 1 === (int) $this->getConfig('ec/gmp/use_measurement_protocol_refund');
	}
	
	/**
	 * Local storage 
	 * 
	 * @return boolean
	 */
	public function getUseLocalStorage()
	{
		return 1 === (int) $this->getConfig('ec/options/use_local_storage');
	}
		
	/**
	 * Remove confirmation
	 *
	 * @return string
	 */
	public function getUseRemoveConfirm()
	{
		return 1 === (int) $this->getConfig('ec/options/use_remove_confirm');
	}
	
	/**
	 * Get brand delimiter 
	 * 
	 * @return string
	 */
	public function getBrandDelimueter() : string
	{
	    $delimiter = $this->getConfig('ec/options/use_brand_attribute_delimiter');
	    
	    if (is_null($delimiter))
	    {
	        $delimiter = chr(44);
	    }
	    
	    return $delimiter;
	}
	
	/**
	 * Get Gtag site tag
	 *
	 * @return string
	 */
	public function getAdwordsConversionTrackingGtagSiteTag()
	{
		return $this->utils->alterGtagSnippet((string) $this->getConfig('ec/adwords/gtag_global_site_tag'), $this->generateNonce());
	}
	
	/**
	 * Gtag "send to" parameter
	 * 
	 * @return string
	 */
	public function getAdwordsConversionTrackingGtagSendToParameter()
	{
		return (string) $this->getConfig('ec/adwords/gtag_send_to');
	}
	
	/**
	 * Get conversion event name
	 * 
	 * @return string
	 */
	public function getAdwordsConversionTrackingGtagConvesionEventName() : string
	{
	    return (string) $this->getConfig('ec/adwords/gtag_conversion_event');
	}
	
	
	/**
	 * Get AdWords Conversion Tracking conversion event JSON 
	 * 
	 * @param OrderInterface $order
	 * @return string
	 */
	public function getAdwordsConversionTrackingGtagConvesionEvent(OrderInterface $order)
	{
		/**
		 * Get revenue 
		 */
		$revenue = $this->getRevenue($order);

		$conversion = 
		[
			'send_to' 			=> $this->getAdwordsConversionTrackingGtagSendToParameter(),
			'value' 			=> $this->getRevenue($order),
			'currency' 			=> $this->getCurrency(),
			'transaction_id' 	=> $order->getIncrementId(),
		    'new_customer'      => $this->getIsNewCustomer()
		];

		return $this->getJsonHelper()->encode($conversion,JSON_UNESCAPED_SLASHES);
	}
	
	/**
	 * Get strategy 
	 * 
	 * @return int
	 */
	public function getGoogleAdsStrategy() : int 
	{
		return (int) $this->getConfig('ec/adwords/strategy');
	}

	/**
	 * Get attribute used for POAS profit calculation 
	 * 
	 * @return string
	 */
	public function getGoogleAdsPoasAttribute() : string 
	{
		return (string) $this->getConfig('ec/adwords/poas_attribute');
	}

	/**
	 * Add profit attribute to purchase payload 
	 * 
	 * @return bool
	 */
	public function getAddPoasProfit() : bool 
	{
		return 1 === (int) $this->getConfig('ec/adwords/poas_add_profit');
	}

	/**
	 * Get enhanced conversions variable 
	 * 
	 * @param OrderInterface $order
	 * @return string
	 */
	public function getEnhancedConversionVariable(OrderInterface $order)
	{
	    $variable = [];
	    
	    if ($this->supportEnhancedConversions())
	    {
	        try 
	        {
    	        $shippingAddress = $order->getShippingAddress();
    	        
    	        if ($shippingAddress)
    	        {
    	            $variable['email']         = $this->normalizeEmail($shippingAddress->getEmail());
    	            $variable['phone_number']  = $shippingAddress->getTelephone();
    	            $variable['first_name']    = $shippingAddress->getFirstname();
    	            $variable['last_name']     = $shippingAddress->getLastname();
    	            $variable['street']        = is_array($shippingAddress->getStreet()) ? $shippingAddress->getStreet()[0] : $shippingAddress->getStreet();
    	            $variable['city']          = $shippingAddress->getCity();
    	            $variable['region']        = $shippingAddress->getRegionCode();
    	            $variable['country']       = $shippingAddress->getCountryId();
    	            $variable['postal_code']   = $shippingAddress->getPostcode();
    	        }
	        }
	        catch (\Exception $e)
	        {
	            $variable['error'] = __('Shipping address cannot be found');
	        }
	    }

	    return $variable;
	}

	/**
	 * Normalize Gmail accounts, leave rest as is
	 * 
	 * @param string $email
	 * 
	 * @return string
	 */
	function normalizeEmail(string $email = '') : string 
	{
		list($account, $provider) = explode('@', trim($email));

		if (in_array($provider, ['gmail.com','googlemail.com']))
		{
			$account = join('', explode('.', $account));
		}

		$account = explode('+', $account)[0] ?? $account;

		return join('@',[$account, $provider]);
	}

	/**
	 * Get initial binding parameters 
	 * 
	 * @return string
	 */
	public function getInitialBinding()
	{
		return $this->jsonHelper->encode
		(
			[
				'performance' => $this->supportPerformance()	
			]
		);
	}
	
	/**
	 * Use default admin labels for product variants
	 * 
	 * @return boolean	
	 */
	public function useDefaultValues()
	{
		return 1 === (int) $this->getConfig('ec/options/use_skip_translate');
	}
	
	/**
	 * Use simple SKU(s) instead of configurable parent SKU. Applicable for configurable products only.
	 * 
	 * @return boolean
	 */
	public function useSimples() : bool
	{
		return 1 === (int) $this->getConfig('ec/options/use_simples');
	}

	/**
	 * Check if the module should fire view_item event
	 * 
	 * @return bool
	 */
	public function useGroupViewEvent() : bool
	{
		$config = (int) $this->getConfig('ec/options/use_group_view_item_event');

		return 1 === $config;
	}

	/**
	 * Fire view_item event for grouped associated products 
	 * 
	 * @return bool
	 */
	public function useGroupAssociatedViewEvent() : bool
	{
		return 1 === (int) $this->getConfig('ec/options/use_group_associated_view_item_event');
	}

	/**
	 * Use variant sku instead of configurable in product detail
	 * 
	 * @return bool
	 */
	public function useVariantSku() : bool
	{
	    return 1 === (int) $this->getConfig('ec/options/use_variant_sku');
	}
	
	/**
	 * Use placeholders
	 *
	 * @return boolean
	 */
	public function usePlaceholders()
	{
		return 1 === (int) $this->getConfig('ec/selectors/beta_placeholders');
	}
	
	/**
	 * Enable debug mode
	 * 
	 * @return bool
	 */
	public function useDebugMode() : bool
	{
	    return 1 === (int) $this->getConfig('ec/selectors/debug');
	}
	
	/**
	 * Optimize and render scripts in HTML
	 * 
	 * @return bool
	 */
	public function useOptimize() : bool
	{
	    return 1 === (int) $this->getConfig('ec/selectors/optimize');
	}
	
	/**
	 * Compress scripts
	 *
	 * @return bool
	 */
	public function useOptimizeCompress() : bool
	{
	    return 1 === (int) $this->getConfig('ec/selectors/optimize_compress');
	}

	/**
	 * Use partytown 
	 * 
	 * @return bool
	 */
	public function useWorker() : bool
	{
		return 1 === (int) $this->getConfig('ec/general/worker');
	}

	/**
	 * Pre-render impression payload model 
	 * 
	 * @return boolean
	 */
	public function usePreRenderImpressionPayloadModel()
	{
	    return \Anowave\Ec\Model\System\Config\Source\PayloadModel\Impression::MODEL_PRE_RENDER === (int) $this->getImpressionPayloadModel();
	}
	
	/**
	 * Post-render impression payload model
	 *
	 * @return boolean
	 */
	public function usePostRenderImpressionPayloadModel()
	{
	    return \Anowave\Ec\Model\System\Config\Source\PayloadModel\Impression::MODEL_POST_RENDER === (int) $this->getImpressionPayloadModel();
	}
	
	/**
	 * Use summary callback 
	 * 
	 * @return bool
	 */
	public function useSummary() : bool 
	{
	    return 1 === (int) $this->getConfig('ec/options/use_summary');
	}
	
	/**
	 * Reset dataLayer[] object before each new push
	 * 
	 * @return bool
	 */
	public function useReset() : bool
	{
	    return 1 === (int) $this->getConfig('ec/options/use_reset');
	}

	/**
	 * Check if emails should be hased
	 * 
	 * @return bool
	 */
	public function useEmailHash() : bool
	{
		return 1 === (int) $this->getConfig('ec/options/hash_email');
	}

	/**
	 * Hash email 
	 * 
	 * @param string $email
	 * 
	 * @return string
	 */
	public function hashEmail(string $email) : string
	{
		return hash('sha256', strtolower($email));
	}
	
	/**
	 * Performance API support
	 *
	 * @return boolean
	 */
	public function supportPerformance()
	{
		return 1 === (int) $this->getConfig('ec/performance/enable');
	}
	
	/**
	 * Check for AMP support
	 * 
	 * @return boolean
	 */
	public function supportAmp()
	{
		return 1 === (int) $this->getConfig('ec/amp/enable');
	}
	
	/**
	 * Support extended AdWords Dynamic Remarketing (for other sites)
	 * 
	 * @return boolean
	 */
	public function supportDynx()
	{
		return 1 === (int) $this->getConfig('ec/adwords/dynx');
	}
	
	/**
	 * Support AdWords Enhanced Conversions 
	 * 
	 * @return bool
	 */
	public function supportEnhancedConversions() : bool
	{
	    return 1 === (int) $this->getConfig('ec/adwords/allow_enhanced_conversions');
	}
	
	/**
	 * Support Automated Discounts
	 *
	 * @return bool
	 */
	public function supportAutomatedDiscounts() : bool
	{
	    return 1 === (int) $this->getConfig('ec/adwords/automated_discounts');
	}
	
	/**
	 * Support Facebook Conversions API 
	 * 
	 * @return bool
	 */
	public function supportFacebookConversionsApi() : bool
	{
	    return 1 === (int) $this->getConfig('ec/facebook/facebook_conversions_api');
	}

	/**
	 * Check support for preconnect tags
	 * 
	 * @return bool
	 */
	public function supportPreconnect() : bool
	{
		return 1 === (int) $this->getConfig('ec/general/preconnect');
	}
	
	/**
	 * Add support for internal search
	 * 
	 * @return boolean
	 */
	public function supportInternalSearch() : bool
	{
		return 1 === (int) $this->getConfig('ec/search/enable');
	}
	
	/**
	 * Check if cookie directive support is enabled 
	 * 
	 * @return boolean
	 */
	public function supportCookieDirective() : bool
	{
		return 1 === $this->supportCookieDirectiveEngine();
	}

	/**
	 * Get cookie consent engine 
	 * 
	 * @return int
	 */
	public function supportCookieDirectiveEngine() : int
	{
		return (int) $this->getConfig('ec/cookie/enable');
	}
	
	/**
	 * Preselect GDPR choices 
	 * 
	 * @return bool
	 */
	public function getSegmentCheckall() : bool
	{
	    return 1 === (int) $this->getConfig('ec/cookie/mode_segment_checkall');
	}
	
	/**
	 * Check if cookie directive support is enabled
	 *
	 * @return boolean
	 */
	public function getKeepCookieWidget() : int
	{
	    return (int) $this->getConfig('ec/cookie/widget');
	}

	public function getConsentScheme() : int
	{
	    return (int) $this->getConfig('ec/cookie/scheme');
	}

	/**
	 * Check if opt-in scheme
	 * 
	 * @return bool
	 */
	public function isOptInScheme() : bool
	{
		return \Anowave\Ec\Model\System\Config\Source\Consent\Scheme::OPT_IN === $this->getConsentScheme();
	}

	/**
	 * Check if opt-put scheme
	 * 
	 * @return bool
	 */
	public function isOptOutScheme() : bool
	{
		return !$this->isOptInScheme();
	}
	
	/**
	 * Get cookie widget gradient color start
	 *
	 * @return boolean
	 */
	public function getKeepCookieWidgetColor() : string
	{
	    return (string) $this->getConfig('ec/cookie/widget_color');
	}
	
	/**
	 * Get cookie widget gradient color end
	 *
	 * @return boolean
	 */
	public function getKeepCookieWidgetColorEnd() : string
	{
	    return (string) $this->getConfig('ec/cookie/widget_color_end');
	}
	
	/**
	 * Hide cookie widget
	 * 
	 * @return bool
	 */
	public function hideCookieWidget() : bool
	{
	    return \Anowave\Ec\Model\System\Config\Source\Consent\Widget::KEEP_HIDE === $this->getKeepCookieWidget();
	}
	
	/**
	 * Get cookie widget options
	 * 
	 * @return array
	 */
	public function getCookieWidgetOptions() : array
	{
	    return 
	    [
	        'display'  => $this->getKeepCookieWidget(),
	        'color'    => $this->getKeepCookieWidgetColor(),
	        'colorEnd' => $this->getKeepCookieWidgetColorEnd()
	    ];
	}

	/**
	 * Get acquire cookies option
	 * 
	 * @return bool
	 */
	public function getCookieAcquire() : bool 
	{
		return 1 === (int) (int) $this->getConfig('ec/cookie/acquire');
	}

	/**
	 * Display detailed cookie information
	 * 
	 * @return bool
	 */
	public function getDisplayCookies() : bool
	{
		return 1 === (int) $this->getConfig('ec/cookie/display_cookies');
	}
	
	/**
	 * Get built-in Google Consent
	 * 
	 * @return string
	 */
	public function getGoogleBuiltinConsent() : string
	{
	    return json_encode(
	    [
	        'ad_storage'               => 'denied',
	        'analytics_storage'        => 'denied',
	        'functionality_storage'    => 'denied',
	        'personalization_storage'  => 'denied',
	        'security_storage'         => 'denied',
	        'ad_user_data'             => 'denied',
	        'ad_personalization'       => 'denied',
	        'wait_for_update'          => 500
	    ]);
	}
	/**
	 * Check if cookie consent is accepted 
	 * 
	 * @return boolean
	 */
	public function isCookieConsentAccepted()
	{
		if (1 === (int) $this->directive->get())
		{
			return true;
		}
		
		return false;
	}
	

	/**
	 * Get JSON key
	 * 
	 * @return string
	 */
	public function getJsonKey() : string
	{
		$key = $this->getKey();

		if ($key)
		{
			return $this->filesystem->getDirectoryWrite(DirectoryList::VAR_DIR)->getAbsolutePath(Key::UPLOAD_DIR . DIRECTORY_SEPARATOR . $key);
		}

		return '';
	}

	/**
	 * Get JSON key content 
	 * 
	 * @return string
	 */
	public function getJsonKeyContent() : string
	{
		if ($this->jsonKeyContentCache === null) {
			$this->jsonKeyContentCache = \file_get_contents(
				$this->getJsonKey()
			);
		}

		return $this->jsonKeyContentCache;
	}

	/**
	 * Get Google Ads API key
	 * 
	 * @return string
	 */
	public function getKey() : string 
	{
		return (string) $this->getConfig('ec/adwords/key');
	}


	/**
	 * Get Facebook Conversions API Pixel Id
	 *
	 * @return bool
	 */
	public function tiktok() : bool
	{
	    return 1 === (int) $this->getConfig('ec/tiktok/active');
	}

	/**
	 * Get Facebook Conversions API Pixel Id
	 *
	 * @return bool
	 */
	public function getTikTokPixel() : string
	{
	    return $this->utils->alterTikTokSnippet($this->getTikTokPixelCode(), $this->generateNonce(), $this->supportCookieDirective(), $this->useLazyload());
	}

	/**
	 * Get TikTok Pixel code
	 * 
	 * @return string
	 */
	public function getTikTokPixelCode() : string 
	{
		return (string) $this->getConfig('ec/tiktok/tiktok_pixel_code');
	}

	/**
	 * Get facebook pixel trigger event
	 * 
	 * @return string
	 */
	public function getTikTokCookieTriggerEvent() : string 
	{
	    $event = (string) $this->getConfig('ec/tiktok/tiktok_consent_trigger_event');
	    
	    if ($event)
	    {
	        return $event;
	    }
	        
	    return Constants::COOKIE_CONSENT_GRANTED_EVENT;
	}

	/**
	 * Get Facebook Conversions API Pixel Id
	 *
	 * @return bool
	 */
	public function getFacebookConversionsApiPixelId() : string
	{
	    return (string) $this->getConfig('ec/facebook/facebook_conversions_api_pixel_id');
	}
	
	/**
	 * Get Facebook Conversions API Pixel Id
	 *
	 * @return bool
	 */
	public function getFacebookConversionsApiAccessToken() : string
	{
	    return (string) $this->getConfig('ec/facebook/facebook_conversions_api_access_token');
	}
	
	/**
	 * Get Facebook Conversions API test event code
	 * @return string
	 */
	public function getFacebookConversionsApiTestEventCode() : string
	{
	    return (string) $this->getConfig('ec/facebook/facebook_conversions_api_test_event_code');
	}

	/**
	 * Get supported Conversion API events
	 * 
	 * @return array
	 */
	public function getFacebookConversionsApiEvents() : array
	{
	    $events = (string) $this->getConfig('ec/facebook/facebook_conversions_api_events');

		return explode(chr(44), $events);
	}
	
	/**
	 * Get cookie directive content 
	 * 
	 * @return mixed
	 */
	public function getCookieDirectiveContent()
	{
		return $this->getConfig('ec/cookie/content');
	}

	/**
	 * Allow custom CSS for cookie consent
	 * 
	 * @return bool
	 */
	public function getCookieDirectiveContentCssAllow() : bool
	{
		return 1 === (int) $this->getConfig('ec/cookie/content_css_allow');
	}

	/**
	 * Get additional css
	 * 
	 * @return string
	 */
	public function getCookieDirectiveContentCss() : string
	{
		if ($this->getCookieDirectiveContentCssAllow())
		{
			return (string) $this->getConfig('ec/cookie/content_css');
		}

		return '';
	}
	
	/**
	 * Allow dataLayer[] push on cookieConsentDecline, Google Consent Mode signals will still ge set to denied
	 * 
	 * @return bool
	 */
	public function getCookieDirectiveOverrideDecline() : bool
	{
	    return 1 === (int) $this->getConfig('ec/cookie/mode_segment_override_decline');
	}
	
	/**
	 * Get cookie consent mode
	 * 
	 * @return mixed
	 */
	public function getCookieDirectiveConsentMode()
	{
		return $this->getConfig('ec/cookie/mode');
	}
	
	/**
	 * Check if consent mode is segment 
	 * 
	 * @return boolean
	 */
	public function getCookieDirectiveIsSegmentMode() : bool
	{
		return \Anowave\Ec\Model\System\Config\Source\Consent\Mode::SEGMENT === (int) $this->getCookieDirectiveConsentMode();	
	}
	
	/**
	 * Get cookie directive conent segments
	 * 
	 * @return string[]
	 */
	public function getCookieDirectiveConsentSegments()
	{
		if ($this->getCookieDirectiveIsSegmentMode())
		{
			return $this->getCookieDirectiveConsentSegmentsAll();
		}
		
		return 
		[
			Constants::COOKIE_CONSENT_GRANTED_EVENT
		];
	}

	/**
	 * Get all segments 
	 * 
	 * @return array
	 */
	public function getCookieDirectiveConsentSegmentsAll() : array 
	{
		return 
		[
			Constants::COOKIE_CONSENT_GRANTED_EVENT,
			Constants::COOKIE_CONSENT_MARKETING_GRANTED_EVENT,
			Constants::COOKIE_CONSENT_PREFERENCES_GRANTED_EVENT,
			Constants::COOKIE_CONSENT_ANALYTICS_GRANTED_EVENT,
			Constants::COOKIE_CONSENT_AD_USER_DATA_EVENT,
			Constants::COOKIE_CONSENT_AD_PERSONALIZATION_EVENT
		];
	}

	public function getCookieDirectiveOnetrustMap() : array 
	{
		$config = $this->getConfig('ec/cookie/onetrust_map');

		if ($config)
		{
			$config = json_decode($config, true);

			$map = [];

			foreach($config as $value)
			{
				$map[$value['onetrust_group']][] = $value['consent'];
			}

			foreach($map as &$consent)
			{
				if (!in_array(Constants::COOKIE_CONSENT_GRANTED_EVENT, $consent))
				{
					array_unshift($consent, Constants::COOKIE_CONSENT_GRANTED_EVENT);
				}
			}

			unset($consent);
			
		}
		else 
		{
			$map = 
			[
				'C0001' => 
				[
					Constants::COOKIE_CONSENT_GRANTED_EVENT
				],
				'C0002' => 
				[
					Constants::COOKIE_CONSENT_GRANTED_EVENT,
					Constants::COOKIE_CONSENT_ANALYTICS_GRANTED_EVENT
				],
				'C0003' => 
				[
					Constants::COOKIE_CONSENT_GRANTED_EVENT,
					Constants::COOKIE_CONSENT_PREFERENCES_GRANTED_EVENT
				],
				[
					Constants::COOKIE_CONSENT_GRANTED_EVENT,
					Constants::COOKIE_CONSENT_AD_USER_DATA_EVENT,
					Constants::COOKIE_CONSENT_AD_PERSONALIZATION_EVENT,
					Constants::COOKIE_CONSENT_MARKETING_GRANTED_EVENT
				]
			];
		}
		
		return $map;
	}
	
	/**
	 * Get cookie directive background color
	 * 
	 * @return mixed
	 */
	public function getCookieDirectiveBackgroundColor()
	{
		return $this->getConfig('ec/cookie/content_background_color');
	}
	
	/**
	 * Get cookie directive background color
	 *
	 * @return mixed
	 */
	public function getCookieDirectiveCloseIconBackgroundColor()
	{
	    return $this->getConfig('ec/cookie/content_close_icon_background_color');
	}
	
	/**
	 * Get customize link color 
	 * 
	 * @return mixed
	 */
	public function getCookieDirectiveCustomizeLinkColor()
	{
	    return $this->getConfig('ec/cookie/content_text_customize_color');
	}
	
	/**
	 * Get border radius 
	 * 
	 * @return string
	 */
	public function getCookieDirectiveRadius() : string
	{
	    return sprintf('%dpx', (int) $this->getConfig('ec/cookie/content_border_radius'));
	}

	/**
	 * Get filter-out payment methods
	 * 
	 * @return []
	 */
	public function getOrderFilterOutMethods()
	{
		$methods = (string) $this->getConfig('ec/options/use_disable_payment_method_tracking');
		
		return array_filter(explode(chr(44), $methods));
	}
	
	/**
	 * Get cookie directive text color
	 *
	 * @return mixed
	 */
	public function getCookieDirectiveTextColor()
	{
		return $this->getConfig('ec/cookie/content_text_color');
	}
	
	/**
	 * Get cookie directive accept link color
	 *
	 * @return mixed
	 */
	public function getCookieDirectiveTextAcceptColor()
	{
		return $this->getConfig('ec/cookie/content_accept_color');
	}
	
	/**
	 * Get cookie directive accept link color
	 *
	 * @return mixed
	 */
	public function getCookieDirectiveCheckboxColor()
	{
	    return $this->getConfig('ec/cookie/content_checkbox_color');
	}
	
	/**
	 * Show decline button 
	 * 
	 * @return bool
	 */
	public function getCookieDirectiveShowDeclineButton() : bool
	{
	    return 1 === (int) $this->getConfig('ec/cookie/show_decline_button');
	}
	
	/**
	 * Get payload model 
	 * 
	 * @return integer
	 */
	public function getImpressionPayloadModel()
	{
	    return (int) $this->getConfig('ec/options/impression_payload_model');
	}
	
	/**
	 * Get price render tax type
	 * 
	 * @return int
	 */
	public function getPriceRenderTaxType() : int
	{
	    return (int) $this->getConfig('ec/tax/payload_product');
	}

	public function getValueRenderTaxType() : int
	{
	    return (int) $this->getConfig('ec/tax/payload_value');
	}
	
	/**
	 * Get internal search dimensions
	 * 
	 * @return number
	 */
	public function getInternalSearchDimension()
	{
		$dimension = (int) $this->getConfig('ec/search/dimension');
		
		if (!$dimension)
		{
			$dimension = Constants::INTERNAL_SEARCH_DEFAULT_DIMENSION;
		}
		
		return $dimension;
	}
	
	/**
	 * Get default stock dimension index
	 * 
	 * @return number
	 */
	public function getStockDimensionIndex($key = false)
	{
		$dimension = (int) $this->getConfig('ec/dimensions/stock');
		
		if (!$dimension)
		{
			$dimension = Constants::INTERNAL_STOCK_DEFAULT_DIMENSION;
		}
		
		/**
		 * Return dimension as dimension[index] pair
		 */
		if ($key)
		{
			return "dimension{$dimension}";
		}
		
		return $dimension;
	}

	/**
	 * Add internal search dimension 
	 * 
	 * @param array $attributes
	 */
	public function addInternalSearchDimension(array &$attributes = [])
	{
		if ($this->supportInternalSearch())
		{
			if (null !== $query = $this->request->getParam('q'))
			{
				/**
				 * Basic sanitization
				 */
				$query = preg_replace('/[^a-zA-Z0-9]/i','', strip_tags($query));
				
				/**
				 * Add query to parameters
				 */
				$attributes["dimension{$this->getInternalSearchDimension()}"] = $query;
			}
		}
		
		return $attributes;
	}
	
	
	/**
	 * Check if current Magento is Enterprise (EE) edition
	 * 
	 * @return boolean
	 */
	public function isEnterprise()
	{
		return $this->productMetadata->getEdition() === 'Enterprise';
	}
	
	/**
	 * Check if current Magento is Community (CE) edition
	 *
	 * @return boolean
	 */
	public function isCommunity()
	{
		return $this->productMetadata->getEdition() === 'Community';
	}
	
	/**
	 * Get product categories 
	 * 
	 * @param \Magento\Catalog\Model\Product $product
	 */
	public function getCurrentStoreProductCategories(?\Magento\Catalog\Model\Product $product = null)
	{
		if (!$product)
		{
			return [];
		}
		
	    return $this->getCurrentStoreCategories((array) $product->getCategoryIds());
	}

	/**
	 * Get current store categories for product 
	 * 
	 * @param array $intersect
	 * @return array
	 */
	public function getCurrentStoreCategories(array $intersect = [])
	{
		return array_intersect($this->getRootChildren(), $intersect);
	}

	public function getCurrentProduct() : ProductInterface
	{
		return $this->currentContext->getCurrentProduct()->get();
	}

	public function getCurrentCategory() : CategoryInterface
	{
		return $this->currentContext->getCurrentCategory()->get();
	}

	/**
     * Get current category
     *
     * @return mixed|NULL
     */
    public function getCurrentCategoryName() : string
    {
        return $this->getJsonHelper()->encode
        (
            $this->getCurrentCategory()->getName()
        );
    }

	/**
	 * Get root child catgeories
	 */
	public function getRootChildren() : array 
	{
		if (!$this->rootChildren)
		{
			$root = $this->categoryRepository->get
			(
				$this->getStoreManager()->getStore()->getRootCategoryId()
			);

			$this->rootChildren = $root->getAllChildren(true);
		}
		
		return $this->rootChildren;
	}

	    
    
    /**
     * Get Measurement ID
     *
     * @param int $store
     * @return string
     */
    public function getMeasurementId(int $store = 0) : string
    {
        if ($store)
        {
            return (string) $this->scopeConfig->getValue($this->getMeasurementIdConfig(), \Magento\Store\Model\ScopeInterface::SCOPE_STORE, $store);
        }
            
        return (string) $this->getConfig($this->getMeasurementIdConfig());
    }
    
    /**
     * Get measurement api secret
     *
     * @return string
     */
    public function getMeasurementApiSecret(int $store = 0) : string
    {
        if ($store)
        {
            return (string) $this->scopeConfig->getValue('ec/api/google_gtm_ua4_measurement_api_secret', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, $store);
        }
        
        return (string) $this->getConfig('ec/api/google_gtm_ua4_measurement_api_secret');
    }
    
    /**
     * Get measurement id config string
     *
     * @return string
     */
    public function getMeasurementIdConfig() : string
    {
        return \Anowave\Ec4\Model\System\Config\Source\Type::TYPE_DEDICATED === (int) $this->getConfig('ec/api/measurement_id_type') ? \Anowave\Ec4\Helper\Data::CONFIG_DEDICATED : \Anowave\Ec4\Helper\Data::CONFIG_SHARED;
    }
	
	/**
	 * Check if customer is returning customer
	 * 
	 * @return boolean
	 */
	public function getIsReturnCustomer()
	{
		return $this->getJsonHelper()->encode($this->returnCustomer);
	}
	
	/**
	 * Get if new customer 
	 * 
	 * @param OrderInterface $order
	 * @return bool
	 */
	public function getIsNewCustomer() : bool
	{
	    return false;
	}
	
	/**
	 * Get Facebook remarketing identifier 
	 * 
	 * @param \Magento\Catalog\Model\Product $product
	 * @return mixed
	 */
	public function getFacebookRemarketingId(\Magento\Catalog\Model\Product $product)
	{
	    return $this->getAttribute($product, 'ec/facebook/facebook_content_id');
	}

	public function getFacebookContentId()
	{
		return $this->getConfig('ec/facebook/facebook_content_id');
	}

	/**
	 * Get revenue calculation
	 * 
	 * @return int
	 */
	public function getRevenueCalculation() : int
	{
		return (int) $this->getConfig('ec/tax/revenue');
	}

	
	
	/**
	 * Get Adwords remarketing identifier
	 * 
	 * @param \Magento\Catalog\Model\Product $product
	 * @return mixed
	 */
	public function getAdwordsRemarketingId(\Magento\Catalog\Model\Product $product)
	{
	    return $this->getAttribute($product, 'ec/adwords/ecomm_prodid');
	}
	
	/**
	 * Get attribute 
	 * 
	 * @param \Magento\Catalog\Model\Product $product
	 * @param string $attribute
	 * @return mixed
	 */
	public function getAttribute(\Magento\Catalog\Model\Product $product, $config)
	{
		$attribute_code = (string) $this->getConfig($config);
		
	    $attribute = strtolower($attribute_code);
	    
	    if ('id' === $attribute)
	    {
	        return $product->getId();
	    }
	    
	    return $product->getData($attribute);
	}
	
	/**
	 * Get ecomm_prodid
	 * 
	 * @param \Magento\Framework\Api\ExtensibleDataInterface $item
	 * @return string
	 */
	public function getAdwordsEcommProdId(\Magento\Framework\Api\ExtensibleDataInterface $item)
	{
		/**
		 * Get attribute 
		 * 
		 * @var string $attribute
		 */
		$attribute = strtolower
		(
			$this->getConfig('ec/adwords/ecomm_prodid')
		);
		
		switch ($attribute)
		{
			case \Anowave\Ec\Model\System\Config\Source\Id::ID_ID: 	
			{
				/**
				 * Checkout/cart items
				 */
				if ($item instanceof \Magento\Quote\Model\Quote\Item)
				{
					return (int) $item->getProductId();	
				}

				/**
				 * Purchase items
				 */
				if ($item instanceof \Magento\Sales\Model\Order\Item)
				{
					return (int) $item->getProductId();
				}
				
				return (int) $item->getId();
			}
			case \Anowave\Ec\Model\System\Config\Source\Id::ID_SKU: 
			{
				return (string) $item->getSku();
			}
			default:
				
				$value = null;
				
				/**
				 * Checkout/cart items
				 */
				if ($item instanceof \Magento\Quote\Model\Quote\Item)
				{
					$value = $item->getProduct()->getData($attribute);
				}
				
				/**
				 * Purchase items
				 */
				if ($item instanceof \Magento\Sales\Model\Order\Item)
				{
					$value = $item->getProduct()->getData($attribute);
				}
				
				if ($value)
				{
					return $value;
				}

				/**
				 * Get item attribute value
				 */
				$value = $item->getData($attribute);
				
				if ($value)
				{
					return $value;
				}
				
				return (string) $item->getSku();
		}
		
		return (string) $item->getSku();
	}

	/**
	 * Get first product child (for configurable products) 
	 * 
	 * @param \Magento\Catalog\Model\Product $product
	 * @return Magento\Catalog\Model\Product|NULL
	 */
	public function getFirstChild($product)
	{
	    if(\Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE === $product->getTypeId())
	    {
    	    /**
    	     * Get variants (children)
    	     */
    	    $children = $product->getTypeInstance()->getUsedProducts($product);
    	    
    	    /**
    	     * Sort children to find the lowest price possible
    	     */
    	    usort($children, function($a, $b)
    	    {
    	        return $a->getFinalPrice() <=> $b->getFinalPrice();
    	    });
    	    
    	    if ($children)
    	    {
    	        $child = array_shift($children);
    	        
    	        return $child;
    	    } 
	    }
	    
	    return null;
	}

	/**
	 * Get identifier 
	 * 
	 * @param \Magento\Catalog\Model\Product $product
	 * @return mixed
	 */
	public function getIdentifier(\Magento\Framework\Model\AbstractExtensibleModel $product)
	{
	    $identifier = (string) $this->getConfig('ec/options/identifier');
	    
	    switch($identifier)
	    {
	        case \Anowave\Ec\Model\System\Config\Source\Id::ID_ID:  return $product->getId();
	        case \Anowave\Ec\Model\System\Config\Source\Id::ID_SKU: return $product->getSku();
	        default:
	            try 
	            {
	                return $product->getData($identifier);
	            }
	            catch (\Exception $e) {}
	            break;
	    }
	    
	    return $product->getSku();
	}
	
	/**
	 * Get quote/sales order item identifier
	 * 
	 * @param \Magento\Framework\Api\ExtensibleDataInterface $item
	 * @return mixed
	 */
	public function getIdentifierItem(\Magento\Framework\Api\ExtensibleDataInterface $item)
	{
	    if (!$this->useSimples())
	    {
	        $product = $this->productRepository->getById
			(
				$item->getProduct() ? $item->getProduct()->getId() : $item->getProductId()
			);
	    }
	    else 
	    {
	        $product = $item->getProduct();
	    };
	    
	    if (!$product)
	    {
	        if ($item->getProductId())
	        {
	            try
	            {
	                $product = $this->productRepository->getById($item->getProductId());
	            }
	            catch (\Exception $e)
	            {
	                return $item->getSku();
	            }
	        }
	    }
	    
	    
	    return $this->getIdentifier($product);
	}
	
	/**
	 * Get product attributes to be included in dataLayer[] object
	 * @return array
	 */
	public function getDatalayerAttributes() : array
	{
	    $attributes = [];
	    
	    foreach($this->eavConfig->getAttributes(\Magento\Catalog\Model\Product::ENTITY) as $attribute)
	    {
	        if (1 === (int) $attribute['datalayer'])
	        {
	            $attributes[] = $attribute['attribute_code'];
	        }
	    }

	    return $attributes;
	}
	
	/**
	 * Get attribute values by SKU 
	 * 
	 * @param string $sku
	 * @return array
	 */
	public function getDataLayerAttributesArray(string $sku = '') : array
	{
	    $values = [];
	    
	    $attributes = $this->getDatalayerAttributes();
	    
	    if ($attributes)
	    {
    	    foreach($this->productCollectionFactory->create()->addAttributeToSelect($attributes)->addAttributeToFilter('sku', [$sku]) as $product)
    	    {
    	        foreach ($attributes as $attribute)
    	        {
    	            $values["item_$attribute"] = $product->getData($attribute);
    	        }
    	    }
    	    
    	    /**
    	     * Filter empty or NULL values
    	     */
    	    $values = array_filter($values);
    	}
	    
	    return $values; 
	}
	
	/**
	 * Category items selector 
	 * 
	 * @return string
	 */
	public function getListSelector() : string
	{  
		if ('' !== $selector = (string) $this->getConfig('ec/selectors/list'))
		{
			return $selector;
			
		}
		
		return Constants::XPATH_LIST_SELECTOR;
	}
	
	/**
	 * Get cross sell list items selector
	 * 
	 * @return string
	 */
	public function getListCrossSellSelector() : string
	{
		if ('' !== $selector = (string) $this->getConfig('ec/selectors/list_crosssell'))
		{
			return $selector;
			
		}
		
		return Constants::XPATH_LIST_CROSS_SELECTOR;
	}
	
	/**
	 * Add to wishlist selector
	 *
	 * @returnstring
	 */
	public function getWishlistSelector() : string
	{
		if ('' !== $selector = (string) $this->getConfig('ec/selectors/add_wishlist'))
		{
			return $selector;
			
		}
		
		return Constants::XPATH_ADD_WISHLIST_SELECTOR;
	}
	
	/**
	 * Add to wishlist selector
	 *
	 * @return string
	 */
	public function getWishlistItemsSelector() : string
	{
	    if ('' !== $selector = (string) $this->getConfig('ec/selectors/list_wishlist_items'))
	    {
	        return $selector;
	        
	    }
	    
	    return Constants::XPATH_ADD_WISHLIST_ITEMS_SELECTOR;
	}
	
	/**
	 * Add to wishlist selector
	 *
	 * @return string
	 */
	public function getWishlistItemsAddSelector() : string
	{
	    if ('' !== $selector = (string) $this->getConfig('ec/selectors/list_wishlist_items_add'))
	    {
	        return $selector;
	        
	    }
	    
	    return Constants::XPATH_ADD_WISHLIST_ITEMS_ADD_SELECTOR;
	}
	
	/**
	 * Add to wishlist selector
	 *
	 * @return string
	 */
	public function getWishlistItemsRemoveSelector() : string
	{
	    if ('' !== $selector = (string) $this->getConfig('ec/selectors/remove_wishlist'))
	    {
	        return $selector;
	        
	    }
	    
	    return Constants::XPATH_ADD_WISHLIST_ITEMS_REMOVE_SELECTOR;
	}
	
	/**
	 * Add to wishlist selector
	 *
	 * @return string
	 */
	public function getCompareSelector() : string
	{
		if ('' !== $selector = (string) $this->getConfig('ec/selectors/add_compare'))
		{
			return $selector;
			
		}
		
		return Constants::XPATH_ADD_COMPARE_SELECTOR;
	}
	
	/**
	 * NewProduct widget selector
	 *
	 * @return string
	 */
	public function getListWidgetSelector() : string
	{
		if ('' !== $selector = (string) $this->getConfig('ec/selectors/list_widget'))
		{
			return $selector;
			
		}
		
		return Constants::XPATH_LIST_WIDGET_SELECTOR;
	}
	
	/**
	 * NewProduct widget click selector
	 *
	 * @return string
	 */
	public function getListWidgetClickSelector() : string
	{
		if ('' !== $selector = (string) $this->getConfig('ec/selectors/list_widget_click'))
		{
			return $selector;
			
		}
		
		return Constants::XPATH_LIST_WIDGET_CLICK_SELECTOR;
	}
	
	/**
	 * Get widget add to cart selector
	 * 
	 * @return string
	 */
	public function getListWidgetCartCategorySelector() : string
	{
		if ('' !== $selector = (string) $this->getConfig('ec/selectors/list_widget_cart'))
		{
			return $selector;
			
		}
		
		return Constants::XPATH_LIST_WIDGET_CART_SELECTOR;
	}
	
	
	/**
	 * Category items click selector
	 *
	 * @return string
	 */
	public function getListClickSelector() : string
	{
		if ('' !== $selector = (string) $this->getConfig('ec/selectors/click'))
		{
			return $selector;
			
		}
		
		return Constants::XPATH_LIST_CLICK_SELECTOR;
	}
	
	/**
	 * Category items add to wishlist selector
	 *
	 * @return string
	 */
	public function getListWishlistSelector() : string
	{
	    if ('' !== $selector = (string) $this->getConfig('ec/selectors/list_wishlist'))
	    {
	        return $selector;
	        
	    }
	    
	    return Constants::XPATH_LIST_WISHLIST_SELECTOR;
	}
	
	/**
	 * Category items add to compare selector
	 *
	 * @return string
	 */
	public function getListCompareSelector() : string
	{
	    if ('' !== $selector = (string) $this->getConfig('ec/selectors/list_compare'))
	    {
	        return $selector;
	        
	    }
	    
	    return Constants::XPATH_LIST_COMPARE_SELECTOR;
	}
	
	/**
	 * Add to cart selector (product detail page)
	 *
	 * @return string
	 */
	public function getCartSelector() : string
	{
		if ('' !== $selector = (string) $this->getConfig('ec/selectors/cart'))
		{
			return $selector;
			
		}
		
		return Constants::XPATH_CART_SELECTOR;
	}
	
	/**
	 * Add to cart selector (direct button from categories)
	 *
	 * @return string
	 */
	public function getCartCategorySelector() : string
	{
		if ('' !== $selector = (string) $this->getConfig('ec/selectors/cart_list'))
		{
			return $selector;
			
		}
		
		return Constants::XPATH_CART_CATEGORY_SELECTOR;
	}
	
	/**
	 * Remove from cart selector
	 *
	 * @return string
	 */
	public function getDeleteSelector() : string
	{
		if ('' !== $selector = (string) $this->getConfig('ec/selectors/cart_delete'))
		{
			return $selector;
			
		}
		
		return Constants::XPATH_CART_DELETE_SELECTOR;
	}
	
	/**
	 * Get customer reviews badge position
	 * 
	 * @return mixed
	 */
	public function getCustomerReviewsPosition()
	{
	    return $this->getConfig('ec/customer_reviews/position');
	}
	
	public function getCustomerReviewsMerchantId()
	{
	    return $this->getConfig('ec/customer_reviews/merchant_id');
	}
	
	/**
	 * Get customer reviews dalivery date offset 
	 * 
	 * @return mixed
	 */
	public function getCustomerReviewsDeliveryOffset()
	{
	    return $this->getConfig('ec/customer_reviews/delivery_date');
	}
	
	/**
	 * Get customer reviews delivery 
	 * 
	 * @param number $offset
	 */
	public function getCustomerReviewsDeliveryDate($offset)
	{
	    $date = time();
	    
	    try 
	    {
	        $date = strtotime($this->getCustomerReviewsDeliveryOffset(), $offset);
	    }
	    catch (\Exception $e){}
	    
	    return date('Y-m-d', $date);
	}
	
	/**
	 * Get customer reviews payload 
	 * 
	 * @param \Magento\Framework\View\Element\AbstractBlock $block
	 */
	public function getCustomerReviewsPayload($block)
	{
	    /**
	     * Products array 
	     * 
	     * @var array $products
	     */
	    $products = [];
	    
	    /**
	     * Payload 
	     * 
	     * @var array $payload
	     */
	    $payload = 
	    [
	        'merchant_id'              => $this->getCustomerReviewsMerchantId(),
	        'email'                    => null,
	        'order_id'                 => null,
	        'delivery_country'         => null,
	        'estimated_delivery_date'  => null,
	        'products'                 => [],
	        'opt_in_style'             => $this->getConfig('ec/customer_reviews/position')
	    ];
	    
	    foreach ($this->getOrders($block) as $order)
	    {
	        if ($this->isCustomerReviewsGTIN())
	        {
	            $attribute = $this->getCustomerReviewsGTINAttribute();
	            
	            if ($attribute)
	            {
        	        foreach ($order->getAllVisibleItems() as $item)
        	        {
        	            $gtin = $this->escape($item->getData($attribute));
        	            
        	            if ($gtin)
        	            {
            	            $payload['products'][] = 
            	            [
            	                'gtin' => $gtin
            	            ];
        	            }
        	        }
	            }
	        }
	        
	        $payload['order_id'] = $order->getIncrementId();
	        
	        if ($order->getShippingAddress())
	        {
	           $payload['delivery_country'] = $order->getShippingAddress()->getCountryId();
	        }
	        
	        /**
	         * Set email
	         */
	        $payload['email'] = $this->getCustomerEmail($order);
	        
	        /**
	         * Set delivery date
	         */
	        $payload['estimated_delivery_date'] = $this->getCustomerReviewsDeliveryDate(time());
	    }
	    
	    if (!$payload['products'])
	    {
	        unset($payload['products']);
	    }

	    return $this->getJsonHelper()->encode($payload);
	}
	
	/**
	 * Get search attributes 
	 * 
	 * @return string
	 */
	public function getSearchAttributes()
	{
		return $this->attributes->getAttributes();
	}
	/**
	 * Get event manager 
	 * 
	 * @return \Magento\Framework\Event\ManagerInterface
	 */
	public function getEventManager()
	{
		return $this->eventManager;
	}
	
	/**
	 * Get category repository 
	 * 
	 * @return CategoryRepository
	 */
	public function getCategoryRepository()
	{
	    return $this->categoryRepository;
	}
	
	/**
	 * Get sales order collection
	 *
	 * @return \Magento\Sales\Model\ResourceModel\Order\CollectionFactory
	 */
	public function getSalesOrderCollection()
	{
		return $this->salesOrderCollection;
	}
	
	/**
	 * Get JSON helper 
	 * 
	 * @return Json
	 */
	public function getJsonHelper()
	{
		return $this->jsonHelper;
	}
	
	/**
	 * Get layer resolver
	 * 
	 * @return Resolver
	 */
	public function getLayerResolver()
	{
	    return $this->layerResolver;
	}
	
	/**
	 * Get attributes
	 * 
	 * @return \Anowave\Ec\Helper\Attributes
	 */
	public function getAttributes()
	{
	    return $this->attributes;
	}
	
	/**
	 * Get store manager
	 * 
	 * @return StoreManagerInterface
	 */
	public function getStoreManager()
	{
	    return $this->storeManager;
	}
	
	/**
	 * Get request 
	 * 
	 * @return \Magento\Framework\App\Request\Http
	 */
	public function getRequest()
	{
	    return $this->request;
	}
	
	public function getProductRepository()
	{
	    return $this->productRepository;
	}
	
	/**
	 * Get form key 
	 * 
	 * @return string
	 */
	public function getFormKey() : string
	{
	    return $this->utils->getFormKey();
	}
	
	/**
	 * Get Facebook Conversions API endpoint 
	 * `
	 * @return \Anowave\Ec\Model\Facebook\ConversionsApi
	 */
	public function getFacebookConversionsApi()
	{
	    if (!$this->facebook_conversions_api)
	    {
	        $user_data = [];
	        
	        if ($this->isLogged())
	        {
	            /**
	             * Get customer
	             *
	             * @var \Magento\Customer\Model\Data\Customer $customer
	             */
	            $customer = $this->getCustomer();
	            
	            if ($customer)
	            {
	                if ($customer->getEmail())
	                {
	                    $user_data['email'] = strtolower(trim($customer->getEmail()));
	                }
	                
	                if ($customer->getFirstname())
	                {
	                    $user_data['first_name'] = strtolower(trim($customer->getFirstname()));
	                }
	                
	                if ($customer->getLastname())
	                {
	                    $user_data['last_name'] = strtolower(trim($customer->getLastname()));
	                }
	                
	                if ($customer->getGender())
	                {
	                    $user_data['gender'] = $this->eavConfig->getAttribute('customer', 'gender')->getSource()->getOptionText($customer->getGender());
	                }
	                
	                foreach ($customer->getAddresses() as $address)
	                {
	                    if ($address->getTelephone())
	                    {
	                        $user_data['phone'] =  preg_replace('/[^0-9]+/i', '', $address->getTelephone());
	                    }
	                    
	                    if ($address->getCity())
	                    {
	                        $user_data['city'] = strtolower(preg_replace('/[^a-zA-Z]/i','',$address->getCity()));
	                    }
	                    
	                    if ($address->getPostcode())
	                    {
	                        $user_data['zip_code'] = $address->getPostcode();
	                    }
	                    
	                    if ($address->getCountryId())
	                    {
	                        $user_data['country_code'] = strtolower($address->getCountryId());
	                    }
	                    
	                    if ($address->getRegion())
	                    {
	                        if (is_string($address->getRegion()))
	                        {
	                            $region = preg_replace('/[^a-z]/i','', $address->getRegionCode() ?? '');
	                        }
	                        elseif (is_object($address->getRegion()))
	                        {
	                            $region = preg_replace('/[^a-z]/i','', $address->getRegion()->getRegionCode() ?? '');
	                        }
	                        else 
	                        {
	                            $region = null;
	                        }

	                        if ($region)
	                        {
	                            $user_data['state'] = $region;
	                        }
	                    }
	                }
	                
	                $user_data = array_map(function($data)
	                {
	                    return hash('sha256', $data);
	                    
	                }, $user_data);
	            }
	        }

			
	        
	        $this->facebook_conversions_api = $this->facebookConversionsApiFactory->create(
            [
                'pixel_id'                          => $this->getFacebookConversionsApiPixelId(),
                'access_token'                      => $this->getFacebookConversionsApiAccessToken(),
                'test_event_code'                   => $this->getFacebookConversionsApiTestEventCode(),
                'user_data'                         => $user_data,
                'cookie_directive'                  => $this->supportCookieDirective(),
				'cookie_directive_engine'			=> $this->supportCookieDirectiveEngine(),
                'cookie_directive_constent_granted' => $this->isCookieConsentAccepted(), 
                'logger'                            => $this->utils->getLogger(),
				'cookieBuiltIn'						=> $this->utils->getCookieBuiltIn($this->getFacebookCookieTriggerEvent()),
				'cookiebot'							=> $this->utils->getCookiebot(),
				'onetrust'						    => $this->utils->getOnetrust
				(
					[
						'anonymize_group' => (string) $this->getConfig('ec/cookie/onetrust_capi_anonymize_group')
					]
				)
            ]); 
	        
	        /**
	         * Activate Facebook Conversons API
	         */
	        if ($this->supportFacebookConversionsApi())
	        {
	            $this->facebook_conversions_api->enable
				(
					$this->getFacebookConversionsApiEvents()
				);
	        }
	    }
	    
	    return $this->facebook_conversions_api;
	}

	/**
	 * Get current consent id based on cookieUuid cookie value
	 * 
	 * @param string $cookie
	 * 
	 * @return int
	 */
	public function getConsentId(string $cookie = '') : int
	{
		if ($cookie)
		{
			$consent = [];
			
			$collection = $this->consentCollection->create()->addFieldToFilter('consent_uuid', $cookie);

			foreach($collection as $row)
			{
				$consent[] = (int) $row->getConsentId();
			}

			if ($consent)
			{
				return array_pop($consent);
			}
		}

		return 0;
	}

	/**
	 * Get vertical 
	 * 
	 * @return string
	 */
	public function getGoogleBusinessVertical() : string 
	{
		return 'retail';
	}
	
	/**
	 * Returns information whether moving JS to footer is enabled
	 *
	 * @return bool
	 */
	public function isDeferEnabled(): bool
	{
	    return $this->scopeConfig->isSetFlag('dev/js/move_script_to_bottom',\Magento\Store\Model\ScopeInterface::SCOPE_STORE);
	}

	/**
	 * Get country 
	 * 
	 * @return string
	 */
	public function getCountryByWebsite(): string
    {
        return (string) $this->scopeConfig->getValue('general/country/default',\Magento\Store\Model\ScopeInterface::SCOPE_WEBSITES);
    }

	/**
	 * Get language 
	 * 
	 * @return string
	 */
	public function getLanguageByWebsite(): string
    {
        return (string) $this->utils->getLocale();
    }


	/**
	 * Get const config 
	 * 
	 * @return array
	 */
	public function getConst() : string 
	{
		return $this->getJsonHelper()->encode
		(
			[
				'URL' 											=> $this->getStore()->getBaseUrl(),
				'TIMING_CATEGORY_ADD_TO_CART' 					=> __('Add To Cart Time'),
				'TIMING_CATEGORY_REMOVE_FROM_CART' 				=> __('Remove From Cart Time'),
				'TIMING_CATEGORY_PRODUCT_CLICK' 				=> __('Product Detail Click Time'),
				'TIMING_CATEGORY_CHECKOUT' 						=> __('Checkout Time'),
				'TIMING_CATEGORY_CHECKOUT_STEP' 				=> __('Checkout Step Tim'),
				'TIMING_CATEGORY_PRODUCT_WISHLIST' 				=> __('Add to Wishlist Time'),
				'TIMING_CATEGORY_PRODUCT_COMPARE' 				=> __('Add to Compare Time'),
				'VARIANT_DELIMITER' 							=> static::VARIANT_DELIMITER,
				'VARIANT_DELIMITER_ATT' 						=> static::VARIANT_DELIMITER_ATT,
				'CHECKOUT_STEP_SHIPPING' 						=> Constants::CHECKOUT_STEP_SHIPPING,
				'CHECKOUT_STEP_PAYMENT' 						=> Constants::CHECKOUT_STEP_PAYMENT,
				'CHECKOUT_STEP_ORDER' 							=> Constants::CHECKOUT_STEP_ORDER,
				'DIMENSION_SEARCH'								=> (int) $this->getInternalSearchDimension(),
				'COOKIE_DIRECTIVE' 								=> $this->supportCookieDirective(),
				'COOKIE_DIRECTIVE_ENGINE' 						=> $this->supportCookieDirectiveEngine(),
				'COOKIE_DIRECTIVE_SEGMENT_MODE' 				=> $this->getCookieDirectiveIsSegmentMode(),
				'COOKIE_DIRECTIVE_SEGMENT_MODE_EVENTS' 			=> $this-> getCookieDirectiveConsentSegments(),
				'COOKIE_DIRECTIVE_CONSENT_GRANTED_EVENT' 		=> Constants::COOKIE_CONSENT_GRANTED_EVENT,
				'COOKIE_DIRECTIVE_CONSENT_DECLINE_EVENT'		=> Constants::COOKIE_CONSENT_DECLINE_EVENT,
				'COOKIE_DIRECTIVE_OVERRIDE_DECLINE'	 			=> $this->getCookieDirectiveOverrideDecline(),
				'COOKIE_DOMAIN'									=> $this->sessionManagerInterface->getCookieDomain(),
				'CATALOG_CATEGORY_ADD_TO_CART_REDIRECT_EVENT'	=> Constants::CATALOG_CATEGORY_ADD_TO_CART_REDIRECT_EVENT,
				'META_CONVERSIONS_API_EVENTS'					=> $this->getFacebookConversionsApiEvents()
			], 
			JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
		);
	}

	/**
	 * Get AEC properties 
	 * 
	 * @return string
	 */
	public function getProperties() : string
	{
		return $this->getJsonHelper()->encode
		(
			[
				'Message'				=> $this->getMessage(),
				'storeName' 			=> $this->getStoreName(),
				'currencyCode'			=> $this->getCurrency(),
				'useDefaultValues' 		=> $this->useDefaultValues(),
				'facebook' 				=> $this->facebook(),
				'tiktok' 				=> $this->tiktok(),
				'SUPER' 				=> $this->getSuper(),
				'CONFIGURABLE_SIMPLES' 	=> $this->getConfigurableSimples(),
				'BUNDLE' 				=> $this->getBundle(),
				'localStorage' 			=> $this->useLocalStorage(),
				'summary' 				=> $this->useSummary(),
				'reset' 				=> $this->useReset(),
				'tax' 					=> $this->getPriceRenderTaxType(),
				'simples' 				=> $this->useSimples()
			], 
			JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
		);
	}

	public function getCurlOptions(array $data = []) : array 
    {
        $options = [
            CURLOPT_HEADER         => 0,
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_POST           => 1,
            CURLOPT_SSL_VERIFYHOST => $this->getVerifyHost(),
            CURLOPT_SSL_VERIFYPEER => $this->getVerifyPeer(),
            CURLOPT_USERAGENT      => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36',
            CURLOPT_POSTFIELDS     => mb_convert_encoding(
                json_encode($data, JSON_UNESCAPED_UNICODE),
                'UTF-8',
                mb_list_encodings()
            ),
            CURLOPT_NOSIGNAL      => 1,
            CURLOPT_CONNECTTIMEOUT => $this->getConnectTimeout(),
            CURLOPT_TIMEOUT        => $this->getTimeout()
        ];

        return $options;
    }

    protected function getVerifyHost() : int
    {
        return (int) $this->getConfig('ec/performance/ssl_verify_host');
    }

    protected function getVerifyPeer() : int 
    {
        return (int) $this->getConfig('ec/performance/ssl_verify_peer');
    }

    protected function getConnectTimeout() : int 
    {
        return (int) $this->getConfig('ec/performance/connect_timeout');
    }

    protected function getTimeout() : int 
    {
        return (int) $this->getConfig('ec/performance/timeout');
    }

	/**
	 * Get message config 
	 * 
	 * @return array
	 */
	public function getMessage() : array 
	{
		return 
		[
			'confirm' 				=> $this->useRemoveConfirm(),
			'confirmRemoveTitle' 	=> __('Are you sure?'),
			'confirmRemove' 		=> __('Are you sure you would like to remove this item from the shopping cart?')
		];
	}

	public function getCheckoutSession()
	{
		return $this->checkoutSession;
	}

	/**
	 * Get utils 
	 * 
	 * @return Utils
	 */
	public function getUtils() : Utils
	{
		return $this->utils;
	}
	
	
	/**
	 * Escape quotes
	 * 
	 * @param string $string
	 * @return string
	 */
	public function escape($data)
	{
		return addcslashes((string) $data, '\'');
	}
	
	/**
	 * Escape string for HTML5 data attribute 
	 * 
	 * @param string $data
	 * @return string
	 */
	public function escapeDataArgument($data)
	{
		return str_replace(array('"','\''), array('&quot;','&apos;'), (string) $data);
	}
	
	/**
	 * Encode string
	 *
	 * @param string $string
	 * @return string
	 */
	public function utf($source) : string
	{
	    if (version_compare(phpversion(), '8.2.0', '<'))
	    {
	        return mb_convert_encoding($source, 'HTML-ENTITIES', 'utf-8');
	    }
	    
	    return htmlspecialchars_decode(mb_encode_numericentity( htmlentities( $source, ENT_COMPAT, 'UTF-8' ), [0x80, 0x10FFFF, 0, ~0], 'UTF-8' ));
	}
}