<?php

declare(strict_types=1);

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Bridge\Symfony\Bundle\Utils;

use Cocur\Slugify\Slugify;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Filesystem\Filesystem;
use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Cart\Model\PaymentMethod;
use CpCreation\VitiCore\Contact\Model\Contact;
use CpCreation\VitiCore\Shop\Model\ShopConfig;
use CpCreation\VitiCore\Shop\Repository\ShopConfigRepository;
use Knp\Snappy\Pdf as SnappyPdf;
use Spatie\Browsershot\Browsershot;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Environment;

class Pdf
{
    public function __construct(
        private SnappyPdf $pdf,
        private Environment $twig,
        private Filesystem $filesystem,
        private TranslatorInterface $translator,
        private ShopConfigRepository $shopConfigRepository,
        private string $kernelProjectDir = '',
        private string $includePathNode = '',
        private string $includePathNpm = '',
        private string $includePath = '',
        private string $includePathChromium = '',
    ) {
    }

    /**
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     */
    public function createPdfOrder(Contact $contact, Cart $cart, ?string $locale = null, string $documentLabelKey = 'checkout_design.document_order'): string
    {
        return $this->createDocumentPdf($contact, $cart, false, 'pdf/order.html.twig', $locale, $documentLabelKey);
    }

    /**
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     */
    public function createPdfOrderAdmin(Contact $contact, Cart $cart, ?string $locale = null, string $documentLabelKey = 'checkout_design.document_order'): string
    {
        return $this->createDocumentPdf($contact, $cart, true, 'pdf/order.html.twig', $locale, $documentLabelKey);
    }

    /**
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     */
    public function createPdfInvoice(Contact $contact, Cart $cart, ?string $locale = null, string $documentLabelKey = 'checkout_design.document_invoice'): string
    {
        return $this->createDocumentPdf($contact, $cart, false, 'pdf/invoice.html.twig', $locale, $documentLabelKey);
    }

    /**
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     */
    public function createPdfInvoiceAdmin(Contact $contact, Cart $cart, ?string $locale = null, string $documentLabelKey = 'checkout_design.document_invoice'): string
    {
        return $this->createDocumentPdf($contact, $cart, true, 'pdf/invoice.html.twig', $locale, $documentLabelKey);
    }

    /**
     * @param list<array{cart: Cart, credit_only: bool}> $documents
     */
    public function createPdfInvoiceBatchAdmin(Contact $contact, array $documents, ?string $locale = null, string $documentLabelKey = 'Factures et avoirs'): string
    {
        if ([] === $documents) {
            throw new \RuntimeException('Aucun document de facturation à générer.');
        }

        if (null !== $locale && '' !== $locale) {
            $this->translator->setLocale($locale);
        }

        $shopConfig = $this->shopConfigRepository->findOne();
        $style = null;
        $bodies = [];

        foreach ($documents as $document) {
            $html = $this->renderDocumentHtml(
                $contact,
                $document['cart'],
                true,
                'pdf/invoice.html.twig',
                $locale,
                $document['credit_only'] ? 'Avoir' : 'checkout_design.document_invoice',
                $document['credit_only'] ? ['credit_only' => true] : [],
                $shopConfig
            );

            $style ??= $this->extractStyleBlock($html);
            $bodies[] = $this->extractBodyContent($html);
        }

        $combinedHtml = $this->buildCombinedPdfHtml(
            $bodies,
            $style ?? '',
            $this->translator->trans($documentLabelKey, [], 'messages', $locale ?: null)
        );

        return $this->storePdfFromHtml($combinedHtml, $documentLabelKey, 'facturation');
    }

    /**
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     */
    public function createPdfCreditAdmin(Contact $contact, Cart $cart, ?string $locale = null, string $documentLabelKey = 'Avoir'): string
    {
        return $this->createDocumentPdf(
            $contact,
            $cart,
            true,
            'pdf/invoice.html.twig',
            $locale,
            $documentLabelKey,
            ['credit_only' => true]
        );
    }

    /**
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     */
    public function createPdfCredit(Contact $contact, Cart $cart, ?string $locale = null, string $documentLabelKey = 'Avoir'): string
    {
        return $this->createDocumentPdf(
            $contact,
            $cart,
            false,
            'pdf/invoice.html.twig',
            $locale,
            $documentLabelKey,
            ['credit_only' => true]
        );
    }

    /**
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     */
    public function createPdfCartDocument(Contact $contact, Cart $cart, ?string $locale = null): string
    {
        return $cart->isInvoiceDocumentRequested()
            ? $this->createPdfInvoice($contact, $cart, $locale, $cart->getDocumentLabelKey())
            : $this->createPdfOrder($contact, $cart, $locale, $cart->getDocumentLabelKey());
    }

    /**
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     */
    public function createPdfCartDocumentAdmin(Contact $contact, Cart $cart, ?string $locale = null): string
    {
        return $cart->isInvoiceDocumentRequested()
            ? $this->createPdfInvoiceAdmin($contact, $cart, $locale, $cart->getDocumentLabelKey())
            : $this->createPdfOrderAdmin($contact, $cart, $locale, $cart->getDocumentLabelKey());
    }

    /**
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     */
    private function createDocumentPdf(
        Contact $contact,
        Cart $cart,
        bool $admin,
        string $template,
        ?string $locale = null,
        string $documentLabelKey = 'checkout_design.document_order',
        array $templateContext = [],
    ): string
    {
        $html = $this->renderDocumentHtml($contact, $cart, $admin, $template, $locale, $documentLabelKey, $templateContext);

        return $this->storePdfFromHtml(
            $html,
            $documentLabelKey,
            (string) $cart->getLastname(),
            $locale
        );
    }

    private function renderDocumentHtml(
        Contact $contact,
        Cart $cart,
        bool $admin,
        string $template,
        ?string $locale = null,
        string $documentLabelKey = 'checkout_design.document_order',
        array $templateContext = [],
        ?ShopConfig $shopConfig = null,
    ): string {
        if (null !== $locale && '' !== $locale) {
            $this->translator->setLocale($locale);
        }

        $documentLabel = $this->translator->trans($documentLabelKey, [], 'messages', $locale ?: null);
        $shopConfig ??= $this->shopConfigRepository->findOne();

        return $this->twig->render(
            $template,
            array_merge(
                [
                    'contact' => $contact,
                    'cart' => $cart,
                    'admin' => $admin,
                    'shop_config' => $shopConfig,
                    'document_label' => $documentLabel,
                    'invoice_context' => $this->buildInvoiceContext($cart, $shopConfig),
                    'credit_context' => $this->buildCreditContext($cart, $shopConfig),
                ],
                $templateContext
            )
        );
    }

    private function storePdfFromHtml(
        string $html,
        string $documentLabelKey,
        string $subject = '',
        ?string $locale = null,
    ): string {
        $documentLabel = $this->translator->trans($documentLabelKey, [], 'messages', $locale ?: null);
        $slugify = new Slugify();

        $fileName = $slugify->slugify($documentLabel)
            .'_'
            .$slugify->slugify($subject)
            .'_'.date('d-m-Y_H-i')
            .'_'.uniqid('', true)
            .'.pdf';

        $temporaryPath = sys_get_temp_dir().'/'.$fileName;
        $content = $this->renderPdfContent($html, $temporaryPath);

        $this->filesystem->fs('order')->write($fileName, $content);

        @unlink($temporaryPath);

        return $fileName;
    }

    private function renderPdfContent(string $html, string $targetPath): string
    {
        $this->createBrowsershot($html)->savePdf($targetPath);

        $content = file_get_contents($targetPath);

        if (false === $content) {
            throw new \RuntimeException(\sprintf('Impossible de lire le PDF généré: %s', $targetPath));
        }

        return $content;
    }

    public function saveHtmlAsPdf(string $html, string $targetPath, bool $landscape = false): void
    {
        $browsershot = $this->createBrowsershot($html)
            ->margins(8, 8, 8, 8);

        if ($landscape) {
            $browsershot->landscape();
        }

        $browsershot->savePdf($targetPath);
    }

    /**
     * @param list<string> $documentBodies
     */
    private function buildCombinedPdfHtml(array $documentBodies, string $style, string $title): string
    {
        $sections = [];
        foreach ($documentBodies as $body) {
            $sections[] = '<section class="batch-document">'.$body.'</section>';
        }

        return sprintf(
            "<!DOCTYPE html>\n<html lang=\"fr\">\n<head>\n<meta charset=\"utf-8\">\n<title>%s</title>\n<style>%s\n.batch-document { page-break-after: always; }\n.batch-document:last-child { page-break-after: auto; }\nbody { margin: 0; background: #ffffff; }\n</style>\n</head>\n<body>%s</body>\n</html>",
            htmlspecialchars($title, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8'),
            $style,
            implode("\n", $sections)
        );
    }

    private function extractStyleBlock(string $html): ?string
    {
        if (1 === preg_match('/<style[^>]*>(.*?)<\/style>/is', $html, $matches)) {
            return $matches[1];
        }

        return null;
    }

    private function extractBodyContent(string $html): string
    {
        if (1 === preg_match('/<body[^>]*>(.*)<\/body>/is', $html, $matches)) {
            return $matches[1];
        }

        return $html;
    }

    /**
     * @return array{
     *     invoice_date: \DateTimeInterface,
     *     order_date: \DateTimeInterface,
     *     payment_due_at: ?\DateTimeInterface,
     *     payment_paid_at: ?\DateTimeInterface,
     *     payment_term_label: ?string,
     *     payment_method_label: ?string,
     *     product_total_ht: int,
     *     product_total_ht_before_promos: int,
     *     product_total_ttc: int,
     *     product_tax: int,
     *     product_tax_rate_label: ?string,
     *     shipping_tax_rate_label: ?string,
     *     total_tax_rate_label: ?string,
     *     invoice_number: ?string,
     *     vat_breakdown: list<array{rate_label: string, base_ht: int, amount_tax: int}>,
     *     shipping_ht_before_promos: int,
     *     shipping_ht: int,
     *     shipping_ttc: int,
     *     shipping_tax: int,
     *     discount_ht: int,
     *     degressive_ht: int,
     *     discount_total_ht: int,
     *     promo_item_discounts: list<array{label: string, amount_ht: int}>,
     *     promo_shipping_discounts: list<array{label: string, amount_ht: int}>
     * }
     */
    private function buildInvoiceContext(Cart $cart, ?ShopConfig $shopConfig): array
    {
        $pricing = $cart->getPricing();
        $invoiceDate = $cart->getCartCheckout()?->getPayedAt()
            ?? $cart->getCartCheckout()?->getOrderedAt()
            ?? $cart->getCreatedAt();
        $orderDate = $cart->getCartCheckout()?->getOrderedAt() ?? $cart->getCreatedAt();
        $shipping = $cart->getCartShipping();
        $shippingTaxRate = max(0.0, (float) ($shipping?->taxRate ?? $pricing?->taxRate ?? 0.0));
        $shippingTTC = max(0, (int) ($shipping?->amountTTC ?? 0));
        $promoShippingTTC = $this->resolvePromoShippingDiscountTTC($cart);
        $shippingNetTTC = max(0, $shippingTTC - $promoShippingTTC);
        $shippingHT = $this->computeHTFromTTC($shippingNetTTC, $shippingTaxRate);
        $shippingTax = $shippingNetTTC - $shippingHT;
        $paymentTerm = $shopConfig?->getInvoicePaymentTerm();
        $paymentTermLabel = $this->resolveInvoicePaymentTermLabel($paymentTerm);
        $paymentMethodLabel = $this->resolvePaymentMethodLabel($cart->getPaymentMethod());
        $productTotalHT = max(0, ($pricing?->totalHT ?? 0) - $shippingHT);
        $productTotalTTC = max(0, ($pricing?->totalTTC ?? 0) - $shippingNetTTC);
        $productTax = max(0, ($pricing?->totalTax ?? 0) - $shippingTax);
        $promoItemDiscounts = $this->buildPromoItemDiscounts($cart, $pricing?->taxRate ?? 0.0);
        $promoShippingDiscounts = $this->buildPromoShippingDiscounts($cart, $shippingTaxRate);
        $promoItemsDiscountHTTotal = array_sum(array_map(static fn (array $discount): int => $discount['amount_ht'], $promoItemDiscounts));
        $promoShippingDiscountHTTotal = array_sum(array_map(static fn (array $discount): int => $discount['amount_ht'], $promoShippingDiscounts));
        $productTaxRateLabel = $this->formatTaxRatePercentLabel($pricing?->taxRate ?? 0.0);
        $shippingTaxRateLabel = $this->formatTaxRatePercentLabel($shippingTaxRate);
        $totalTaxRateLabel = $productTaxRateLabel === $shippingTaxRateLabel
            ? $productTaxRateLabel
            : null;

        return [
            'invoice_date' => $invoiceDate,
            'order_date' => $orderDate,
            'payment_due_at' => $this->resolvePaymentDueDate($invoiceDate, $paymentTerm),
            'payment_paid_at' => $cart->getCartCheckout()?->getPayedAt(),
            'payment_term_label' => $paymentTermLabel,
            'payment_method_label' => $paymentMethodLabel,
            'product_total_ht' => $productTotalHT,
            'product_total_ht_before_promos' => $productTotalHT + $promoItemsDiscountHTTotal,
            'product_total_ttc' => $productTotalTTC,
            'product_tax' => $productTax,
            'product_tax_rate_label' => $productTaxRateLabel,
            'shipping_tax_rate_label' => $shippingTaxRateLabel,
            'total_tax_rate_label' => $totalTaxRateLabel,
            'invoice_number' => $this->formatDebitInvoiceNumber($cart, $shopConfig),
            'vat_breakdown' => $this->buildVatBreakdown($cart, $shippingHT, $shippingTax),
            'shipping_ht_before_promos' => $shippingHT + $promoShippingDiscountHTTotal,
            'shipping_ht' => $shippingHT,
            'shipping_ttc' => $shippingNetTTC,
            'shipping_tax' => $shippingTax,
            'discount_ht' => $pricing?->discountHT ?? 0,
            'degressive_ht' => $pricing?->degressiveHT ?? 0,
            'discount_total_ht' => ($pricing?->discountHT ?? 0) + ($pricing?->degressiveHT ?? 0),
            'promo_item_discounts' => $promoItemDiscounts,
            'promo_shipping_discounts' => $promoShippingDiscounts,
        ];
    }

    /**
     * @return array{
     *     credit_number: ?string,
     *     credit_date: ?\DateTimeInterface,
     *     invoice_number: ?string,
     *     order_reference: ?string,
     *     mode: ?string,
     *     created_at: ?\DateTimeInterface,
     *     stripe_refund: array<string, mixed>,
     *     items: list<array{
     *         item_id?: string,
     *         product_name: string,
     *         type_name: ?string,
     *         unit_label: ?string,
     *         quantity_ordered: int,
     *         quantity_refunded: int,
     *         amount_ttc: int
     *     }>,
     *     shipping: array<string, mixed>,
     *     totals: array<string, mixed>
     * }|null
     */
    private function buildCreditContext(Cart $cart, ?ShopConfig $shopConfig): ?array
    {
        $invoice = $cart->getInvoice();

        if (null === $invoice || !$invoice->isCredit() || null === $invoice->getNumberCredit()) {
            return null;
        }

        $details = $invoice->getCreditDetails() ?? [];
        $createdAt = null;

        if (\is_string($details['created_at'] ?? null)) {
            $createdAt = new \DateTimeImmutable($details['created_at']);
        }

        $items = \is_array($details['items'] ?? null) ? $details['items'] : [];
        $shipping = \is_array($details['shipping'] ?? null) ? $details['shipping'] : [];
        $totals = \is_array($details['totals'] ?? null) ? $details['totals'] : [];
        $stripeRefund = \is_array($details['stripe_refund'] ?? null) ? $details['stripe_refund'] : [];

        [$items, $shipping, $totals] = $this->normalizeCreditAccountingDetails($cart, $items, $shipping, $totals);

        return [
            'credit_number' => $this->formatCreditInvoiceNumber($cart, $shopConfig),
            'credit_date' => $invoice->getCreditAt(),
            'invoice_number' => $this->formatDebitInvoiceNumber($cart, $shopConfig),
            'order_reference' => $cart->getToken(),
            'mode' => \is_string($details['mode'] ?? null) ? $details['mode'] : null,
            'created_at' => $createdAt,
            'stripe_refund' => $stripeRefund,
            'items' => $items,
            'shipping' => $shipping,
            'totals' => $totals,
        ];
    }

    /**
     * @param list<array<string, mixed>> $items
     * @param array<string, mixed>       $shipping
     * @param array<string, mixed>       $totals
     *
     * @return array{0: list<array<string, mixed>>, 1: array<string, mixed>, 2: array<string, mixed>}
     */
    private function normalizeCreditAccountingDetails(Cart $cart, array $items, array $shipping, array $totals): array
    {
        $pricingLinesByItem = [];
        foreach ($cart->getPricing()?->lines ?? [] as $line) {
            if (null !== $line->itemId) {
                $pricingLinesByItem[$line->itemId] = $line;
            }
        }

        $itemsHT = 0;
        $itemsTax = 0;
        $itemsTTC = 0;

        foreach ($items as $index => $item) {
            $amountTTC = max(0, (int) ($item['amount_ttc'] ?? 0));
            $quantityRefunded = max(0, (int) ($item['quantity_refunded'] ?? 0));
            $quantityOrdered = max(1, (int) ($item['quantity_ordered'] ?? 1));
            $pricingLine = \is_string($item['item_id'] ?? null) ? ($pricingLinesByItem[$item['item_id']] ?? null) : null;

            $amountHT = isset($item['amount_ht'])
                ? max(0, (int) $item['amount_ht'])
                : (null !== $pricingLine
                    ? $this->computeProratedAmount(max(0, $pricingLine->netHT), $quantityRefunded, $quantityOrdered)
                    : $this->computeHTFromTTC($amountTTC, (float) ($cart->getPricing()?->taxRate ?? 0.0)));
            $amountTax = isset($item['amount_tax'])
                ? max(0, (int) $item['amount_tax'])
                : max(0, $amountTTC - $amountHT);

            $items[$index]['amount_ht'] = $amountHT;
            $items[$index]['amount_tax'] = $amountTax;
            $items[$index]['tax_rate_label'] = $item['tax_rate_label'] ?? $this->formatTaxRateLabel($amountHT, $amountTax);

            $itemsHT += $amountHT;
            $itemsTax += $amountTax;
            $itemsTTC += $amountTTC;
        }

        $shippingTTC = max(0, (int) ($shipping['amount_ttc'] ?? $totals['shipping_ttc'] ?? 0));
        $shippingTaxRate = max(0.0, (float) ($cart->getCartShipping()?->taxRate ?? $cart->getPricing()?->taxRate ?? 0.0));
        $shippingHT = isset($shipping['amount_ht'])
            ? max(0, (int) $shipping['amount_ht'])
            : $this->computeHTFromTTC($shippingTTC, $shippingTaxRate);
        $shippingTax = isset($shipping['amount_tax'])
            ? max(0, (int) $shipping['amount_tax'])
            : max(0, $shippingTTC - $shippingHT);

        $shipping['amount_ht'] = $shippingHT;
        $shipping['amount_tax'] = $shippingTax;
        $shipping['amount_ttc'] = $shippingTTC;
        $shipping['tax_rate_label'] = $shippingTaxRate > 0.0
            ? $this->formatTaxRatePercentLabel($shippingTaxRate)
            : ($shipping['tax_rate_label'] ?? $this->formatTaxRateLabel($shippingHT, $shippingTax));

        $totals['items_ht'] = max(0, (int) ($totals['items_ht'] ?? $itemsHT));
        $totals['items_tax'] = max(0, (int) ($totals['items_tax'] ?? $itemsTax));
        $totals['items_ttc'] = max(0, (int) ($totals['items_ttc'] ?? $itemsTTC));
        $totals['shipping_ht'] = max(0, (int) ($totals['shipping_ht'] ?? $shippingHT));
        $totals['shipping_tax'] = max(0, (int) ($totals['shipping_tax'] ?? $shippingTax));
        $totals['shipping_ttc'] = max(0, (int) ($totals['shipping_ttc'] ?? $shippingTTC));
        $totals['total_ht'] = max(0, (int) ($totals['total_ht'] ?? ($totals['items_ht'] + $totals['shipping_ht'])));
        $totals['total_tax'] = max(0, (int) ($totals['total_tax'] ?? ($totals['items_tax'] + $totals['shipping_tax'])));
        $totals['total_ttc'] = max(0, (int) ($totals['total_ttc'] ?? ($totals['items_ttc'] + $totals['shipping_ttc'])));

        return [$items, $shipping, $totals];
    }

    private function computeProratedAmount(int $amount, int $quantityRefunded, int $quantityOrdered): int
    {
        return (int) round(
            ($amount * $quantityRefunded) / max(1, $quantityOrdered),
            0,
            \PHP_ROUND_HALF_UP
        );
    }

    /**
     * @return list<array{label: string, amount_ht: int}>
     */
    private function buildPromoItemDiscounts(Cart $cart, float $taxRate): array
    {
        $discounts = [];

        foreach ($cart->getPromos() as $promo) {
            if ($promo->discountItemsTTC <= 0) {
                continue;
            }

            $discounts[] = [
                'label' => $this->buildPromoDiscountLabel($promo),
                'amount_ht' => $this->computeHTFromTTC($promo->discountItemsTTC, $taxRate),
            ];
        }

        return $discounts;
    }

    /**
     * @return list<array{label: string, amount_ht: int}>
     */
    private function buildPromoShippingDiscounts(Cart $cart, float $taxRate): array
    {
        $discounts = [];

        foreach ($cart->getPromos() as $promo) {
            if ($promo->discountShippingTTC <= 0) {
                continue;
            }

            $discounts[] = [
                'label' => $this->buildPromoDiscountLabel($promo),
                'amount_ht' => $this->computeHTFromTTC($promo->discountShippingTTC, $taxRate),
            ];
        }

        return $discounts;
    }

    private function buildPromoDiscountLabel(object $promo): string
    {
        $label = '';

        if (null !== $promo->percent && $promo->percent > 0) {
            $label = number_format($promo->percent * 100, 0, ',', ' ').'%';
        } elseif (null !== $promo->amountTTC && $promo->amountTTC > 0) {
            $label = $this->formatPriceLabel($promo->amountTTC).' TTC';
        }

        if (null !== $promo->code && '' !== mb_trim($promo->code)) {
            $label .= ('' !== $label ? ' ' : '').'('.$promo->code.')';
        }

        return '' !== $label ? $label : 'promo';
    }

    private function formatPriceLabel(int $price): string
    {
        return number_format($price / 100, 2, ',', ' ');
    }

    private function formatDebitInvoiceNumber(Cart $cart, ?ShopConfig $shopConfig = null): ?string
    {
        $invoice = $cart->getInvoice();

        if (null === $invoice) {
            return null;
        }

        $number = $invoice->getNumberDebit();
        $year = $invoice->getYear();

        if (null === $number || null === $year) {
            return null;
        }

        if (null !== $shopConfig) {
            return $shopConfig->formatInvoiceReference($year, $number, false);
        }

        return implode('-', [
            (string) $year,
            sprintf('%05d', $number),
        ]);
    }

    private function formatCreditInvoiceNumber(Cart $cart, ?ShopConfig $shopConfig = null): ?string
    {
        $invoice = $cart->getInvoice();

        if (null === $invoice) {
            return null;
        }

        $number = $invoice->getNumberCredit();
        $year = $invoice->getYear();

        if (null === $number || null === $year) {
            return null;
        }

        if (null !== $shopConfig) {
            return $shopConfig->formatInvoiceReference($year, $number, true);
        }

        $parts = ['Avoir'];

        $parts[] = (string) $year;
        $parts[] = sprintf('%05d', $number);

        return implode('-', $parts);
    }

    private function resolveInvoicePaymentTermLabel(?string $paymentTerm): ?string
    {
        $labels = array_flip(ShopConfig::getInvoicePaymentTermChoices());

        return $labels[$paymentTerm] ?? null;
    }

    private function resolvePaymentMethodLabel(?PaymentMethod $paymentMethod): ?string
    {
        if (null === $paymentMethod) {
            return null;
        }

        $configuration = method_exists($paymentMethod, 'getConfiguration')
            ? $paymentMethod->getConfiguration()
            : [];

        $configuredName = $configuration['name'] ?? null;
        if (\is_string($configuredName) && '' !== $configuredName) {
            return $configuredName;
        }

        $method = $paymentMethod->getMethod();

        return match ($method) {
            'check' => $this->translator->trans('checkout_design.payment_check'),
            'bankTransfert', 'banktransfert' => $this->translator->trans('checkout_design.payment_banktransfert'),
            default => $paymentMethod->isCheckoutOnlineMethod()
                ? $this->translator->trans('checkout_design.payment_card')
                : $method,
        };
    }

    private function resolvePaymentDueDate(\DateTimeInterface $invoiceDate, ?string $paymentTerm): ?\DateTimeImmutable
    {
        $date = \DateTimeImmutable::createFromInterface($invoiceDate);

        return match ($paymentTerm) {
            ShopConfig::INVOICE_PAYMENT_TERM_IMMEDIATE => $date,
            ShopConfig::INVOICE_PAYMENT_TERM_RECEIPT => $date,
            ShopConfig::INVOICE_PAYMENT_TERM_15_DAYS => $date->modify('+15 days'),
            ShopConfig::INVOICE_PAYMENT_TERM_30_DAYS => $date->modify('+30 days'),
            ShopConfig::INVOICE_PAYMENT_TERM_30_DAYS_END_OF_MONTH => $date->modify('last day of this month')->modify('+30 days'),
            ShopConfig::INVOICE_PAYMENT_TERM_45_DAYS_END_OF_MONTH => $date->modify('last day of this month')->modify('+45 days'),
            ShopConfig::INVOICE_PAYMENT_TERM_60_DAYS => $date->modify('+60 days'),
            default => null,
        };
    }

    /**
     * @return list<array{rate_label: string, base_ht: int, amount_tax: int}>
     */
    private function buildVatBreakdown(Cart $cart, int $shippingHT, int $shippingTax): array
    {
        $pricing = $cart->getPricing();
        $breakdown = [];

        foreach ($pricing?->lines ?? [] as $line) {
            $rateLabel = $this->formatTaxRateLabel($line->netHT, $line->tax);
            if (!isset($breakdown[$rateLabel])) {
                $breakdown[$rateLabel] = [
                    'rate_label' => $rateLabel,
                    'base_ht' => 0,
                    'amount_tax' => 0,
                ];
            }

            $breakdown[$rateLabel]['base_ht'] += $line->netHT;
            $breakdown[$rateLabel]['amount_tax'] += $line->tax;
        }

        if ($shippingHT > 0 || $shippingTax > 0) {
            $rateLabel = $this->formatTaxRateLabel($shippingHT, $shippingTax);
            if (!isset($breakdown[$rateLabel])) {
                $breakdown[$rateLabel] = [
                    'rate_label' => $rateLabel,
                    'base_ht' => 0,
                    'amount_tax' => 0,
                ];
            }

            $breakdown[$rateLabel]['base_ht'] += $shippingHT;
            $breakdown[$rateLabel]['amount_tax'] += $shippingTax;
        }

        return array_values($breakdown);
    }

    private function formatTaxRateLabel(int $baseHt, int $taxAmount): string
    {
        if ($baseHt <= 0 || $taxAmount <= 0) {
            return '0%';
        }

        $rate = ($taxAmount / $baseHt) * 100;

        return number_format($rate, 1, ',', ' ').'%';
    }

    private function formatTaxRatePercentLabel(float $rate): ?string
    {
        if ($rate <= 0.0) {
            return null;
        }

        $percent = $rate * 100;
        $formatted = floor($percent) === $percent
            ? number_format($percent, 0, ',', ' ')
            : number_format($percent, 1, ',', ' ');

        return $formatted.'%';
    }

    private function resolvePromoShippingDiscountTTC(Cart $cart): int
    {
        $promoShippingTTC = 0;

        foreach ($cart->getPromos() as $promo) {
            $promoShippingTTC += max(0, (int) $promo->discountShippingTTC);
        }

        return $promoShippingTTC;
    }

    private function computeHTFromTTC(int $ttc, float $taxRate): int
    {
        if ($taxRate <= 0.0) {
            return max(0, $ttc);
        }

        return (int) round($ttc / (1 + $taxRate));
    }

    private function createBrowsershot(string $html): Browsershot
    {
        $pdf = Browsershot::html($html)
            ->emulateMedia('print')
            ->noSandbox()
            ->format('A4')
            ->ignoreHttpsErrors()
            ->showBackground()
            ->timeout(120)
            ->setOption('args', [
                '--no-sandbox',
                '--disable-dev-shm-usage',
                '--disable-gpu',
                '--disable-web-security',
            ]);

        if ($this->includePathChromium) {
            $pdf->setChromePath($this->includePathChromium);
        }
        if ($this->includePath) {
            $pdf->setIncludePath('$PATH:'.$this->includePath);
        }
        if ($this->includePathNode) {
            $pdf->setNodeBinary($this->includePathNode);
        }
        if ($this->includePathNpm) {
            $pdf->setNpmBinary($this->includePathNpm);
        }

        return $pdf;
    }
}
