<?php

declare(strict_types=1);

namespace Gls\GlsPoland\Download;

use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;

final class SpreadsheetFile implements StreamableFileInterface
{
    private const FORMAT_CSV = 'csv';
    private const FORMAT_XLS = 'xls';
    private const FORMAT_XLSX = 'xlsx';

    private const FORMATS = [
        self::FORMAT_CSV,
        self::FORMAT_XLS,
        self::FORMAT_XLSX,
    ];

    private $format;
    private $dataFactory;

    /**
     * @param callable(): iterable<array> $dataFactory
     */
    public function __construct(string $format, callable $dataFactory)
    {
        if (!in_array($format, self::FORMATS, true)) {
            throw new \InvalidArgumentException(sprintf('Unrecognized format: "%s".', $format));
        }

        $this->format = $format;
        $this->dataFactory = $dataFactory;
    }

    public function stream(): void
    {
        $data = ($this->dataFactory)();
        if ($data instanceof \Traversable) {
            $data = iterator_to_array($data);
        }

        $spreadsheet = new Spreadsheet();
        $worksheet = $spreadsheet->getActiveSheet();
        $worksheet->fromArray($data);

        foreach (range('A', $worksheet->getHighestColumn()) as $column) {
            $worksheet->getColumnDimension($column)->setAutoSize(true);
        }
        $worksheet->calculateColumnWidths();

        IOFactory::createWriter($spreadsheet, $this->getWriterType())->save('php://output');
    }

    public function getContentType(): string
    {
        switch ($this->format) {
            case self::FORMAT_XLS:
                return 'application/vnd.ms-excel';
            case self::FORMAT_XLSX:
                return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
            default:
                return 'text/csv';
        }
    }

    public function getFileExtension(): string
    {
        return $this->format;
    }

    private function getWriterType(): string
    {
        return \Tools::ucfirst($this->format);
    }
}
