<?php

namespace NoahVet\Reef\Domain\Http\Multipart;

class HeaderParser implements HeaderParserInterface
{
    protected const BUFFER_SIZE = 8192;
    protected const MAX_HEADER_LENGTH = 8192;

    /**
     * @param resource $stream
     *
     * @return array{headers: array<string, string|string[]>, bodyOffset: int}
     */
    #[\Override]
    public function parse(mixed $stream): array
    {
        \rewind($stream);

        $endOfHeaders = false;
        $headerLines = [];
        $buffer = '';

        while (false !== ($line = \fgets($stream, self::BUFFER_SIZE))) {
            $buffer .= \rtrim($line, "\r\n");

            if (\strlen($line) === self::BUFFER_SIZE - 1) {
                continue;
            }

            if ('' === $buffer) {
                $endOfHeaders = true;
                break;
            }

            $trimmed = \ltrim($buffer);
            if (\strlen($buffer) > \strlen($trimmed)) {
                $headerLines[\count($headerLines) - 1] .= "\x20".$trimmed;
            } else {
                $headerLines[] = $buffer;
            }

            $buffer = '';
        }

        if (false === $endOfHeaders) {
            throw new \InvalidArgumentException('Content is not valid');
        }

        $headers = [];

        foreach ($headerLines as $line) {
            if (self::MAX_HEADER_LENGTH < \strlen($line)) {
                throw new \InvalidArgumentException('Malformed header: header value is too long');
            }

            $lineSplit = \explode(':', $line, 2);

            if (2 === \count($lineSplit)) {
                [$key, $value] = $lineSplit;
                $value = \mb_decode_mimeheader(\trim($value));
            } else {
                $key = $lineSplit[0];
                $value = '';
            }

            $key = \strtolower($key);

            if (!\array_key_exists($key, $headers)) {
                $headers[$key] = $value;
            } elseif (\is_array($headers[$key])) {
                $headers[$key][] = $value;
            } else {
                $headers[$key] = [$headers[$key], $value];
            }
        }

        $bodyOffset = \ftell($stream);
        if (false === $bodyOffset) {
            throw new \RuntimeException('Could not determine stream position');
        }

        return [
            'headers' => $headers,
            'bodyOffset' => $bodyOffset,
        ];
    }
}
