#pragma once

#include "ThermoproReading.hpp"
#include <NimBLEDevice.h>
#include <functional>

namespace cyd {

// NimBLE-based client for the ThermoPro BLE BBQ thermometer protocol.
// Replicates the behavior of thermopro_cli.py from the proof of concept.
class ThermoproBLE {
public:
    using StateChangeCallback = std::function<void(const ThermoproReading&)>;

    ThermoproBLE();

    // Set the target BLE address before calling connect().
    void setAddress(const String& mac);

    // Initialize the BLE stack and register this instance as the client.
    bool init(const String& deviceName);

    // Non-blocking connect. Returns true if the async connect started successfully.
    bool startConnect();

    // Call this frequently from loop(). Handles connection state machine,
    // reconnection backoff, and notifications.
    void update();

    // Disconnect cleanly.
    void disconnect();

    // Latest decoded reading, thread-safe enough because NimBLE callbacks run on the
    // same core/loop in Arduino by default.
    ThermoproReading getReading() const { return reading_; }

    // Human-readable current connection state name for diagnostics.
    const char* getStateName() const;

    // Register a callback invoked whenever the reading state changes.
    void onStateChange(StateChangeCallback cb) { stateCallback_ = cb; }

private:
    // BLE UUIDs (ThermoPro custom service)
    static const NimBLEUUID kServiceUuid;
    static const NimBLEUUID kNotifyUuid;
    static const NimBLEUUID kWriteUuid;

    // Static handshake bytes captured from the Android app.
    static constexpr uint8_t kHandshake[] = { 0x01, 0x09, 0x8a, 0x7a, 0x13, 0xb7, 0x3e, 0xd6, 0x8b, 0x67, 0xc2, 0xa0 };

    enum class State {
        Idle,
        Scanning,
        Connecting,
        Discovering,
        EnablingNotify,
        SendingHandshake,
        Running,
        Reconnecting
    };

    String address_;
    State state_ = State::Idle;
    ThermoproReading reading_;
    StateChangeCallback stateCallback_;

    NimBLEClient* client_ = nullptr;
    NimBLERemoteCharacteristic* notifyChar_ = nullptr;
    NimBLERemoteCharacteristic* writeChar_ = nullptr;

    unsigned long stateStartMillis_ = 0;
    unsigned long lastActivityMillis_ = 0;
    uint8_t reconnectFailures_ = 0;
    unsigned long reconnectDelayMs_ = 5000;
    bool callbacksRegistered_ = false;

    // Address discovered during a scan that we want to connect to.
    // The callback records it; update() initiates the connect after stopping the scan.
    NimBLEAddress pendingConnectAddress_;
    bool pendingConnectFound_ = false;
    unsigned long pendingConnectSeenMillis_ = 0;

    bool scanAndConnect_();
    bool connectToAddress_(const NimBLEAddress& address);
    bool setupConnection_(NimBLEClient* client);
    void cleanupClient_();
    void setConnected_(bool connected);
    void startReconnectDelay_();

    // Notification handler (called from NimBLE task).
    static void notifyCallback_(BLERemoteCharacteristic* characteristic, uint8_t* data, size_t length, bool isNotify);
    void handlePacket_(const uint8_t* data, size_t length);
    void parseTemperaturePacket_(const uint8_t* data, size_t length);

    // Reusable scan callback. Uses the global instance pointer, so only one
    // ThermoproBLE object may be active at a time.
    class ScanCallbacks : public NimBLEAdvertisedDeviceCallbacks {
    public:
        void onResult(NimBLEAdvertisedDevice* device) override;
    };
    ScanCallbacks scanCallbacks_;

    // Helpers
    static float decodeTemperature_(uint8_t byte1, uint8_t byte2);
    static uint8_t calculateChecksum_(const uint8_t* data, size_t len);
    static bool isThermoproDevice_(const String& name);

    static ThermoproBLE* instance_;
};

} // namespace cyd
