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

use Doctrine\ORM\Mapping as ORM;
use InPost\International\Carrier\CarrierRepository;
use InPost\International\Carrier\CarrierType;
use InPost\International\Carrier\Exception\CarrierException;
use InPost\International\Country;

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

/**
 * @ORM\Entity(repositoryClass=CarrierRepository::class)
 * @ORM\Table(name=Carrier::TABLE_NAME)
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="type", type="string")
 * @ORM\DiscriminatorMap({
 *     CourierCarrier::TYPE = CourierCarrier::class,
 *     PointDeliveryCarrier::TYPE = PointDeliveryCarrier::class,
 * })
 */
abstract class Carrier
{
    /**
     * @internal
     */
    public const TABLE_NAME = _DB_PREFIX_ . 'inpost_intl_carrier';

    /**
     * @var int
     *
     * @ORM\Id
     * @ORM\Column(name="id_reference", type="integer")
     */
    private $referenceId;

    /**
     * @var string[]
     *
     * @ORM\Column(type="simple_array", name="countries")
     */
    private $countryCodes;

    /**
     * @var Country[]|null
     */
    private $countries;

    public function __construct(\Carrier $carrier, Country ...$countries)
    {
        $this->setCarrierId($carrier);
        $this->setCountries(...$countries);
    }

    public function getReferenceId(): int
    {
        return $this->referenceId;
    }

    /**
     * @return Country[]
     */
    public function getCountries(): array
    {
        return $this->countries ?? $this->countries = array_map([Country::class, 'from'], $this->countryCodes);
    }

    public function setCountries(Country ...$countries): void
    {
        if ([] === $countries) {
            throw new CarrierException('Carrier has to be available in at least a single country.');
        }

        $invalidCountries = array_udiff($countries, $this->getType()->getPossibleDestinationCountries(), static function (Country $c1, Country $c2): int {
            return $c1->value <=> $c2->value;
        });

        if ([] !== $invalidCountries) {
            throw new CarrierException(sprintf('Not all of the countries are valid destinations for the carrier. Invalid values: "%s".', implode('", "', self::getCountryCodes($invalidCountries))));
        }

        $this->countries = $countries;
        $this->countryCodes = self::getCountryCodes($countries);
    }

    abstract public function getType(): CarrierType;

    private function setCarrierId(\Carrier $carrier): void
    {
        if (0 >= (int) $carrier->id || 0 >= $referenceId = (int) $carrier->id_reference) {
            throw new CarrierException('Invalid carrier ID.');
        }

        if ($carrier->deleted) {
            throw new CarrierException('Cannot create an association with a deleted carrier.');
        }

        if (\InPostInternational::MODULE_NAME !== $carrier->external_module_name) {
            throw new CarrierException('Carrier is not associated with InPost International module.');
        }

        $this->referenceId = $referenceId;
    }

    private static function getCountryCodes(array $countries): array
    {
        return array_map(static function (Country $country): string {
            return $country->value;
        }, $countries);
    }
}
