#pragma once

#include <stdint.h>

#include "crArray.h"

// crNet — polled HTTP + WebSocket client. main thread only: Update() ticks the transports,
// PollEvent() drains the results — nothing ever calls back into game code.

// a request ends with exactly one HTTP_RESULT or HTTP_FAILED; a socket with exactly one WS_CLOSED (WS_OPEN may never arrive)
enum class ENetEventType : uint8_t
{
    NONE = 0,
    HTTP_RESULT,   // 2xx/3xx — status = HTTP status code, data = response body
    HTTP_FAILED,   // transport failure (status 0) or 4xx/5xx — status = HTTP status code or 0, data = response body or error text
    WS_OPEN,
    WS_TEXT,
    WS_BINARY,
    WS_CLOSED,     // status = close code, 0 = transport error
};

struct crNetBackend;   // transport internals — one definition per backend .cpp, only one is ever linked

struct crNetEvent
{
    int32_t        id;
    ENetEventType  type;
    int32_t        status;
    const uint8_t* data;     // never null, NUL-terminated beyond size; valid only until the next PollEvent
    int32_t        size;
};

class crNet
{
public:
    static constexpr int32_t     INVALID_ID     = 0;
    static constexpr const char* CA_BUNDLE_PATH = "crassets/certs/cacert.pem";   // native only

    // timeouts — tune here
    static constexpr int32_t HTTP_CONNECT_TIMEOUT_MS = 10000;   // native only
    static constexpr int32_t HTTP_TOTAL_TIMEOUT_MS   = 30000;
    static constexpr int32_t WS_CONNECT_TIMEOUT_MS   = 10000;   // crNet-enforced — both backends

private:
    struct EventRecord
    {
        int32_t       id;
        ENetEventType type;
        int32_t       status;
        int32_t       payloadOffset;
        int32_t       payloadSize;
    };

    crArray<EventRecord> _events;
    crArray<uint8_t>     _payloads;
    int32_t              _idCounter = 0;
    crNetBackend*        _backend   = nullptr;

public:
    bool Init();
    void Cleanup();

    bool IsInitted() const { return _backend != nullptr; }   // false = backend init failed (missing cacert.pem / lib init) — not a connectivity check

    void Update();    // once per render frame

    // returns INVALID_ID on failure to start. headers entries are full "Name: value" strings
    int32_t Get ( const char* url, const char* const* headers = nullptr, int32_t headerCount = 0 );
    int32_t Post( const char* url, const char* contentType, const void* body, int32_t bodySize,
                  const char* const* headers = nullptr, int32_t headerCount = 0 );

    int32_t WsConnect( const char* url );
    bool    WsSendText( int32_t id, const char* text );   // must be valid UTF-8 (RFC 6455 — peers kill the connection on violation)
    bool    WsSendBinary( int32_t id, const void* data, int32_t size );
    void    WsClose( int32_t id );                        // WS_CLOSED still arrives through the queue

    // non-destructive queue: read this frame's events, then ClearAllEvents once when every reader is done.
    // an event (and its data) is valid until the next ClearAllEvents() or Update() — copy out to keep it longer.
    int32_t    EventCount() const;
    crNetEvent EventAt( int32_t index ) const;   // index 0 = oldest
    void       ClearAllEvents();

private:
    void PushEvent( int32_t id, ENetEventType type, int32_t status, const void* payload, int32_t payloadSize );

    friend struct crNetBackend;
};
