#pragma once

#include <cerrno>
#include <cstdint>
#include <cstring>
#include <string>

namespace tw::net::quicr {

/**
 * Error categories surfaced by the QUICr stack.
 *
 * The socket-level values are taken straight from <cerrno> so a QuicrError can
 * be built directly from `errno` after a failed UDP syscall (see from_errno).
 * The protocol-level values use a negative range to stay clear of the errno
 * space; they describe QUICr conditions that have no errno equivalent.
 */
enum class QuicrErrorType : int32_t {
    // Protocol-level errors (no errno equivalent).
    ConnectionClosed = -1,
    Unknown          = -2,

    // Socket / errno-derived errors raised by UDP operations.
    MessageTooLong             = EMSGSIZE,
    AddressFamilyNotSupported  = EAFNOSUPPORT,
    BadFileDescriptor          = EBADF,
    ConnectionReset            = ECONNRESET,
    WouldBlock                 = EWOULDBLOCK,
    Interrupted                = EINTR,
    InvalidArgument            = EINVAL,
    NotConnected               = ENOTCONN,
    NotSocket                  = ENOTSOCK,
    OperationNotSupported      = EOPNOTSUPP,
    TimedOut                   = ETIMEDOUT,
    IoError                    = EIO,
    NoBufferSpace              = ENOBUFS,
    NotEnoughMemory            = ENOMEM,
    DestinationAddressRequired = EDESTADDRREQ,
    BrokenPipe                 = EPIPE,
};

struct QuicrError {
public:
    QuicrError(QuicrErrorType type)
        : type_(type), message_(map_quicr_error_type(type)) {}
    QuicrError(QuicrErrorType type, std::string message)
        : type_(type), message_(std::move(message)) {}

    QuicrErrorType type() const { return type_; }
    std::string message() const { return message_; }

    /**
     * Builds a QuicrError from a raw errno value (as returned by UDP socket
     * syscalls). Unknown codes still carry the errno through and fall back to
     * strerror() for their message.
     */
    static QuicrError from_errno(int32_t err) {
        return QuicrError(static_cast<QuicrErrorType>(err));
    }

private:
    static std::string map_quicr_error_type(QuicrErrorType type) {
        switch (type) {
            case QuicrErrorType::ConnectionClosed:
                return "The QUICr connection has been closed.";
            case QuicrErrorType::MessageTooLong:
                return "The message is larger than the maximum supported datagram size.";
            case QuicrErrorType::AddressFamilyNotSupported:
                return "The address family is not supported.";
            case QuicrErrorType::BadFileDescriptor:
                return "The socket is not a valid file descriptor.";
            case QuicrErrorType::ConnectionReset:
                return "The connection was forcibly closed by the peer.";
            case QuicrErrorType::WouldBlock:
                return "The operation would block on a non-blocking socket.";
            case QuicrErrorType::Interrupted:
                return "The operation was interrupted by a signal before any data was transferred.";
            case QuicrErrorType::InvalidArgument:
                return "An invalid argument was supplied.";
            case QuicrErrorType::NotConnected:
                return "The socket is not connected.";
            case QuicrErrorType::NotSocket:
                return "The operation was attempted on a non-socket.";
            case QuicrErrorType::OperationNotSupported:
                return "The operation is not supported for this socket type or protocol.";
            case QuicrErrorType::TimedOut:
                return "The operation timed out.";
            case QuicrErrorType::IoError:
                return "An I/O error occurred.";
            case QuicrErrorType::NoBufferSpace:
                return "Insufficient buffer space was available to complete the operation.";
            case QuicrErrorType::NotEnoughMemory:
                return "Insufficient memory was available to complete the operation.";
            case QuicrErrorType::DestinationAddressRequired:
                return "A destination address is required for this operation.";
            case QuicrErrorType::BrokenPipe:
                return "The write end of the socket has been closed.";
            case QuicrErrorType::Unknown:
                return "Unknown QUICr error.";
            default:
                return std::string(std::strerror(static_cast<int>(type)));
        }
    }

    QuicrErrorType type_;
    std::string message_;
};

}
