#include "lluav_sdk/client.hpp"

#include <websocketpp/config/asio_no_tls_client.hpp>
#include <websocketpp/client.hpp>

#include <mutex>
#include <condition_variable>
#include <queue>
#include <atomic>
#include <sstream>
#include <iomanip>
#include <thread>
#include <map>

namespace lluav {

// ---------------------------------------------------------------------------
// Exceptions
// ---------------------------------------------------------------------------

LLUAVError::LLUAVError(const std::string& msg) : msg_(msg) {}
const char* LLUAVError::what() const noexcept { return msg_.c_str(); }

LLUAVCommandError::LLUAVCommandError(const std::string& msg, int ret_code, const json& response)
    : LLUAVError(msg), ret_code_(ret_code), response_(response) {}
int LLUAVCommandError::ret_code() const noexcept { return ret_code_; }
const json& LLUAVCommandError::response() const noexcept { return response_; }

LLUAVConnectionError::LLUAVConnectionError(const std::string& msg) : LLUAVError(msg) {}

// ---------------------------------------------------------------------------
// Implementation
// ---------------------------------------------------------------------------

using WsClient = websocketpp::client<websocketpp::config::asio_no_tls_client>;

struct QueuedCommand {
    std::string message;
    std::chrono::steady_clock::time_point deadline;
    std::promise<json> promise;
    int retry_count = 0;
};

class LLUAVClient::Impl {
public:
    Impl(const std::string& host, int port, const std::string& password,
         Duration timeout, bool auto_reconnect, Duration reconnect_interval,
         Duration max_reconnect_interval, int max_retries,
         Duration heartbeat_interval, const std::string& heartbeat_cmd)
        : host_(host), port_(port), password_(password), timeout_(timeout),
          auto_reconnect_(auto_reconnect), reconnect_interval_(reconnect_interval),
          max_reconnect_interval_(max_reconnect_interval), max_retries_(max_retries),
          heartbeat_interval_(heartbeat_interval), heartbeat_cmd_(heartbeat_cmd),
          connected_(false), shutdown_(true) {

        client_.clear_access_channels(websocketpp::log::alevel::all);
        client_.clear_error_channels(websocketpp::log::elevel::all);
        client_.init_asio();
        client_.start_perpetual();

        client_.set_open_handler([this](websocketpp::connection_hdl hdl) {
            std::lock_guard<std::mutex> lock(mutex_);
            handle_ = hdl;
            connected_ = true;
            cv_.notify_all();
        });

        client_.set_close_handler([this](websocketpp::connection_hdl) {
            std::lock_guard<std::mutex> lock(mutex_);
            connected_ = false;
            handle_.reset();
            cv_.notify_all();
        });

        client_.set_fail_handler([this](websocketpp::connection_hdl) {
            std::lock_guard<std::mutex> lock(mutex_);
            connected_ = false;
            handle_.reset();
            cv_.notify_all();
        });

        client_.set_message_handler([this](websocketpp::connection_hdl, WsClient::message_ptr msg) {
            this->on_message(msg->get_payload());
        });
    }

    ~Impl() {
        disconnect();
    }

    void connect() {
        if (!shutdown_) return;
        shutdown_ = false;
        do_connect();
        asio_thread_ = std::thread([this]() { client_.run(); });
        worker_thread_ = std::thread([this]() { worker_loop(); });
        if (heartbeat_interval_.count() > 0) {
            heartbeat_thread_ = std::thread([this]() { heartbeat_loop(); });
        }
        periodic_thread_ = std::thread([this]() { periodic_loop(); });
    }

    void disconnect() {
        shutdown_ = true;
        cv_.notify_all();
        queue_cv_.notify_all();
        login_cv_.notify_all();
        current_cmd_cv_.notify_all();

        // Cancel pending commands
        {
            std::lock_guard<std::mutex> lock(queue_mutex_);
            while (!queue_.empty()) {
                auto cmd = queue_.front();
                queue_.pop();
                if (!cmd->promise.get_future().wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
                    try {
                        cmd->promise.set_exception(std::make_exception_ptr(
                            LLUAVConnectionError("Disconnected by user")));
                    } catch (...) {}
                }
            }
        }

        close_ws();
        client_.stop_perpetual();

        if (asio_thread_.joinable()) asio_thread_.join();
        if (worker_thread_.joinable()) worker_thread_.join();
        if (heartbeat_thread_.joinable()) heartbeat_thread_.join();
        if (periodic_thread_.joinable()) periodic_thread_.join();
    }

    bool is_connected() const {
        std::lock_guard<std::mutex> lock(mutex_);
        return connected_ && handle_.lock();
    }

    std::string url() const {
        return "ws://" + host_ + ":" + std::to_string(port_);
    }

    json send_cmd(const std::string& command, const std::string& args, Duration timeout) {
        auto cmd = std::make_shared<QueuedCommand>();
        cmd->message = command;
        if (!args.empty()) cmd->message += " " + args;
        auto dur = timeout.count() > 0 ? timeout : timeout_;
        cmd->deadline = std::chrono::steady_clock::now() + dur;

        {
            std::lock_guard<std::mutex> lock(queue_mutex_);
            queue_.push(cmd);
            queue_cv_.notify_one();
        }

        auto future = cmd->promise.get_future();
        future.wait();
        return future.get();
    }

    void add_periodic_task(const std::string& name, int interval_sec,
                           const std::string& cmd, const std::string& args) {
        std::lock_guard<std::mutex> lock(periodic_mutex_);
        periodic_tasks_[name] = std::make_tuple(interval_sec, cmd, args,
            std::chrono::steady_clock::now());
    }

    void remove_periodic_task(const std::string& name) {
        std::lock_guard<std::mutex> lock(periodic_mutex_);
        periodic_tasks_.erase(name);
    }

private:
    void do_connect() {
        websocketpp::lib::error_code ec;
        auto con = client_.get_connection(url(), ec);
        if (ec) {
            shutdown_ = true;
            throw LLUAVConnectionError("Connection error: " + ec.message());
        }
        client_.connect(con);

        std::unique_lock<std::mutex> lock(mutex_);
        if (!cv_.wait_for(lock, timeout_, [this]() { return connected_ || shutdown_; })) {
            shutdown_ = true;
            throw LLUAVConnectionError("Connection timeout to " + url());
        }
        if (shutdown_) {
            throw LLUAVConnectionError("Connection cancelled");
        }

        if (!password_.empty()) {
            lock.unlock();
            do_login();
        }
    }

    void do_login() {
        websocketpp::lib::error_code ec;
        client_.send(handle_, "login " + password_, websocketpp::frame::opcode::text, ec);
        if (ec) {
            shutdown_ = true;
            throw LLUAVConnectionError("Login send failed: " + ec.message());
        }

        std::unique_lock<std::mutex> lock(login_mutex_);
        login_done_ = false;
        if (!login_cv_.wait_for(lock, timeout_, [this]() { return login_done_ || shutdown_; })) {
            shutdown_ = true;
            throw LLUAVConnectionError("Login timeout");
        }
        if (shutdown_) {
            throw LLUAVConnectionError("Login cancelled");
        }
        if (login_resp_.value("ret", -1) != 0) {
            shutdown_ = true;
            throw LLUAVCommandError(login_resp_.value("msg", "Login failed"),
                                    login_resp_.value("ret", -1), login_resp_);
        }
    }

    void close_ws() {
        std::lock_guard<std::mutex> lock(mutex_);
        connected_ = false;
        if (handle_.lock()) {
            websocketpp::lib::error_code ec;
            client_.close(handle_, websocketpp::close::status::normal, "disconnect", ec);
        }
        handle_.reset();
    }

    void on_message(const std::string& payload) {
        json resp;
        try {
            resp = json::parse(payload);
        } catch (...) {
            return;
        }

        // Check for login response
        {
            std::lock_guard<std::mutex> lock(login_mutex_);
            if (!login_done_) {
                login_resp_ = resp;
                login_done_ = true;
                login_cv_.notify_one();
                return;
            }
        }

        // Normal command response
        std::lock_guard<std::mutex> lock(current_cmd_mutex_);
        if (!current_cmd_) return;

        auto cmd = current_cmd_;
        current_cmd_ = nullptr;
        current_cmd_cv_.notify_one();

        if (resp.value("ret", 0) != 0) {
            try {
                cmd->promise.set_exception(std::make_exception_ptr(
                    LLUAVCommandError(resp.value("msg", "Command failed"),
                                      resp.value("ret", -1), resp)));
            } catch (...) {}
        } else {
            try {
                cmd->promise.set_value(resp);
            } catch (...) {}
        }
    }

    void worker_loop() {
        while (!shutdown_) {
            std::shared_ptr<QueuedCommand> cmd;
            {
                std::unique_lock<std::mutex> lock(queue_mutex_);
                queue_cv_.wait_for(lock, std::chrono::milliseconds(500),
                    [this]() { return !queue_.empty() || shutdown_; });
                if (shutdown_) break;
                if (queue_.empty()) continue;
                cmd = queue_.front();
            }

            if (std::chrono::steady_clock::now() > cmd->deadline) {
                {
                    std::lock_guard<std::mutex> lock(queue_mutex_);
                    if (!queue_.empty() && queue_.front() == cmd) queue_.pop();
                }
                try {
                    cmd->promise.set_exception(std::make_exception_ptr(
                        LLUAVCommandError("Command timeout", -1, json{})));
                } catch (...) {}
                continue;
            }

            if (!is_connected()) {
                if (auto_reconnect_ && !shutdown_) {
                    if (!try_reconnect()) {
                        std::this_thread::sleep_for(std::chrono::milliseconds(500));
                        continue;
                    }
                } else {
                    {
                        std::lock_guard<std::mutex> lock(queue_mutex_);
                        if (!queue_.empty() && queue_.front() == cmd) queue_.pop();
                    }
                    try {
                        cmd->promise.set_exception(std::make_exception_ptr(
                            LLUAVConnectionError("Not connected")));
                    } catch (...) {}
                    continue;
                }
            }

            // Send
            {
                std::lock_guard<std::mutex> lock(current_cmd_mutex_);
                current_cmd_ = cmd;
            }

            websocketpp::lib::error_code ec;
            client_.send(handle_, cmd->message, websocketpp::frame::opcode::text, ec);
            if (ec) {
                std::lock_guard<std::mutex> lock(current_cmd_mutex_);
                current_cmd_ = nullptr;
                connected_ = false;
                handle_retry_or_fail(cmd, "Send error: " + ec.message());
                continue;
            }

            // Wait response
            {
                std::unique_lock<std::mutex> lock(current_cmd_mutex_);
                bool got = current_cmd_cv_.wait_for(lock, timeout_,
                    [this]() { return current_cmd_ == nullptr || shutdown_; });
                if (shutdown_) break;
                if (!got) {
                    current_cmd_ = nullptr;
                    connected_ = false;
                    handle_retry_or_fail(cmd, "Response timeout");
                    continue;
                }
            }

            // Success - pop queue
            {
                std::lock_guard<std::mutex> lock(queue_mutex_);
                if (!queue_.empty() && queue_.front() == cmd) queue_.pop();
            }
        }
    }

    void handle_retry_or_fail(std::shared_ptr<QueuedCommand> cmd, const std::string& reason) {
        if (auto_reconnect_ && !shutdown_) {
            cmd->retry_count++;
            if (cmd->retry_count <= max_retries_) {
                std::this_thread::sleep_for(std::chrono::milliseconds(500));
                // Command stays at front of queue for retry
                return;
            }
        }
        {
            std::lock_guard<std::mutex> lock(queue_mutex_);
            if (!queue_.empty() && queue_.front() == cmd) queue_.pop();
        }
        try {
            cmd->promise.set_exception(std::make_exception_ptr(
                LLUAVConnectionError(reason)));
        } catch (...) {}
    }

    bool try_reconnect() {
        auto delay = reconnect_interval_;
        while (!shutdown_ && !is_connected()) {
            try {
                websocketpp::lib::error_code ec;
                auto con = client_.get_connection(url(), ec);
                if (ec) {
                    std::this_thread::sleep_for(delay);
                    delay = std::min(delay * 2, max_reconnect_interval_);
                    continue;
                }
                client_.connect(con);
                {
                    std::unique_lock<std::mutex> lock(mutex_);
                    if (!cv_.wait_for(lock, timeout_, [this]() { return connected_ || shutdown_; })) {
                        std::this_thread::sleep_for(delay);
                        delay = std::min(delay * 2, max_reconnect_interval_);
                        continue;
                    }
                }
                if (shutdown_) return false;

                if (!password_.empty()) {
                    // Send login and wait for response via on_message
                    {
                        std::lock_guard<std::mutex> lock(login_mutex_);
                        login_done_ = false;
                    }
                    websocketpp::lib::error_code ec2;
                    client_.send(handle_, "login " + password_, websocketpp::frame::opcode::text, ec2);
                    if (ec2) {
                        connected_ = false;
                        std::this_thread::sleep_for(delay);
                        delay = std::min(delay * 2, max_reconnect_interval_);
                        continue;
                    }
                    {
                        std::unique_lock<std::mutex> lock(login_mutex_);
                        if (!login_cv_.wait_for(lock, timeout_, [this]() { return login_done_ || shutdown_; })) {
                            connected_ = false;
                            std::this_thread::sleep_for(delay);
                            delay = std::min(delay * 2, max_reconnect_interval_);
                            continue;
                        }
                    }
                    if (shutdown_) return false;
                    if (login_resp_.value("ret", -1) != 0) {
                        connected_ = false;
                        std::this_thread::sleep_for(delay);
                        delay = std::min(delay * 2, max_reconnect_interval_);
                        continue;
                    }
                }
                return true;
            } catch (...) {
                connected_ = false;
                std::this_thread::sleep_for(delay);
                delay = std::min(delay * 2, max_reconnect_interval_);
            }
        }
        return is_connected();
    }

    void heartbeat_loop() {
        while (!shutdown_) {
            std::this_thread::sleep_for(heartbeat_interval_);
            if (shutdown_) break;
            if (is_connected()) {
                enqueue_fire_and_forget(heartbeat_cmd_);
            }
        }
    }

    void periodic_loop() {
        while (!shutdown_) {
            std::this_thread::sleep_for(std::chrono::milliseconds(200));
            if (shutdown_) break;
            auto now = std::chrono::steady_clock::now();
            std::vector<std::tuple<std::string, std::string, std::string>> tasks;
            {
                std::lock_guard<std::mutex> lock(periodic_mutex_);
                for (auto& it : periodic_tasks_) {
                    auto& tup = it.second;
                    auto interval = std::get<0>(tup);
                    auto last = std::get<3>(tup);
                    if (std::chrono::duration_cast<std::chrono::seconds>(now - last).count() >= interval) {
                        tasks.emplace_back(it.first, std::get<1>(tup), std::get<2>(tup));
                        std::get<3>(tup) = now;
                    }
                }
            }
            for (auto& t : tasks) {
                enqueue_fire_and_forget(std::get<1>(t), std::get<2>(t));
            }
        }
    }

    void enqueue_fire_and_forget(const std::string& cmd, const std::string& args = "") {
        auto qcmd = std::make_shared<QueuedCommand>();
        qcmd->message = cmd;
        if (!args.empty()) qcmd->message += " " + args;
        qcmd->deadline = std::chrono::steady_clock::now() + timeout_;
        // Prevent broken_promise by getting future and storing it
        auto fut = qcmd->promise.get_future();
        {
            std::lock_guard<std::mutex> lock(forget_mutex_);
            forget_futures_.push_back(std::move(fut));
            if (forget_futures_.size() > 100) {
                forget_futures_.erase(
                    std::remove_if(forget_futures_.begin(), forget_futures_.end(),
                        [](std::future<json>& f) {
                            return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
                        }),
                    forget_futures_.end());
            }
        }
        {
            std::lock_guard<std::mutex> lock(queue_mutex_);
            queue_.push(qcmd);
            queue_cv_.notify_one();
        }
    }

    std::string host_;
    int port_;
    std::string password_;
    Duration timeout_;
    bool auto_reconnect_;
    Duration reconnect_interval_;
    Duration max_reconnect_interval_;
    int max_retries_;
    Duration heartbeat_interval_;
    std::string heartbeat_cmd_;

    WsClient client_;
    websocketpp::connection_hdl handle_;
    std::thread asio_thread_;
    std::thread worker_thread_;
    std::thread heartbeat_thread_;
    std::thread periodic_thread_;

    mutable std::mutex mutex_;
    std::condition_variable cv_;
    bool connected_;
    bool shutdown_;

    std::queue<std::shared_ptr<QueuedCommand>> queue_;
    std::mutex queue_mutex_;
    std::condition_variable queue_cv_;

    std::shared_ptr<QueuedCommand> current_cmd_;
    std::mutex current_cmd_mutex_;
    std::condition_variable current_cmd_cv_;

    std::mutex login_mutex_;
    std::condition_variable login_cv_;
    bool login_done_ = false;
    json login_resp_;

    std::map<std::string, std::tuple<int, std::string, std::string,
             std::chrono::steady_clock::time_point>> periodic_tasks_;
    std::mutex periodic_mutex_;

    std::vector<std::future<json>> forget_futures_;
    std::mutex forget_mutex_;
};

// ---------------------------------------------------------------------------
// Public interface
// ---------------------------------------------------------------------------

LLUAVClient::LLUAVClient(const std::string& host, int port,
                         const std::string& password, Duration timeout,
                         bool auto_reconnect, Duration reconnect_interval,
                         Duration max_reconnect_interval, int max_retries,
                         Duration heartbeat_interval, const std::string& heartbeat_cmd)
    : impl_(std::make_unique<Impl>(host, port, password, timeout, auto_reconnect,
                                   reconnect_interval, max_reconnect_interval,
                                   max_retries, heartbeat_interval, heartbeat_cmd)) {}

LLUAVClient::~LLUAVClient() = default;
LLUAVClient::LLUAVClient(LLUAVClient&&) noexcept = default;
LLUAVClient& LLUAVClient::operator=(LLUAVClient&&) noexcept = default;

void LLUAVClient::connect() { impl_->connect(); }
void LLUAVClient::disconnect() { impl_->disconnect(); }
bool LLUAVClient::is_connected() const { return impl_->is_connected(); }
std::string LLUAVClient::url() const { return impl_->url(); }

json LLUAVClient::send_cmd(const std::string& cmd, const std::string& args, Duration timeout) {
    return impl_->send_cmd(cmd, args, timeout);
}

void LLUAVClient::add_periodic_task(const std::string& name, int interval_sec,
                                    const std::string& cmd, const std::string& args) {
    impl_->add_periodic_task(name, interval_sec, cmd, args);
}

void LLUAVClient::remove_periodic_task(const std::string& name) {
    impl_->remove_periodic_task(name);
}

// Helper macro to reduce boilerplate
#define CMD0(name, cmd_str) json LLUAVClient::name() { return send_cmd(cmd_str); }
#define CMD1(name, cmd_str) json LLUAVClient::name(const std::string& a) { return send_cmd(cmd_str, a); }

CMD0(login, "login")
CMD0(admin_lock, "admin_lock")
CMD0(admin_unlock, "admin_unlock")
CMD0(get_state, "state")
CMD0(get_version, "ver")
CMD0(get_sysinfo, "sysinfo")
CMD0(get_disk_usage, "diskusage")
CMD0(get_statics, "statics")
CMD0(get_machine_id, "machineid")
CMD0(get_hud1, "hud1")
CMD0(get_lidar_pose, "lidarpose")
CMD0(get_apron_state, "apron_state")
CMD0(get_mavlink_msgs, "mavlink_msgs")
CMD0(get_submap_info, "get_submap_info")
CMD0(takeoff, "takeoff")
CMD0(land, "land")
CMD0(rtl, "rtl")
CMD0(mission_rtl, "mrtl")
CMD0(tether_land, "tether_land")
CMD0(disarm, "disarm")
CMD0(sleep, "sleep")
CMD0(wakeup, "wakeup")
CMD0(unlock_plan, "unlock_plan")
CMD0(mission_wp_list, "mw_list")
CMD0(dump_mission_list, "dump_missionlist")
CMD0(save_map, "savemap")
CMD0(list_maps, "list_maps")
CMD0(capture, "capture")
CMD0(snapshot, "snapshot")
CMD0(record_toggle, "record")
CMD0(siyi_reboot, "siyi_reboot")
CMD0(siyi_format, "siyi_format")
CMD0(siyi_center, "siyi_center")
CMD0(siyi_init, "siyi_init")
CMD0(siyi_sync, "siyi_sync")
CMD0(led_on, "ledon")
CMD0(led_off, "ledoff")
CMD0(led_enable, "ledenable")
CMD0(led_disable, "leddisable")
CMD0(led_blink, "led_blink")
CMD0(reset_params, "reset_param")
CMD0(reboot_node, "reboot_node")
CMD0(reboot_ap, "reboot_ap")
CMD0(reboot_system, "reboot_system")
CMD0(poweroff_system, "poweroff_system")
CMD0(prepare_poweroff, "prepare_poweroff")
CMD0(reboot_dlo, "reboot_dlo")
CMD0(restart_dlo, "restart_dlo")
CMD0(lidar_warn, "lidarwarn")
CMD0(utg_get, "utg_get")
CMD0(ara8_center, "ara8_center")
CMD0(cv_test, "cv_test")
CMD0(preflight_check, "preflight_check")
CMD0(show_setpoint, "show_sp")
CMD0(show_ap_guid2, "show_apguid2")
CMD0(help, "help")
CMD0(save_msg, "savemsg")

json LLUAVClient::get_param(const std::string& name) {
    return name.empty() ? send_cmd("param") : send_cmd("get_param", name);
}

json LLUAVClient::set_sp_ned(float x, float y, float z, float yaw) {
    return send_cmd("set_sp_ned", std::to_string(x) + ":" + std::to_string(y) + ":" + std::to_string(z) + ":" + std::to_string(yaw));
}
json LLUAVClient::relocate_pos(float x, float y, float z, float yaw) {
    return send_cmd("relocate_pos", std::to_string(x) + "," + std::to_string(y) + "," + std::to_string(z) + "," + std::to_string(yaw));
}
json LLUAVClient::tunnel_fly(bool enable) { return send_cmd("tunnelfly", enable ? "1" : "0"); }
json LLUAVClient::auto_sleep_enable(bool enable) { return send_cmd("autosleep_enable", enable ? "1" : "0"); }
json LLUAVClient::zyhk(const std::vector<float>& heights, float land_x, float land_y, float land_yaw) {
    std::ostringstream oss;
    for (size_t i = 0; i < heights.size(); ++i) {
        if (i > 0) oss << ",";
        oss << heights[i];
    }
    if (land_x != 0.0f || land_y != 0.0f || land_yaw != 0.0f) {
        oss << " " << land_x << "," << land_y << "," << land_yaw;
    }
    return send_cmd("zyhk", oss.str());
}
json LLUAVClient::mission(int32_t ext_id, const std::string& path_or_json) {
    return send_cmd("mission", std::to_string(ext_id) + ":" + path_or_json);
}
json LLUAVClient::start_plan(const std::string& plan_str) { return send_cmd("plan", plan_str); }
json LLUAVClient::mission_wp_exec(int32_t ext_id, const std::string& wp_ids) {
    return send_cmd("mw_exec", std::to_string(ext_id) + ":" + wp_ids);
}
json LLUAVClient::ai1_mission(const std::string& task_id, const std::string& mission_file) {
    return send_cmd("ai1_mission", task_id + ":" + mission_file);
}
json LLUAVClient::obs_mission(bool enable) { return send_cmd("obs_mission", enable ? "1" : "0"); }
json LLUAVClient::dump_mission_result(int32_t ext_id) {
    return send_cmd("dump_missionresult", ext_id >= 0 ? std::to_string(ext_id) : "");
}
json LLUAVClient::abort_report(int32_t abort_id) { return send_cmd("abort_report", std::to_string(abort_id)); }
json LLUAVClient::set_taskname(const std::string& name) { return send_cmd("set_taskname", name); }
json LLUAVClient::set_map_locate(bool enable) { return send_cmd("set_maplocate", enable ? "1" : "0"); }
json LLUAVClient::set_map_path(const std::string& path) { return send_cmd("set_mappath", path); }
json LLUAVClient::rm_map(const std::string& name) { return send_cmd("rm_map", name); }
json LLUAVClient::savebag(bool start) { return send_cmd("savebag", start ? "1" : "0"); }
json LLUAVClient::siyi_downvideo(int vid) { return send_cmd("siyi_downvideo", std::to_string(vid)); }
json LLUAVClient::led_pwm(int pwm) { return send_cmd("ledpwm", std::to_string(pwm)); }
json LLUAVClient::set_param(const std::string& name, const std::string& value) {
    return send_cmd("set_param", name + ":" + value);
}
json LLUAVClient::get_param_value(const std::string& name) { return send_cmd("get_param", name); }
json LLUAVClient::save_params(bool force) { return send_cmd("save_param", force ? "1" : "0"); }
json LLUAVClient::change_password(const std::string& new_password) { return send_cmd("passwd", new_password); }
json LLUAVClient::set_apron_vol(float voltage) { return send_cmd("apron_vol", std::to_string(voltage)); }
json LLUAVClient::set_apron_amp(float amperage) { return send_cmd("apron_amp", std::to_string(amperage)); }
json LLUAVClient::kill_livox(const std::string& script) { return send_cmd("kill_livox", script); }
json LLUAVClient::lidar_onoff(bool on) { return send_cmd("lidaronoff", on ? "1" : "0"); }
json LLUAVClient::utg_set(int speed) { return send_cmd("utg_set", std::to_string(speed)); }
json LLUAVClient::grabber_ctrl(int pwm) { return send_cmd("grabber_ctrl", std::to_string(pwm)); }
json LLUAVClient::ara8_pitch(int angle) { return send_cmd("ara8_pitch", std::to_string(angle)); }
json LLUAVClient::preflight_enable(bool enable) { return send_cmd("preflight_enable", enable ? "1" : "0"); }
json LLUAVClient::clear_bags(int min_gb) { return send_cmd("clear_bags", std::to_string(min_gb)); }
json LLUAVClient::clear_images(int min_gb) { return send_cmd("clear_images", std::to_string(min_gb)); }
json LLUAVClient::list_imagedir(int size) { return send_cmd("list_imagedir", std::to_string(size)); }
json LLUAVClient::rm_imagedir(const std::string& name) { return send_cmd("rm_imagedir", name); }
json LLUAVClient::test_wp(float x, float y, float z) {
    return send_cmd("test_wp", std::to_string(x) + ":" + std::to_string(y) + ":" + std::to_string(z));
}
json LLUAVClient::test_wp_list(const std::string& file_path) { return send_cmd("test_listwp", file_path); }
json LLUAVClient::show_msg(int count) { return send_cmd("showmsg", std::to_string(count)); }
json LLUAVClient::bms_setpower(bool on) { return send_cmd("bms_setpower", on ? "1" : "0"); }
json LLUAVClient::bms_setcharge(bool on) { return send_cmd("bms_setcharge", on ? "1" : "0"); }

#undef CMD0
#undef CMD1

} // namespace lluav
