<?php
/**
 * Copyright since 2025 InPost S.A.
 *
 * For the full license information, please view the LICENSE file bundled with the module.
 *
 * @author InPost S.A.
 * @copyright since 2025 InPost S.A.
 * @license MIT
 */

declare(strict_types=1);

namespace InPost\International\Configuration\Repository;

use InPost\International\Configuration\CheckoutConfiguration;
use InPost\International\Configuration\Enum\ClosestPointSearchMode;
use InPost\International\Geocoding\GoogleMaps\GoogleMapsGeocoder;
use PrestaShop\PrestaShop\Core\Domain\Configuration\ShopConfigurationInterface;
use PrestaShop\PrestaShop\Core\Domain\Shop\ValueObject\ShopConstraint;

final class CheckoutConfigurationRepository implements CheckoutConfigurationRepositoryInterface, GeocodingConfigurationRepositoryInterface
{
    private const CLOSES_POINT_SEARCH_MODE_KEY = 'INPOST_INTL_CLOSEST_POINT_SEARCH_MODE';
    private const GEOCODER_OPTIONS_KEY_PATTERN = 'INPOST_INT_{TYPE}_GEOCODER_OPTIONS';

    /**
     * @var ShopConfigurationInterface
     */
    private $configuration;

    /**
     * @var array{checkout?: CheckoutConfiguration}
     */
    private $cache = [];

    public function __construct(ShopConfigurationInterface $configuration)
    {
        $this->configuration = $configuration;
    }

    public function getCheckoutConfiguration(): CheckoutConfiguration
    {
        if (array_key_exists('checkout', $this->cache)) {
            return $this->cache['checkout'];
        }

        return $this->cache['checkout'] = new CheckoutConfiguration(
            $this->getClosestPointSearchMode()
        );
    }

    public function saveCheckoutConfiguration(CheckoutConfiguration $configuration, ShopConstraint $shops = null): void
    {
        $this->setClosestPointSearchMode($configuration->getClosestPointSearchMode(), $shops);

        if (null === $shops) {
            $this->cache['checkout'] = $configuration;
        } else {
            unset($this->cache['checkout']);
        }
    }

    public function getGeocoderTypeId(): ?string
    {
        $searchMode = $this->getCheckoutConfiguration()->getClosestPointSearchMode();

        switch ($searchMode) {
            case ClosestPointSearchMode::GoogleMapsGeocoder():
                return GoogleMapsGeocoder::ID;
            default:
                return null;
        }
    }

    public function getGeocoderOptions(string $type): ?array
    {
        $key = self::getGeocoderOptionsKey($type);

        if ('' === $value = (string) $this->configuration->get($key)) {
            return null;
        }

        $options = json_decode($value, true);

        if (null === $options && JSON_ERROR_NONE !== $errorCode = json_last_error()) {
            throw new \UnexpectedValueException(json_last_error_msg(), $errorCode);
        }

        if (!is_array($options)) {
            throw new \UnexpectedValueException(sprintf('Expected geocoder options to decode to an array, "%s" returned".', gettype($options)));
        }

        return $options;
    }

    public function saveGeocoderOptions(string $type, array $options): void
    {
        $key = self::getGeocoderOptionsKey($type);
        $value = json_encode($options);

        $this->configuration->set($key, $value);
    }

    public function removeGeocoderOptions(string $type): void
    {
        $key = self::getGeocoderOptionsKey($type);

        $this->configuration->set($key, null);
    }

    private function getClosestPointSearchMode(): ClosestPointSearchMode
    {
        $value = (string) $this->configuration->get(self::CLOSES_POINT_SEARCH_MODE_KEY);

        return ClosestPointSearchMode::tryFrom($value) ?? ClosestPointSearchMode::ByPostcode();
    }

    private function setClosestPointSearchMode(ClosestPointSearchMode $mode, ?ShopConstraint $shops): void
    {
        $this->configuration->set(self::CLOSES_POINT_SEARCH_MODE_KEY, $mode->value, $shops);
    }

    private static function getGeocoderOptionsKey(string $type): string
    {
        return strtr(self::GEOCODER_OPTIONS_KEY_PATTERN, ['{TYPE}' => strtoupper($type)]);
    }
}
