#include "InputBinding.hpp"

namespace MayaFlux::Core {

InputBinding InputBinding::hid(uint32_t device_id)
{
    return { .backend = InputType::HID, .device_id = device_id };
}

InputBinding InputBinding::midi(uint32_t device_id, std::optional<uint8_t> channel)
{
    return {
        .backend = InputType::MIDI,
        .device_id = device_id,
        .midi_channel = channel
    };
}

InputBinding InputBinding::osc(const std::string& pattern)
{
    return {
        .backend = InputType::OSC,
        .device_id = 0,
        .osc_address_pattern = pattern.empty() ? std::nullopt : std::optional(pattern)
    };
}

InputBinding InputBinding::serial(uint32_t device_id)
{
    return { .backend = InputType::SERIAL, .device_id = device_id };
}

InputBinding InputBinding::tablet(uint32_t device_id)
{
    return { .backend = InputType::TABLET, .device_id = device_id };
}

InputBinding InputBinding::hid_by_vid_pid(uint16_t vid, uint16_t pid)
{
    return {
        .backend = InputType::HID,
        .device_id = 0,
        .hid_vendor_id = vid,
        .hid_product_id = pid
    };
}

InputBinding InputBinding::midi_cc(
    std::optional<uint8_t> cc_number,
    std::optional<uint8_t> channel,
    uint32_t device_id)
{
    return {
        .backend = InputType::MIDI,
        .device_id = device_id,
        .midi_channel = channel,
        .midi_message_type = 0xB0, // Control Change
        .midi_cc_number = cc_number
    };
}

InputBinding InputBinding::midi_note_on(
    std::optional<uint8_t> channel,
    uint32_t device_id)
{
    return {
        .backend = InputType::MIDI,
        .device_id = device_id,
        .midi_channel = channel,
        .midi_message_type = 0x90 // Note On
    };
}

InputBinding InputBinding::midi_note_off(
    std::optional<uint8_t> channel,
    uint32_t device_id)
{
    return {
        .backend = InputType::MIDI,
        .device_id = device_id,
        .midi_channel = channel,
        .midi_message_type = 0x80 // Note Off
    };
}

InputBinding InputBinding::midi_pitch_bend(
    std::optional<uint8_t> channel,
    uint32_t device_id)
{
    return {
        .backend = InputType::MIDI,
        .device_id = device_id,
        .midi_channel = channel,
        .midi_message_type = 0xE0 // Pitch Bend
    };
}

InputBinding& InputBinding::with_midi_channel(uint8_t channel)
{
    midi_channel = channel;
    return *this;
}

InputBinding& InputBinding::with_midi_cc(uint8_t cc)
{
    midi_cc_number = cc;
    return *this;
}

InputBinding& InputBinding::with_osc_pattern(const std::string& pattern)
{
    osc_address_pattern = pattern;
    return *this;
}

// ────────────────────────────────────────────────────────────────────────────
// OSCMessage Argument Extraction Methods
// ────────────────────────────────────────────────────────────────────────────

std::optional<float> InputValue::OSCMessage::get_float(size_t index) const noexcept
{
    if (index >= arguments.size())
        return std::nullopt;
    return std::visit([](const auto& v) -> std::optional<float> {
        using T = std::decay_t<decltype(v)>;
        if constexpr (std::is_same_v<T, float>)
            return v;
        if constexpr (std::is_same_v<T, int32_t>)
            return static_cast<float>(v);
        return std::nullopt;
    },
        arguments[index]);
}

std::optional<int32_t> InputValue::OSCMessage::get_int(size_t index) const noexcept
{
    if (index >= arguments.size())
        return std::nullopt;
    return std::visit([](const auto& v) -> std::optional<int32_t> {
        using T = std::decay_t<decltype(v)>;
        if constexpr (std::is_same_v<T, int32_t>)
            return v;
        if constexpr (std::is_same_v<T, float>)
            return static_cast<int32_t>(v);
        return std::nullopt;
    },
        arguments[index]);
}

std::optional<std::string> InputValue::OSCMessage::get_string(size_t index) const noexcept
{
    if (index >= arguments.size())
        return std::nullopt;
    return std::visit([](const auto& v) -> std::optional<std::string> {
        using T = std::decay_t<decltype(v)>;
        if constexpr (std::is_same_v<T, std::string>)
            return v;
        return std::nullopt;
    },
        arguments[index]);
}

std::optional<std::vector<uint8_t>> InputValue::OSCMessage::get_blob(size_t index) const noexcept
{
    if (index >= arguments.size())
        return std::nullopt;
    return std::visit([](const auto& v) -> std::optional<std::vector<uint8_t>> {
        using T = std::decay_t<decltype(v)>;
        if constexpr (std::is_same_v<T, std::vector<uint8_t>>)
            return v;
        return std::nullopt;
    },
        arguments[index]);
}

// ────────────────────────────────────────────────────────────────────────────
// InputDeviceInfo Implementation (inline)
// ────────────────────────────────────────────────────────────────────────────

InputBinding InputDeviceInfo::to_binding() const
{
    return InputBinding {
        .backend = backend_type,
        .device_id = id
    };
}

InputBinding InputDeviceInfo::to_binding_midi(std::optional<uint8_t> channel) const
{
    return InputBinding {
        .backend = backend_type,
        .device_id = id,
        .midi_channel = channel
    };
}

InputBinding InputDeviceInfo::to_binding_osc(const std::string& pattern) const
{
    return InputBinding {
        .backend = backend_type,
        .device_id = id,
        .osc_address_pattern = pattern.empty() ? std::nullopt : std::optional(pattern)
    };
}

InputValue InputValue::make_scalar(double v, uint32_t dev_id, InputType src)
{
    return {
        .type = Type::SCALAR,
        .data = v,
        .timestamp_ns = static_cast<uint64_t>(
            std::chrono::steady_clock::now().time_since_epoch().count()),
        .device_id = dev_id,
        .source_type = src
    };
}

InputValue InputValue::make_vector(std::vector<double> v, uint32_t dev_id, InputType src)
{
    return {
        .type = Type::VECTOR,
        .data = std::move(v),
        .timestamp_ns = static_cast<uint64_t>(
            std::chrono::steady_clock::now().time_since_epoch().count()),
        .device_id = dev_id,
        .source_type = src
    };
}

InputValue InputValue::make_bytes(std::vector<uint8_t> v, uint32_t dev_id, InputType src)
{
    return {
        .type = Type::BYTES,
        .data = std::move(v),
        .timestamp_ns = static_cast<uint64_t>(
            std::chrono::steady_clock::now().time_since_epoch().count()),
        .device_id = dev_id,
        .source_type = src
    };
}

InputValue InputValue::make_midi(uint8_t status, uint8_t d1, uint8_t d2, uint32_t dev_id)
{
    return {
        .type = Type::MIDI,
        .data = MIDIMessage { .status = status, .data1 = d1, .data2 = d2 },
        .timestamp_ns = static_cast<uint64_t>(
            std::chrono::steady_clock::now().time_since_epoch().count()),
        .device_id = dev_id,
        .source_type = InputType::MIDI
    };
}

InputValue InputValue::make_osc(std::string addr, std::vector<OSCArg> args, uint32_t dev_id)
{
    return {
        .type = Type::OSC,
        .data = OSCMessage { .address = std::move(addr), .arguments = std::move(args) },
        .timestamp_ns = static_cast<uint64_t>(
            std::chrono::steady_clock::now().time_since_epoch().count()),
        .device_id = dev_id,
        .source_type = InputType::OSC
    };
}

std::optional<double> InputValue::try_scalar() const noexcept
{
    if (type != Type::SCALAR)
        return std::nullopt;
    return std::get<double>(data);
}

const std::vector<double>* InputValue::try_vector() const noexcept
{
    if (type != Type::VECTOR)
        return nullptr;
    return &std::get<std::vector<double>>(data);
}

const std::vector<uint8_t>* InputValue::try_bytes() const noexcept
{
    if (type != Type::BYTES)
        return nullptr;
    return &std::get<std::vector<uint8_t>>(data);
}

const InputValue::MIDIMessage* InputValue::try_midi() const noexcept
{
    if (type != Type::MIDI)
        return nullptr;
    return &std::get<MIDIMessage>(data);
}

const InputValue::OSCMessage* InputValue::try_osc() const noexcept
{
    if (type != Type::OSC)
        return nullptr;
    return &std::get<OSCMessage>(data);
}

} // namespace MayaFlux::Core
