#pragma once

#include "fgc/IMotorController.h"  // AxisState
#include "fgc/Logger.h"            // LogLevel

#include <string>
#include <vector>

namespace fgc {

// Plain-data view model handed from the control loop to a user interface. It is
// a pure snapshot: no references to the live motor/camera/channel objects, so
// the UI thread can copy and render it without touching (or racing) application
// logic. New subsystems become new structs here + a panel in the TUI.

// Colour intent, mapped to concrete terminal colours by the renderer.
enum class UiColor { Default, Dim, Green, Yellow, Red, Cyan };

// One gimbal axis.
struct AxisView {
    std::string label;                 // "YAW" / "PITCH"
    AxisState   state      = AxisState::Unknown;
    double      deg        = 0.0;      // heading/elevation, from xenc via Geometry
    long        xactual    = 0;
    long        xenc       = 0;
    double      target_deg = 0.0;      // current scheduler target
    int         sg         = 0;        // SG_RESULT (live, from ST line)
    int         cs         = 0;        // CS_ACTUAL
    int         pwm        = 0;        // PWM_SCALE_SUM
    unsigned    drv_status = 0;        // DRV_STATUS register
    bool        moving     = false;
    bool        standstill = false;
    bool        stall      = false;
    bool        overtemp   = false;
    bool        endstop_l  = false;
    bool        endstop_r  = false;
    // Homing travel limits (from the last firmware dump): the endstop positions
    // found during homing, in encoder counts and converted to degrees.
    bool        has_limits  = false;
    long        lim_neg     = 0;
    long        lim_pos     = 0;
    double      lim_neg_deg = 0.0;
    double      lim_pos_deg = 0.0;
};

struct GimbalView {
    bool     present       = false;    // motor link usable
    AxisView yaw;
    AxisView pitch;
    bool     pitch_present = false;
};

// One labelled sensor reading. `present=false` => render the dim "pending" form.
struct SensorField {
    std::string label;                 // "Temp", "Humid", "Roll"...
    std::string value;                 // formatted value, or placeholder when absent
    std::string unit;                  // "°C", "%RH", "°"...
    bool        present = false;
};

// A labelled group of readings from one physical sensor, rendered as its own
// subsection in the Sensors panel.
struct SensorGroup {
    std::string              title;            // "MTi (orientation)" / "DHT11 (ambient)"
    std::string              status = "pending";  // "live" / "pending" / "no fix"
    bool                     present = false;  // true once the driver feeds real data
    std::vector<SensorField> fields;
};

// The Sensors panel, split by source so it is clear which parameters come from
// which device. Each group is filled with pending placeholders until its driver
// lands. NOTE: the MTi's Temp is the device's *internal* temperature; the DHT11
// Temp (separate group) is *ambient* — deliberately kept apart.
struct SensorsView {
    SensorGroup imu;   // Xsens MTi: Roll/Pitch/Yaw + internal temperature
    SensorGroup dht;   // DHT11: ambient temperature + humidity
};

struct CaptureView {
    bool                     present      = false;
    bool                     active       = false;
    double                   image_rate   = 0.0;   // img/s
    int                      camera_count = 0;
    std::vector<std::string> labels;
    // Last published capture (from ImagePipeline::lastEvent()).
    bool                     has_last         = false;
    std::string              last_label;
    double                   last_heading_deg = 0.0;
    double                   last_pitch_deg   = 0.0;
    long long                last_ts_ms       = 0;
};

struct ConnView {
    bool        mqtt_enabled   = false;
    bool        mqtt_connected = false;
    std::string broker;
    std::string tower;
    int         control_code   = 0;    // 0 = auto-sweep, 1 = directed
    std::string target_heading;
    int         last_status_code = 0;
};

struct HeaderView {
    std::string tower;
    std::string build;
    long long   uptime_ms = 0;
    bool        live      = true;      // LIVE vs MOCK
};

struct LogLine {
    LogLevel    level = LogLevel::Info;
    std::string text;                  // formatted line (no trailing newline)
};

// Most recent firmware DUMP block (raw text), surfaced in the TUI help pane.
struct DumpView {
    bool        has = false;
    std::string text;
};

// Device configuration read back from the MTi at startup, formatted for the
// expanded Sensors view. `present` is false until the handshake reports it (or
// for backends that cannot report it).
// One XKF (Xsens Kalman Filter) profile the device supports, with whether it is
// the active one.
struct ImuProfileView {
    std::string name;            // human label, no numeric IDs ("General")
    bool        selected = false;  // the profile currently in use
};

struct ImuConfigView {
    bool        present = false;
    std::string product_code;     // "MTi-28A53G35"
    std::string device_id;        // "0x00990ABC"
    std::string firmware;         // "2.8.1 build 0"
    std::string output_mode;      // "Temp · Calibrated · Orientation"
    std::string output_settings;  // "Euler · Sample counter · Float"
    std::string channels;         // "acc gyr mag"
    std::string sample_rate;      // "100 Hz"
    // Available XKF profiles with the active one flagged. Empty if the device
    // did not report them.
    std::vector<ImuProfileView> xkf_profiles;
};

// Full Xsens MTi reading for the expanded Sensors view (units: acc m/s^2,
// gyr rad/s, mag a.u., angles deg, temp °C).
struct ImuView {
    bool          present = false;
    float         roll_deg = 0, pitch_deg = 0, yaw_deg = 0;
    float         acc[3] = {0, 0, 0};
    float         gyr[3] = {0, 0, 0};
    float         mag[3] = {0, 0, 0};
    float         temp_c = 0;
    unsigned      sample_counter = 0;
    ImuConfigView config;
};

// Status of the currently-running special operation (calibration, diagnostics,
// homing, capture) plus the last completed result, for the activity strip below
// the log. The result block persists until replaced so it never scrolls away.
struct ActivityView {
    bool        active = false;        // a special op is running now
    std::string title;                // "CALIBRATING" / "DIAGNOSTICS" / "HOMING" / "CAPTURE"
    std::string status;               // live one-line progress
    bool        has_result = false;
    std::string result_title;         // e.g. "Calibration · 2m ago"
    std::vector<std::string> result;  // summary lines
    UiColor     result_color = UiColor::Default;  // green pass / red fail
    std::string prompt;               // a yes/no question awaiting the operator (empty = none)
};

// Last calibration fit, per axis, for the gimbal expanded view.
struct CalibAxisView {
    bool   ok = false;
    double counts_per_deg = 0;
    long   zero_count = 0;
    double r2 = 0;
    int    n = 0;
};
struct CalibResultView {
    bool          has = false;
    long long     ts_ms = 0;
    bool          pitch_present = false;
    CalibAxisView yaw, pitch;
};

struct UiSnapshot {
    HeaderView           header;
    GimbalView           gimbal;
    SensorsView          sensors;
    CaptureView          capture;
    ConnView             conn;
    std::vector<LogLine> log;
    DumpView             dump;
    ImuView              imu;
    ActivityView         activity;
    CalibResultView      calib;
};

// ---- Pure formatting helpers (unit-tested in tests/test_uisnapshot.cpp) ----

// Short state label for an axis state char (B/R/H/A/E -> these strings).
const char* axisStateLabel(AxisState s);

// Colour intent for an axis state (READY=green, HOMING=yellow, ERROR=red,
// BOOT/RESET=dim, Unknown=default).
UiColor axisStateColor(AxisState s);

// "12.3°" (one decimal, sign preserved).
std::string formatDegrees(double deg);

// Human "Ns ago" / "Nm ago" for an absolute ms timestamp relative to now_ms.
// Returns "—" when then_ms is 0 (never).
std::string formatTimeAgo(long long now_ms, long long then_ms);

// MVP placeholder: the Sensors panel fields shown before the DHT11/MTi drivers
// exist (all `present=false`). Centralised so the panel and tests agree.
SensorsView pendingSensorsView();

}  // namespace fgc
