<?php

namespace NoahVet\Reef\Domain\Http\Multipart;

class PartExtractor implements PartExtractorInterface
{
    private const BUFFER_SIZE = 8192;

    /**
     * @param resource $stream
     *
     * @return resource[]
     */
    #[\Override]
    public function extract(mixed $stream, string $boundary, int $bodyOffset): array
    {
        $separator = '--'.$boundary;
        $parts = [];
        $partOffset = 0;
        $endOfBody = false;
        $eofLength = 0;

        \fseek($stream, $bodyOffset);

        while ($line = \fgets($stream, self::BUFFER_SIZE)) {
            $trimmed = \rtrim($line, "\r\n");

            if ($trimmed !== $separator && $trimmed !== $separator.'--') {
                $eofLength = \strlen($line) - \strlen($trimmed);
                continue;
            }

            if (0 < $partOffset) {
                $currentOffset = \ftell($stream);
                if (false === $currentOffset) {
                    throw new \RuntimeException('Could not determine stream position');
                }
                $partLength = $currentOffset - $partOffset - \strlen($line) - $eofLength;

                $partStream = \fopen('php://temp', 'rw');
                if (!\is_resource($partStream)) {
                    throw new \RuntimeException('Failed to open temporary stream');
                }
                \stream_copy_to_stream($stream, $partStream, $partLength, $partOffset);
                $parts[] = $partStream;

                \fseek($stream, $currentOffset);
            }

            if ($trimmed === $separator.'--') {
                $endOfBody = true;
                break;
            }

            $partOffset = \ftell($stream);
            if (false === $partOffset) {
                throw new \RuntimeException('Could not determine stream position');
            }
            $eofLength = \strlen($line) - \strlen($trimmed);
        }

        if (0 === \count($parts) || false === $endOfBody) {
            throw new \LogicException("Can't find multi-part content");
        }

        return $parts;
    }
}
