<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Http;

use InvalidArgumentException;
use Plugin\tnt_mcp\src\Facade\HttpResult;
use RuntimeException;

/**
 * The one way this plugin talks to the outside world over HTTP (design §4.7).
 *
 * It lives in its own namespace rather than under `Facade\` on purpose — same reason as
 * {@see \Plugin\tnt_mcp\src\Mail\MailSender}: it is meant for both lanes, the `CallURL` ACTION
 * a merchant clicks together (design step 10, still on `Request::http_get_contents()`) and
 * `Facade\NotifyApi::webhook()` a snippet calls. A seam under `Facade\` would suggest the action
 * goes through the facade, which it does not.
 *
 * The two failure kinds are kept apart, because they need different reactions:
 * - `InvalidArgumentException` — the CALLER asked wrongly (not a URL, not http(s), a method we do
 *   not send, a header with a newline in it, or a target the SSRF guard refuses). Retrying is
 *   pointless; the snippet has to be fixed.
 * - `RuntimeException` — the TRANSPORT failed (DNS, connect, TLS, timeout). Nothing was answered.
 *
 * A non-2xx response is NEITHER: it is an answer, and it comes back as an {@see HttpResult} with
 * `ok() === false`. Deciding what a 429 means is the snippet's business, not the client's.
 */
interface HttpClient
{
    /** What a snippet may send. `TRACE`/`CONNECT` are not on the list and never will be. */
    public const METHODS = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'];

    /** Response bytes we are willing to read (design §4.7). Past this the body is cut, not failed. */
    public const MAX_BODY_BYTES = 1048576;

    /** Redirect hops followed, each one re-checked by the SSRF guard (design §4.7). */
    public const MAX_REDIRECTS = 3;

    /** Seconds, total. Long enough for a slow webhook receiver, short enough not to hold a cron. */
    public const DEFAULT_TIMEOUT = 10;

    /**
     * @param array<string,string> $headers header name => value; `Host` and the length/encoding
     *                                      headers are the client's business and are ignored
     * @param int                  $timeout seconds, total (connect + transfer)
     *
     * @throws InvalidArgumentException when the request itself is not something we will send
     * @throws RuntimeException         when nothing came back at all
     */
    public function request(
        string $method,
        string $url,
        array $headers = [],
        ?string $body = null,
        int $timeout = self::DEFAULT_TIMEOUT,
    ): HttpResult;
}
