#pragma once

#include "fgc/Config.h"       // TestProfile
#include "fgc/Geometry.h"
#include "fgc/TestReport.h"

#include <atomic>
#include <cstdint>
#include <mutex>
#include <optional>
#include <string>
#include <thread>

namespace fgc {

class IMotorController;
class IImuSource;

// Leaf-test selection bitmask. Subsystem masks are unions of their leaves.
enum TestLeaf : uint32_t {
    T_GIMBAL_HOMING   = 1u << 0,
    T_GIMBAL_ENCODER  = 1u << 1,
    T_GIMBAL_FRICTION = 1u << 2,
    T_GIMBAL_BACKLASH = 1u << 3,
    T_GIMBAL_BALANCE  = 1u << 4,
    T_IMU_CONFIG      = 1u << 5,
    T_IMU_HEALTH      = 1u << 6,
    T_IMU_DRIFT       = 1u << 7,
    T_HOST_THERMAL    = 1u << 8,
    T_HOST_DISK       = 1u << 9,
    T_HOST_MEMORY     = 1u << 10,
    T_HOST_LOAD       = 1u << 11,
};
constexpr uint32_t T_GIMBAL_ALL = T_GIMBAL_HOMING | T_GIMBAL_ENCODER | T_GIMBAL_FRICTION |
                                  T_GIMBAL_BACKLASH | T_GIMBAL_BALANCE;
constexpr uint32_t T_IMU_ALL  = T_IMU_CONFIG | T_IMU_HEALTH | T_IMU_DRIFT;
constexpr uint32_t T_HOST_ALL = T_HOST_THERMAL | T_HOST_DISK | T_HOST_MEMORY | T_HOST_LOAD;
constexpr uint32_t T_ALL      = T_GIMBAL_ALL | T_IMU_ALL | T_HOST_ALL;

// Which leaf masks need real motor / IMU hardware (used by the caller to guard).
constexpr uint32_t T_NEEDS_MOTOR = T_GIMBAL_ALL;
constexpr uint32_t T_NEEDS_IMU   = T_IMU_ALL;

// Resolve "subsystem [test]" tokens to a leaf mask. Empty subsystem => T_ALL;
// a subsystem with empty test => that subsystem's leaves. Returns false and sets
// `err` for an unknown subsystem/test token.
bool resolveTestSelection(const std::string& subsystem, const std::string& test,
                          uint32_t& mask, std::string& err);

// Live progress for the activity strip (mirrors CalibProgress).
struct TestProgress {
    bool        running = false;
    std::string section;   // current leaf id
    int         step = 0;
    int         total = 0;
    std::string phase;
};

// Runs the selected hardware self-tests on a worker thread, off the control
// loop. Mirrors CalibrationRoutine's lifecycle: start()/cancel()/running(), live
// progress(), and takeReport() once finished. The motor/imu/Logger interfaces are
// thread-safe; the produced TestReport is handed back for the control thread to
// compare against the baseline, write to disk, and surface in the UI.
class TestRunner {
public:
    TestRunner(IMotorController& motor, IImuSource* imu, Geometry geo,
               uint32_t selection, TestProfile profile, std::string disk_path);
    ~TestRunner();

    bool start();
    void cancel();
    bool running() const { return running_.load(); }

    std::optional<TestReport> takeReport();
    TestProgress              progress() const;

private:
    void run();
    void setProgress(const std::string& section, int step, int total, const char* phase);
    bool cancelled() const { return cancel_.load(); }

    // Leaf modules — each appends one TestSection to `report_`.
    void testHoming(TestReport& r);
    void testEncoder(TestReport& r);
    void testFriction(TestReport& r);
    void testBacklash(TestReport& r);
    void testBalance(TestReport& r);
    void testImuConfig(TestReport& r);
    void testImuHealth(TestReport& r);
    void testImuDrift(TestReport& r);
    void testHost(TestReport& r);

    IMotorController& motor_;
    IImuSource*       imu_;
    Geometry          geo_;
    uint32_t          selection_;
    TestProfile       profile_;
    std::string       disk_path_;

    std::thread       thread_;
    std::atomic<bool> running_{false};
    std::atomic<bool> cancel_{false};

    mutable std::mutex        result_mutex_;
    std::optional<TestReport> report_;

    mutable std::mutex progress_mutex_;
    TestProgress       progress_;
};

}  // namespace fgc
