<?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\Shipment\Form\Type;

use Doctrine\ORM\EntityRepository;
use InPost\International\Api\Shipment\Model\Service\Currency;
use InPost\International\Api\Shipment\Model\ValueAddedServices;
use InPost\International\Carrier\CarrierType;
use InPost\International\Common\DTO\Address;
use InPost\International\Common\Form\Type\AddressType;
use InPost\International\Common\Form\Type\ContactDetailsType;
use InPost\International\Common\Form\Type\ReferenceType;
use InPost\International\Country;
use InPost\International\Entity\PickupAddress;
use InPost\International\Enum\Form\Type\EnumType;
use InPost\International\Form\EventListener\UseInitialDataListener;
use InPost\International\Shipment\Message\CreateShipmentCommand;
use InPost\International\Shipment\ShippingMethod;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Contracts\Translation\TranslatorInterface;

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

final class ShipmentType extends AbstractType
{
    /**
     * @var TranslatorInterface
     */
    private $translator;

    public function __construct(TranslatorInterface $translator)
    {
        $this->translator = $translator;
    }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('service', EnumType::class, [
                'class' => CarrierType::class,
                'label' => $this->translator->trans('Service', [], 'Modules.Inpostinternational.Shipment'),
                'attr' => [
                    'class' => 'js-inpost-intl-service-choice',
                    'autocomplete' => 'off',
                ],
            ])
            ->add('recipient', ContactDetailsType::class, [
                'label' => $this->translator->trans('Recipient', [], 'Modules.Inpostinternational.Shipment'),
            ])
            ->add('parcel', ParcelType::class, [
                'label' => $this->translator->trans('Parcel', [], 'Modules.Inpostinternational.Shipment'),
            ])
            ->add('shippingMethod', EnumType::class, [
                'class' => ShippingMethod::class,
                'label' => $this->translator->trans('Shipping method', [], 'Modules.Inpostinternational.Shipment'),
                'attr' => [
                    'class' => 'js-inpost-intl-shipping-method-choice',
                    'autocomplete' => 'off',
                ],
            ])
            ->add('references', CollectionType::class, [
                'entry_type' => ReferenceType::class,
                'allow_add' => true,
                'allow_delete' => true,
                'by_reference' => false,
                'required' => false,
                'label' => $this->translator->trans('References', [], 'Modules.Inpostinternational.Shipment'),
                'block_prefix' => 'dynamic_collection',
            ])
            ->add('submit', SubmitType::class, [
                'label' => $this->translator->trans('Create', [], 'Admin.Actions'),
                'attr' => [
                    'class' => 'btn-primary',
                ],
            ])
            ->add('submitAndPrintLabel', SubmitType::class, [
                'label' => $this->translator->trans('Create and print label', [], 'Modules.Inpostinternational.Shipment'),
                'attr' => [
                    'class' => 'btn-primary',
                ],
            ])
            ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
                $form = $event->getForm();
                $data = $event->getData();

                if ($data instanceof CreateShipmentCommand) {
                    $service = $data->getService();
                    $shippingMethod = $data->getShippingMethod();
                    $country = $this->getDestinationCountry($data);
                } else {
                    $service = $shippingMethod = $country = null;
                }

                $this->addDestinationField($form, $service);
                $this->addOrRemovePickupAddressField($form, $shippingMethod);
                $this->addOrRemoveOptionalServiceFields($form, $country, $data);
            });

        $builder->get('service')->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
            $form = $event->getForm()->getParent();
            $this->addDestinationField($form, $event->getForm()->getData());
        });

        $builder->get('shippingMethod')->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
            $form = $event->getForm()->getParent();
            $this->addOrRemovePickupAddressField($form, $event->getForm()->getData());
        });
    }

    public function finishView(FormView $view, FormInterface $form, array $options): void
    {
        $countryChoice = $view['destination']['countryCode'] ?? $view['destination'];

        $class = $countryChoice->vars['attr']['class'] ?? '';
        $countryChoice->vars['attr']['class'] = trim($class . ' js-inpost-intl-destination-country-choice');
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => CreateShipmentCommand::class,
        ]);
    }

    private function addDestinationField(FormInterface $form, ?CarrierType $service): void
    {
        if (CarrierType::Courier() === $service) {
            $this->addDestinationAddressField($form);
        } else {
            $this->addDestinationPointField($form);
        }
    }

    private function addOrRemovePickupAddressField(FormInterface $form, ?ShippingMethod $shippingMethod): void
    {
        if (ShippingMethod::FromPoint() === $shippingMethod) {
            $form->remove('origin');
        } else {
            $this->addPickupAddressField($form);
        }
    }

    /**
     * @param mixed $data
     */
    private function addOrRemoveOptionalServiceFields(FormInterface $form, ?Country $country, $data): void
    {
        if (null === $country || ValueAddedServices::isInsuranceAvailable($country)) {
            $this->addInsuranceField($form);
        } else {
            $form->remove('insurance');

            if ($data instanceof CreateShipmentCommand) {
                $data->setInsurance(null);
            }
        }
    }

    private function addDestinationPointField(FormInterface $form): void
    {
        $child = $form->getConfig()
            ->getFormFactory()
            ->createNamedBuilder('destination', TextType::class, null, [
                'property_path' => 'destinationPointId',
                'constraints' => new NotBlank(),
                'label' => $this->translator->trans('Destination point', [], 'Modules.Inpostinternational.Shipment'),
                'attr' => [
                    'data-country' => implode(',', array_map(static function (Country $country) {
                        return $country->value;
                    }, CarrierType::PointDelivery()->getPossibleDestinationCountries())),
                ],
                'block_prefix' => 'inpost_intl_point_id',
            ])
            ->setAutoInitialize(false)
            ->addEventSubscriber(new UseInitialDataListener())
            ->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
                $form = $event->getForm();
                $parent = $form->getParent();
                $command = $parent->getData();

                if ($command instanceof CreateShipmentCommand) {
                    $command->setDestinationAddress(null);
                }

                $country = $this->getCountryByPointId($form->getData());
                $this->addOrRemoveOptionalServiceFields($parent, $country, $command);
            })
            ->getForm();

        $form->add($child);
    }

    private function addDestinationAddressField(FormInterface $form): void
    {
        $child = $form->getConfig()
            ->getFormFactory()
            ->createNamedBuilder('destination', AddressType::class, null, [
                'property_path' => 'destinationAddress',
                'constraints' => new NotBlank(),
                'label' => $this->translator->trans('Destination address', [], 'Modules.Inpostinternational.Shipment'),
                'allowed_countries' => CarrierType::Courier()->getPossibleDestinationCountries(),
                'error_bubbling' => false,
            ])
            ->setAutoInitialize(false)
            ->addEventSubscriber(new UseInitialDataListener())
            ->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
                $form = $event->getForm();
                $parent = $form->getParent();

                /** @var Address|null $data */
                $data = $form->getData();
                $country = null === $data ? null : $data->getCountryCode();

                $this->addOrRemoveOptionalServiceFields($parent, $country, $parent->getData());
            })
            ->getForm();

        $form->add($child);
    }

    private function addPickupAddressField(FormInterface $form): void
    {
        $child = $form->getConfig()
            ->getFormFactory()
            ->createNamedBuilder('origin', EntityType::class, null, [
                'class' => PickupAddress::class,
                'query_builder' => static function (EntityRepository $repository) {
                    return $repository
                        ->createQueryBuilder('a')
                        ->where('a.deleted = 0');
                },
                'property_path' => 'pickupAddress',
                'label' => $this->translator->trans('Pickup address', [], 'Modules.Inpostinternational.Pickup'),
                'constraints' => new NotBlank(),
            ])
            ->setAutoInitialize(false)
            ->addEventSubscriber(new UseInitialDataListener())
            ->getForm();

        $form->add($child);
    }

    private function addInsuranceField(FormInterface $form): void
    {
        $child = $form->getConfig()
            ->getFormFactory()
            ->createNamedBuilder('insurance', MoneyType::class, null, [
                'currency' => Currency::getDefault()->value,
                'scale' => 2,
                'required' => false,
                'label' => $this->translator->trans('Insurance', [], 'Modules.Inpostinternational.Shipment'),
            ])
            ->setAutoInitialize(false)
            ->addEventSubscriber(new UseInitialDataListener())
            ->getForm();

        $form->add($child);
    }

    private function getDestinationCountry(CreateShipmentCommand $data): ?Country
    {
        if ($address = $data->getDestinationAddress()) {
            return $address->getCountryCode();
        }

        return $this->getCountryByPointId($data->getDestinationPointId());
    }

    private function getCountryByPointId(?string $pointId): ?Country
    {
        if (null === $pointId) {
            return null;
        }

        [$countryCode] = explode('_', $pointId);

        return Country::tryFrom($countryCode);
    }
}
