#pragma once

#include <string>
#include <memory>
#include <functional>
#include <chrono>
#include <vector>
#include <future>
#include <nlohmann/json.hpp>

namespace lluav {

using json = nlohmann::json;

class LLUAVError : public std::exception {
public:
    explicit LLUAVError(const std::string& msg);
    const char* what() const noexcept override;
private:
    std::string msg_;
};

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

class LLUAVConnectionError : public LLUAVError {
public:
    explicit LLUAVConnectionError(const std::string& msg);
};

/**
 * @brief Synchronous WebSocket client for LLUAV drone firmware.
 *
 * Features:
 *   - Automatic login after connect
 *   - Auto-reconnect with exponential backoff
 *   - Heartbeat keep-alive
 *   - Strict command queue (one command in flight at a time)
 *   - Per-command timeout and retry
 *   - Periodic task scheduler
 */
class LLUAVClient {
public:
    using Duration = std::chrono::milliseconds;

    explicit LLUAVClient(const std::string& host = "localhost",
                         int port = 53001,
                         const std::string& password = "",
                         Duration timeout = Duration(10000),
                         bool auto_reconnect = true,
                         Duration reconnect_interval = Duration(1000),
                         Duration max_reconnect_interval = Duration(30000),
                         int max_retries = 3,
                         Duration heartbeat_interval = Duration(5000),
                         const std::string& heartbeat_cmd = "state");
    ~LLUAVClient();

    LLUAVClient(const LLUAVClient&) = delete;
    LLUAVClient& operator=(const LLUAVClient&) = delete;
    LLUAVClient(LLUAVClient&&) noexcept;
    LLUAVClient& operator=(LLUAVClient&&) noexcept;

    void connect();
    void disconnect();
    bool is_connected() const;
    std::string url() const;

    /**
     * @brief Send a command through the local queue.
     * @param cmd Command name.
     * @param args Command arguments.
     * @param timeout Override default timeout.
     * @return JSON response from drone.
     */
    json send_cmd(const std::string& cmd, const std::string& args = "",
                  Duration timeout = Duration(0));

    // Periodic tasks
    void add_periodic_task(const std::string& name, int interval_sec,
                           const std::string& cmd, const std::string& args = "");
    void remove_periodic_task(const std::string& name);

    // Auth
    json login(const std::string& password);
    json admin_lock();
    json admin_unlock();

    // State & Info
    json get_state();
    json get_param(const std::string& name = "");
    json get_version();
    json get_sysinfo();
    json get_disk_usage();
    json get_statics();
    json get_machine_id();
    json get_hud1();
    json get_lidar_pose();
    json get_apron_state();
    json get_mavlink_msgs();
    json get_submap_info();

    // Flight Control
    json takeoff();
    json land();
    json rtl();
    json mission_rtl();
    json tether_land();
    json set_sp_ned(float x, float y, float z, float yaw);
    json relocate_pos(float x, float y, float z, float yaw);
    json tunnel_fly(bool enable);
    json disarm();
    json sleep();
    json wakeup();
    json auto_sleep_enable(bool enable);
    json zyhk(const std::vector<float>& heights,
              float land_x = 0.0f, float land_y = 0.0f, float land_yaw = 0.0f);

    // Mission
    json mission(int32_t ext_id, const std::string& path_or_json);
    json start_plan(const std::string& plan_str);
    json unlock_plan();
    json mission_wp_list();
    json mission_wp_exec(int32_t ext_id, const std::string& wp_ids);
    json ai1_mission(const std::string& task_id, const std::string& mission_file);
    json obs_mission(bool enable);
    json dump_mission_result(int32_t ext_id = -1);
    json dump_mission_list();
    json abort_report(int32_t abort_id = -1);
    json set_taskname(const std::string& name);

    // Map
    json save_map(const std::string& path);
    json set_map_locate(bool enable);
    json set_map_path(const std::string& path);
    json list_maps();
    json rm_map(const std::string& name);

    // Camera
    json capture();
    json snapshot();
    json record_toggle();
    json savebag(bool start);

    // SIYI
    json siyi_reboot();
    json siyi_format();
    json siyi_center();
    json siyi_init();
    json siyi_downvideo(int vid = -1);
    json siyi_sync();

    // LED
    json led_on();
    json led_off();
    json led_enable();
    json led_disable();
    json led_pwm(int pwm);
    json led_blink();

    // Params
    json set_param(const std::string& name, const std::string& value);
    json get_param_value(const std::string& name);
    json reset_params();
    json save_params(bool force = false);
    json change_password(const std::string& new_password);

    // Apron
    json set_apron_vol(float voltage);
    json set_apron_amp(float amperage);

    // System
    json reboot_node();
    json reboot_ap();
    json reboot_system();
    json poweroff_system();
    json prepare_poweroff();
    json reboot_dlo();
    json restart_dlo();
    json kill_livox(const std::string& script = "");

    // Lidar
    json lidar_onoff(bool on);
    json lidar_warn();
    json utg_get();
    json utg_set(int speed);

    // Other
    json grabber_ctrl(int pwm);
    json ara8_center();
    json ara8_pitch(int angle);
    json cv_test();
    json preflight_check();
    json preflight_enable(bool enable);
    json clear_bags(int min_gb = -1);
    json clear_images(int min_gb = -1);
    json list_imagedir(int size = -1);
    json rm_imagedir(const std::string& name);
    json test_wp(float x, float y, float z);
    json test_wp_list(const std::string& file_path);
    json show_msg(int count = -1);
    json save_msg();
    json bms_setpower(bool on);
    json bms_setcharge(bool on);
    json show_setpoint();
    json show_ap_guid2();
    json help();

private:
    class Impl;
    std::unique_ptr<Impl> impl_;
};

} // namespace lluav
