<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Magento\Directory\Helper;

use Magento\Directory\Model\AllowedCountries;
use Magento\Directory\Model\Currency;
use Magento\Directory\Model\CurrencyFactory;
use Magento\Directory\Model\ResourceModel\Country\Collection;
use Magento\Directory\Model\ResourceModel\Region\CollectionFactory;
use Magento\Framework\App\Cache\Type\Config;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Framework\App\Helper\Context;
use Magento\Framework\Json\Helper\Data as JsonData;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\StoreManagerInterface;

/**
 * Directory data helper
 *
 * @api
 * @since 100.0.2
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 */
class Data extends AbstractHelper
{
    private const STORE_ID = 'store_id';

    /**
     * Config value that lists ISO2 country codes which have optional Zip/Postal pre-configured
     */
    public const OPTIONAL_ZIP_COUNTRIES_CONFIG_PATH = 'general/country/optional_zip_countries';

    /**
     * Path to config value, which lists countries, for which state is required.
     */
    public const XML_PATH_STATES_REQUIRED = 'general/region/state_required';

    /**
     * Path to config value, which detects whether or not display the state for the country, if it is not required
     */
    public const XML_PATH_DISPLAY_ALL_STATES = 'general/region/display_all';

    /**#@+
     * Path to config value, which is default country
     */
    public const XML_PATH_DEFAULT_COUNTRY = 'general/country/default';
    public const XML_PATH_DEFAULT_LOCALE = 'general/locale/code';
    public const XML_PATH_DEFAULT_TIMEZONE = 'general/locale/timezone';
    /**#@-*/

    /**
     * Path to config value that contains codes of the most used countries.
     * Such countries can be shown on the top of the country list.
     */
    public const XML_PATH_TOP_COUNTRIES = 'general/country/destinations';

    /**
     * Path to config value that contains weight unit
     */
    public const XML_PATH_WEIGHT_UNIT = 'general/locale/weight_unit';

    /**
     * @var Collection
     */
    protected $_countryCollection;

    /**
     * @var \Magento\Directory\Model\ResourceModel\Region\Collection
     */
    protected $_regionCollection;

    /**
     * @var string
     */
    protected $_regionJson;

    /**
     * @var array
     */
    protected $_currencyCache = [];

    /**
     * @var array
     */
    protected $_optZipCountries = null;

    /**
     * @var Config
     */
    protected $_configCacheType;

    /**
     * @var CollectionFactory
     */
    protected $_regCollectionFactory;

    /**
     * @var JsonData
     */
    protected $jsonHelper;

    /**
     * @var StoreManagerInterface
     */
    protected $_storeManager;

    /**
     * @var CurrencyFactory
     */
    protected $_currencyFactory;

    /**
     * Data constructor.
     *
     * @param Context $context
     * @param Config $configCacheType
     * @param Collection $countryCollection
     * @param CollectionFactory $regCollectionFactory
     * @param JsonData $jsonHelper
     * @param StoreManagerInterface $storeManager
     * @param CurrencyFactory $currencyFactory
     */
    public function __construct(
        Context $context,
        Config $configCacheType,
        Collection $countryCollection,
        CollectionFactory $regCollectionFactory,
        JsonData $jsonHelper,
        StoreManagerInterface $storeManager,
        CurrencyFactory $currencyFactory
    ) {
        parent::__construct($context);
        $this->_configCacheType = $configCacheType;
        $this->_countryCollection = $countryCollection;
        $this->_regCollectionFactory = $regCollectionFactory;
        $this->jsonHelper = $jsonHelper;
        $this->_storeManager = $storeManager;
        $this->_currencyFactory = $currencyFactory;
    }

    /**
     * Retrieve region collection
     *
     * @return \Magento\Directory\Model\ResourceModel\Region\Collection
     */
    public function getRegionCollection()
    {
        if (!$this->_regionCollection) {
            $this->_regionCollection = $this->_regCollectionFactory->create();
            // phpstan:ignore
            $this->_regionCollection->addCountryFilter($this->getAddress()->getCountryId())->load();
        }
        return $this->_regionCollection;
    }

    /**
     * Retrieve country collection
     *
     * @param null|int|string|\Magento\Store\Model\Store $store
     * @return Collection
     */
    public function getCountryCollection($store = null)
    {
        if (!$this->_countryCollection->isLoaded()) {
            $this->_countryCollection->loadByStore($store);
        }
        return $this->_countryCollection;
    }

    /**
     * Retrieve regions data json
     *
     * @return string
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    public function getRegionJson()
    {
        \Magento\Framework\Profiler::start('TEST: ' . __METHOD__, ['group' => 'TEST', 'method' => __METHOD__]);
        if (!$this->_regionJson) {
            $scope = $this->getCurrentScope();
            $scopeKey = $scope['value'] ? '_' . implode('_', $scope) : null;
            $cacheKey = 'DIRECTORY_REGIONS_JSON_STORE' . $scopeKey;
            $json = $this->_configCacheType->load($cacheKey);
            if (empty($json)) {
                $regions = $this->getRegionData();
                $json = $this->jsonHelper->jsonEncode($regions);
                if ($json === false) {
                    $json = 'false';
                }
                $this->_configCacheType->save($json, $cacheKey);
            }
            $this->_regionJson = $json;
        }

        \Magento\Framework\Profiler::stop('TEST: ' . __METHOD__);
        return $this->_regionJson;
    }

    /**
     * Convert currency
     *
     * @param float $amount
     * @param string $from
     * @param string $to
     *
     * @return float
     * @SuppressWarnings(PHPMD.ShortVariable)
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    public function currencyConvert($amount, $from, $to = null)
    {
        if (empty($this->_currencyCache[$from])) {
            $this->_currencyCache[$from] = $this->_currencyFactory->create()->load($from);
        }
        if ($to === null) {
            $to = $this->_storeManager->getStore()->getCurrentCurrencyCode();
        }
        $converted = $this->_currencyCache[$from]->convert($amount, $to);
        return $converted;
    }

    /**
     * Return ISO2 country codes, which have optional Zip/Postal pre-configured
     *
     * @param bool $asJson
     * @return array|string
     */
    public function getCountriesWithOptionalZip($asJson = false)
    {
        if (null === $this->_optZipCountries) {
            $value = trim(
                (string)$this->scopeConfig->getValue(
                    self::OPTIONAL_ZIP_COUNTRIES_CONFIG_PATH,
                    ScopeInterface::SCOPE_STORE
                )
            );
            $this->_optZipCountries = preg_split('/\,/', $value, 0, PREG_SPLIT_NO_EMPTY);
        }
        if ($asJson) {
            return $this->jsonHelper->jsonEncode($this->_optZipCountries);
        }
        return $this->_optZipCountries;
    }

    /**
     * Check whether zip code is optional for specified country code
     *
     * @param string $countryCode
     * @return boolean
     */
    public function isZipCodeOptional($countryCode)
    {
        $this->getCountriesWithOptionalZip();
        return in_array($countryCode, $this->_optZipCountries);
    }

    /**
     * Returns the list of countries, for which region is required
     *
     * @param boolean $asJson
     * @return array|string
     */
    public function getCountriesWithStatesRequired($asJson = false)
    {
        $value = trim(
            (string)$this->scopeConfig->getValue(
                self::XML_PATH_STATES_REQUIRED,
                ScopeInterface::SCOPE_STORE
            )
        );
        $countryList = preg_split('/\,/', $value, 0, PREG_SPLIT_NO_EMPTY);
        if ($asJson) {
            return $this->jsonHelper->jsonEncode($countryList);
        }
        return $countryList;
    }

    /**
     * Return, whether non-required state should be shown
     *
     * @return bool
     */
    public function isShowNonRequiredState()
    {
        return $this->scopeConfig->isSetFlag(
            self::XML_PATH_DISPLAY_ALL_STATES,
            ScopeInterface::SCOPE_STORE
        );
    }

    /**
     * Returns flag, which indicates whether region is required for specified country
     *
     * @param string $countryId
     * @return bool
     */
    public function isRegionRequired($countryId)
    {
        $countyList = $this->getCountriesWithStatesRequired();
        if (!is_array($countyList)) {
            return false;
        }
        return in_array($countryId, $countyList);
    }

    /**
     * Retrieve application base currency code
     *
     * @return string
     */
    public function getBaseCurrencyCode()
    {
        return $this->scopeConfig->getValue(
            Currency::XML_PATH_CURRENCY_BASE,
            'default'
        );
    }

    /**
     * Return default country code
     *
     * @param \Magento\Store\Model\Store|string|int $store
     * @return string
     */
    public function getDefaultCountry($store = null)
    {
        return $this->scopeConfig->getValue(
            self::XML_PATH_DEFAULT_COUNTRY,
            ScopeInterface::SCOPE_STORE,
            $store
        );
    }

    /**
     * Retrieve regions data
     *
     * @return array
     */
    public function getRegionData()
    {
        $scope = $this->getCurrentScope();
        $allowedCountries = $this->scopeConfig->getValue(
            AllowedCountries::ALLOWED_COUNTRIES_PATH,
            $scope['type'],
            $scope['value']
        ) ?? '';
        $countryIds = explode(',', $allowedCountries);
        $collection = $this->_regCollectionFactory->create();
        $collection->addCountryFilter($countryIds)->load();
        $regions = [
            'config' => [
                'show_all_regions' => $this->isShowNonRequiredState(),
                'regions_required' => $this->getCountriesWithStatesRequired(),
            ],
        ];
        foreach ($collection as $region) {
            /** @var $region \Magento\Directory\Model\Region */
            if (!$region->getRegionId()) {
                continue;
            }
            $regions[$region->getCountryId()][$region->getRegionId()] = [
                'code' => $region->getCode(),
                'name' => (string)__($region->getName()),
            ];
        }
        return $regions;
    }

    /**
     * Retrieve list of codes of the most used countries
     *
     * @return array
     */
    public function getTopCountryCodes()
    {
        $configValue = (string)$this->scopeConfig->getValue(
            self::XML_PATH_TOP_COUNTRIES,
            ScopeInterface::SCOPE_STORE
        );
        return !empty($configValue) ? explode(',', $configValue) : [];
    }

    /**
     * Retrieve weight unit
     *
     * @return string
     */
    public function getWeightUnit()
    {
        return $this->scopeConfig->getValue(self::XML_PATH_WEIGHT_UNIT, ScopeInterface::SCOPE_STORE);
    }

    /**
     * Get current scope from request
     *
     * @return array
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    private function getCurrentScope(): array
    {
        $scope = [
            'type' => ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
            'value' => null,
        ];
        $request = $this->_getRequest();
        if ($request->getParam(ScopeInterface::SCOPE_WEBSITE)) {
            $scope = [
                'type' => ScopeInterface::SCOPE_WEBSITE,
                'value' => $request->getParam(ScopeInterface::SCOPE_WEBSITE),
            ];
        } elseif ($request->getParam(ScopeInterface::SCOPE_STORE)) {
            $scope = [
                'type' => ScopeInterface::SCOPE_STORE,
                'value' => $request->getParam(ScopeInterface::SCOPE_STORE),
            ];
        } elseif ($request->getParam(self::STORE_ID)) {
            $scope = [
                'type' => ScopeInterface::SCOPE_STORE,
                'value' => $request->getParam(self::STORE_ID),
            ];
        } else {
            $storeId = $this->_storeManager->getStore()->getId() ?? null;
            if ($storeId) {
                $scope = [
                    'type' => ScopeInterface::SCOPE_STORE,
                    'value' => $storeId,
                ];
            }
        }

        return $scope;
    }
}
