<?php

declare(strict_types=1);

/*
 * This file is a part of the DiscordPHP project.
 *
 * Copyright (c) 2015-2022 David Cole <david.cole1340@gmail.com>
 * Copyright (c) 2020-present Valithor Obsidion <valithor@discordphp.org>
 *
 * This file is subject to the MIT license that is bundled
 * with this source code in the LICENSE.md file.
 */

namespace Discord\Helpers;

use Stringable;

use function Discord\poly_strlen;

/**
 * Builds a multipart request.
 *
 * @since 5.1.0
 *
 * @author David Cole <david.cole1340@gmail.com>
 */
class Multipart implements Stringable
{
    /**
     * The boundary separating multipart sections.
     */
    private const BOUNDARY = '----DiscordPHPSendFileBoundary';

    /**
     * Fields part of the request.
     *
     * @var array
     */
    protected $fields = [];

    /**
     * Field boundary.
     *
     * @var string
     */
    protected $boundary;

    /**
     * Multipart constructor.
     *
     * @param array  $fields
     * @param string $boundary Defaults to DiscordPHPSendFileBoundary
     */
    public function __construct(array $fields = [], string $boundary = self::BOUNDARY)
    {
        $this->fields = $fields;
        $this->boundary = $boundary;
    }

    /**
     * Adds a field to the request.
     *
     * ```php
     * $field = [
     *     'name' => 'Field name',
     *     'content' => 'Field content',
     *
     *      // Optional
     *     'filename' => 'File name',
     *     'headers' => [
     *         // ...
     *     ],
     * ];
     * ```
     *
     * @param array ...$fields
     *
     * @return $this
     */
    public function add(...$fields): self
    {
        foreach ($fields as $field) {
            $this->fields[] = $field;
        }

        return $this;
    }

    /**
     * Gets the headers for the given request.
     *
     * @return array
     */
    public function getHeaders(): array
    {
        return [
            'Content-Type' => $this->getContentType(),
            'Content-Length' => poly_strlen((string) $this),
        ];
    }

    /**
     * Gets the content type for the multipart request.
     *
     * @return string
     */
    public function getContentType(): string
    {
        return 'multipart/form-data; boundary='.substr($this->boundary, 2);
    }

    /**
     * Converts the multipart request to string.
     *
     * @return string
     */
    public function __toString(): string
    {
        $body = '';

        foreach ($this->fields as $field) {
            $body .= $this->boundary."\n";
            $body .= "Content-Disposition: form-data; name={$field['name']}";

            if (isset($field['filename'])) {
                $body .= "; filename={$field['filename']}";
            }

            $body .= "\n";

            if (isset($field['headers'])) {
                foreach ($field['headers'] as $header => $value) {
                    $body .= $header.': '.$value."\n";
                }
            }

            $body .= "\n".$field['content']."\n";
        }

        $body .= $this->boundary."--\n";

        return $body;
    }
}
