<?php

declare(strict_types=1);

namespace App\Services;

/**
 * Phoenix P3.1E.3 — Document Engine Master Template v1.0
 *
 * Modèle PDF validé : facture A4 sobre, logo haut gauche, titre centré,
 * cartouche document à droite, bloc société/client, grand tableau bleu clair,
 * totaux en bas à droite et pied de page légal Maroc.
 */
final class DocumentEngine
{
    private const PAGE_WIDTH = 595.28;
    private const PAGE_HEIGHT = 841.89;

    /** @param array<string,mixed> $facture @param array<int,array<string,mixed>> $lignes */
    public function streamFacture(array $facture, array $lignes): never
    {
        $societe = (new ParametreSocieteService())->get();
        $this->streamPdf((string)($facture['numero'] ?? 'facture'), $this->generateFacturePdf($facture, $lignes, $societe));
    }

    /** @param array<string,mixed> $facture @param array<int,array<string,mixed>> $lignes @param array<string,mixed> $societe */
    public function generateFacturePdf(array $facture, array $lignes, array $societe = []): string
    {
        $societe = $societe ?: (new ParametreSocieteService())->get();
        $pdf = new SimplePdfDocument();
        $pdf->addPage();
        $this->drawMasterPage($pdf, 'FACTURE', $facture, $lignes, $societe);
        return $pdf->output();
    }


    /** @param array<string,mixed> $document @param array<int,array<string,mixed>> $lignes */
    public function streamDocument(array $document, array $lignes): never
    {
        $type = strtoupper((string)($document['type_document'] ?? ''));
        match ($type) {
            'DEVIS' => $this->streamDevis($document, $lignes),
            'BL' => $this->streamBonLivraison($document, $lignes),
            'FACTURE' => $this->streamFacture($document, $lignes),
            'AVOIR' => $this->streamAvoir($document, $lignes),
            default => throw new \RuntimeException('Type de document non supporté pour le PDF.'),
        };
    }

    /** @param array<string,mixed> $devis @param array<int,array<string,mixed>> $lignes */
    public function streamDevis(array $devis, array $lignes): never
    {
        $devis['date_document'] = $devis['date_devis'] ?? ($devis['date_document'] ?? '');
        $devis['date_echeance'] = $devis['validite'] ?? ($devis['date_echeance'] ?? '');
        $this->streamPdf((string)($devis['numero'] ?? 'devis'), $this->generateDevisPdf($devis, $lignes));
    }

    /** @param array<string,mixed> $bon @param array<int,array<string,mixed>> $lignes */
    public function streamBonLivraison(array $bon, array $lignes): never
    {
        $bon['date_document'] = $bon['date_bl'] ?? ($bon['date_document'] ?? '');
        $bon['date_echeance'] = '';
        $this->streamPdf((string)($bon['numero'] ?? 'bon_livraison'), $this->generateBonLivraisonPdf($bon, $lignes));
    }

    /** @param array<string,mixed> $avoir @param array<int,array<string,mixed>> $lignes */
    public function streamAvoir(array $avoir, array $lignes): never
    {
        $avoir['date_document'] = $avoir['date_document'] ?? ($avoir['date_facture'] ?? '');
        $avoir['date_echeance'] = '';
        $this->streamPdf((string)($avoir['numero'] ?? 'avoir'), $this->generateAvoirPdf($avoir, $lignes));
    }

    /** @param array<string,mixed> $devis @param array<int,array<string,mixed>> $lignes @param array<string,mixed> $societe */
    public function generateDevisPdf(array $devis, array $lignes, array $societe = []): string
    {
        $societe = $societe ?: (new ParametreSocieteService())->get();
        $pdf = new SimplePdfDocument();
        $pdf->addPage();
        $this->drawMasterPage($pdf, 'DEVIS', $devis, $lignes, $societe);
        return $pdf->output();
    }

    /** @param array<string,mixed> $bon @param array<int,array<string,mixed>> $lignes @param array<string,mixed> $societe */
    public function generateBonLivraisonPdf(array $bon, array $lignes, array $societe = []): string
    {
        $societe = $societe ?: (new ParametreSocieteService())->get();
        $pdf = new SimplePdfDocument();
        $pdf->addPage();
        $this->drawMasterPage($pdf, 'BON DE LIVRAISON', $bon, $lignes, $societe);
        return $pdf->output();
    }

    /** @param array<string,mixed> $avoir @param array<int,array<string,mixed>> $lignes @param array<string,mixed> $societe */
    public function generateAvoirPdf(array $avoir, array $lignes, array $societe = []): string
    {
        $societe = $societe ?: (new ParametreSocieteService())->get();
        $pdf = new SimplePdfDocument();
        $pdf->addPage();
        $this->drawMasterPage($pdf, 'AVOIR', $avoir, $lignes, $societe);
        return $pdf->output();
    }

    private function streamPdf(string $filename, string $pdf): never
    {
        if (ob_get_length()) {
            ob_end_clean();
        }

        header('Content-Type: application/pdf');
        header('Content-Disposition: inline; filename="' . $this->safeFilename($filename) . '.pdf"');
        header('Content-Length: ' . strlen($pdf));
        header('Cache-Control: private, max-age=0, must-revalidate');
        header('Pragma: public');
        echo $pdf;
        exit;
    }

    /** @param array<string,mixed> $document @param array<int,array<string,mixed>> $lignes @param array<string,mixed> $societe */
	/**
	 * @param array<string,mixed> $document
	 * @param array<int,array<string,mixed>> $lignes
	 * @param array<string,mixed> $societe
	 */
	private function drawMasterPage(
		SimplePdfDocument $pdf,
		string $type,
		array $document,
		array $lignes,
		array $societe
	): void {
		$blue = $this->hexToRgb(
			(string)($societe['couleur_principale'] ?? '#0E4591')
		);

		$blueDark = [7, 48, 115];
		$blueLine = [142, 178, 229];
		$blueSoft = [242, 247, 255];
		$rowSoft = [247, 250, 254];
		$text = [20, 24, 31];
		$muted = [55, 65, 81];

		/*
		 * Capacité réelle du tableau :
		 * top 516, entête 28, bas 200, ligne 19.
		 */
		$maxRowsPerPage = 15;

		$pages = array_chunk(
			$lignes,
			$maxRowsPerPage
		);

		if (empty($pages)) {
			$pages = [[]];
		}

		$pageCount = count($pages);

		foreach ($pages as $pageIndex => $pageLines) {
			if ($pageIndex > 0) {
				$pdf->addPage();
			}

			$pageNumber = $pageIndex + 1;
			$isLastPage = $pageNumber === $pageCount;

			$this->drawTopIdentity(
				$pdf,
				$type,
				$document,
				$societe,
				$blue,
				$blueDark,
				$blueSoft,
				$text,
				$muted
			);

			$this->drawParties(
				$pdf,
				$document,
				$societe,
				$blue,
				$blueSoft,
				$text,
				$muted
			);

			$this->drawArticleTable(
				$pdf,
				$type,
				$document,
				$pageLines,
				$lignes,
				$societe,
				$blue,
				$blueLine,
				$rowSoft,
				$text,
				$isLastPage
			);

			$this->drawLegalFooter(
				$pdf,
				$societe,
				$blueDark,
				$text,
				$pageNumber,
				$pageCount
			);
		}
	}

    /** @param array<string,mixed> $document @param array<string,mixed> $societe @param array<int,int> $blue @param array<int,int> $blueDark @param array<int,int> $text @param array<int,int> $muted */
    private function drawTopIdentity(SimplePdfDocument $pdf, string $type, array $document, array $societe, array $blue, array $blueDark, array $blueSoft, array $text, array $muted): void
    {
        // Logo haut gauche, proche de la maquette validée.
        $logoPath = $this->resolveLogoPath((string)($societe['logo_path'] ?? ''));
        if ($logoPath !== null && $pdf->image($logoPath, 20, 760, 184, 62)) {
            // Logo réel rendu.
        } else {
            // Cartouche de secours si aucun logo n'est configuré.
            $pdf->text(28, 792, 'Darlink', 25, 'B', $blueDark);
            $pdf->text(128, 792, 'ERP', 16, 'B', [0, 136, 255]);
            $pdf->text(70, 775, 'Solutions de gestion', 8, '', $muted);
        }

        // Titre central.
        $titleSize = strlen($type) > 12 ? 14 : 19;
        $titleX = strlen($type) > 12 ? 218 : 245;
        $pdf->text($titleX, 786, $type, $titleSize, 'B', $blueDark);

        // Cartouche document à droite, fond bleu clair comme le bloc client.
        $pdf->fillRect(410, 762, 160, 72, $blueSoft);
        $pdf->text(428, 812, 'N° ' . (string)($document['numero'] ?? ''), 10, 'B', $blueDark);
        $pdf->text(428, 790, 'Date', 9, 'B', $blueDark);
        $pdf->text(482, 790, ': ' . $this->dateFr((string)($document['date_facture'] ?? $document['date_document'] ?? '')), 9, '', $text);
        $echeance = $this->dateFr((string)($document['date_echeance'] ?? ''));
        if ($echeance !== '-') {
            $pdf->text(428, 773, $this->secondaryDateLabel($type), 9, 'B', $blueDark);
            $pdf->text(482, 773, ': ' . $echeance, 9, '', $text);
        }

        // Ligne bleue de séparation.
        $pdf->line(18, 744, 577, 744, $blueDark);
    }

    /** @param array<string,mixed> $document @param array<string,mixed> $societe @param array<int,int> $blue @param array<int,int> $blueSoft @param array<int,int> $text @param array<int,int> $muted */
    private function drawParties(SimplePdfDocument $pdf, array $document, array $societe, array $blue, array $blueSoft, array $text, array $muted): void
    {
        // Bloc société à gauche.
        $x = 22.0;
        $y = 710.0;
        $pdf->text($x, $y, strtoupper((string)($societe['raison_sociale'] ?? 'DARLINK SARLAU')), 13, 'B', $blue);
        $y -= 17;

        foreach ($this->companyAddressLines($societe) as $line) {
            $pdf->text($x, $y, $line, 10, '', $text);
            $y -= 13;
        }
        $y -= 4;

        foreach ($this->companyLegalLines($societe) as [$label, $value]) {
            $pdf->text($x, $y, $label, 10, 'B', $blue);
            $pdf->text($x + 45, $y, ': ' . $value, 10, '', $text);
            $y -= 13;
        }
        $y -= 8;

        $phone = trim((string)($societe['telephone'] ?? $societe['mobile'] ?? ''));
        if ($phone !== '') {
            $pdf->text($x, $y, 'Tél.', 9, 'B', $blue);
            $pdf->text($x + 45, $y, $phone, 10, '', $text);
            $y -= 14;
        }
        $email = trim((string)($societe['email'] ?? ''));
        if ($email !== '') {
            $pdf->text($x, $y, 'Email', 9, 'B', $blue);
            $pdf->text($x + 45, $y, $email, 10, '', $text);
        }

        // Bloc client à droite, fond très clair comme la maquette.
        $cx = 315.0;
        $cy = 572.0;
        $cw = 255.0;
        $ch = 143.0;
        $pdf->fillRect($cx, $cy, $cw, $ch, $blueSoft);
        $pdf->text($cx + 18, $cy + $ch - 28, 'CLIENT', 14, 'B', $blue);

        $clientY = $cy + $ch - 58;
        foreach ($this->clientLines($document) as $line) {
            $pdf->text($cx + 18, $clientY, $line, 10, '', $text);
            $clientY -= 14;
        }
    }

    /** @param array<string,mixed> $document @param array<int,array<string,mixed>> $lignes @param array<string,mixed> $societe @param array<int,int> $blue @param array<int,int> $blueLine @param array<int,int> $rowSoft @param array<int,int> $text */
	/**
	 * @param array<string,mixed> $document
	 * @param array<int,array<string,mixed>> $pageLines
	 * @param array<int,array<string,mixed>> $allLines
	 * @param array<string,mixed> $societe
	 * @param array<int,int> $blue
	 * @param array<int,int> $blueLine
	 * @param array<int,int> $rowSoft
	 * @param array<int,int> $text
	 */
	private function drawArticleTable(
		SimplePdfDocument $pdf,
		string $type,
		array $document,
		array $pageLines,
		array $allLines,
		array $societe,
		array $blue,
		array $blueLine,
		array $rowSoft,
		array $text,
		bool $showTotals
	): void {
		$x = 18.0;
		$top = 516.0;
		$bottom = 200.0;
		$w = 559.0;
		$headerH = 28.0;
		$rowH = 19.0;

		/*
		 * Colonnes :
		 * Réf. | Désignation | Qté. | Prix U. HT | TVA | Total HT
		 */
		$cols = [
			18.0,
			78.0,
			322.0,
			372.0,
			452.0,
			502.0,
			577.0,
		];

		$pdf->fillRect(
			$x,
			$top - $headerH,
			$w,
			$headerH,
			[236, 244, 255]
		);

		$firstRowTop = $top - $headerH;

		foreach ($pageLines as $index => $ligne) {
			$rowTop = $firstRowTop - ($index * $rowH);
			$rowBottom = $rowTop - $rowH;

			if ($rowBottom < $bottom) {
				break;
			}

			if ($index % 2 === 1) {
				$pdf->fillRect(
					$x,
					$rowBottom,
					$w,
					$rowH,
					$rowSoft
				);
			}
		}

		$pdf->rect(
			$x,
			$bottom,
			$w,
			$top - $bottom,
			$blueLine
		);

		foreach ($cols as $columnX) {
			$pdf->line(
				$columnX,
				$bottom,
				$columnX,
				$top,
				$blueLine
			);
		}

		$pdf->line(
			$x,
			$top - $headerH,
			$x + $w,
			$top - $headerH,
			$blueLine
		);

		$headers = [
			['Réf.', 48],
			['Désignation', 195],
			['Qté.', 345],
			['Prix U. HT', 397],
			['TVA', 475],
			['Total HT', 535],
		];

		foreach ($headers as [$label, $headerX]) {
			$pdf->text(
				(float)$headerX,
				$top - 18,
				(string)$label,
				9,
				'B',
				$blue
			);
		}

		foreach ($pageLines as $index => $ligne) {
			$rowTop = $firstRowTop - ($index * $rowH);
			$rowBottom = $rowTop - $rowH;

			if ($rowBottom < $bottom) {
				break;
			}

			$pdf->line(
				$x,
				$rowBottom,
				$x + $w,
				$rowBottom,
				[226, 235, 247]
			);

			$textY = $rowTop - 14;

			$pdf->text(
				31,
				$textY,
				$this->truncate(
					(string)($ligne['reference'] ?? ''),
					10
				),
				9,
				'',
				$text
			);

			/*
			 * Une seule ligne pour éviter tout chevauchement.
			 */
			$pdf->text(
				86,
				$textY,
				$this->truncate(
					(string)($ligne['designation'] ?? ''),
					46
				),
				9,
				'',
				$text
			);

			$pdf->textRight(
				360,
				$textY,
				$this->quantity(
					(float)($ligne['quantite'] ?? 0)
				),
				9,
				'',
				$text
			);

			$pdf->textRight(
				442,
				$textY,
				$this->money(
					(float)($ligne['prix_vente_ht'] ?? 0),
					true,
					$societe
				),
				9,
				'',
				$text
			);

			$pdf->textRight(
				492,
				$textY,
				$this->number(
					(float)($ligne['taux_tva'] ?? 0)
				) . ' %',
				9,
				'',
				$text
			);

			$pdf->textRight(
				565,
				$textY,
				$this->money(
					(float)($ligne['total_ht'] ?? 0),
					true,
					$societe
				),
				9,
				'',
				$text
			);
		}

		/*
		 * Les totaux ne figurent que sur la dernière page.
		 */
		if ($showTotals) {
			$this->drawDocumentNotes(
				$pdf,
				$type,
				$document,
				$societe,
				$blue,
				$text
			);

			$this->drawDetachedTotals(
				$pdf,
				$document,
				$allLines,
				$societe,
				$blue,
				$blueLine,
				$text
			);
		}
	}
		private function drawDocumentNotes(
			SimplePdfDocument $pdf,
			string $type,
			array $document,
			array $societe,
			array $blue,
			array $text
		): void {
		$x = 22.0;
		$y = 178.0;
		$maxLength = 82;

		$notes = [];

		$conditionField = match (strtoupper($type)) {
			'DEVIS' => 'conditions_devis',

			'BL',
			'BON DE LIVRAISON' => 'conditions_bl',

			'FACTURE' => 'conditions_facture',

			'AVOIR' => 'conditions_avoir',

			default => 'conditions_reglement',
		};

		$conditions = trim(
			(string)($societe[$conditionField] ?? '')
		);

		/*
		 * Compatibilité avec l’ancien champ général.
		 */
		if ($conditions === '') {
			$conditions = trim(
				(string)($societe['conditions_reglement'] ?? '')
			);
		}

		if ($conditions !== '') {
			$notes[] = [
				'Conditions :',
				$conditions,
			];
		}

		$penalties = trim(
			(string)($societe['penalites_retard'] ?? '')
		);

		if ($penalties !== '') {
			$notes[] = [
				'Pénalités :',
				$penalties,
			];
		}

		$ownership = trim(
			(string)($societe['reserve_propriete'] ?? '')
		);

		if ($ownership !== '') {
			$notes[] = [
				'Réserve de propriété :',
				$ownership,
			];
		}

		$legal = trim(
			(string)($societe['mentions_legales'] ?? '')
		);

		if ($legal !== '') {
			$notes[] = [
				'Mentions :',
				$legal,
			];
		}

		foreach ($notes as [$label, $value]) {
			if ($y < 112) {
				break;
			}

			$pdf->text(
				$x,
				$y,
				$label,
				7,
				'B',
				$blue
			);

			$pdf->text(
				$x + 72,
				$y,
				$this->truncate(
					preg_replace(
						'/\s+/',
						' ',
						$value
					) ?? '',
					$maxLength
				),
				6,
				'',
				$text
			);

			$y -= 13;
		}

		$bankParts = [];

		if (!empty($societe['raison_sociale'])) {
			$bankParts[] = 'Titulaire : ' . $societe['raison_sociale'];
		}

		if (!empty($societe['banque'])) {
			$bankParts[] = 'Banque : ' . $societe['banque'];
		}

		if (!empty($societe['rib'])) {
			$bankParts[] = 'RIB : ' . $societe['rib'];
		}

		if (!empty($societe['iban'])) {
			$bankParts[] = 'IBAN : ' . $societe['iban'];
		}

		if (!empty($societe['swift'])) {
			$bankParts[] = 'BIC / SWIFT : ' . $societe['swift'];
		}

		/*
		 * L'encadré n'est affiché que lorsqu'un IBAN est renseigné.
		 * Les informations proviennent exclusivement des paramètres société.
		 */
		if (!empty($societe['iban']) && $bankParts !== [] && $y >= 98) {
			$pdf->text(
				$x,
				$y,
				'Règlement par virement bancaire',
				7,
				'B',
				$blue
			);

			$y -= 12;

			$pdf->text(
				$x,
				$y,
				$this->truncate(
					implode(' • ', $bankParts),
					105
				),
				6,
				'',
				$text
			);

			$y -= 11;

			if (strtoupper($type) === 'FACTURE' && $y >= 86) {
				$numero = trim((string)($document['numero'] ?? ''));
				$reference = $numero !== ''
					? 'Merci de rappeler le numéro de facture ' . $numero . ' lors de votre virement.'
					: 'Merci de rappeler le numéro de facture lors de votre virement.';

				$pdf->text(
					$x,
					$y,
					$this->truncate($reference, 105),
					6,
					'',
					$text
				);

				$y -= 13;
			}
		}

		$signature = trim(
			(string)($societe['signature_document'] ?? '')
		);

		if ($signature !== '' && $y >= 90) {
			$pdf->text(
				$x,
				$y,
				$this->truncate($signature, 60),
				7,
				'B',
				$blue
			);
		}
	}

    /** @param array<string,mixed> $document @param array<string,mixed> $societe @param array<int,int> $blue @param array<int,int> $blueLine @param array<int,int> $text */
    private function drawDetachedTotals(SimplePdfDocument $pdf, array $document, array $lignes, array $societe, array $blue, array $blueLine, array $text): void
    {
        $boxX = 342.0;
        $boxY = 90.0;
        $boxW = 235.0;
        $boxH = 100.0;
        $labelX = $boxX + 22.0;
        $valueX = $boxX + $boxW - 14.0;

		$totalBrutHt = 0.0;
		$totalNetHt = 0.0;

		foreach ($lignes as $ligne) {
			$quantity = round(
				(float)($ligne['quantite'] ?? 0),
				4
			);

			$unitPrice = round(
				(float)($ligne['prix_vente_ht'] ?? 0),
				2
			);

			$totalBrutHt += round(
				$quantity * $unitPrice,
				2
			);

			$totalNetHt += round(
				(float)($ligne['total_ht'] ?? 0),
				2
			);
		}

		$totalBrutHt = round($totalBrutHt, 2);
		$totalNetHt = round($totalNetHt, 2);

		$remise = round(
			max(0, $totalBrutHt - $totalNetHt),
			2
		);

		$totalTva = round(
			(float)($document['total_tva'] ?? 0),
			2
		);

		$totalTtc = round(
			(float)($document['total_ttc'] ?? 0),
			2
		);

        $pdf->rect($boxX, $boxY, $boxW, $boxH, $blueLine);

		$rows = [
			['Total Brut HT :', $totalBrutHt],
			['Remise :', $remise],
			['Total Net HT :', $totalNetHt],
			[$this->tvaLabel($lignes), $totalTva],
		];
        $y = $boxY + $boxH - 18.0;
        foreach ($rows as [$label, $value]) {
            $pdf->text($labelX, $y, (string)$label, 9, '', $text);
            $pdf->textRight($valueX, $y, $this->money((float)$value, true, $societe), 9, '', $text);
            $y -= 14.0;
        }

        $pdf->line($boxX + 14, $boxY + 32, $boxX + $boxW, $boxY + 32, $blueLine);
        $pdf->fillRect($boxX + 14, $boxY + 8, $boxW - 14, 24, [236, 244, 255]);
        $pdf->text($labelX, $boxY + 16, 'TOTAL TTC :', 13, 'B', $blue);
        $pdf->textRight($valueX, $boxY + 16, $this->money($totalTtc, true, $societe), 13, 'B', $blue);
    }

    /** @param array<string,mixed> $societe @param array<int,int> $blueDark @param array<int,int> $text */
	/**
	 * @param array<string,mixed> $societe
	 * @param array<int,int> $blueDark
	 * @param array<int,int> $text
	 */
		private function drawLegalFooter(
			SimplePdfDocument $pdf,
			array $societe,
			array $blueDark,
			array $text,
			int $pageNumber,
			int $pageCount
		): void {
			$pdf->line(
				18,
				72,
				577,
				72,
				$blueDark
			);

			$footer = trim(
				(string)($societe['pied_de_page'] ?? '')
			);

			if ($footer === '') {
				$footer = $this->footerAutomatic($societe);
			}

			$pdf->text(
				22,
				48,
				$this->truncate($footer, 170),
				7,
				'',
				$text
			);

			$pdf->textRight(
				570,
				24,
				$pageNumber . '/' . $pageCount,
				8,
				'',
				$text
			);
		}

    /** @param array<string,mixed> $societe @return array<int,string> */
    private function companyAddressLines(array $societe): array
    {
        $address = trim((string)($societe['adresse'] ?? ''));
        $lines = $address !== '' ? preg_split('/\r\n|\r|\n/', $address) : [];
        $city = trim(((string)($societe['code_postal'] ?? '')) . ' - ' . ((string)($societe['ville'] ?? '')));
        if ($city !== '-' && trim(str_replace('-', '', $city)) !== '') {
            $lines[] = $city;
        }
        return array_values(array_filter(array_map('trim', $lines ?: ['Marrakech Médina', 'Place Jemaa el-Fna', '40000 - MARRAKECH'])));
    }

    /** @param array<string,mixed> $societe @return array<int,array{0:string,1:string}> */
    private function companyLegalLines(array $societe): array
    {
        $mapping = [
            'IF' => 'identifiant_fiscal',
            'ICE' => 'ice',
            'RC' => 'registre_commerce',
            'Patente' => 'patente',
        ];
        $rows = [];
        foreach ($mapping as $label => $key) {
            $value = trim((string)($societe[$key] ?? ''));
            if ($value !== '') {
                $rows[] = [$label, $value];
            }
        }
        return $rows;
    }

    /** @param array<string,mixed> $document @return array<int,string> */
    private function clientLines(array $document): array
    {
        $lines = [];
        foreach (['client_nom', 'adresse'] as $key) {
            $value = trim((string)($document[$key] ?? ''));
            if ($value !== '') {
                foreach (preg_split('/\r\n|\r|\n/', $value) ?: [] as $part) {
                    if (trim($part) !== '') {
                        $lines[] = trim($part);
                    }
                }
            }
        }
        $city = trim(((string)($document['code_postal'] ?? '')) . ' ' . ((string)($document['ville'] ?? '')));
        if ($city !== '') {
            $lines[] = $city;
        }
        $ice = trim((string)($document['client_ice'] ?? ''));
        if ($ice !== '') {
            $lines[] = '';
            $lines[] = 'ICE : ' . $ice;
        }
        return $lines ?: ['Client'];
    }

    /** @param array<string,mixed> $societe */
    private function footerLineOne(array $societe): string
    {
        $parts = [(string)($societe['raison_sociale'] ?? 'DARLINK SARLAU')];
        foreach ([
            'RC' => 'registre_commerce',
            'Patente' => 'patente',
            'IF' => 'identifiant_fiscal',
            'ICE' => 'ice',
        ] as $label => $key) {
            $value = trim((string)($societe[$key] ?? ''));
            if ($value !== '') {
                $parts[] = $label . ': ' . $value;
            }
        }
        return implode(' - ', $parts);
    }

    /** @param array<string,mixed> $societe */
    private function footerLineTwo(array $societe): string
    {
        $parts = [];
        $address = trim((string)($societe['adresse'] ?? ''));
        if ($address !== '') {
            $parts[] = preg_replace('/\s+/', ' ', str_replace(["\r", "\n"], ' ', $address));
        }
        $city = trim(((string)($societe['code_postal'] ?? '')) . ' ' . ((string)($societe['ville'] ?? '')) . ', ' . ((string)($societe['pays'] ?? 'MAROC')));
        if ($city !== '') {
            $parts[] = $city;
        }
        return implode(' - ', array_filter($parts));
    }

private function footerAutomatic(array $societe): string
{
    $address = trim(
        preg_replace(
            '/\s+/',
            ' ',
            str_replace(
                ["\r", "\n"],
                ' ',
                (string)($societe['adresse'] ?? '')
            )
        ) ?? ''
    );

    $city = trim(
        implode(
            ' ',
            array_filter([
                (string)($societe['code_postal'] ?? ''),
                (string)($societe['ville'] ?? ''),
            ])
        )
    );

    return implode(' • ', array_filter([
        trim((string)($societe['raison_sociale'] ?? '')),

        $address,

        $city,

        !empty($societe['telephone'])
            ? 'Tél. : ' . $societe['telephone']
            : '',

        !empty($societe['site_web'])
            ? (string)$societe['site_web']
            : '',

        !empty($societe['registre_commerce'])
            ? 'RC : ' . $societe['registre_commerce']
            : '',

        !empty($societe['patente'])
            ? 'Patente : ' . $societe['patente']
            : '',

        !empty($societe['identifiant_fiscal'])
            ? 'IF : ' . $societe['identifiant_fiscal']
            : '',

        !empty($societe['ice'])
            ? 'ICE : ' . $societe['ice']
            : '',
    ]));
}

	private function resolveLogoPath(string $logoPath): ?string
	{
		$logoPath = trim($logoPath);

		if ($logoPath === '') {
			return null;
		}

		/*
		 * Retire le paramètre de cache éventuel :
		 * assets/img/logo_societe.png?v=123456
		 */
		$relativePath = (string)(
			parse_url($logoPath, PHP_URL_PATH) ?? ''
		);

		$relativePath = ltrim(
			str_replace(
				['/', '\\'],
				DIRECTORY_SEPARATOR,
				$relativePath
			),
			DIRECTORY_SEPARATOR
		);

		if ($relativePath === '') {
			return null;
		}

		$rootPath = dirname(__DIR__, 2);

		/*
		 * Compatibilité si le chemin enregistré commence déjà par public/.
		 */
		if (str_starts_with($relativePath, 'public' . DIRECTORY_SEPARATOR)) {
			$candidate = $rootPath
				. DIRECTORY_SEPARATOR
				. $relativePath;
		} else {
			$candidate = $rootPath
				. DIRECTORY_SEPARATOR
				. 'public'
				. DIRECTORY_SEPARATOR
				. $relativePath;
		}

		return is_file($candidate)
			? $candidate
			: null;
	}

    /** @return array<int,int> */
    private function hexToRgb(string $hex): array
    {
        $hex = ltrim(trim($hex), '#');
        if (strlen($hex) !== 6 || !ctype_xdigit($hex)) {
            return [14, 69, 145];
        }
        return [hexdec(substr($hex, 0, 2)), hexdec(substr($hex, 2, 2)), hexdec(substr($hex, 4, 2))];
    }


    /** @param array<int,array<string,mixed>> $lignes */
    private function tvaLabel(array $lignes): string
    {
        $rates = [];
        foreach ($lignes as $ligne) {
            $rate = (float)($ligne['taux_tva'] ?? 0);
            if ($rate > 0) {
                $rates[] = $this->number($rate) . ' %';
            }
        }
        $rates = array_values(array_unique($rates));
        if (count($rates) === 1) {
            return 'TVA ' . $rates[0] . ' :';
        }
        return 'Total TVA :';
    }

    private function secondaryDateLabel(string $type): string
    {
        return strtoupper($type) === 'DEVIS' ? 'Validité' : 'Échéance';
    }

    /** @param array<string,mixed> $societe */
    private function money(float $value, bool $withCurrency = true, array $societe = []): string
    {
        $currency = trim((string)($societe['devise'] ?? 'DH')) ?: 'DH';
        $currency = $currency === 'MAD' ? 'DH' : $currency;
        return number_format($value, 2, ',', ' ') . ($withCurrency ? ' ' . $currency : '');
    }
	
	private function quantity(float $value): string
	{
		return number_format(
			round($value, 0),
			0,
			',',
			' '
		);
	}
	
    private function number(float $value): string
    {
        return rtrim(rtrim(number_format($value, 2, ',', ' '), '0'), ',');
    }

    private function dateFr(string $date): string
    {
        if ($date === '') {
            return '-';
        }
        $timestamp = strtotime($date);
        return $timestamp ? date('d/m/Y', $timestamp) : $date;
    }

    /** @return array<int,string> */
    private function wrap(string $text, int $length): array
    {
        $text = trim(preg_replace('/\s+/', ' ', $text) ?? '');
        if ($text === '') {
            return [''];
        }
        return explode("\n", wordwrap($text, $length, "\n", true));
    }

    private function truncate(string $text, int $length): string
    {
        $text = trim(preg_replace('/\s+/', ' ', $text) ?? '');
        $strlen = function_exists('mb_strlen') ? mb_strlen($text) : strlen($text);
        if ($strlen <= $length) {
            return $text;
        }
        return (function_exists('mb_substr') ? mb_substr($text, 0, $length - 1) : substr($text, 0, $length - 1)) . '…';
    }

    private function safeFilename(string $name): string
    {
        $name = preg_replace('/[^A-Za-z0-9_\-]+/', '_', $name) ?: 'document';
        return trim($name, '_') ?: 'document';
    }
}

final class SimplePdfDocument
{
    /** @var array<int,string> */
    private array $pages = [];
    private string $current = '';

    /** @var array<string,array{data:string,width:int,height:int}> */
    private array $images = [];

    public function addPage(): void
    {
        if ($this->current !== '') {
            $this->pages[] = $this->current;
        }
        $this->current = '';
    }

    /** @param array<int,int> $color */
    public function text(float $x, float $y, string $text, int $size = 10, string $style = '', array $color = [0, 0, 0]): void
    {
        $font = $style === 'B' ? 'F2' : 'F1';
        $this->setFill($color);
        $this->current .= sprintf("BT /%s %d Tf %.2F %.2F Td (%s) Tj ET\n", $font, $size, $x, $y, $this->escape($this->encode($text)));
    }

    /** @param array<int,int> $color */
    public function textRight(float $rightX, float $y, string $text, int $size = 10, string $style = '', array $color = [0, 0, 0]): void
    {
        $approxWidth = strlen($this->encode($text)) * $size * 0.48;
        $this->text($rightX - $approxWidth, $y, $text, $size, $style, $color);
    }

    /** @param array<int,int> $color */
    public function line(float $x1, float $y1, float $x2, float $y2, array $color = [0, 0, 0]): void
    {
        $this->setStroke($color);
        $this->current .= sprintf("%.2F %.2F m %.2F %.2F l S\n", $x1, $y1, $x2, $y2);
    }

    /** @param array<int,int> $stroke */
    public function rect(float $x, float $y, float $w, float $h, array $stroke = [0, 0, 0]): void
    {
        $this->setStroke($stroke);
        $this->current .= sprintf("%.2F %.2F %.2F %.2F re S\n", $x, $y, $w, $h);
    }

    /** @param array<int,int> $fill */
    public function fillRect(float $x, float $y, float $w, float $h, array $fill): void
    {
        $this->setFill($fill);
        $this->current .= sprintf("%.2F %.2F %.2F %.2F re f\n", $x, $y, $w, $h);
    }

    /** @param array<int,int> $fill @param array<int,int> $stroke */
    public function roundRect(float $x, float $y, float $w, float $h, float $r, array $fill, array $stroke): void
    {
        $this->fillRect($x, $y, $w, $h, $fill);
        $this->rect($x, $y, $w, $h, $stroke);
    }

    public function image(string $path, float $x, float $y, float $w, float $h): bool
    {
        $image = $this->loadImageAsJpeg($path);
        if ($image === null) {
            return false;
        }
        $name = 'Im' . (count($this->images) + 1);
        $this->images[$name] = $image;
        $this->current .= sprintf("q %.2F 0 0 %.2F %.2F %.2F cm /%s Do Q\n", $w, $h, $x, $y, $name);
        return true;
    }

    /** @return array{data:string,width:int,height:int}|null */
    private function loadImageAsJpeg(string $path): ?array
    {
        if (!is_file($path)) {
            return null;
        }
        $info = @getimagesize($path);
        if (!$info || empty($info[0]) || empty($info[1])) {
            return null;
        }
        $mime = (string)($info['mime'] ?? '');
        if ($mime === 'image/jpeg') {
            $data = @file_get_contents($path);
            return $data !== false ? ['data' => $data, 'width' => (int)$info[0], 'height' => (int)$info[1]] : null;
        }
        if ($mime === 'image/png' && function_exists('imagecreatefrompng') && function_exists('imagejpeg')) {
            $src = @imagecreatefrompng($path);
            if (!$src) {
                return null;
            }
            $w = imagesx($src);
            $h = imagesy($src);
            $dst = imagecreatetruecolor($w, $h);
            $white = imagecolorallocate($dst, 255, 255, 255);
            imagefill($dst, 0, 0, $white);
            imagecopy($dst, $src, 0, 0, 0, 0, $w, $h);
            ob_start();
            imagejpeg($dst, null, 90);
            $data = (string)ob_get_clean();
            imagedestroy($src);
            imagedestroy($dst);
            return $data !== '' ? ['data' => $data, 'width' => $w, 'height' => $h] : null;
        }
        return null;
    }

    public function output(): string
    {
        if ($this->current !== '') {
            $this->pages[] = $this->current;
            $this->current = '';
        }
        if (empty($this->pages)) {
            $this->pages[] = '';
        }

        $pageCount = count($this->pages);
        $font1Obj = 3 + ($pageCount * 2);
        $font2Obj = $font1Obj + 1;
        $imageStartObj = $font2Obj + 1;

        $xObjects = [];
        $imageIndex = 0;
        foreach (array_keys($this->images) as $name) {
            $xObjects[] = '/' . $name . ' ' . ($imageStartObj + $imageIndex) . ' 0 R';
            $imageIndex++;
        }
        $xObjectResource = $xObjects ? ' /XObject << ' . implode(' ', $xObjects) . ' >>' : '';

        $objects = [];
        $objects[] = '<< /Type /Catalog /Pages 2 0 R >>';

        $kids = [];
        for ($i = 0; $i < $pageCount; $i++) {
            $kids[] = (3 + ($i * 2)) . ' 0 R';
        }
        $objects[] = '<< /Type /Pages /Kids [' . implode(' ', $kids) . '] /Count ' . $pageCount . ' >>';

        foreach ($this->pages as $i => $content) {
            $pageObj = 3 + ($i * 2);
            $contentObj = $pageObj + 1;
            $objects[] = '<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595.28 841.89] /Resources << /Font << /F1 ' . $font1Obj . ' 0 R /F2 ' . $font2Obj . ' 0 R >>' . $xObjectResource . ' >> /Contents ' . $contentObj . ' 0 R >>';
            $objects[] = '<< /Length ' . strlen($content) . " >>\nstream\n" . $content . "endstream";
        }

        $objects[] = '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>';
        $objects[] = '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>';

        foreach ($this->images as $image) {
            $data = $image['data'];
            $objects[] = '<< /Type /XObject /Subtype /Image /Width ' . $image['width'] . ' /Height ' . $image['height'] . ' /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ' . strlen($data) . " >>\nstream\n" . $data . "\nendstream";
        }

        $pdf = "%PDF-1.4\n";
        $offsets = [0];
        foreach ($objects as $index => $object) {
            $offsets[] = strlen($pdf);
            $pdf .= ($index + 1) . " 0 obj\n" . $object . "\nendobj\n";
        }

        $xref = strlen($pdf);
        $pdf .= "xref\n0 " . (count($objects) + 1) . "\n";
        $pdf .= "0000000000 65535 f \n";
        for ($i = 1; $i <= count($objects); $i++) {
            $pdf .= sprintf("%010d 00000 n \n", $offsets[$i]);
        }
        $pdf .= "trailer\n<< /Size " . (count($objects) + 1) . " /Root 1 0 R >>\nstartxref\n" . $xref . "\n%%EOF";
        return $pdf;
    }

    /** @param array<int,int> $color */
    private function setFill(array $color): void
    {
        $this->current .= sprintf("%.3F %.3F %.3F rg\n", $color[0] / 255, $color[1] / 255, $color[2] / 255);
    }

    /** @param array<int,int> $color */
    private function setStroke(array $color): void
    {
        $this->current .= sprintf("%.3F %.3F %.3F RG\n", $color[0] / 255, $color[1] / 255, $color[2] / 255);
    }

    private function encode(string $text): string
    {
        $encoded = @iconv('UTF-8', 'Windows-1252//TRANSLIT//IGNORE', $text);
        return $encoded !== false ? $encoded : preg_replace('/[^\x20-\x7E]/', '', $text);
    }

    private function escape(string $text): string
    {
        return str_replace(['\\', '(', ')'], ['\\\\', '\\(', '\\)'], $text);
    }
}
