<?php

declare(strict_types=1);

namespace Ost\OstCapabilityDataExport\Service;

final class CsvWriter
{
    /**
     * @param string[] $headers
     * @param array<int, array<string, mixed>> $rows
     */
    public function render(
        array $headers,
        array $rows,
        string $delimiter,
        bool $includeBom,
        bool $includeHeader
    ): string {
        $normalizedDelimiter = $this->normalizeDelimiter($delimiter);

        $stream = fopen('php://temp', 'r+');
        if (!is_resource($stream)) {
            throw new \RuntimeException('Unable to open temporary CSV stream.');
        }

        if ($includeBom) {
            fwrite($stream, "\xEF\xBB\xBF");
        }

        if ($includeHeader) {
            fputcsv($stream, $headers, $normalizedDelimiter);
        }

        foreach ($rows as $row) {
            $ordered = [];
            foreach ($headers as $header) {
                $ordered[] = $this->stringify($row[$header] ?? null);
            }
            fputcsv($stream, $ordered, $normalizedDelimiter);
        }

        rewind($stream);
        $content = stream_get_contents($stream);
        fclose($stream);

        if (!is_string($content)) {
            throw new \RuntimeException('Unable to read generated CSV content.');
        }

        return $content;
    }

    private function normalizeDelimiter(string $delimiter): string
    {
        $trimmed = trim($delimiter);
        if ($trimmed === ';' || $trimmed === ',' || $trimmed === "\t") {
            return $trimmed;
        }

        return ';';
    }

    private function stringify(mixed $value): string
    {
        if ($value instanceof \DateTimeInterface) {
            return $value->format(DATE_ATOM);
        }

        if (is_bool($value)) {
            return $value ? '1' : '0';
        }

        if (is_scalar($value)) {
            return (string) $value;
        }

        if (is_array($value)) {
            $encoded = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

            return is_string($encoded) ? $encoded : '';
        }

        if ($value instanceof \Stringable) {
            return (string) $value;
        }

        return '';
    }
}
