<?php

declare(strict_types=1);

namespace App\Shared\Infrastructure\Export;

use App\Shared\Domain\Contract\SpreadsheetExporterInterface;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use RuntimeException;

/**
 * Implementation XLSX du contrat d'export via la librairie PhpSpreadsheet.
 *
 * Strictement technique : ecrit la ligne d'en-tete puis les lignes de donnees
 * dans l'unique feuille du classeur, et retourne le binaire. Aucune logique
 * metier, aucune reference a une entite d'un module — le mapping des colonnes
 * est de la responsabilite de l'appelant.
 */
final class PhpSpreadsheetExporter implements SpreadsheetExporterInterface
{
    // Excel limite le titre d'un onglet a 31 caracteres et interdit certains
    // caracteres ; on assainit pour ne jamais faire echouer setTitle().
    private const int MAX_SHEET_TITLE_LENGTH = 31;
    private const string INVALID_TITLE_CHARS = '*:/\?[]';

    public function export(string $sheetTitle, array $headers, iterable $rows): string
    {
        $spreadsheet = new Spreadsheet();
        $sheet       = $spreadsheet->getActiveSheet();
        $sheet->setTitle($this->sanitizeSheetTitle($sheetTitle));

        // Ligne 1 : en-tete.
        $sheet->fromArray($headers, null, 'A1');

        // Lignes 2..n : donnees. Iteration manuelle pour supporter un iterable
        // paresseux (generator) sans tout materialiser en memoire.
        $rowNumber = 2;
        foreach ($rows as $row) {
            $sheet->fromArray($row, null, 'A'.$rowNumber);
            ++$rowNumber;
        }

        return $this->toBinary($spreadsheet);
    }

    private function toBinary(Spreadsheet $spreadsheet): string
    {
        $writer = new Xlsx($spreadsheet);

        // Le writer ecrit vers un chemin de fichier : on passe par un fichier
        // temporaire puis on lit son contenu binaire.
        $tmpFile = tempnam(sys_get_temp_dir(), 'xlsx_export_');
        if (false === $tmpFile) {
            throw new RuntimeException('Impossible de creer un fichier temporaire pour l\'export XLSX.');
        }

        try {
            $writer->save($tmpFile);
            $binary = file_get_contents($tmpFile);
            if (false === $binary) {
                throw new RuntimeException('Lecture du fichier XLSX temporaire impossible.');
            }

            return $binary;
        } finally {
            // Libere les references internes de PhpSpreadsheet puis supprime le
            // fichier temporaire, meme en cas d'exception.
            $spreadsheet->disconnectWorksheets();
            @unlink($tmpFile);
        }
    }

    /**
     * Retire les caracteres interdits et tronque a 31 caracteres ; renvoie un
     * titre par defaut si la chaine resultante est vide.
     */
    private function sanitizeSheetTitle(string $title): string
    {
        $clean = str_replace(str_split(self::INVALID_TITLE_CHARS), '', $title);
        $clean = mb_substr($clean, 0, self::MAX_SHEET_TITLE_LENGTH);

        return '' === $clean ? 'Export' : $clean;
    }
}
