<?php

namespace NoahVet\Reef\Domain\Http\Multipart;

class MultipartPart
{
    /**
     * @param array<string, string|string[]> $headers
     * @param resource                       $stream
     * @param self[]                         $parts
     */
    public function __construct(
        private readonly array $headers,
        private readonly mixed $stream,
        private readonly int $bodyOffset,
        private readonly array $parts,
        private readonly ContentDecoderInterface $decoder,
        private readonly HeaderValueParserInterface $headerValueParser,
    ) {
    }

    public function isMultiPart(): bool
    {
        $type = \mb_strstr(
            $this->headerValueParser->getValue($this->getHeader('Content-Type')),
            '/',
            true,
        );

        return 'multipart' === \mb_strtolower(false !== $type ? $type : '');
    }

    /** @return array<string, string|string[]> */
    public function getHeaders(): array
    {
        return $this->headers;
    }

    public function getHeader(string $key, mixed $default = null): mixed
    {
        return $this->headers[\strtolower($key)] ?? $default;
    }

    public function getMimeType(): string
    {
        return $this->headerValueParser->getValue($this->getHeader('Content-Type')) ?: 'application/octet-stream';
    }

    public function getName(): ?string
    {
        return $this->headerValueParser->getOption($this->getHeader('Content-Disposition'), 'name');
    }

    public function getFileName(): ?string
    {
        return $this->headerValueParser->getOption($this->getHeader('Content-Disposition'), 'filename');
    }

    public function isFile(): bool
    {
        return null !== $this->getFileName();
    }

    /**
     * @return self[]
     */
    public function getParts(): array
    {
        if (!$this->isMultiPart()) {
            throw new \LogicException("Not MultiPart content, there aren't any parts");
        }

        return $this->parts;
    }

    /**
     * @return self[]
     */
    public function getPartsByName(string $name): array
    {
        return \array_values(\array_filter(
            $this->getParts(),
            static fn (self $part) => $part->getName() === $name,
        ));
    }

    public function getBody(): string
    {
        if ($this->isMultiPart()) {
            throw new \LogicException("MultiPart content, there aren't body");
        }

        $raw = (string) \stream_get_contents($this->stream, -1, $this->bodyOffset);
        $encoding = \strtolower((string) $this->getHeader('Content-Transfer-Encoding', '7bit'));

        return $this->decoder->decode($raw, $encoding, $this->getHeader('Content-Type'));
    }

    /**
     * @return resource
     */
    public function getBodyStream(): mixed
    {
        if ($this->isMultiPart()) {
            throw new \LogicException("MultiPart content, there aren't body");
        }

        $encoding = \strtolower((string) $this->getHeader('Content-Transfer-Encoding', '7bit'));

        return $this->decoder->decodeToStream($this->stream, $this->bodyOffset, $encoding, $this->getHeader('Content-Type'));
    }
}
