<?php

declare(strict_types=1);

namespace CpCreation\VitiCore\Shop;

use CpCreation\VitiCore\Bridge\Symfony\Bundle\Context\GroupContext;
use CpCreation\VitiCore\Product\Model\ProductPriceGroup;
use CpCreation\VitiCore\Product\Model\ProductType;
use CpCreation\VitiCore\Product\Repository\ProductPriceGroupRepository;
use CpCreation\VitiCore\Product\Repository\ProductTypeRepository;
use CpCreation\VitiCore\Shop\Model\InformationBlock;
use CpCreation\VitiCore\Shop\Model\ShippingAdditional;
use CpCreation\VitiCore\Shop\Model\ShippingAdditionalManual;
use CpCreation\VitiCore\Shop\Model\ShippingAuto;
use CpCreation\VitiCore\Shop\Model\ShippingAutoSup;
use CpCreation\VitiCore\Shop\Model\ShippingConfig;
use CpCreation\VitiCore\Shop\Model\ShippingCountry;
use CpCreation\VitiCore\Shop\Model\ShippingPrice;
use CpCreation\VitiCore\Shop\Model\ShippingZone;
use CpCreation\VitiCore\Shop\Repository\ShippingAdditionalManualRepository;
use CpCreation\VitiCore\Shop\Repository\ShippingAdditionalRepository;
use CpCreation\VitiCore\Shop\Repository\ShippingAutoRepository;
use CpCreation\VitiCore\Shop\Repository\ShippingAutoSupRepository;
use CpCreation\VitiCore\Shop\Repository\ShippingConfigRepository;
use CpCreation\VitiCore\Shop\Repository\ShippingPriceRepository;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Intl\Countries;
use Symfony\Contracts\Translation\TranslatorInterface;

final class InformationBlockModalFactory
{
    public function __construct(
        private readonly ShippingPriceRepository $shippingPriceRepository,
        private readonly ShippingConfigRepository $shippingConfigRepository,
        private readonly ShippingAutoRepository $shippingAutoRepository,
        private readonly ShippingAutoSupRepository $shippingAutoSupRepository,
        private readonly ShippingAdditionalManualRepository $shippingAdditionalManualRepository,
        private readonly ShippingAdditionalRepository $shippingAdditionalRepository,
        private readonly ProductPriceGroupRepository $productPriceGroupRepository,
        private readonly ProductTypeRepository $productTypeRepository,
        private readonly RequestStack $requestStack,
        private readonly TranslatorInterface $translator,
    ) {
    }

    /**
     * @return array<string, mixed>
     */
    public function build(InformationBlock $informationBlock): array
    {
        $payload = [
            'title' => trim((string) ($informationBlock->getModalTitle() ?: $informationBlock->getTitle())),
            'description' => $informationBlock->getModalDescription(),
            'template' => $informationBlock->getModalTemplate(),
            'imagePath' => $informationBlock->getImage()?->getPath(),
            'partnerCode' => null,
            'groupLabel' => null,
            'freeShipping' => false,
            'freeShippingThresholdLabel' => null,
            'countries' => [],
            'productTypes' => [],
            'showProductTypeFilter' => false,
            'shippingRows' => [],
        ];

        if (InformationBlock::MODAL_TEMPLATE_SHIPPING_FEES !== $payload['template']) {
            return $payload;
        }

        $group = $this->resolvePriceGroup();
        $shippingConfig = $this->shippingConfigRepository->findOne();

        $payload['partnerCode'] = $this->resolvePartnerCode();
        $payload['groupLabel'] = $group instanceof ProductPriceGroup && !$group->isByDefault()
            ? $group->getName()
            : null;
        $payload['freeShipping'] = $group?->isFreeShipping() ?? false;
        $payload['freeShippingThresholdLabel'] = $shippingConfig instanceof ShippingConfig && ($shippingConfig->getAmountFreeShipping() ?? 0) > 0
            ? $this->formatAmountLabel($shippingConfig->getAmountFreeShipping())
            : null;

        if (!$group instanceof ProductPriceGroup || !$shippingConfig instanceof ShippingConfig) {
            return $payload;
        }

        $manualPrices = $this->shippingPriceRepository->findAvailableForGroup($group->getId()->toString());
        $displayTypes = $this->resolveDisplayProductTypes($manualPrices);
        $manualConditionals = $this->buildManualConditionalIndex($group);
        $autoConditionals = $this->buildAutoConditionalIndex($group);
        $autoSupIndex = $this->buildAutoSupplementIndex();

        $manualRows = [];

        foreach ($manualPrices as $shippingPrice) {
            $manualRows = [
                ...$manualRows,
                ...$this->buildManualShippingRows($shippingPrice, $group, $shippingConfig, $manualConditionals),
            ];
        }

        $manualMaxByCountry = $this->buildManualMaxQuantityIndex($manualRows);
        $manualTypeFilters = $this->buildManualTypeFilters($manualRows);

        $autoRows = [];

        foreach ($this->shippingAutoRepository->findOrder() as $shippingAuto) {
            foreach ($displayTypes as $type) {
                $row = $this->buildAutoShippingRow(
                    $shippingAuto,
                    $type,
                    $group,
                    $shippingConfig,
                    $manualMaxByCountry,
                    $autoSupIndex,
                    $autoConditionals,
                );

                if (null !== $row) {
                    $autoRows[] = $row;
                }
            }
        }

        $rows = [...$manualRows, ...$autoRows];

        usort($rows, function (array $left, array $right): int {
            return [$left['country_label'], $left['type_label'], $left['source_rank'], $left['quantity_from'], $left['context_label']]
                <=>
                [$right['country_label'], $right['type_label'], $right['source_rank'], $right['quantity_from'], $right['context_label']];
        });

        $payload['countries'] = $this->buildCountryFilters($rows);
        $payload['productTypes'] = array_values($manualTypeFilters);
        $payload['showProductTypeFilter'] = \count($manualTypeFilters) > 1;
        $payload['shippingRows'] = $rows;

        return $payload;
    }

    private function resolvePriceGroup(): ?ProductPriceGroup
    {
        $groupId = GroupContext::$groupId;

        if (!\is_string($groupId) || '' === $groupId) {
            $groupId = $this->requestStack->getSession()?->get('groupPrice');
        }

        if (\is_string($groupId) && '' !== $groupId) {
            $group = $this->productPriceGroupRepository->find($groupId);

            if ($group instanceof ProductPriceGroup) {
                return $group;
            }
        }

        return $this->productPriceGroupRepository->getGroupByDefault();
    }

    private function resolvePartnerCode(): ?string
    {
        $partnerCode = GroupContext::$partnerCode;

        if (\is_string($partnerCode) && '' !== $partnerCode) {
            return $partnerCode;
        }

        $partnerCode = $this->requestStack->getSession()?->get('partnerCode');

        return \is_string($partnerCode) && '' !== $partnerCode ? $partnerCode : null;
    }

    /**
     * @param list<ShippingPrice> $manualPrices
     *
     * @return list<ProductType>
     */
    private function resolveDisplayProductTypes(array $manualPrices): array
    {
        $typesBySlug = [];

        foreach ($this->productTypeRepository->findAll() as $type) {
            if (!$type instanceof ProductType || $type->isAccessory()) {
                continue;
            }

            $slug = trim((string) $type->getSlug());

            if ('' === $slug) {
                continue;
            }

            $typesBySlug[$slug] = $type;
        }

        foreach ($manualPrices as $shippingPrice) {
            foreach ($shippingPrice->getTypes() as $type) {
                if (!$type instanceof ProductType) {
                    continue;
                }

                $slug = trim((string) $type->getSlug());

                if ('' !== $slug) {
                    $typesBySlug[$slug] = $type;
                }
            }
        }

        $types = array_values($typesBySlug);

        usort($types, static fn (ProductType $left, ProductType $right): int => ($left->getPosition() ?? 0) <=> ($right->getPosition() ?? 0));

        return $types;
    }

    /**
     * @return array<string, list<ShippingAdditionalManual>>
     */
    private function buildManualConditionalIndex(ProductPriceGroup $group): array
    {
        $index = [];

        foreach ($this->shippingAdditionalManualRepository->findAll() as $additional) {
            if (!$additional instanceof ShippingAdditionalManual || !$additional->getGroups()->contains($group)) {
                continue;
            }

            foreach ($additional->getZones() as $zone) {
                foreach ($additional->getTypes() as $type) {
                    $typeSlug = trim((string) $type->getSlug());

                    if ('' === $typeSlug) {
                        continue;
                    }

                    $key = $zone->getId()->toString().'|'.$typeSlug;
                    $index[$key] ??= [];
                    $index[$key][] = $additional;
                }
            }
        }

        return $index;
    }

    /**
     * @return array<string, list<ShippingAdditional>>
     */
    private function buildAutoConditionalIndex(ProductPriceGroup $group): array
    {
        $index = [];

        foreach ($this->shippingAdditionalRepository->findAll() as $additional) {
            if (!$additional instanceof ShippingAdditional || !$additional->getGroups()->contains($group)) {
                continue;
            }

            foreach ($additional->getCounties() as $county) {
                $countyCode = trim((string) $county->getCode());

                if ('' === $countyCode) {
                    continue;
                }

                $index[$countyCode] ??= [];
                $index[$countyCode][] = $additional;
            }
        }

        return $index;
    }

    /**
     * @return array<string, list<ShippingAutoSup>>
     */
    private function buildAutoSupplementIndex(): array
    {
        $index = [];

        foreach ($this->shippingAutoSupRepository->findOrder() as $shippingAutoSup) {
            $key = $this->buildAutoLocationKey($shippingAutoSup->getCountry(), $shippingAutoSup->getCounty());
            $index[$key] ??= [];
            $index[$key][] = $shippingAutoSup;
        }

        return $index;
    }

    /**
     * @param array<string, list<ShippingAdditionalManual>> $manualConditionals
     *
     * @return list<array<string, mixed>>
     */
    private function buildManualShippingRows(
        ShippingPrice $shippingPrice,
        ProductPriceGroup $group,
        ShippingConfig $shippingConfig,
        array $manualConditionals,
    ): array {
        $rows = [];
        $seen = [];

        foreach ($shippingPrice->getZones() as $zone) {
            foreach ($zone->getCountries() as $country) {
                if (!$country instanceof ShippingCountry) {
                    continue;
                }

                $countryCode = trim((string) $country->getCode());

                if ('' === $countryCode) {
                    continue;
                }

                foreach ($shippingPrice->getTypes() as $type) {
                    if (!$type instanceof ProductType || !$this->canRenderType($type, true)) {
                        continue;
                    }

                    $typeSlug = trim((string) $type->getSlug());
                    $seenKey = $shippingPrice->getId()->toString().'|'.$countryCode.'|'.$typeSlug;

                    if (isset($seen[$seenKey])) {
                        continue;
                    }

                    $seen[$seenKey] = true;
                    $rows[] = $this->buildManualShippingRow(
                        $shippingPrice,
                        $country,
                        $type,
                        $group,
                        $shippingConfig,
                        $manualConditionals,
                    );
                }
            }
        }

        return $rows;
    }

    /**
     * @param array<string, list<ShippingAdditionalManual>> $manualConditionals
     *
     * @return array<string, mixed>
     */
    private function buildManualShippingRow(
        ShippingPrice $shippingPrice,
        ShippingCountry $country,
        ProductType $type,
        ProductPriceGroup $group,
        ShippingConfig $shippingConfig,
        array $manualConditionals,
    ): array {
        $minEquivalentQuantity = $this->resolveMinimumQuantity($shippingPrice->getQtyOf());
        $maxEquivalentQuantity = max($minEquivalentQuantity, (int) ($shippingPrice->getQtyAt() ?? $minEquivalentQuantity));
        $rawBasePrice = $this->resolveShippingPriceBaseAmount($shippingPrice, $minEquivalentQuantity);
        $displayTotal = $rawBasePrice;
        $tooltipLines = [
            $this->translator->trans('shop.information_modal_shipping_calc_basis', [
                '%quantity%' => $this->formatNumber($minEquivalentQuantity),
            ]),
            $this->translator->trans('shop.information_modal_shipping_calc_base', [
                '%amount%' => $this->formatAmountLabel($rawBasePrice),
            ]),
        ];

        if (($shippingPrice->getPrivateMargePercent() ?? 0.0) > 0) {
            $amount = $this->roundMoney($displayTotal * (float) $shippingPrice->getPrivateMargePercent());
            $displayTotal += $amount;
            $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_private_percent', [
                '%percent%' => $this->formatPercent((float) $shippingPrice->getPrivateMargePercent()),
                '%amount%' => $this->formatAmountLabel($amount),
            ]);
        }

        if (($shippingPrice->getPrivateMarge() ?? 0) > 0) {
            $amount = (int) $shippingPrice->getPrivateMarge();
            $displayTotal += $amount;
            $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_private_amount', [
                '%amount%' => $this->formatAmountLabel($amount),
            ]);
        }

        [$manualAdditionalAmount, $manualSupplementLabels] = $this->resolveManualAdditionalSupplementsForCountryAndType(
            $shippingPrice,
            $country,
            $type,
            $group,
            $shippingConfig,
            $minEquivalentQuantity,
        );

        $displayTotal += $manualAdditionalAmount;
        $tooltipLines = [...$tooltipLines, ...$manualSupplementLabels];

        if ($shippingPrice->isConfigGlobal()) {
            if (($shippingConfig->getAmountFixedManu() ?? 0) > 0) {
                $amount = (int) $shippingConfig->getAmountFixedManu();
                $displayTotal += $amount;
                $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_manual_fixed', [
                    '%amount%' => $this->formatAmountLabel($amount),
                ]);
            }

            if (($shippingConfig->getMarginClientManu() ?? 0.0) > 0) {
                $amount = $this->roundMoney($displayTotal * (float) $shippingConfig->getMarginClientManu());
                $displayTotal += $amount;
                $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_manual_margin_client', [
                    '%percent%' => $this->formatPercent((float) $shippingConfig->getMarginClientManu()),
                    '%amount%' => $this->formatAmountLabel($amount),
                ]);
            }

            if (($shippingConfig->getTvaManu() ?? 0.0) > 0) {
                $amount = $this->roundMoney($displayTotal * (float) $shippingConfig->getTvaManu());
                $displayTotal += $amount;
                $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_manual_tva', [
                    '%percent%' => $this->formatPercent((float) $shippingConfig->getTvaManu()),
                    '%amount%' => $this->formatAmountLabel($amount),
                ]);
            }

            if (($shippingConfig->getAmountByBottleTTC() ?? 0) > 0) {
                $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_manual_per_bottle_possible', [
                    '%amount%' => $this->formatAmountLabel((int) (($shippingConfig->getAmountByBottleTTC() ?? 0) * $minEquivalentQuantity)),
                ]);
            }

            if (($shippingConfig->getMarginManu() ?? 0.0) > 0 || ($shippingConfig->getAmountMinVDManu() ?? 0) > 0) {
                $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_manual_variable_note');
            }
        }

        $tooltipLines = [
            ...$tooltipLines,
            ...$this->buildManualConditionalTooltipLines(
                $shippingPrice,
                $country,
                $type,
                $shippingConfig,
                $manualConditionals,
                $minEquivalentQuantity,
                $maxEquivalentQuantity,
            ),
        ];

        $countryLabel = $this->resolveCountryLabel($country->getCode());
        $typeLabel = trim((string) $type->getName());

        return [
            'key' => $shippingPrice->getId()->toString().'|'.$country->getCode().'|'.trim((string) $type->getSlug()),
            'country_code' => (string) $country->getCode(),
            'country_filter' => $this->normalizeCountryFilterValue($countryLabel),
            'country_label' => $countryLabel,
            'type_slug' => trim((string) $type->getSlug()),
            'type_label' => $typeLabel,
            'type_equivalency_label' => $this->formatTypeEquivalencyLabel($type, true),
            'context_label' => $countryLabel,
            'quantity_from' => $minEquivalentQuantity,
            'quantity_to' => $maxEquivalentQuantity,
            'quantity_label' => $this->formatQuantityLabelForType($minEquivalentQuantity, $maxEquivalentQuantity, $type, true),
            'total_price_label' => $this->formatAmountLabel($displayTotal),
            'tooltip_lines' => [],
            'show_tooltip' => false,
            'source_rank' => 0,
        ];
    }

    /**
     * @param array<string, list<ShippingAutoSup>> $autoSupIndex
     * @param array<string, list<ShippingAdditional>> $autoConditionals
     * @param array<string, int> $manualMaxByCountry
     *
     * @return array<string, mixed>|null
     */
    private function buildAutoShippingRow(
        ShippingAuto $shippingAuto,
        ProductType $type,
        ProductPriceGroup $group,
        ShippingConfig $shippingConfig,
        array $manualMaxByCountry,
        array $autoSupIndex,
        array $autoConditionals,
    ): ?array {
        if (!$this->canRenderType($type, false)) {
            return null;
        }

        $countryCode = trim((string) $shippingAuto->getCountry());
        $countyCode = trim((string) $shippingAuto->getCounty());
        $countryLabel = $this->resolveCountryLabel($countryCode);
        $autoMinEquivalentQuantity = $this->resolveMinimumQuantity($shippingAuto->getQtyOf());
        $effectiveMinEquivalentQuantity = max(
            $autoMinEquivalentQuantity,
            ($manualMaxByCountry[$this->normalizeCountryFilterValue($countryLabel)] ?? 0) + 1
        );
        $maxEquivalentQuantity = max($effectiveMinEquivalentQuantity, (int) ($shippingAuto->getQtyAt() ?? $effectiveMinEquivalentQuantity));

        if ((int) ($shippingAuto->getQtyAt() ?? 0) < $effectiveMinEquivalentQuantity) {
            return null;
        }

        $displayTotal = $this->resolveAutoBaseAmount($shippingAuto, $effectiveMinEquivalentQuantity);
        $tooltipLines = [
            $this->translator->trans('shop.information_modal_shipping_calc_basis', [
                '%quantity%' => $this->formatNumber($effectiveMinEquivalentQuantity),
            ]),
            $this->translator->trans('shop.information_modal_shipping_calc_base', [
                '%amount%' => $this->formatAmountLabel($displayTotal),
            ]),
        ];

        foreach ($this->shippingAdditionalRepository->findByQuantityAndByCountyAndByGroup(
            $effectiveMinEquivalentQuantity,
            $countyCode,
            $group->getId()->toString(),
        ) as $shippingAdditional) {
            $amount = (int) ($shippingAdditional->getPrice() ?? 0);
            $displayTotal += $amount;
            $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_auto_additional', [
                '%amount%' => $this->formatAmountLabel($amount),
            ]);
        }

        if (($shippingConfig->getAmountFixedBeforeMargeAuto() ?? 0) > 0) {
            $amount = (int) $shippingConfig->getAmountFixedBeforeMargeAuto();
            $displayTotal += $amount;
            $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_auto_fixed_before', [
                '%amount%' => $this->formatAmountLabel($amount),
            ]);
        }

        if (($shippingConfig->getMarginAuto() ?? 0.0) > 0) {
            $amount = (int) ceil($displayTotal * (float) $shippingConfig->getMarginAuto());
            $displayTotal += $amount;
            $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_auto_margin', [
                '%percent%' => $this->formatPercent((float) $shippingConfig->getMarginAuto()),
                '%amount%' => $this->formatAmountLabel($amount),
            ]);
        }

        if (($shippingConfig->getMarginGazoleAuto() ?? 0.0) > 0) {
            $amount = (int) ceil($displayTotal * (float) $shippingConfig->getMarginGazoleAuto());
            $displayTotal += $amount;
            $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_auto_fuel', [
                '%percent%' => $this->formatPercent((float) $shippingConfig->getMarginGazoleAuto()),
                '%amount%' => $this->formatAmountLabel($amount),
            ]);
        }

        if (($shippingConfig->getMarginClientAuto() ?? 0.0) > 0) {
            $amount = (int) ceil($displayTotal * (float) $shippingConfig->getMarginClientAuto());
            $displayTotal += $amount;
            $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_auto_margin_client', [
                '%percent%' => $this->formatPercent((float) $shippingConfig->getMarginClientAuto()),
                '%amount%' => $this->formatAmountLabel($amount),
            ]);
        }

        if (($shippingConfig->getAmountFixedAuto() ?? 0) > 0) {
            $amount = (int) $shippingConfig->getAmountFixedAuto();
            $displayTotal += $amount;
            $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_auto_fixed', [
                '%amount%' => $this->formatAmountLabel($amount),
            ]);
        }

        if (($shippingConfig->getAmountAutoByBottleTTC() ?? 0) > 0) {
            $amount = (int) (($shippingConfig->getAmountAutoByBottleTTC() ?? 0) * $effectiveMinEquivalentQuantity);
            $displayTotal += $amount;
            $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_auto_per_bottle', [
                '%amount%' => $this->formatAmountLabel($amount),
            ]);
        }

        if (($shippingConfig->getAmountPercentVDAuto() ?? 0.0) > 0 || ($shippingConfig->getAmountVDAuto() ?? 0) > 0) {
            $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_auto_variable_note');
        }

        $locationKey = $this->buildAutoLocationKey($shippingAuto->getCountry(), $shippingAuto->getCounty());
        $autoSupItems = $autoSupIndex[$locationKey] ?? [];

        if ($shippingAuto->isHM()) {
            $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_auto_hm_rate');
        } elseif ([] !== $autoSupItems && ($shippingConfig->getHighMountainAmount() ?? 0) > 0) {
            $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_auto_hm_possible', [
                '%amount%' => $this->formatAmountLabel((int) $shippingConfig->getHighMountainAmount()),
                '%count%' => \count($autoSupItems),
            ]);
            $tooltipLines[] = $this->translator->trans('shop.information_modal_shipping_auto_hm_cities', [
                '%cities%' => $this->formatCityList($autoSupItems),
            ]);
        }

        $autoConditionalTooltipLines = $this->buildAutoConditionalTooltipLines(
            $countyCode,
            $autoConditionals[$countyCode] ?? [],
            $effectiveMinEquivalentQuantity,
            $maxEquivalentQuantity,
        );

        $typeLabel = trim((string) $type->getName());

        return [
            'key' => $shippingAuto->getId()->toString().'|'.$type->getSlug(),
            'country_code' => $countryCode,
            'country_filter' => $this->normalizeCountryFilterValue($countryLabel),
            'country_label' => $countryLabel,
            'type_slug' => trim((string) $type->getSlug()),
            'type_label' => $typeLabel,
            'type_equivalency_label' => $this->formatTypeEquivalencyLabel($type, false),
            'context_label' => trim($countryLabel.' / '.$countyCode),
            'quantity_from' => $effectiveMinEquivalentQuantity,
            'quantity_to' => $maxEquivalentQuantity,
            'quantity_label' => $this->formatQuantityLabelForType($effectiveMinEquivalentQuantity, $maxEquivalentQuantity, $type, false),
            'total_price_label' => $this->formatAmountLabel($displayTotal),
            'tooltip_lines' => $this->uniqueLines($autoConditionalTooltipLines),
            'show_tooltip' => [] !== $autoConditionalTooltipLines,
            'source_rank' => 1,
        ];
    }

    /**
     * @param array<string, list<ShippingAdditionalManual>> $manualConditionals
     *
     * @return list<string>
     */
    private function buildManualConditionalTooltipLines(
        ShippingPrice $shippingPrice,
        ShippingCountry $country,
        ProductType $type,
        ShippingConfig $shippingConfig,
        array $manualConditionals,
        int $minQuantity,
        int $maxQuantity,
    ): array {
        $lines = [];
        $seen = [];

        foreach ($shippingPrice->getZones() as $zone) {
            if (!$this->zoneContainsCountry($zone, $country->getCode())) {
                continue;
            }

            $conditionals = $manualConditionals[$zone->getId()->toString().'|'.trim((string) $type->getSlug())] ?? [];

            foreach ($conditionals as $additional) {
                if (isset($seen[$additional->getId()->toString()]) || !$this->isRangeOverlapping($minQuantity, $maxQuantity, $additional->getQtyOf(), $additional->getQtyAt())) {
                    continue;
                }

                $seen[$additional->getId()->toString()] = true;
                $amount = null !== $additional->getPackagePrice()
                    ? (int) $additional->getPackagePrice()
                    : (int) (($additional->getBottlePrice() ?? 0) * $minQuantity);

                if ($additional->isHt() && ($shippingConfig->getTvaManu() ?? 0.0) > 0) {
                    $amount = $this->roundMoney($amount * (1 + (float) $shippingConfig->getTvaManu()));
                }

                $lines[] = $this->translator->trans('shop.information_modal_shipping_manual_additional_possible', [
                    '%amount%' => $this->formatAmountLabel($amount),
                    '%period%' => $this->formatDatePeriod($additional->getDateOf(), $additional->getDateAt()),
                ]);
            }
        }

        return $lines;
    }

    /**
     * @param list<ShippingAdditional> $conditionals
     *
     * @return list<string>
     */
    private function buildAutoConditionalTooltipLines(
        string $countyCode,
        array $conditionals,
        int $minQuantity,
        int $maxQuantity,
    ): array {
        $lines = [];
        $seen = [];

        foreach ($conditionals as $additional) {
            if (isset($seen[$additional->getId()->toString()]) || !$this->isRangeOverlapping($minQuantity, $maxQuantity, $additional->getQtyOf(), $additional->getQtyAt())) {
                continue;
            }

            $seen[$additional->getId()->toString()] = true;
            $lines[] = $this->translator->trans('shop.information_modal_shipping_auto_additional_possible', [
                '%county%' => $countyCode,
                '%amount%' => $this->formatAmountLabel((int) ($additional->getPrice() ?? 0)),
                '%period%' => $this->formatDatePeriod($additional->getDateOf(), $additional->getDateAt()),
            ]);
        }

        return $lines;
    }

    /**
     * @return array{int, list<string>}
     */
    private function resolveManualAdditionalSupplementsForCountryAndType(
        ShippingPrice $shippingPrice,
        ShippingCountry $country,
        ProductType $type,
        ProductPriceGroup $group,
        ShippingConfig $shippingConfig,
        int $quantity,
    ): array {
        $total = 0;
        $labels = [];
        $seen = [];

        foreach ($shippingPrice->getZones() as $zone) {
            if (!$this->zoneContainsCountry($zone, $country->getCode())) {
                continue;
            }

            $additional = $this->shippingAdditionalManualRepository->findByQuantityAndByTypeAndByZoneAndByGroup(
                $quantity,
                trim((string) $type->getSlug()),
                $zone,
                $group->getId()->toString(),
            );

            if (!$additional instanceof ShippingAdditionalManual || isset($seen[$additional->getId()->toString()])) {
                continue;
            }

            $seen[$additional->getId()->toString()] = true;

            $amount = null !== $additional->getPackagePrice()
                ? (int) $additional->getPackagePrice()
                : (int) (($additional->getBottlePrice() ?? 0) * $quantity);

            if ($additional->isHt() && ($shippingConfig->getTvaManu() ?? 0.0) > 0) {
                $amount = $this->roundMoney($amount * (1 + (float) $shippingConfig->getTvaManu()));
            }

            $total += $amount;
            $labels[] = $this->translator->trans('shop.information_modal_shipping_manual_additional', [
                '%amount%' => $this->formatAmountLabel($amount),
            ]);
        }

        return [$total, $labels];
    }

    /**
     * @param list<array<string, mixed>> $rows
     *
     * @return array<string, array{value: string, label: string}>
     */
    private function buildManualTypeFilters(array $rows): array
    {
        $filters = [];

        foreach ($rows as $row) {
            $filters[$row['type_slug']] = [
                'value' => $row['type_slug'],
                'label' => $row['type_label'],
            ];
        }

        uasort($filters, static fn (array $left, array $right): int => $left['label'] <=> $right['label']);

        return $filters;
    }

    /**
     * @param list<array<string, mixed>> $rows
     *
     * @return list<array{value: string, label: string}>
     */
    private function buildCountryFilters(array $rows): array
    {
        $filters = [];

        foreach ($rows as $row) {
            $filters[$row['country_filter']] = [
                'value' => $row['country_filter'],
                'label' => $row['country_label'],
            ];
        }

        uasort($filters, static fn (array $left, array $right): int => $left['label'] <=> $right['label']);

        return array_values($filters);
    }

    /**
     * @param list<array<string, mixed>> $rows
     *
     * @return array<string, int>
     */
    private function buildManualMaxQuantityIndex(array $rows): array
    {
        $index = [];

        foreach ($rows as $row) {
            $countryKey = (string) $row['country_filter'];
            $index[$countryKey] = max($index[$countryKey] ?? 0, (int) $row['quantity_to']);
        }

        return $index;
    }

    private function canRenderType(ProductType $type, bool $manual): bool
    {
        $equivalency = $manual ? ($type->getEquivalencyManu() ?? 0.0) : ($type->getEquivalency() ?? 0.0);

        if ($equivalency <= 0) {
            return false;
        }

        return $manual ? !$type->isDisabledShippingManu() : !$type->isDisabledShippingAuto();
    }

    private function resolveShippingPriceBaseAmount(ShippingPrice $shippingPrice, int $quantity): int
    {
        if (null !== $shippingPrice->getBottlePrice() && $shippingPrice->getBottlePrice() > 0) {
            return (int) $shippingPrice->getBottlePrice() * $quantity;
        }

        return (int) ($shippingPrice->getPackagePrice() ?? 0);
    }

    private function resolveAutoBaseAmount(ShippingAuto $shippingAuto, int $quantity): int
    {
        if (null !== $shippingAuto->getBottlePrice() && $shippingAuto->getBottlePrice() > 0) {
            return (int) $shippingAuto->getBottlePrice() * $quantity;
        }

        return (int) ($shippingAuto->getPackagePrice() ?? 0);
    }

    private function resolveMinimumQuantity(?int $qtyOf): int
    {
        return max(1, (int) $qtyOf + 1);
    }

    private function formatAmountLabel(?int $amount): string
    {
        if (null === $amount) {
            return '-';
        }

        if (0 === $amount) {
            return $this->translator->trans('shop.offered');
        }

        return number_format($amount / 100, 2, ',', ' ').' €';
    }

    private function formatPercent(float $value): string
    {
        return number_format($value * 100, 2, ',', ' ').' %';
    }

    private function formatNumber(float|int $value): string
    {
        if ((float) ((int) $value) === (float) $value) {
            return number_format((float) $value, 0, ',', ' ');
        }

        return rtrim(rtrim(number_format((float) $value, 2, ',', ' '), '0'), ',');
    }

    private function formatTypeEquivalencyLabel(ProductType $type, bool $manual): string
    {
        $equivalency = $manual ? ($type->getEquivalencyManu() ?? 1.0) : ($type->getEquivalency() ?? 1.0);

        return $this->translator->trans('shop.information_modal_shipping_type_equivalency', [
            '%equivalency%' => $this->formatNumber($equivalency),
        ]);
    }

    private function formatQuantityLabelForType(int $minEquivalent, int $maxEquivalent, ProductType $type, bool $manual): string
    {
        $equivalency = max(0.01, $manual ? ($type->getEquivalencyManu() ?? 1.0) : ($type->getEquivalency() ?? 1.0));
        $minUnits = $this->convertEquivalentMinToUnits($minEquivalent, $equivalency);
        $maxUnits = $this->convertEquivalentMaxToUnits($maxEquivalent, $equivalency);
        $typeLabel = trim((string) $type->getName());

        if ($minUnits === $maxUnits) {
            $label = $this->formatNumber($minUnits).' '.$this->resolveQuantityUnitLabel($typeLabel, $minUnits);
        } else {
            $label = $this->formatNumber($minUnits).' - '.$this->formatNumber($maxUnits).' '.$this->resolveQuantityUnitLabel($typeLabel, $maxUnits);
        }

        if (abs($equivalency - 1.0) < 0.0001) {
            return $label;
        }

        return $label.' ('.$this->translator->trans('shop.information_modal_shipping_equivalent_range', [
            '%from%' => $this->formatNumber($minEquivalent),
            '%to%' => $this->formatNumber($maxEquivalent),
        ]).')';
    }

    private function convertEquivalentMinToUnits(int $equivalentQuantity, float $equivalency): float|int
    {
        return (int) ceil($equivalentQuantity / $equivalency);
    }

    private function convertEquivalentMaxToUnits(int $equivalentQuantity, float $equivalency): float|int
    {
        return max(1, (int) floor($equivalentQuantity / $equivalency));
    }

    private function resolveQuantityUnitLabel(string $typeLabel, float|int $quantity): string
    {
        $typeLabel = trim($typeLabel);
        $normalizedLabel = mb_strtolower($typeLabel);

        if ((float) $quantity <= 1) {
            return match ($normalizedLabel) {
                'bouteilles' => $this->translator->trans('shop.information_modal_shipping_bottle_singular'),
                'bottles' => $this->translator->trans('shop.information_modal_shipping_bottle_singular'),
                default => $typeLabel,
            };
        }

        return match ($normalizedLabel) {
            'bouteille', 'bouteilles', 'bottle', 'bottles' => $this->translator->trans('shop.information_modal_shipping_bottle_plural'),
            default => $this->pluralizeTypeLabel($typeLabel),
        };
    }

    private function pluralizeTypeLabel(string $typeLabel): string
    {
        $label = trim($typeLabel);
        $normalizedLabel = mb_strtolower($label);

        if ('' === $label || preg_match('/(s|x|z)$/iu', $normalizedLabel)) {
            return $label;
        }

        if (preg_match('/[^aeiou]y$/iu', $normalizedLabel)) {
            return mb_substr($label, 0, -1).'ies';
        }

        if (preg_match('/(s|sh|ch|x|z)$/iu', $normalizedLabel)) {
            return $label.'es';
        }

        return $label.'s';
    }

    private function resolveCountryLabel(?string $countryCode): string
    {
        $countryCode = trim((string) $countryCode);

        if ('' !== $countryCode && Countries::exists($countryCode)) {
            return Countries::getName($countryCode);
        }

        return $countryCode;
    }

    private function normalizeCountryFilterValue(string $countryLabel): string
    {
        $countryLabel = trim(mb_strtolower($countryLabel));

        return '' !== $countryLabel ? $countryLabel : 'unknown';
    }

    private function formatDatePeriod(?int $dateOf, ?int $dateAt): string
    {
        $dateOf = $dateOf ?? 101;
        $dateAt = $dateAt ?? 1231;

        if (101 === $dateOf && 1231 === $dateAt) {
            return $this->translator->trans('shop.information_modal_shipping_all_year');
        }

        return $this->translator->trans('shop.information_modal_shipping_period', [
            '%from%' => $this->formatMonthDay($dateOf),
            '%to%' => $this->formatMonthDay($dateAt),
        ]);
    }

    private function formatMonthDay(int $value): string
    {
        $day = str_pad((string) ($value % 100), 2, '0', \STR_PAD_LEFT);
        $month = max(1, (int) floor($value / 100));

        return str_pad((string) $day, 2, '0', \STR_PAD_LEFT).'/'.str_pad((string) $month, 2, '0', \STR_PAD_LEFT);
    }

    private function zoneContainsCountry(ShippingZone $zone, ?string $countryCode): bool
    {
        foreach ($zone->getCountries() as $country) {
            if ($country instanceof ShippingCountry && $country->getCode() === $countryCode) {
                return true;
            }
        }

        return false;
    }

    private function isRangeOverlapping(int $minQuantity, int $maxQuantity, ?int $qtyOf, ?int $qtyAt): bool
    {
        $additionalMin = $this->resolveMinimumQuantity($qtyOf);
        $additionalMax = max($additionalMin, (int) ($qtyAt ?? $additionalMin));

        return $additionalMin <= $maxQuantity && $additionalMax >= $minQuantity;
    }

    /**
     * @param list<ShippingAutoSup> $items
     */
    private function formatCityList(array $items): string
    {
        $cities = [];

        foreach ($items as $item) {
            $city = trim((string) $item->getCity());

            if ('' !== $city) {
                $cities[] = $city;
            }
        }

        $cities = array_values(array_unique($cities));

        if (\count($cities) > 4) {
            $cities = [...array_slice($cities, 0, 4), '...'];
        }

        return implode(', ', $cities);
    }

    /**
     * @param list<string> $lines
     *
     * @return list<string>
     */
    private function uniqueLines(array $lines): array
    {
        $lines = array_values(array_unique(array_filter(array_map(
            static fn (string $line): string => trim($line),
            $lines,
        ))));

        return $lines;
    }

    private function buildAutoLocationKey(?string $country, ?string $county): string
    {
        return sprintf('%s|%s', (string) $country, (string) $county);
    }

    private function roundMoney(float $value): int
    {
        return (int) round($value, 0, \PHP_ROUND_HALF_UP);
    }
}
