<?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\Checkout;

use InPost\International\Asset\Assets;
use InPost\International\Asset\Provider\AssetsProviderInterface;
use InPost\International\Asset\Provider\GeoWidgetAssetsProvider;
use InPost\International\Checkout\Exception\CheckoutException;
use InPost\International\Checkout\Exception\FormValidationException;
use InPost\International\Checkout\Exception\InvalidPhoneNumberException;
use InPost\International\Checkout\Form\DeliveryFormHandlerInterface;
use InPost\International\Checkout\Phone\PhoneNumberValidatorInterface;
use InPost\International\Checkout\View\CheckoutViewRendererInterface;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Contracts\Service\ServiceSubscriberInterface;

if (!defined('_PS_VERSION_')) {
    exit;
}

abstract class AbstractCheckout implements CheckoutInterface, ServiceSubscriberInterface
{
    /**
     * @var \Context
     */
    protected $context;

    /**
     * @var ContainerInterface
     */
    private $container;

    public function __construct(\Context $context, ContainerInterface $container)
    {
        $this->context = $context;
        $this->container = $container;
    }

    public static function getSubscribedEvents(): array
    {
        return [];
    }

    public static function getSubscribedServices(): array
    {
        return [
            AssetsProviderInterface::class,
            CheckoutViewRendererInterface::class,
            DeliveryFormHandlerInterface::class,
            PhoneNumberValidatorInterface::class,
        ];
    }

    public function getAssets(): Assets
    {
        $assets = $this->get(AssetsProviderInterface::class)->getAssets();

        if (null !== $assets->getJavaScriptVariable(GeoWidgetAssetsProvider::GEO_WIDGET_CONFIG_VAR_NAME)) {
            $this->addCheckoutAssets($assets);
        }

        return $assets;
    }

    public function renderView(Request $request, \Cart $cart, int $carrierId): string
    {
        return $this->get(CheckoutViewRendererInterface::class)->render($cart, $carrierId);
    }

    /**
     * Appends concrete checkout specific assets.
     */
    abstract protected function addCheckoutAssets(Assets $assets): void;

    /**
     * @return bool whether the form was successfully processed
     */
    protected function processDeliveryForm(\Cart $cart, Request $request): bool
    {
        try {
            $this->get(DeliveryFormHandlerInterface::class)->handle($cart, $request);

            return true;
        } catch (FormValidationException $e) {
            foreach ($e->getErrors() as $error) {
                $this->context->controller->errors[] = $error->getMessage();
            }
        } catch (CheckoutException $e) {
            $this->context->controller->errors[] = $e->getMessage();
        }

        return false;
    }

    protected function getDeliveryAddress(\Cart $cart): \Address
    {
        if (0 >= $addressId = (int) $cart->id_address_delivery) {
            throw new CheckoutException('Delivery address is not selected.');
        }

        /** @var \Address[] $addresses */
        $addresses = $cart->getAddressCollection();
        if (null === $address = $addresses[$addressId] ?? null) {
            throw new \RuntimeException('Address not found.');
        }

        return $address;
    }

    /**
     * @return bool whether a valid phone number was provided
     */
    protected function validatePhoneNumber(\Address $address): bool
    {
        try {
            $this->get(PhoneNumberValidatorInterface::class)->validate($address);

            return true;
        } catch (InvalidPhoneNumberException $e) {
            $this->context->controller->errors[] = $e->getMessage();

            return false;
        }
    }

    /**
     * @template T of object
     *
     * @param class-string<T> $name
     *
     * @return T
     */
    protected function get(string $name): object
    {
        return $this->container->get($name);
    }
}
