#pragma once

#include <Arduino.h>

#include "internal/WF100DPZCore.h"

#if defined(ARDUINO_ARCH_ESP32)
#include <driver/spi_master.h>
#endif

class WF100DPZ {
 public:
  enum class Mode : uint8_t {
    TempOnly = 0,
    PressureOnly = 1,
    Combined = 2,
  };

  enum class StatusCode : uint8_t {
    Ok = 0,
    NotInitialized = 1,
    InvalidArgument = 2,
    Busy = 3,
    Timeout = 4,
    TransportError = 5,
    Unsupported = 6,
    InitFailed = 7,
    InternalError = 8,
  };

  enum class Backend : uint8_t {
    Auto = 0,
    BitBang = 1,
    EspIdf3Wire = 2,
  };

  struct StartupPolicy {
    bool require_valid_part_id = true;
    uint8_t part_id_retries = 3;
    uint16_t retry_delay_ms = 15;

    StartupPolicy() = default;
    StartupPolicy(bool require_valid, uint8_t retries, uint16_t retry_delay)
        : require_valid_part_id(require_valid), part_id_retries(retries), retry_delay_ms(retry_delay) {}
  };

  struct AutoRefreshTempPolicy {
    bool enabled = true;
    uint32_t period_ms = 1000;

    AutoRefreshTempPolicy() = default;
    AutoRefreshTempPolicy(bool enabled, uint32_t period_ms)
        : enabled(enabled), period_ms(period_ms) {}
  };

  struct Config {
    Backend backend = Backend::Auto;
    uint32_t spi_hz = 20000000;
#if defined(ARDUINO_ARCH_ESP32)
    spi_host_device_t host = SPI2_HOST;
#endif
    uint8_t sample_interval = 0x00;
    StartupPolicy startup;
    AutoRefreshTempPolicy auto_refresh_temp;
  };

  struct Sample {
    Mode mode = Mode::Combined;
    bool has_temp = false;
    bool has_pressure = false;
    float temp_c = 0.0f;
    float pressure_kpa = 0.0f;
    uint8_t status_raw = 0;
    uint8_t error_code = 0;
    uint32_t elapsed_us = 0;
    uint32_t timestamp_ms = 0;
  };

  WF100DPZ(int pinCsb, int pinSda, int pinClk);
  WF100DPZ(int pinCsb, int pinSda, int pinClk, const Config& cfg);

  bool begin(StatusCode* status = nullptr);
  bool end(StatusCode* status = nullptr);
  bool isInitialized() const { return initialized_; }

  float readTemp(StatusCode* status = nullptr);
  float readPressure(StatusCode* status = nullptr);
  Sample readCombined(StatusCode* status = nullptr);
  Sample read(Mode mode = Mode::Combined, StatusCode* status = nullptr);

  bool start(Mode mode = Mode::Combined, StatusCode* status = nullptr);
  bool ready(StatusCode* status = nullptr);
  Sample getResult(StatusCode* status = nullptr);
  void cancel();

  bool startContinuous(Mode mode = Mode::Combined, StatusCode* status = nullptr);
  bool continuousReady(StatusCode* status = nullptr);
  Sample getContinuousResult(StatusCode* status = nullptr);
  void stopContinuous();

  uint8_t readPartId(StatusCode* status = nullptr);
  uint8_t readStatus(StatusCode* status = nullptr);
  bool setConfig(const Config& cfg, StatusCode* status = nullptr);
  bool setSampleInterval(uint8_t interval, StatusCode* status = nullptr);
  uint8_t getSampleInterval() const;

  Config getConfig() const { return config_; }

  StatusCode lastStatus() const { return last_status_; }

  const char* statusString() const;
  const char* statusString(StatusCode code) const;

 private:
  Config config_;
  WF100DPZCore core_;
  StatusCode last_status_ = StatusCode::NotInitialized;
  bool initialized_ = false;
  uint32_t last_temp_refresh_ms_ = 0;
  bool one_shot_active_ = false;
  uint32_t one_shot_start_us_ = 0;
  Mode active_mode_ = Mode::Combined;
  bool continuous_active_ = false;
  Mode continuous_requested_mode_ = Mode::Combined;
  WF100DPZCore::Mode continuous_core_mode_ = WF100DPZCore::Mode::Combined;
  uint32_t continuous_last_sample_us_ = 0;

  static WF100DPZCore::Mode toCoreMode(Mode mode);
  static WF100DPZCore::Backend toCoreBackend(Backend backend);
  Backend getActiveBackend() const;
  bool setStatus(StatusCode code, StatusCode* out_status);

  StatusCode mapCoreBeginError() const;
  StatusCode mapCoreFailureToStatus(Mode attempted_mode);
  void fillSampleMetadata(Sample& out, Mode mode, uint32_t elapsed_us);
  StatusCode performOptionalPressureRefreshIfNeeded(bool force);
};


