#pragma once
#ifndef MOZA_HPP
#define MOZA_HPP

#include "moza.h"

#include <cstring>
#include <functional>
#include <mutex>
#include <string>
#include <utility>
#include <vector>

namespace moza {

/** @brief Error code type alias. */
using Errno = MOZA_ERRNO;
/** @brief Boolean type alias. */
using Bool = MOZA_BOOL;
/** @brief Size/count type alias. */
using Size = MOZA_SIZE;
/** @brief Device identifier type alias. */
using DeviceId = MOZA_DEVICE_ID;
/**
 * @brief Command identifier type alias.
 *
 * The SDK package includes ApiDebugTool.exe, which displays the available commands for a device
 * and can be used to send commands. This tool can help applications locate the required command
 * identifiers and command names.
 */
using CommandId = MOZA_COMMAND_ID;
/**
 * @brief Parameter identifier type alias.
 *
 * The SDK package includes ApiDebugTool.exe, which displays the available parameters for a device
 * and can be used to fetch, get, and set parameter values. This tool can help applications locate
 * the required parameter identifiers and parameter names.
 */
using ParameterId = MOZA_PARAMETER_ID;
/** @brief Device handle type alias. */
using DeviceHandle = MOZA_DEVICE_HANDLE;

/** @brief Event type alias. */
using EventType = MOZA_EVENT_TYPE;
/** @brief Event payload type alias. */
using EventData = MOZA_EVENT_DATA;
/** @brief C++ event callback type alias. */
using EventCallback = std::function<void(const EventData*, void*)>;
/** @brief Log level type alias. */
using LogLevel = MOZA_LOG_LEVEL;
/** @brief Log context type alias. */
using LogContext = MOZA_LOG_CONTEXT;
/** @brief C++ log callback type alias. */
using LogCallback = std::function<void(const LogContext*, void*)>;

/** @brief Device category type alias. */
using DeviceCategory = MOZA_DEVICE_CATEGORY;
/** @brief Device state type alias. */
using DeviceState = MOZA_DEVICE_STATE;
/** @brief Variant value type alias. */
using ValueType = MOZA_VALUE_TYPE;
/** @brief HID axis type alias. */
using HidAxis = MOZA_HID_AXIS;

/** @brief Device information type alias. */
using DeviceInfo = MOZA_DEVICE_INFO;
/** @brief HID device information type alias. */
using HidDeviceInfo = MOZA_HID_DEVICE_INFO;
/** @brief Parameter information type alias. */
using ParameterInfo = MOZA_PARAMETER_INFO;
/** @brief Command information type alias. */
using CommandInfo = MOZA_COMMAND_INFO;
/** @brief HID axis sample type alias. */
using HidAxisData = MOZA_HID_AXIS_DATA;
/** @brief HID input state type alias. */
using HidInputState = MOZA_HID_INPUT_STATE;

/**
 * @brief Implementation details for the C++ convenience layer.
 *
 * This namespace is not part of the stable public interface.
 * The declarations inside it may change without notice.
 */
namespace internal {

/** @brief Storage used by the C++ event callback adapter. */
struct EventCallbackStorage
{
    std::mutex    mutex;
    EventCallback callback;
    void*         userData = nullptr;
};

/** @brief Storage used by the C++ log callback adapter. */
struct LogCallbackStorage
{
    std::mutex  mutex;
    LogCallback callback;
    void*       userData = nullptr;
};

/** @brief Returns the storage used by the C++ event callback adapter. */
inline EventCallbackStorage& eventCallbackStorage()
{
    static EventCallbackStorage storage;
    return storage;
}

/** @brief Returns the storage used by the C++ log callback adapter. */
inline LogCallbackStorage& logCallbackStorage()
{
    static LogCallbackStorage storage;
    return storage;
}

/** @brief Forwards the C callback to the registered C++ callback object. */
inline void eventCallbackTrampoline(const EventData* event, void* userData)
{
    (void)userData;

    EventCallback callback;
    void*         callbackUserData = nullptr;
    {
        std::scoped_lock lock(eventCallbackStorage().mutex);
        callback         = eventCallbackStorage().callback;
        callbackUserData = eventCallbackStorage().userData;
    }

    if (callback)
    {
        callback(event, callbackUserData);
    }
}

/** @brief Forwards the C log callback to the registered C++ callback object. */
inline void logCallbackTrampoline(const LogContext* context, void* userData)
{
    (void)userData;

    LogCallback callback;
    void*       callbackUserData = nullptr;
    {
        std::scoped_lock lock(logCallbackStorage().mutex);
        callback         = logCallbackStorage().callback;
        callbackUserData = logCallbackStorage().userData;
    }

    if (callback)
    {
        callback(context, callbackUserData);
    }
}

/** @brief Deep-copies a C value object for the C++ convenience layer. */
inline bool cloneValue(const MOZA_VALUE& source, MOZA_VALUE* destination)
{
    return Moza_ValueCopy(&source, destination) == MOZA_TRUE;
}

} // namespace internal

/**
 * @brief Initializes the SDK.
 *
 * @return SDK error code.
 */
inline Errno initialize()
{
    return Moza_Initialize();
}

/**
 * @brief Returns the encoded SDK version number.
 *
 * @return SDK version number encoded by MOZA_SDK_MAKE_VERSION.
 */
inline uint32_t getSdkVersionNumber()
{
    return Moza_GetSdkVersionNumber();
}

/**
 * @brief Returns the SDK version string.
 *
 * @return SDK version string in `major.minor.patch.tweak` form.
 */
inline const char* getSdkVersionString()
{
    return Moza_GetSdkVersionString();
}

/**
 * @brief Shuts down the SDK and releases all resources.
 */
inline void shutdown()
{
    Moza_Shutdown();
}

/**
 * @brief Registers a raw C-style event callback.
 *
 * This overload forwards the callback directly to the C API.
 *
 * @param callback Raw C callback function. nullptr clears the callback.
 * @param userData Caller-defined pointer passed back to the callback.
 * @return SDK error code.
 */
inline Errno setEventCallback(MOZA_EVENT_CALLBACK callback, void* userData)
{
    {
        std::scoped_lock lock(internal::eventCallbackStorage().mutex);
        internal::eventCallbackStorage().callback = {};
        internal::eventCallbackStorage().userData = nullptr;
    }

    return Moza_SetEventCallback(callback, userData);
}

/**
 * @brief Registers a C++ event callback.
 *
 * The callback is invoked asynchronously in serialized order.
 * Long-running callback work may delay later event delivery.
 *
 * @param callback C++ callback object. An empty callback clears the registration.
 * @param userData Caller-defined pointer passed back to the callback.
 * @return SDK error code.
 */
inline Errno setEventCallback(EventCallback callback, void* userData = nullptr)
{
    bool hasCallback;
    {
        std::scoped_lock lock(internal::eventCallbackStorage().mutex);
        internal::eventCallbackStorage().callback = std::move(callback);
        internal::eventCallbackStorage().userData = userData;
        hasCallback                               = static_cast<bool>(internal::eventCallbackStorage().callback);
    }

    return Moza_SetEventCallback(hasCallback ? &internal::eventCallbackTrampoline : nullptr, nullptr);
}

/**
 * @brief Registers a raw C-style log callback.
 *
 * This overload forwards the callback directly to the C API.
 *
 * @param callback Raw C log callback function. nullptr clears the callback.
 * @param userData Caller-defined pointer passed back to the callback.
 * @return SDK error code.
 */
inline Errno setLogCallback(MOZA_LOG_CALLBACK callback, void* userData)
{
    {
        std::scoped_lock lock(internal::logCallbackStorage().mutex);
        internal::logCallbackStorage().callback = {};
        internal::logCallbackStorage().userData = nullptr;
    }

    return Moza_SetLogCallback(callback, userData);
}

/**
 * @brief Registers a C++ log callback.
 *
 * The callback receives forwarded internal SDK/DeviceSDK log records.
 * Log level, message, and category are provided through the LogContext object.
 * Register the callback before initialize() if initialization-time logs are needed.
 *
 * @param callback C++ callback object. An empty callback clears the registration.
 * @param userData Caller-defined pointer passed back to the callback.
 * @return SDK error code.
 */
inline Errno setLogCallback(LogCallback callback, void* userData = nullptr)
{
    bool hasCallback;
    {
        std::scoped_lock lock(internal::logCallbackStorage().mutex);
        internal::logCallbackStorage().callback = std::move(callback);
        internal::logCallbackStorage().userData = userData;
        hasCallback                             = static_cast<bool>(internal::logCallbackStorage().callback);
    }

    return Moza_SetLogCallback(hasCallback ? &internal::logCallbackTrampoline : nullptr, nullptr);
}

/**
 * @brief Configures the minimum dispatch interval for HID axis change events.
 *
 * @param intervalMs Minimum interval in milliseconds.
 * @return SDK error code.
 */
inline Errno setHidAxisEventMinInterval(Size intervalMs)
{
    return Moza_SetHidAxisEventMinInterval(intervalMs);
}

/**
 * @brief Tests whether a 1-based HID button number is pressed in a HID input state snapshot.
 *
 * This function is the C++ convenience wrapper for MOZA_HID_INPUT_STATE_IS_BUTTON_PRESSED.
 * Button numbers start from 1, matching the button numbers used by HID button events.
 *
 * @param state HID input state snapshot.
 * @param buttonNumber 1-based button number.
 * @return true if the specified button is pressed; otherwise false.
 */
inline bool isHidButtonPressed(const HidInputState& state, int buttonNumber)
{
    return MOZA_HID_INPUT_STATE_IS_BUTTON_PRESSED(&state, buttonNumber) != MOZA_FALSE;
}

/**
 * @brief C++ wrapper for MOZA_VALUE.
 *
 * This class owns the underlying MOZA_VALUE and releases any heap-backed payloads automatically.
 */
class Value
{
public:
    /** @brief Constructs an invalid value. */
    Value() = default;

    /** @brief Constructs a signed integer value. */
    Value(int val) { setValueImpl(static_cast<int64_t>(val)); }
    /** @brief Constructs an unsigned integer value. */
    Value(unsigned val) { setValueImpl(static_cast<uint64_t>(val)); }
    /** @brief Constructs a signed integer value. */
    Value(int64_t val) { setValueImpl(val); }
    /** @brief Constructs an unsigned integer value. */
    Value(uint64_t val) { setValueImpl(val); }
    /** @brief Constructs a floating-point value. */
    Value(double val) { setValueImpl(val); }
    /** @brief Constructs an API boolean value. */
    Value(Bool val) { setValueImpl(val); }
    /** @brief Constructs a boolean value. */
    Value(bool val) { setValueImpl(val); }
    /** @brief Constructs a UTF-8 string value. */
    Value(const char* val) { setValueImpl(std::string(val ? val : "")); }
    /** @brief Constructs a UTF-8 string value. */
    Value(const std::string& val) { setValueImpl(val); }
    /** @brief Constructs a binary value. */
    Value(const std::vector<uint8_t>& val) { setValueImpl(val); }
    /** @brief Constructs a list value. */
    Value(const std::vector<Value>& val) { setValueImpl(val); }

    /** @brief Constructs a value by deep-copying another value. */
    Value(const Value& other)
    {
        assign(other);
    }

    /** @brief Constructs a value by moving another value. */
    Value(Value&& other) noexcept
        : m_value(other.m_value)
    {
        std::memset(&other.m_value, 0, sizeof(MOZA_VALUE));
        other.m_value.type = MOZA_VT_INVALID;
    }

    /** @brief Destroys the value and releases any owned memory. */
    ~Value()
    {
        reset();
    }

    /** @brief Deep-copies another value into this value. */
    Value& operator=(const Value& other)
    {
        if (this != &other)
        {
            assign(other);
        }
        return *this;
    }

    /** @brief Moves another value into this value. */
    Value& operator=(Value&& other) noexcept
    {
        if (this != &other)
        {
            reset();
            m_value = other.m_value;
            std::memset(&other.m_value, 0, sizeof(MOZA_VALUE));
            other.m_value.type = MOZA_VT_INVALID;
        }
        return *this;
    }

    /** @brief Returns true if the value contains a valid payload. */
    bool isValid() const { return m_value.type != MOZA_VT_INVALID; }
    /** @brief Returns true if the value is null or invalid. */
    bool isNull() const { return !isValid() || m_value.type == MOZA_VT_NULL; }
    /** @brief Returns the current value type. */
    ValueType type() const { return m_value.type; }
    /** @brief Returns true if the value is an integer (signed or unsigned). */
    bool isInteger() const { return m_value.type == MOZA_VT_INT || m_value.type == MOZA_VT_UINT; }
    /** @brief Returns true if the value is a signed integer. */
    bool isUnsigned() const { return m_value.type == MOZA_VT_UINT; }
    /** @brief Returns true if the value is a floating-point number. */
    bool isFloat() const { return m_value.type == MOZA_VT_DOUBLE; }
    /** @brief Returns true if the value is a boolean. */
    bool isBool() const { return m_value.type == MOZA_VT_BOOL; }
    /** @brief Returns true if the value is a string. */
    bool isString() const { return m_value.type == MOZA_VT_STRING; }
    /** @brief Returns true if the value is binary data. */
    bool isBinary() const { return m_value.type == MOZA_VT_BINARY; }
    /** @brief Returns true if the value is a list. */
    bool isList() const { return m_value.type == MOZA_VT_LIST; }

    /** @brief Returns a const pointer to the underlying C value. */
    const MOZA_VALUE* raw() const { return &m_value; }
    /** @brief Returns a mutable pointer to the underlying C value. */
    MOZA_VALUE* raw() { return &m_value; }

    /**
     * @brief Creates a deep-copied C++ value from a C value.
     *
     * @param value Source C value.
     * @return Deep-copied wrapper value.
     */
    static Value fromRaw(const MOZA_VALUE& value)
    {
        Value result;
        internal::cloneValue(value, &result.m_value);
        return result;
    }

    /** @brief Resets the value to an invalid state and releases any owned memory. */
    void reset()
    {
        Moza_ValueDestroy(&m_value);
    }

    /** @brief Returns the signed integer value if available; otherwise returns @p fallback. */
    int64_t asInt(int64_t fallback = 0) const { return isInteger() ? m_value.data.intValue : fallback; }
    /** @brief Returns the unsigned integer value if available; otherwise returns @p fallback. */
    uint64_t asUInt(uint64_t fallback = 0) const { return isInteger() ? m_value.data.uintValue : fallback; }
    /** @brief Returns the double value if available; otherwise returns @p fallback. */
    double asDouble(double fallback = 0.0) const { return isFloat() ? m_value.data.doubleValue : fallback; }
    /** @brief Returns the boolean value if available; otherwise returns @p fallback. */
    bool asBool(bool fallback = false) const { return isBool() ? (m_value.data.boolValue != MOZA_FALSE) : fallback; }

    /** @brief Returns the UTF-8 string value, or an empty string if the value is not a string. */
    std::string asString() const
    {
        if (!isString() || !m_value.data.stringValue.data)
        {
            return {};
        }
        return std::string(m_value.data.stringValue.data, m_value.data.stringValue.size);
    }

    /** @brief Returns the binary value, or an empty vector if the value is not binary. */
    std::vector<uint8_t> asBinary() const
    {
        if (!isBinary() || !m_value.data.binaryValue.data)
        {
            return {};
        }
        return std::vector<uint8_t>(m_value.data.binaryValue.data,
                                    m_value.data.binaryValue.data + m_value.data.binaryValue.size);
    }

    /** @brief Returns the list value, or an empty vector if the value is not a list. */
    std::vector<Value> asList() const
    {
        std::vector<Value> result;
        if (!isList() || !m_value.data.listValue.items)
        {
            return result;
        }
        result.reserve(m_value.data.listValue.size);
        for (Size i = 0; i < m_value.data.listValue.size; ++i)
        {
            result.push_back(Value::fromRaw(m_value.data.listValue.items[i]));
        }
        return result;
    }

private:
    MOZA_VALUE m_value{};

    void assign(const Value& other)
    {
        reset();
        if (!internal::cloneValue(other.m_value, &m_value))
        {
            std::memset(&m_value, 0, sizeof(MOZA_VALUE));
            m_value.type = MOZA_VT_INVALID;
        }
    }

    void setValueImpl(int64_t val)
    {
        reset();
        m_value.type          = MOZA_VT_INT;
        m_value.data.intValue = val;
    }

    void setValueImpl(uint64_t val)
    {
        reset();
        m_value.type           = MOZA_VT_UINT;
        m_value.data.uintValue = val;
    }

    void setValueImpl(double val)
    {
        reset();
        m_value.type             = MOZA_VT_DOUBLE;
        m_value.data.doubleValue = val;
    }

    void setValueImpl(MOZA_BOOL val)
    {
        reset();
        m_value.type           = MOZA_VT_BOOL;
        m_value.data.boolValue = val;
    }

    void setValueImpl(bool val)
    {
        reset();
        m_value.type           = MOZA_VT_BOOL;
        m_value.data.boolValue = val ? MOZA_TRUE : MOZA_FALSE;
    }

    void setValueImpl(const std::string& string)
    {
        reset();
        if (Moza_ValueCreate(MOZA_VT_STRING, &m_value, string.size()) != MOZA_TRUE)
        {
            m_value.type = MOZA_VT_INVALID;
            return;
        }
        if (m_value.data.stringValue.data && !string.empty())
        {
            std::memcpy(m_value.data.stringValue.data, string.data(), string.size());
            m_value.data.stringValue.data[string.size()] = '\0';
        }
    }

    void setValueImpl(const std::vector<uint8_t>& bytes)
    {
        reset();
        if (Moza_ValueCreate(MOZA_VT_BINARY, &m_value, bytes.size()) != MOZA_TRUE)
        {
            m_value.type = MOZA_VT_INVALID;
            return;
        }
        if (m_value.data.binaryValue.data && !bytes.empty())
        {
            std::memcpy(m_value.data.binaryValue.data, bytes.data(), bytes.size());
        }
    }

    void setValueImpl(const std::vector<Value>& values)
    {
        reset();
        if (Moza_ValueCreate(MOZA_VT_LIST, &m_value, values.size()) != MOZA_TRUE)
        {
            m_value.type = MOZA_VT_INVALID;
            return;
        }

        for (size_t i = 0; i < values.size(); ++i)
        {
            if (!internal::cloneValue(values[i].m_value, &m_value.data.listValue.items[i]))
            {
                Moza_ValueDestroy(&m_value);
                m_value.type = MOZA_VT_INVALID;
                return;
            }
        }
    }
};

/**
 * @brief Gets the current device list.
 *
 * @param devices Output device list.
 * @return SDK error code.
 */
inline Errno getDeviceList(std::vector<DeviceInfo>& devices)
{
    Size  count = 0;
    Errno err   = Moza_DeviceGetList(nullptr, &count);
    if (err != MOZA_ERR_NO_ERROR)
    {
        return err;
    }

    devices.assign(count, DeviceInfo{});
    if (count == 0)
    {
        return MOZA_ERR_NO_ERROR;
    }

    return Moza_DeviceGetList(devices.data(), &count);
}

/**
 * @brief Gets the current device list.
 *
 * @return Pair of SDK error code and device list.
 */
inline std::pair<Errno, std::vector<DeviceInfo>> getDeviceList()
{
    std::vector<DeviceInfo> devices;
    const Errno             err = getDeviceList(devices);
    return {err, std::move(devices)};
}

/**
 * @brief Queries device information by device identifier.
 *
 * @param deviceId Device identifier.
 * @param info Output device information.
 * @return SDK error code.
 */
inline Errno getDeviceInfoById(DeviceId deviceId, DeviceInfo* info)
{
    return Moza_DeviceGetInfoById(deviceId, info);
}

/**
 * @brief Queries device information by device identifier.
 *
 * @param deviceId Device identifier.
 * @return Pair of SDK error code and device info.
 */
inline std::pair<Errno, DeviceInfo> getDeviceInfoById(DeviceId deviceId)
{
    DeviceInfo  info{};
    const Errno err = Moza_DeviceGetInfoById(deviceId, &info);
    return {err, info};
}

class RawParameter
{
public:
    /** @brief Constructs an invalid parameter object. */
    RawParameter() = default;

    /** @brief Constructs a parameter object from an open device handle and parameter identifier. */
    RawParameter(DeviceHandle handle, ParameterId parameterId)
        : m_handle(handle), m_parameterId(parameterId) {}

    /** @brief Returns true if the object refers to a device parameter. */
    bool isValid() const { return m_handle != MOZA_INVALID_DEVICE_HANDLE && m_parameterId >= 0; }
    /** @brief Returns the associated device handle. */
    DeviceHandle deviceHandle() const { return m_handle; }
    /** @brief Returns the parameter identifier. */
    ParameterId id() const { return m_parameterId; }

    /** @brief Asynchronously requests that the SDK refresh the latest value for the parameter. */
    Errno fetch() const
    {
        return Moza_DeviceParameterFetch(m_handle, m_parameterId);
    }

    /** @brief Synchronously requests the latest parameter value and waits for the reply. */
    Errno fetchSync(int32_t timeoutMs, Value* value) const
    {
        return Moza_DeviceParameterFetchSync(m_handle, m_parameterId, timeoutMs, value ? value->raw() : nullptr);
    }

    /** @brief Synchronously requests the latest parameter value and waits for the reply. */
    std::pair<Errno, Value> fetchSync(int32_t timeoutMs) const
    {
        Value       value;
        const Errno err = fetchSync(timeoutMs, &value);
        return {err, std::move(value)};
    }

    /** @brief Queries parameter information. */
    Errno getInfo(ParameterInfo* info) const
    {
        return Moza_DeviceParameterGetInfoById(m_handle, m_parameterId, info);
    }

    /** @brief Queries parameter information. */
    std::pair<Errno, ParameterInfo> getInfo() const
    {
        ParameterInfo info{};
        const Errno   err = getInfo(&info);
        return {err, info};
    }

    /** @brief Gets the current cached value of the parameter without waiting. On cache miss, the SDK also triggers one background get request and the later result can be observed through the parameter value changed event. */
    Errno getValue(Value* value) const
    {
        if (!value)
        {
            return MOZA_ERR_INVALID_ARGUMENT;
        }
        return Moza_DeviceParameterGetValue(m_handle, m_parameterId, value->raw());
    }

    /** @brief Gets the current cached value of the parameter without waiting. On cache miss, the SDK also triggers one background get request and the later result can be observed through the parameter value changed event. */
    std::pair<Errno, Value> getValue() const
    {
        Value       value;
        const Errno err = getValue(&value);
        return {err, std::move(value)};
    }

    /** @brief Synchronously gets the parameter value, waiting for a refresh when needed. */
    Errno getValueSync(int32_t timeoutMs, Value* value) const
    {
        if (!value)
        {
            return MOZA_ERR_INVALID_ARGUMENT;
        }
        return Moza_DeviceParameterGetValueSync(m_handle, m_parameterId, timeoutMs, value->raw());
    }

    /** @brief Synchronously gets the parameter value, waiting for a refresh when needed. */
    std::pair<Errno, Value> getValueSync(int32_t timeoutMs) const
    {
        Value       value;
        const Errno err = getValueSync(timeoutMs, &value);
        return {err, std::move(value)};
    }

    /** @brief Asynchronously sets the value of the parameter. */
    Errno setValue(const Value& value) const
    {
        return Moza_DeviceParameterSetValue(m_handle, m_parameterId, value.raw());
    }

    /** @brief Synchronously sets the value of the parameter and waits for the result value. */
    Errno setValueSync(const Value& value, int32_t timeoutMs, Value* resultValue) const
    {
        return Moza_DeviceParameterSetValueSync(
            m_handle,
            m_parameterId,
            value.raw(),
            timeoutMs,
            resultValue ? resultValue->raw() : nullptr);
    }

    /** @brief Synchronously sets the value of the parameter and waits for the result value. */
    std::pair<Errno, Value> setValueSync(const Value& value, int32_t timeoutMs) const
    {
        Value       resultValue;
        const Errno err = setValueSync(value, timeoutMs, &resultValue);
        return {err, std::move(resultValue)};
    }

private:
    DeviceHandle m_handle      = MOZA_INVALID_DEVICE_HANDLE;
    ParameterId  m_parameterId = -1;
};

/**
 * @brief C++ wrapper for a device command.
 *
 * Command objects identify a device command by device handle and command identifier.
 */
class RawCommand
{
public:
    /** @brief Constructs an invalid command object. */
    RawCommand() = default;

    /** @brief Constructs a command object from an open device handle and command identifier. */
    RawCommand(DeviceHandle handle, CommandId commandId)
        : m_handle(handle), m_commandId(commandId) {}

    /** @brief Returns true if the object refers to a device command. */
    bool isValid() const { return m_handle != MOZA_INVALID_DEVICE_HANDLE && m_commandId >= 0; }
    /** @brief Returns the associated device handle. */
    DeviceHandle deviceHandle() const { return m_handle; }
    /** @brief Returns the command identifier. */
    CommandId id() const { return m_commandId; }

    /** @brief Asynchronously sends a command with a single encoded argument value. Use a list value for multiple arguments. */
    Errno send(const Value& arguments) const
    {
        return Moza_DeviceCommandSend(m_handle, m_commandId, arguments.raw());
    }

    /** @brief Asynchronously sends a command with no arguments. */
    Errno send() const
    {
        return Moza_DeviceCommandSend(m_handle, m_commandId, nullptr);
    }

    /** @brief Synchronously sends a command with a single encoded argument value and waits for the result value. */
    Errno sendSync(const Value& arguments, int32_t timeoutMs, Value* resultValue) const
    {
        return Moza_DeviceCommandSendSync(m_handle, m_commandId, arguments.raw(), timeoutMs, resultValue ? resultValue->raw() : nullptr);
    }

    /** @brief Synchronously sends a command with no arguments and waits for the result value. */
    Errno sendSync(int32_t timeoutMs, Value* resultValue) const
    {
        return Moza_DeviceCommandSendSync(m_handle, m_commandId, nullptr, timeoutMs, resultValue ? resultValue->raw() : nullptr);
    }

    /** @brief Synchronously sends a command with a single encoded argument value and waits for the result value. */
    std::pair<Errno, Value> sendSync(const Value& arguments, int32_t timeoutMs) const
    {
        Value       resultValue;
        const Errno err = sendSync(arguments, timeoutMs, &resultValue);
        return {err, std::move(resultValue)};
    }

    /** @brief Synchronously sends a command with no arguments and waits for the result value. */
    std::pair<Errno, Value> sendSync(int32_t timeoutMs) const
    {
        Value       resultValue;
        const Errno err = sendSync(timeoutMs, &resultValue);
        return {err, std::move(resultValue)};
    }

    /**
     * @brief Asynchronously sends a command with a C++ argument vector.
     *
     * @param arguments Command arguments.
     * @return SDK error code.
     */
    Errno send(const std::vector<Value>& arguments) const
    {
        return send(Value(arguments));
    }

    /** @brief Synchronously sends a command with a C++ argument vector and waits for the reply. */
    Errno sendSync(const std::vector<Value>& arguments, int32_t timeoutMs, Value* resultValue) const
    {
        return sendSync(Value(arguments), timeoutMs, resultValue);
    }

    /** @brief Synchronously sends a command with a C++ argument vector and waits for the reply. */
    std::pair<Errno, Value> sendSync(const std::vector<Value>& arguments, int32_t timeoutMs) const
    {
        return sendSync(Value(arguments), timeoutMs);
    }

    /** @brief Queries command information. */
    Errno getInfo(CommandInfo* info) const
    {
        return Moza_DeviceCommandGetInfoById(m_handle, m_commandId, info);
    }

    /** @brief Queries command information. */
    std::pair<Errno, CommandInfo> getInfo() const
    {
        CommandInfo info{};
        const Errno err = getInfo(&info);
        return {err, info};
    }

private:
    DeviceHandle m_handle    = MOZA_INVALID_DEVICE_HANDLE;
    CommandId    m_commandId = -1;
};

/**
 * @brief C++ wrapper for a HID device associated with an open device.
 *
 * HID devices are addressed by index and provide access to HID information and input state snapshots.
 */
class HidDevice
{
public:
    /** @brief Constructs an invalid HID device object. */
    HidDevice() = default;

    /** @brief Constructs a HID device object from an open device handle and HID index. */
    HidDevice(DeviceHandle handle, uint8_t hidIndex)
        : m_handle(handle), m_hidIndex(hidIndex) {}

    /** @brief Returns true if the object refers to a HID device. */
    bool isValid() const { return m_handle != MOZA_INVALID_DEVICE_HANDLE; }
    /** @brief Returns the associated device handle. */
    DeviceHandle deviceHandle() const { return m_handle; }
    /** @brief Returns the HID index. */
    uint8_t index() const { return m_hidIndex; }

    /** @brief Returns true if the object refers to a valid HID device. */
    explicit operator bool() const { return isValid(); }

    /** @brief Queries HID device information. */
    Errno getInfo(HidDeviceInfo* info) const
    {
        if (!info)
        {
            return MOZA_ERR_INVALID_ARGUMENT;
        }

        Size  count = 0;
        Errno err   = Moza_DeviceHidGetList(m_handle, nullptr, &count);
        if (err != MOZA_ERR_NO_ERROR)
        {
            return err;
        }
        if (m_hidIndex >= count)
        {
            return MOZA_ERR_INDEX_OUT_OF_BOUNDS;
        }

        std::vector<HidDeviceInfo> infos(count);
        err = Moza_DeviceHidGetList(m_handle, infos.data(), &count);
        if (err != MOZA_ERR_NO_ERROR)
        {
            return err;
        }

        *info = infos[m_hidIndex];
        return MOZA_ERR_NO_ERROR;
    }

    /** @brief Queries HID device information. */
    std::pair<Errno, HidDeviceInfo> getInfo() const
    {
        HidDeviceInfo info{};
        const Errno   err = getInfo(&info);
        return {err, info};
    }

    /** @brief Opens the HID device for reading. */
    Errno open() const
    {
        return Moza_DeviceHidOpen(m_handle, m_hidIndex);
    }

    /** @brief Closes the HID device. */
    Errno close() const
    {
        return Moza_DeviceHidClose(m_handle, m_hidIndex);
    }

    /** @brief Gets the latest HID input state snapshot. */
    Errno getInputState(HidInputState* inputState) const
    {
        return Moza_DeviceHidGetInputState(m_handle, m_hidIndex, inputState);
    }

    /** @brief Gets the latest HID input state snapshot. */
    std::pair<Errno, HidInputState> getInputState() const
    {
        HidInputState inputState{};
        const Errno   err = getInputState(&inputState);
        return {err, inputState};
    }

private:
    DeviceHandle m_handle   = MOZA_INVALID_DEVICE_HANDLE;
    uint8_t      m_hidIndex = 0;
};

/**
 * @brief RAII wrapper for an open device handle.
 *
 * This class owns the underlying device handle and closes it automatically on destruction.
 */
class Device
{
public:
    /** @brief Constructs an invalid device object. */
    Device() = default;
    /** @brief Constructs a device object from an open C handle. */
    explicit Device(DeviceHandle handle) : m_handle(handle) {}

    /** @brief Device handles are non-copyable. */
    Device(const Device&) = delete;
    /** @brief Device handles are non-copyable. */
    Device& operator=(const Device&) = delete;

    /** @brief Constructs a device by moving another device. */
    Device(Device&& other) noexcept : m_handle(other.m_handle)
    {
        other.m_handle = MOZA_INVALID_DEVICE_HANDLE;
    }

    /** @brief Moves another device into this device. */
    Device& operator=(Device&& other) noexcept
    {
        if (this != &other)
        {
            close();
            m_handle       = other.m_handle;
            other.m_handle = MOZA_INVALID_DEVICE_HANDLE;
        }
        return *this;
    }

    /** @brief Destroys the device and closes it if it is still open. */
    ~Device()
    {
        close();
    }

    /**
     * @brief Opens a device by device identifier.
     *
     * @param deviceId Device identifier.
     * @return Pair of SDK error code and device wrapper.
     */
    static std::pair<Errno, Device> open(DeviceId deviceId)
    {
        DeviceHandle handle = MOZA_INVALID_DEVICE_HANDLE;
        const Errno  err    = Moza_DeviceOpen(deviceId, &handle);
        return {err, Device(handle)};
    }

    /** @brief Closes the device handle. */
    Errno close()
    {
        if (!isOpen())
        {
            return MOZA_ERR_NO_ERROR;
        }

        const Errno err = Moza_DeviceClose(m_handle);
        if (err == MOZA_ERR_NO_ERROR)
        {
            m_handle = MOZA_INVALID_DEVICE_HANDLE;
        }
        return err;
    }

    /** @brief Returns true if the device handle is open. */
    bool isOpen() const { return m_handle != MOZA_INVALID_DEVICE_HANDLE; }
    /** @brief Returns the underlying C device handle. */
    DeviceHandle raw() const { return m_handle; }
    /** @brief Returns true if the object refers to an open device. */
    explicit operator bool() const { return isOpen(); }

    /** @brief Finds a parameter identifier by parameter name. */
    Errno getParameterIdByName(const char* parameterName, ParameterId* parameterId) const
    {
        return Moza_DeviceParameterGetIdByName(m_handle, parameterName, parameterId);
    }

    /** @brief Finds a parameter identifier by parameter name. */
    std::pair<Errno, ParameterId> getParameterIdByName(const std::string& parameterName) const
    {
        ParameterId parameterId{};
        const Errno err = getParameterIdByName(parameterName.c_str(), &parameterId);
        return {err, parameterId};
    }

    /**
     * @brief Gets the full parameter information list supported by the open device.
     *
     * ApiDebugTool.exe included with the SDK can also be used to inspect the same parameter list
     * and to test fetch, get, and set operations interactively.
     */
    Errno getParameterList(std::vector<ParameterInfo>& infos) const
    {
        Size  count = 0;
        Errno err   = Moza_DeviceParameterGetList(m_handle, nullptr, &count);
        if (err != MOZA_ERR_NO_ERROR)
        {
            return err;
        }

        infos.assign(count, ParameterInfo{});
        if (count == 0)
        {
            return MOZA_ERR_NO_ERROR;
        }

        return Moza_DeviceParameterGetList(m_handle, infos.data(), &count);
    }

    /**
     * @brief Gets the full parameter information list supported by the open device.
     *
     * ApiDebugTool.exe included with the SDK can also be used to inspect the same parameter list
     * and to test fetch, get, and set operations interactively.
     */
    std::pair<Errno, std::vector<ParameterInfo>> getParameterList() const
    {
        std::vector<ParameterInfo> infos;
        const Errno                err = getParameterList(infos);
        return {err, std::move(infos)};
    }

    /** @brief Creates a parameter object from a parameter identifier. */
    RawParameter parameter(ParameterId parameterId) const
    {
        return RawParameter(m_handle, parameterId);
    }

    /** @brief Finds a parameter by name and creates a parameter object. */
    Errno getParameter(const char* parameterName, RawParameter* parameter) const
    {
        if (!parameter)
        {
            return MOZA_ERR_INVALID_ARGUMENT;
        }

        ParameterId parameterId{};
        const Errno err = getParameterIdByName(parameterName, &parameterId);
        if (err != MOZA_ERR_NO_ERROR)
        {
            return err;
        }

        *parameter = RawParameter(m_handle, parameterId);
        return MOZA_ERR_NO_ERROR;
    }

    /** @brief Finds a parameter by name and creates a parameter object. */
    std::pair<Errno, RawParameter> getParameter(const std::string& parameterName) const
    {
        RawParameter parameter;
        const Errno  err = getParameter(parameterName.c_str(), &parameter);
        return {err, parameter};
    }

    /** @brief Queries parameter information by parameter identifier. */
    Errno getParameterInfo(ParameterId parameterId, ParameterInfo* info) const
    {
        return Moza_DeviceParameterGetInfoById(m_handle, parameterId, info);
    }

    /** @brief Queries parameter information by parameter identifier. */
    std::pair<Errno, ParameterInfo> getParameterInfo(ParameterId parameterId) const
    {
        ParameterInfo info{};
        const Errno   err = getParameterInfo(parameterId, &info);
        return {err, info};
    }

    /** @brief Queries parameter information by parameter name. */
    Errno getParameterInfoByName(const char* parameterName, ParameterInfo* info) const
    {
        return Moza_DeviceParameterGetInfoByName(m_handle, parameterName, info);
    }

    /** @brief Queries parameter information by parameter name. */
    std::pair<Errno, ParameterInfo> getParameterInfoByName(const std::string& parameterName) const
    {
        ParameterInfo info{};
        const Errno   err = getParameterInfoByName(parameterName.c_str(), &info);
        return {err, info};
    }

    /** @brief Creates a command object from a command identifier. */
    RawCommand command(CommandId commandId) const
    {
        return RawCommand(m_handle, commandId);
    }

    /** @brief Creates a command object from a command identifier. */
    RawCommand getCommand(CommandId commandId) const { return command(commandId); }

    /** @brief Queries command information by command identifier. */
    Errno getCommandInfo(CommandId commandId, CommandInfo* info) const
    {
        return Moza_DeviceCommandGetInfoById(m_handle, commandId, info);
    }

    /** @brief Queries command information by command identifier. */
    std::pair<Errno, CommandInfo> getCommandInfo(CommandId commandId) const
    {
        CommandInfo info{};
        const Errno err = getCommandInfo(commandId, &info);
        return {err, info};
    }

    /** @brief Queries command information by command name. */
    Errno getCommandInfoByName(const char* commandName, CommandInfo* info) const
    {
        return Moza_DeviceCommandGetInfoByName(m_handle, commandName, info);
    }

    /** @brief Queries command information by command name. */
    std::pair<Errno, CommandInfo> getCommandInfoByName(const std::string& commandName) const
    {
        CommandInfo info{};
        const Errno err = getCommandInfoByName(commandName.c_str(), &info);
        return {err, info};
    }

    /**
     * @brief Gets the full command information list supported by the open device.
     *
     * ApiDebugTool.exe included with the SDK can also be used to inspect the same command list
     * and to test command sending interactively.
     */
    Errno getCommandList(std::vector<CommandInfo>& infos) const
    {
        Size  count = 0;
        Errno err   = Moza_DeviceCommandGetList(m_handle, nullptr, &count);
        if (err != MOZA_ERR_NO_ERROR)
        {
            return err;
        }

        infos.assign(count, CommandInfo{});
        if (count == 0)
        {
            return MOZA_ERR_NO_ERROR;
        }

        return Moza_DeviceCommandGetList(m_handle, infos.data(), &count);
    }

    /**
     * @brief Gets the full command information list supported by the open device.
     *
     * ApiDebugTool.exe included with the SDK can also be used to inspect the same command list
     * and to test command sending interactively.
     */
    std::pair<Errno, std::vector<CommandInfo>> getCommandList() const
    {
        std::vector<CommandInfo> infos;
        const Errno              err = getCommandList(infos);
        return {err, std::move(infos)};
    }

    /** @brief Gets the HID device list associated with the open device. */
    Errno getHidList(std::vector<HidDeviceInfo>& infos) const
    {
        Size  count = 0;
        Errno err   = Moza_DeviceHidGetList(m_handle, nullptr, &count);
        if (err != MOZA_ERR_NO_ERROR)
        {
            return err;
        }

        infos.assign(count, HidDeviceInfo{});
        if (count == 0)
        {
            return MOZA_ERR_NO_ERROR;
        }

        return Moza_DeviceHidGetList(m_handle, infos.data(), &count);
    }

    /** @brief Gets the HID device list associated with the open device. */
    std::pair<Errno, std::vector<HidDeviceInfo>> getHidList() const
    {
        std::vector<HidDeviceInfo> infos;
        const Errno                err = getHidList(infos);
        return {err, std::move(infos)};
    }

    /** @brief Queries HID device information by HID index. */
    Errno getHidInfo(uint8_t hidIndex, HidDeviceInfo* info) const
    {
        if (!info)
        {
            return MOZA_ERR_INVALID_ARGUMENT;
        }

        auto hid = getHid(hidIndex);
        return hid.getInfo(info);
    }

    /** @brief Queries HID device information by HID index. */
    std::pair<Errno, HidDeviceInfo> getHidInfo(uint8_t hidIndex) const
    {
        HidDeviceInfo info{};
        const Errno   err = getHidInfo(hidIndex, &info);
        return {err, info};
    }

    /** @brief Creates a HID device object from a HID index. */
    HidDevice getHid(uint8_t hidIndex) const
    {
        return HidDevice(m_handle, hidIndex);
    }

private:
    DeviceHandle m_handle = MOZA_INVALID_DEVICE_HANDLE;
};

} // namespace moza

#endif // MOZA_HPP
