<?php
namespace App\Core;

class Exporter
{
    public static function csv(string $filename, array $rows): never
    {
        self::headers('text/csv; charset=utf-8', $filename);
        $output = fopen('php://output', 'wb');
        fwrite($output, "\xEF\xBB\xBF");
        if ($rows) {
            fputcsv($output, array_keys($rows[0]));
            foreach ($rows as $row) {
                fputcsv($output, array_map([self::class, 'safeCell'], $row));
            }
        }
        fclose($output);
        exit;
    }

    public static function excel(string $filename, array $rows): never
    {
        self::headers('application/vnd.ms-excel; charset=utf-8', $filename);
        echo "\xEF\xBB\xBF";
        if ($rows) {
            echo implode("\t", array_map([self::class, 'safeCell'], array_keys($rows[0]))) . "\n";
            foreach ($rows as $row) {
                echo implode("\t", array_map([self::class, 'safeCell'], $row)) . "\n";
            }
        }
        exit;
    }

    public static function pdf(string $filename, string $title, array $rows): never
    {
        $lines = [$title, str_repeat('-', 60)];
        foreach (array_slice($rows, 0, 40) as $row) {
            $lines[] = implode(' | ', array_map(static fn($value): string => mb_substr((string)$value, 0, 32), $row));
        }
        $content = 'BT /F1 10 Tf 40 780 Td ';
        foreach ($lines as $index => $line) {
            $safe = str_replace(['\\', '(', ')'], ['\\\\', '\\(', '\\)'], $line);
            $content .= ($index ? ' 0 -14 Td ' : '') . '(' . $safe . ') Tj';
        }
        $content .= ' ET';
        $objects = [
            '1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj',
            '2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj',
            '3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >> endobj',
            '4 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj',
            '5 0 obj << /Length ' . strlen($content) . " >> stream\n" . $content . "\nendstream endobj",
        ];
        $pdf = "%PDF-1.4\n";
        $offsets = [0];
        foreach ($objects as $object) {
            $offsets[] = strlen($pdf);
            $pdf .= $object . "\n";
        }
        $xref = strlen($pdf);
        $pdf .= 'xref' . "\n0 " . (count($objects) + 1) . "\n0000000000 65535 f \n";
        for ($index = 1; $index < count($offsets); $index++) {
            $pdf .= sprintf('%010d 00000 n ', $offsets[$index]) . "\n";
        }
        $pdf .= 'trailer << /Size ' . (count($objects) + 1) . " /Root 1 0 R >>\nstartxref\n" . $xref . "\n%%EOF";
        self::headers('application/pdf', $filename);
        echo $pdf;
        exit;
    }

    private static function safeCell(mixed $value): string
    {
        $cell = str_replace(["\t", "\r", "\n"], ' ', (string)$value);
        if ($cell !== '' && in_array($cell[0], ['=', '+', '-', '@'], true)) {
            $cell = "'" . $cell;
        }
        return $cell;
    }

    private static function headers(string $contentType, string $filename): void
    {
        $filename = Security::safeFilename($filename);
        header('Content-Type: ' . $contentType);
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        header('Cache-Control: private, no-store, max-age=0');
        header('X-Content-Type-Options: nosniff');
    }
}
