<?php

declare(strict_types=1);

namespace App\Service\Pdf;

use App\Entity\Ecole;
use App\Entity\Etudiant;
use App\Entity\Inscription;
use App\Entity\InscriptionParcours;
use App\Entity\ParcoursEtudiant;

final class InscriptionPdfGenerator
{
    public function __construct(
        private readonly string $projectDir,
        private readonly PdfDocumentBranding $branding,
    ) {
    }

    public function generate(Inscription $inscription, ?Etudiant $etudiant = null): string
    {
        $numero = $inscription->getNumeroDossier() ?: 'sans-numero';
        $relativePath = 'uploads/inscriptions/fiche-inscription-'.$this->safeFileName($numero).'.pdf';
        $absolutePath = $this->projectDir.'/public/'.$relativePath;
        @mkdir(dirname($absolutePath), 0775, true);

        $pdf = new SalsabilTcpdf('P', 'mm', 'A4', true, 'UTF-8');
        $pdf->setPrintHeader(true);
        $pdf->setPrintFooter(true);
        $pdf->SetCreator('SALSABIL Madrassas');
        $pdf->SetAuthor((string) ($inscription->getEcole()?->getNom() ?: 'SALSABIL Madrassas'));
        $pdf->SetTitle($this->documentTitle($inscription).' - '.$numero);
        $pdf->SetMargins(14, 40, 14);
        $pdf->SetHeaderMargin(0);
        $pdf->SetFooterMargin(24);
        $pdf->SetAutoPageBreak(true, 29);

        $generatedAt = new \DateTimeImmutable();
        $status = $this->humanStatus($inscription->getStatut());
        $matricule = $inscription->getDisplayMatricule() ?: $etudiant?->getMatricule();
        $qrPayload = $this->branding->buildQrPayload(
            $this->documentTitle($inscription),
            $inscription->getEcole(),
            $inscription->getNumeroDossier(),
            $matricule,
            $status,
            $generatedAt,
        );
        $pdf->setSalsabilFooterContext(
            $this->footerContacts($inscription->getEcole()),
            (string) ($inscription->getEcole()?->getNom() ?: 'Madrassa'),
            $qrPayload,
        );

        $this->renderMainPage($pdf, $inscription, $etudiant, $generatedAt);
        $this->renderRulesPage($pdf, $inscription, $etudiant);

        $pdf->Output($absolutePath, 'F');

        return $relativePath;
    }

    private function renderMainPage(SalsabilTcpdf $pdf, Inscription $inscription, ?Etudiant $etudiant, \DateTimeInterface $generatedAt): void
    {
        $ecole = $inscription->getEcole();
        $primary = $this->hexToRgb($ecole?->getCouleurPrincipale() ?: '#0F5C45');
        $title = $this->documentTitle($inscription);
        $status = $this->humanStatus($inscription->getStatut());
        $matricule = $inscription->getDisplayMatricule() ?: $etudiant?->getMatricule() ?: '-';

        $pdf->setSalsabilHeaderContext(
            (string) ($ecole?->getNom() ?: 'Madrassa'),
            $title,
            $this->branding->publicImagePath($ecole?->getLogo()),
            $inscription->getNumeroDossier(),
            $matricule,
            $status,
            $primary,
        );
        $pdf->AddPage();
        $pdf->SetFont('dejavusans', '', 9);

        $cardsY = 40.0;
        $cardW = 42.5;
        $this->infoCard($pdf, 14, $cardsY, $cardW, 'Numéro de dossier', $inscription->getNumeroDossier() ?: '-', $primary);
        $this->infoCard($pdf, 60.5, $cardsY, $cardW, 'Matricule', $matricule, $primary);
        $this->infoCard($pdf, 107, $cardsY, $cardW, 'Année scolaire', $inscription->getAnneeScolaire()?->getLibelle() ?: '-', $primary);
        $this->infoCard($pdf, 153.5, $cardsY, $cardW, 'Type', $this->humanType($inscription->getTypeCandidat()), $primary);

        $this->sectionBox($pdf, 14, 63, 88, 48, 'Identité de l’étudiant(e)', [
            ['Nom complet', $inscription->getNomComplet()],
            ['Sexe', $this->humanValue($inscription->getSexe())],
            ['Naissance', $this->dateLine($inscription)],
            ['Situation', $this->humanValue($inscription->getSituationMatrimoniale())],
            ['Enfants', $inscription->hasEnfants() ? 'Oui' : 'Non'],
        ], $primary);

        $this->sectionBox($pdf, 108, 63, 88, 48, 'Contacts', [
            ['Téléphone', $inscription->getTelephonePrincipal() ?: '-'],
            ['WhatsApp', $inscription->getTelephoneWhatsapp() ?: '-'],
            ['Email', $inscription->getEmail() ?: '-'],
            ['Adresse', $inscription->getAdresse() ?: '-'],
        ], $primary);

        $pdf->SetY(116);
        if ($inscription->getStatut() === Inscription::STATUT_VALIDEE) {
            $this->renderValidatedParcoursSection($pdf, $inscription, $etudiant, $primary);
        } else {
            $this->renderRequestedParcoursSection($pdf, $inscription, $primary);
        }
        $this->renderFinancialSection($pdf, $inscription, $etudiant, $primary);

        $this->ensureSpace($pdf, 44, $inscription, $etudiant, $primary);
        $y = $pdf->GetY() + 3;
        $this->sectionBox($pdf, 14, $y, 88, 40, 'Suivi administratif', [
            ['Soumission', $inscription->getDateSoumission()?->format('d/m/Y H:i') ?: '-'],
            ['Validation', $inscription->getDateValidation()?->format('d/m/Y H:i') ?: '-'],
            ['Observation', $inscription->getObservationAdmin() ?: $inscription->getObservationCandidat() ?: '-'],
        ], $primary);

        $this->signatureBox($pdf, 108, $y, 88, 40, $primary, $ecole);

        $pdf->SetDrawColor(224, 228, 235);
        $pdf->SetFillColor(250, 251, 253);
        $contactY = max($y + 44, $pdf->GetY() + 2);
        if ($contactY + 10 > 262.0) {
            return;
        }
        $pdf->Rect(14, $contactY, 182, 10, 'DF');
        $pdf->SetTextColor(76, 86, 100);
        $pdf->SetFont('dejavusans', '', 8);
        $contact = $ecole?->getWhatsapp() ?: $ecole?->getTelephone1() ?: '-';
        $pdf->SetXY(19, $contactY + 2.4);
        $pdf->MultiCell(172, 4.5, 'Contact administration : '.$contact.'    |    Document généré le '.$generatedAt->format('d/m/Y à H:i').' via SALSABIL Madrassas.', 0, 'C');
    }

    /** @param array{0:int,1:int,2:int} $primary */
    private function renderRequestedParcoursSection(SalsabilTcpdf $pdf, Inscription $inscription, array $primary): void
    {
        $items = $this->requestedParcours($inscription);
        $this->renderSectionTitle($pdf, 'Programmes souhaités', 'Parcours demandés par le candidat avant validation administrative.', $primary);

        foreach ($items as $index => $item) {
            $this->ensureSpace($pdf, 33, $inscription, $inscription->getEtudiant(), $primary);
            $section = $item instanceof InscriptionParcours ? $item->getSectionSouhaitee() : $inscription->getSectionSouhaitee();
            $niveau = $item instanceof InscriptionParcours ? $item->getNiveauSouhaite() : $inscription->getNiveauSouhaite();
            $classe = $item instanceof InscriptionParcours ? $item->getClasseSouhaitee() : $inscription->getClasseSouhaitee();
            $langue = $item instanceof InscriptionParcours ? $item->getLangueSouhaitee() : $inscription->getLangueSouhaitee();
            $mode = $item instanceof InscriptionParcours ? $item->getModeSouhaite() : $inscription->getModeSouhaite();
            $observation = $item instanceof InscriptionParcours ? $item->getObservation() : $inscription->getObservationCandidat();
            $principal = $item instanceof InscriptionParcours ? $item->isPrincipal() : true;

            $this->renderProgramCard($pdf, sprintf('Programme %d%s', $index + 1, $principal ? ' · Principal' : ''), [
                ['Programme / section', $section?->getNom() ?: '-'],
                ['Niveau souhaité', $niveau?->getNom() ?: '-'],
                ['Classe / groupe', $classe?->getNom() ?: '-'],
                ['Langue', $this->humanValue($langue)],
                ['Mode', $this->humanValue($mode)],
                ['Observation', $observation ?: '-'],
            ], $primary);
        }
    }

    /** @param array{0:int,1:int,2:int} $primary */
    private function renderValidatedParcoursSection(SalsabilTcpdf $pdf, Inscription $inscription, ?Etudiant $etudiant, array $primary): void
    {
        $items = $this->validatedParcours($inscription, $etudiant);
        $this->renderSectionTitle($pdf, 'Parcours validés', 'Affectations confirmées après validation administrative.', $primary);

        foreach ($items as $index => $item) {
            $this->ensureSpace($pdf, 34, $inscription, $etudiant, $primary);
            if ($item instanceof ParcoursEtudiant) {
                $rows = [
                    ['Année scolaire', $item->getAnneeScolaire()?->getLibelle() ?: $inscription->getAnneeScolaire()?->getLibelle() ?: '-'],
                    ['Section', $item->getSection()?->getNom() ?: '-'],
                    ['Niveau', $item->getNiveau()?->getNom() ?: '-'],
                    ['Classe', $item->getClasse()?->getNom() ?: '-'],
                    ['Langue', $this->humanValue($item->getLangue())],
                    ['Mode', $this->humanValue($item->getMode())],
                    ['Statut', $this->humanValue($item->getStatut())],
                    ['Tarif appliqué', $this->formatMoney($item->getTarifApplique() ?: $item->getClasse()?->getTarifDefaut())],
                ];
                $principal = $item->isPrincipal();
            } else {
                $rows = [
                    ['Année scolaire', $etudiant?->getAnneeScolaire()?->getLibelle() ?: $inscription->getAnneeScolaire()?->getLibelle() ?: '-'],
                    ['Section', $etudiant?->getSection()?->getNom() ?: '-'],
                    ['Niveau', $etudiant?->getNiveau()?->getNom() ?: '-'],
                    ['Classe', $etudiant?->getClasse()?->getNom() ?: '-'],
                    ['Langue', '-'],
                    ['Mode', '-'],
                    ['Statut', 'Validée'],
                    ['Tarif appliqué', $this->formatMoney($etudiant?->getClasse()?->getTarifDefaut())],
                ];
                $principal = true;
            }

            $this->renderProgramCard($pdf, sprintf('Parcours %d%s', $index + 1, $principal ? ' · Principal' : ''), $rows, $primary);
        }
    }

    /** @param array{0:int,1:int,2:int} $primary */
    private function renderFinancialSection(SalsabilTcpdf $pdf, Inscription $inscription, ?Etudiant $etudiant, array $primary): void
    {
        $rows = $this->financialRows($inscription, $etudiant);
        $this->ensureSpace($pdf, $rows === [] ? 30 : 24 + (count($rows) * 8), $inscription, $etudiant, $primary);
        $this->renderSectionTitle($pdf, 'Modalités financières prévisionnelles', 'Ce montant est indicatif et pourra être ajusté par l’administration.', $primary);

        if ($rows === []) {
            $pdf->SetTextColor(76, 86, 100);
            $pdf->SetFillColor(250, 251, 253);
            $pdf->SetDrawColor(224, 228, 235);
            $pdf->Rect(14, $pdf->GetY(), 182, 15, 'DF');
            $pdf->SetXY(18, $pdf->GetY() + 4.3);
            $pdf->SetFont('dejavusans', '', 8.3);
            $pdf->Cell(174, 5, 'Les modalités de paiement seront confirmées par l’administration de la madrassa.', 0, 1, 'L');
            $pdf->Ln(4);

            return;
        }

        $total = 0.0;
        $html = '<table cellpadding="4" cellspacing="0" border="0" style="font-family:dejavusans;font-size:7.6px;color:#1f2937;width:100%;">';
        $html .= '<tr style="background-color:#eef2f7;font-weight:bold;color:#334155;"><td width="30%">Programme</td><td width="28%">Classe</td><td width="22%">Modalité</td><td width="20%" align="right">Montant indicatif</td></tr>';
        foreach ($rows as $row) {
            $total += $row['amount'];
            $html .= '<tr><td width="30%">'.$this->html($row['program']).'</td><td width="28%">'.$this->html($row['class']).'</td><td width="22%">'.$this->html($row['mode']).'</td><td width="20%" align="right">'.$this->html($this->formatMoney((string) $row['amount'])).'</td></tr>';
        }
        $html .= '<tr style="background-color:#f8fafc;font-weight:bold;"><td width="80%" colspan="3" align="right">Total indicatif</td><td width="20%" align="right">'.$this->html($this->formatMoney((string) $total)).'</td></tr>';
        $html .= '</table>';

        $y = $pdf->GetY();
        $pdf->SetDrawColor(224, 228, 235);
        $pdf->SetFillColor(255, 255, 255);
        $pdf->Rect(14, $y, 182, 12 + (count($rows) * 7.4), 'DF');
        $pdf->writeHTMLCell(174, 0, 18, $y + 3, $html, 0, 1, false, true, 'L');
        $pdf->Ln(4);
    }

    /** @param array{0:int,1:int,2:int} $primary */
    private function renderSectionTitle(SalsabilTcpdf $pdf, string $title, string $subtitle, array $primary): void
    {
        $this->ensureSpace($pdf, 19, null, null, $primary);
        $pdf->SetFillColor($primary[0], $primary[1], $primary[2]);
        $pdf->RoundedRect(14, $pdf->GetY(), 182, 10, 2.5, '1111', 'F');
        $pdf->SetTextColor(255, 255, 255);
        $pdf->SetFont('dejavusans', 'B', 9.2);
        $pdf->SetXY(18, $pdf->GetY() + 2.2);
        $pdf->Cell(82, 4, mb_strtoupper($title, 'UTF-8'), 0, 0, 'L');
        $pdf->SetFont('dejavusans', '', 6.8);
        $pdf->Cell(92, 4, $this->short($subtitle, 86), 0, 1, 'R');
        $pdf->Ln(3);
    }

    /** @param list<array{0:string,1:string}> $rows @param array{0:int,1:int,2:int} $primary */
    private function renderProgramCard(SalsabilTcpdf $pdf, string $title, array $rows, array $primary): void
    {
        $x = 14.0;
        $y = $pdf->GetY();
        $extraRows = max(0, count($rows) - 6);
        $h = 25.0 + ($extraRows * 4.8);
        $pdf->SetDrawColor(224, 228, 235);
        $pdf->SetFillColor(255, 255, 255);
        $pdf->Rect($x, $y, 182, $h, 'DF');
        $pdf->SetFillColor(250, 251, 253);
        $pdf->Rect($x, $y, 182, 7.5, 'F');
        $pdf->SetTextColor($primary[0], $primary[1], $primary[2]);
        $pdf->SetFont('dejavusans', 'B', 8.1);
        $pdf->SetXY($x + 4, $y + 1.8);
        $pdf->Cell(174, 4, $title, 0, 1, 'L');

        $colW = 58.0;
        foreach (array_slice($rows, 0, 6) as $index => [$label, $value]) {
            $rowX = $x + 4 + (($index % 3) * ($colW + 1));
            $rowY = $y + 9.2 + (floor($index / 3) * 7.4);
            $pdf->SetXY($rowX, $rowY);
            $pdf->SetTextColor(100, 116, 139);
            $pdf->SetFont('dejavusans', 'B', 5.7);
            $pdf->Cell($colW, 2.6, $label, 0, 1, 'L');
            $pdf->SetXY($rowX, $rowY + 2.7);
            $pdf->SetTextColor(30, 42, 56);
            $pdf->SetFont('dejavusans', '', 6.5);
            $pdf->MultiCell($colW, 3, $this->cleanPdfText($value), 0, 'L', false, 1, '', '', true, 0, false, true, 5.2);
        }

        foreach (array_slice($rows, 6) as $index => [$label, $value]) {
            $rowY = $y + 22.8 + ($index * 4.8);
            $pdf->SetXY($x + 4, $rowY);
            $pdf->SetTextColor(100, 116, 139);
            $pdf->SetFont('dejavusans', 'B', 5.7);
            $pdf->Cell(26, 3, $label, 0, 0, 'L');
            $pdf->SetTextColor(30, 42, 56);
            $pdf->SetFont('dejavusans', '', 6.5);
            $pdf->Cell(148, 3, $this->short($value, 82), 0, 1, 'L');
        }

        $pdf->SetY($y + $h + 2.4);
    }

    /** @return list<InscriptionParcours|null> */
    private function requestedParcours(Inscription $inscription): array
    {
        $items = [];
        foreach ($inscription->getParcours() as $parcours) {
            if ($parcours->isActif() && $this->hasRequestedParcoursContent($parcours)) {
                $items[] = $parcours;
            }
        }

        return $items !== [] ? $items : [null];
    }

    /** @return list<ParcoursEtudiant|null> */
    private function validatedParcours(Inscription $inscription, ?Etudiant $etudiant): array
    {
        $items = [];
        foreach ($etudiant?->getParcours() ?? [] as $parcours) {
            if (!$parcours->isActif()) {
                continue;
            }

            $sourceInscription = $parcours->getInscription();
            if ($sourceInscription === null || $sourceInscription === $inscription || $sourceInscription->getId() === $inscription->getId()) {
                $items[] = $parcours;
            }
        }

        return $items !== [] ? $items : [null];
    }

    /** @return list<array{program:string,class:string,mode:string,amount:float}> */
    private function financialRows(Inscription $inscription, ?Etudiant $etudiant): array
    {
        $rows = [];

        if ($inscription->getStatut() === Inscription::STATUT_VALIDEE) {
            foreach ($this->validatedParcours($inscription, $etudiant) as $parcours) {
                if (!$parcours instanceof ParcoursEtudiant) {
                    $amount = $etudiant?->getClasse()?->getTarifDefaut();
                    if ($amount === null) {
                        continue;
                    }
                    $rows[] = [
                        'program' => $etudiant?->getSection()?->getNom() ?: 'Parcours principal',
                        'class' => $etudiant?->getClasse()?->getNom() ?: '-',
                        'mode' => 'Tarif classe',
                        'amount' => (float) $amount,
                    ];
                    continue;
                }

                $amount = $parcours->getTarifApplique() ?: $parcours->getClasse()?->getTarifDefaut();
                if ($amount === null) {
                    continue;
                }
                $rows[] = [
                    'program' => $parcours->getSection()?->getNom() ?: 'Parcours',
                    'class' => $parcours->getClasse()?->getNom() ?: '-',
                    'mode' => $parcours->getTarifApplique() ? 'Tarif appliqué' : 'Tarif classe',
                    'amount' => (float) $amount,
                ];
            }

            return $rows;
        }

        foreach ($this->requestedParcours($inscription) as $parcours) {
            if (!$parcours instanceof InscriptionParcours) {
                $classe = $inscription->getClasseFinale() ?: $inscription->getClasseSouhaitee();
                $amount = $classe?->getTarifDefaut();
                if ($amount === null) {
                    continue;
                }
                $rows[] = [
                    'program' => ($inscription->getSectionFinale() ?: $inscription->getSectionSouhaitee())?->getNom() ?: 'Parcours principal',
                    'class' => $classe?->getNom() ?: '-',
                    'mode' => 'Tarif classe',
                    'amount' => (float) $amount,
                ];
                continue;
            }

            $classe = $parcours->getClasseFinale() ?: $parcours->getClasseSouhaitee();
            $amount = $parcours->getTarifPropose() ?: $classe?->getTarifDefaut();
            if ($amount === null) {
                continue;
            }
            $rows[] = [
                'program' => ($parcours->getSectionFinale() ?: $parcours->getSectionSouhaitee())?->getNom() ?: 'Parcours',
                'class' => $classe?->getNom() ?: '-',
                'mode' => $parcours->getTarifPropose() ? 'Tarif proposé' : 'Tarif classe',
                'amount' => (float) $amount,
            ];
        }

        return $rows;
    }

    private function hasRequestedParcoursContent(InscriptionParcours $parcours): bool
    {
        return $parcours->getSectionSouhaitee()
            || $parcours->getNiveauSouhaite()
            || $parcours->getClasseSouhaitee()
            || $parcours->getSectionFinale()
            || $parcours->getNiveauFinal()
            || $parcours->getClasseFinale()
            || $parcours->getLangueSouhaitee()
            || $parcours->getModeSouhaite()
            || $parcours->getObservation();
    }

    /** @param array{0:int,1:int,2:int} $primary */
    private function ensureSpace(SalsabilTcpdf $pdf, float $neededHeight, ?Inscription $inscription, ?Etudiant $etudiant, array $primary): void
    {
        if ($pdf->GetY() + $neededHeight <= 258) {
            return;
        }

        if ($inscription !== null) {
            $ecole = $inscription->getEcole();
            $pdf->setSalsabilHeaderContext(
                (string) ($ecole?->getNom() ?: 'Madrassa'),
                $this->documentTitle($inscription),
                $this->branding->publicImagePath($ecole?->getLogo()),
                $inscription->getNumeroDossier(),
                $inscription->getDisplayMatricule() ?: $etudiant?->getMatricule() ?: '-',
                $this->humanStatus($inscription->getStatut()),
                $primary,
            );
        }

        $pdf->AddPage();
        $pdf->SetY(42);
    }

    private function renderRulesPage(SalsabilTcpdf $pdf, Inscription $inscription, ?Etudiant $etudiant): void
    {
        $ecole = $inscription->getEcole();
        $primary = $this->hexToRgb($ecole?->getCouleurPrincipale() ?: '#0F5C45');
        $rules = $ecole?->getReglementInterieur();
        $matricule = $inscription->getDisplayMatricule() ?: $etudiant?->getMatricule() ?: '-';

        $pdf->setSalsabilHeaderContext(
            (string) ($ecole?->getNom() ?: 'Madrassa'),
            'Règlement intérieur',
            $this->branding->publicImagePath($ecole?->getLogo()),
            $inscription->getNumeroDossier(),
            $matricule,
            $this->humanStatus($inscription->getStatut()),
            $primary,
        );
        $pdf->AddPage();

        $pdf->SetXY(14, 42);
        $pdf->SetTextColor(76, 86, 100);
        $pdf->SetFont('dejavusans', '', 9);
        $pdf->MultiCell(182, 5, 'Ce règlement accompagne la fiche d’inscription. Il rappelle les engagements attendus pour un bon suivi pédagogique et administratif.', 0, 'L');

        $pdf->Ln(4);
        $pdf->SetDrawColor(224, 228, 235);
        $pdf->SetFillColor(250, 251, 253);
        $y = $pdf->GetY();
        $boxH = 160.0;
        $pdf->Rect(14, $y, 182, $boxH, 'DF');
        $pdf->SetFillColor($primary[0], $primary[1], $primary[2]);
        $pdf->Rect(14, $y, 182, 9, 'F');
        $pdf->SetTextColor(255, 255, 255);
        $pdf->SetFont('dejavusans', 'B', 10);
        $pdf->SetXY(14, $y + 1.7);
        $pdf->Cell(182, 5, mb_strtoupper('Règlement intérieur', 'UTF-8'), 0, 1, 'C');
        $pdf->SetXY(20, $y + 15);

        if ($rules) {
            $lineHeight = mb_strlen($rules) < 700 ? '2.1' : '1.5';
            $fontSize = mb_strlen($rules) < 700 ? '10.2px' : '9.3px';
            $html = '<div style="font-family: dejavusans; font-size: '.$fontSize.'; line-height: '.$lineHeight.'; color: #263238;">'.$this->paragraphHtml($rules).'</div>';
        } else {
            $html = '<div style="font-family: dejavusans; font-size: 10.2px; line-height: 2.1; color: #4C5664;">Aucun règlement intérieur n’a encore été configuré pour cette madrassa. L’administration pourra compléter ce texte depuis la configuration école.</div>';
        }

        $pdf->writeHTMLCell(170, 0, 20, $y + 15, $html, 0, 1, false, true, 'L');

        if ($pdf->GetY() > 228) {
            $pdf->AddPage();
            $pdf->SetY(48);
        } else {
            $pdf->Ln(8);
        }

        $signatureY = max(222.0, $pdf->GetY());
        $this->rulesSignatureBlock($pdf, 14, $signatureY, 182, $primary, $ecole);
    }

    /** @param array{0:int,1:int,2:int} $primary */
    private function rulesSignatureBlock(SalsabilTcpdf $pdf, float $x, float $y, float $w, array $primary, ?Ecole $ecole): void
    {
        $pdf->SetDrawColor(224, 228, 235);
        $pdf->SetFillColor(255, 255, 255);
        $pdf->Rect($x, $y, $w, 34, 'DF');
        $pdf->SetFillColor($primary[0], $primary[1], $primary[2]);
        $pdf->Rect($x, $y, $w, 8, 'F');
        $pdf->SetTextColor(255, 255, 255);
        $pdf->SetFont('dejavusans', 'B', 9.2);
        $pdf->SetXY($x, $y + 1.2);
        $pdf->Cell($w, 5, mb_strtoupper('Validation', 'UTF-8'), 0, 1, 'C');
        $pdf->SetXY($x, $y + 6);
        $pdf->SetTextColor(30, 42, 56);
        $pdf->SetFont('dejavusans', 'B', 9);
        $columnWidth = ($w - 10) / 2;
        $pdf->SetXY($x, $y + 10);
        $pdf->Cell($columnWidth, 6, 'Administration', 0, 0, 'C');
        $pdf->Cell(10, 6, '', 0, 0);
        $pdf->Cell($columnWidth, 6, 'Parent / étudiant', 0, 1, 'C');
        $this->branding->renderAdministrativeMarks($pdf, $ecole, $x + 10, $y + 17, 66, 10);
        $pdf->SetDrawColor(170, 178, 190);
        $pdf->Line($x + 8, $y + 28, $x + 80, $y + 28);
        $pdf->Line($x + 102, $y + 28, $x + $w - 8, $y + 28);
        $pdf->SetFont('dejavusans', '', 8);
        $pdf->SetTextColor(90, 98, 110);
        $pdf->SetXY($x, $y + 29);
        $pdf->Cell($w, 5, 'Signature précédée de la mention “lu et approuvé” si nécessaire.', 0, 1, 'C');
    }

    /** @param array{0:int,1:int,2:int} $primary */
    private function infoCard(SalsabilTcpdf $pdf, float $x, float $y, float $w, string $label, string $value, array $primary): void
    {
        $pdf->SetDrawColor(224, 228, 235);
        $pdf->SetFillColor(220, 214, 224);
        $pdf->Rect($x, $y, $w, 18, 'DF');
        $pdf->SetXY($x + 4, $y + 4.2);
        $pdf->SetTextColor(92, 101, 115);
        $pdf->SetFont('dejavusans', '', 5.7);
        $pdf->Cell($w - 8, 3.4, mb_strtoupper($label, 'UTF-8'), 0, 1, 'L');
        $pdf->SetTextColor($primary[0], $primary[1], $primary[2]);
        $this->fitCellText($pdf, $value, $x + 4, $y + 10.2, $w - 8, 5, 7.8, 'L');
    }

    /** @param list<array{0:string,1:string}> $rows @param array{0:int,1:int,2:int} $primary */
    private function sectionBox(SalsabilTcpdf $pdf, float $x, float $y, float $w, float $h, string $title, array $rows, array $primary, bool $twoColumns = false): void
    {
        $pdf->SetDrawColor(224, 228, 235);
        $pdf->SetFillColor(255, 255, 255);
        $pdf->Rect($x, $y, $w, $h, 'DF');
        $pdf->SetFillColor($primary[0], $primary[1], $primary[2]);
        $pdf->Rect($x, $y, $w, 9, 'F');
        $pdf->SetTextColor(255, 255, 255);
        $pdf->SetFont('dejavusans', 'B', 10);
        $pdf->SetXY($x + 4, $y + 1.9);
        $pdf->Cell($w - 8, 5, mb_strtoupper($title, 'UTF-8'), 0, 1, 'C');

        $startY = $y + 13;
        if ($twoColumns) {
            $colW = ($w - 14) / 2;
            foreach (array_slice($rows, 0, 4) as $index => [$label, $value]) {
                $rowX = $x + 5 + (($index % 2) * ($colW + 4));
                $rowY = $startY + (floor($index / 2) * 10.6);
                $pdf->SetXY($rowX, $rowY);
                $pdf->SetTextColor(92, 101, 115);
                $pdf->SetFont('dejavusans', 'B', 6.8);
                $pdf->Cell($colW, 3.2, $label, 0, 1, 'L');
                $pdf->SetXY($rowX, $rowY + 3.4);
                $pdf->SetTextColor(30, 42, 56);
                $pdf->SetFont('dejavusans', '', 7.1);
                $pdf->MultiCell($colW, 3.2, $this->cleanPdfText($value), 0, 'L', false, 1, '', '', true, 0, false, true, 6.8);
            }

            foreach (array_slice($rows, 4) as $index => [$label, $value]) {
                $rowY = $startY + 22 + ($index * 8.6);
                $pdf->SetXY($x + 5, $rowY);
                $pdf->SetTextColor(92, 101, 115);
                $pdf->SetFont('dejavusans', 'B', 6.8);
                $pdf->Cell(28, 3.8, $label, 0, 0, 'L');
                $pdf->SetTextColor(30, 42, 56);
                $pdf->SetFont('dejavusans', '', 7.0);
                $pdf->MultiCell($w - 43, 3.4, $this->cleanPdfText($value), 0, 'L', false, 1, '', '', true, 0, false, true, 7.2);
            }

            return;
        }

        $lineHeight = $twoColumns ? 12.3 : (($h - 15) / max(1, count($rows)));
        foreach ($rows as $index => [$label, $value]) {
            $rowX = $x + 5;
            $rowY = $startY + ($index * $lineHeight);
            $rowW = $w - 10;

            $pdf->SetXY($rowX, $rowY);
            $pdf->SetTextColor(92, 101, 115);
            $pdf->SetFont('dejavusans', 'B', 6.8);
            $pdf->Cell(27, 4.5, $label, 0, 0, 'L');
            $pdf->SetTextColor(30, 42, 56);
            $pdf->SetFont('dejavusans', $label === 'Nom complet' ? 'B' : '', $label === 'Nom complet' ? 8.3 : 7.1);
            $pdf->MultiCell($rowW - 27, 4, $this->cleanPdfText($value), 0, 'L', false, 1, '', '', true, 0, false, true, $lineHeight);
        }
    }

    /** @param array{0:int,1:int,2:int} $primary */
    private function signatureBox(SalsabilTcpdf $pdf, float $x, float $y, float $w, float $h, array $primary, ?Ecole $ecole): void
    {
        $pdf->SetDrawColor(224, 228, 235);
        $pdf->SetFillColor(255, 255, 255);
        $pdf->Rect($x, $y, $w, $h, 'DF');
        $pdf->SetFillColor($primary[0], $primary[1], $primary[2]);
        $pdf->Rect($x, $y, $w, 9, 'F');
        $pdf->SetTextColor(255, 255, 255);
        $pdf->SetFont('dejavusans', 'B', 10);
        $pdf->SetXY($x + 5, $y + 1.9);
        $pdf->Cell($w - 10, 5, mb_strtoupper('Validation et signature', 'UTF-8'), 0, 1, 'C');
        $pdf->SetTextColor(92, 101, 115);
        $pdf->SetFont('dejavusans', '', 7.4);
        $columnWidth = ($w - 14) / 2;
        $pdf->SetXY($x + 5, $y + 13);
        $pdf->Cell($columnWidth, 4, 'Administration', 0, 0, 'C');
        $pdf->Cell(4, 4, '', 0, 0);
        $pdf->Cell($columnWidth, 4, 'Parent / étudiant', 0, 1, 'C');
        $this->branding->renderAdministrativeMarks($pdf, $ecole, $x + 7, $y + 18, $columnWidth - 4, 14);
        $pdf->SetDrawColor(170, 178, 190);
        $pdf->Line($x + 7, $y + 33, $x + 7 + $columnWidth - 4, $y + 33);
        $pdf->Line($x + 49, $y + 33, $x + $w - 7, $y + 33);
        $pdf->SetTextColor(90, 98, 110);
        $pdf->SetFont('dejavusans', '', 5.6);
        $pdf->SetXY($x + 49, $y + 34.8);
        $pdf->MultiCell($columnWidth, 2.8, 'Mention “lu et approuvé” si nécessaire.', 0, 'C');
    }

    private function documentTitle(Inscription $inscription): string
    {
        return $inscription->getStatut() === Inscription::STATUT_VALIDEE
            ? 'Fiche d’inscription'
            : 'Fiche provisoire d’inscription';
    }

    private function dateLine(Inscription $inscription): string
    {
        $date = $inscription->getDateNaissance()?->format('d/m/Y');
        $lieu = $inscription->getLieuNaissance();

        return trim(($date ?: '').($date && $lieu ? ' à ' : '').($lieu ?: '')) ?: '-';
    }

    private function humanStatus(string $status): string
    {
        return match ($status) {
            Inscription::STATUT_A_TESTER => 'À tester',
            Inscription::STATUT_A_AFFECTER => 'À affecter',
            Inscription::STATUT_VALIDEE => 'Validée',
            Inscription::STATUT_REJETEE => 'Rejetée',
            Inscription::STATUT_ANNULEE => 'Annulée',
            default => 'Soumise',
        };
    }

    private function humanType(string $type): string
    {
        return $type === Inscription::TYPE_ANCIEN ? 'Ancien étudiant' : 'Nouvel étudiant';
    }

    private function humanValue(?string $value): string
    {
        return $value ? str_replace('_', ' ', mb_convert_case($value, MB_CASE_TITLE, 'UTF-8')) : '-';
    }

    private function formatMoney(null|string|float $amount): string
    {
        if ($amount === null || $amount === '') {
            return '-';
        }

        return number_format((float) $amount, 0, ',', ' ').' FCFA';
    }

    private function html(?string $value): string
    {
        return htmlspecialchars($this->cleanPdfText($value), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    }

    private function footerContacts(?Ecole $ecole): string
    {
        $lines = [];
        if ($ecole?->getTelephone1()) {
            $lines[] = 'Téléphone : '.$ecole->getTelephone1();
        }
        if ($ecole?->getWhatsapp()) {
            $lines[] = 'WhatsApp : '.$ecole->getWhatsapp();
        }
        if ($ecole?->getEmail()) {
            $lines[] = 'E-mail : '.$ecole->getEmail();
        }

        return implode("\n", $lines ?: ['Document administratif']);
    }

    private function paragraphHtml(string $value): string
    {
        $paragraphs = preg_split('/\R{2,}/', trim($value)) ?: [];

        return implode('', array_map(
            static fn (string $paragraph): string => '<p>'.nl2br(htmlspecialchars(trim($paragraph), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')).'</p>',
            array_filter($paragraphs, static fn (string $paragraph): bool => trim($paragraph) !== '')
        ));
    }

    /** @return array{0:int,1:int,2:int} */
    private function hexToRgb(string $hex): array
    {
        $hex = ltrim($hex, '#');
        if (strlen($hex) !== 6) {
            return [15, 92, 69];
        }

        return [hexdec(substr($hex, 0, 2)), hexdec(substr($hex, 2, 2)), hexdec(substr($hex, 4, 2))];
    }

    private function cleanPdfText(?string $value): string
    {
        $value = trim((string) $value);

        return $value !== '' ? $value : '-';
    }

    private function fitCellText(SalsabilTcpdf $pdf, string $text, float $x, float $y, float $w, float $h, float $maxFontSize, string $align): void
    {
        $text = $this->cleanPdfText($text);
        $fontSize = $maxFontSize;
        do {
            $pdf->SetFont('dejavusans', 'B', $fontSize);
            if ($pdf->GetStringWidth($text) <= $w || $fontSize <= 5.8) {
                break;
            }
            $fontSize -= 0.3;
        } while ($fontSize > 5.8);

        $pdf->SetXY($x, $y);
        $pdf->Cell($w, $h, $text, 0, 0, $align);
    }

    private function short(?string $value, int $length): string
    {
        $value = trim((string) $value);
        if ($value === '') {
            return '-';
        }

        return mb_strlen($value) > $length ? mb_substr($value, 0, $length - 1).'…' : $value;
    }

    private function safeFileName(string $value): string
    {
        return preg_replace('/[^A-Za-z0-9_-]+/', '-', $value) ?: 'inscription';
    }
}
