#include "WF100DPZ.h"

WF100DPZ::WF100DPZ(int pinCsb, int pinSda, int pinClk)
    : WF100DPZ(pinCsb, pinSda, pinClk, Config{}) {}

WF100DPZ::WF100DPZ(int pinCsb, int pinSda, int pinClk, const Config& cfg)
    : config_(cfg),
#if defined(ARDUINO_ARCH_ESP32)
      core_(pinCsb, pinSda, pinClk, toCoreBackend(cfg.backend), cfg.spi_hz, cfg.host) {}
#else
      core_(pinCsb, pinSda, pinClk, toCoreBackend(cfg.backend), cfg.spi_hz) {}
#endif

WF100DPZCore::Mode WF100DPZ::toCoreMode(Mode mode) {
  return static_cast<WF100DPZCore::Mode>(static_cast<uint8_t>(mode));
}

WF100DPZCore::Backend WF100DPZ::toCoreBackend(Backend backend) {
  switch (backend) {
    case Backend::Auto:
      return WF100DPZCore::Backend::Auto;
    case Backend::BitBang:
      return WF100DPZCore::Backend::BitBang;
    case Backend::EspIdf3Wire:
      return WF100DPZCore::Backend::EspIdf3Wire;
  }
  return WF100DPZCore::Backend::Auto;
}

bool WF100DPZ::setStatus(StatusCode code, StatusCode* out_status) {
  last_status_ = code;
  if (out_status != nullptr) {
    *out_status = code;
  }
  return code == StatusCode::Ok;
}

WF100DPZ::StatusCode WF100DPZ::mapCoreBeginError() const {
  switch (core_.lastError()) {
    case WF100DPZCore::Error::None:
      return StatusCode::Ok;
    case WF100DPZCore::Error::UnsupportedBackendOnPlatform:
      return StatusCode::Unsupported;
    case WF100DPZCore::Error::BackendInitFailed:
      return StatusCode::InitFailed;
    case WF100DPZCore::Error::BackendNotInitialized:
      return StatusCode::NotInitialized;
  }
  return StatusCode::InternalError;
}

WF100DPZ::StatusCode WF100DPZ::mapCoreFailureToStatus(Mode attempted_mode) {
  if (!initialized_) {
    return StatusCode::NotInitialized;
  }

  const WF100DPZCore::Status s = core_.readStatus();
  if (!s.drdy) {
    return StatusCode::Timeout;
  }
  if (s.error_code != 0) {
    return StatusCode::TransportError;
  }

  if (attempted_mode == Mode::PressureOnly || attempted_mode == Mode::Combined) {
    const bool refresh_enabled = config_.auto_refresh_temp.enabled;
    if (refresh_enabled) {
      return StatusCode::Busy;
    }
  }
  return StatusCode::InternalError;
}

void WF100DPZ::fillSampleMetadata(Sample& out, Mode mode, uint32_t elapsed_us) {
  out.mode = mode;
  out.elapsed_us = elapsed_us;
  out.timestamp_ms = millis();
  const auto st = core_.readStatus();
  out.status_raw = st.raw;
  out.error_code = st.error_code;
}

WF100DPZ::StatusCode WF100DPZ::performOptionalPressureRefreshIfNeeded(bool force) {
  if (!config_.auto_refresh_temp.enabled && !force) {
    return StatusCode::Ok;
  }
  const uint32_t now_ms = millis();
  if (!force && (now_ms - last_temp_refresh_ms_) < config_.auto_refresh_temp.period_ms) {
    return StatusCode::Ok;
  }
  float temp_c = 0.0f;
  float pressure_kpa = 0.0f;
  uint32_t wait_us = 0;
  bool drdy_hit = false;
  if (!core_.measureCombined(temp_c, pressure_kpa, wait_us, drdy_hit)) {
    const WF100DPZCore::Status s = core_.readStatus();
    if (!s.drdy) {
      return StatusCode::Timeout;
    }
    if (s.error_code != 0) {
      return StatusCode::TransportError;
    }
    return StatusCode::InternalError;
  }
  last_temp_refresh_ms_ = now_ms;
  return StatusCode::Ok;
}

bool WF100DPZ::begin(StatusCode* status) {
  initialized_ = false;
  one_shot_active_ = false;
  one_shot_start_us_ = 0;
  last_temp_refresh_ms_ = 0;
  continuous_active_ = false;
  continuous_requested_mode_ = Mode::Combined;
  continuous_core_mode_ = WF100DPZCore::Mode::Combined;
  continuous_last_sample_us_ = 0;

  if (!core_.begin()) {
    return setStatus(mapCoreBeginError(), status);
  }

  // Reflect the resolved backend (especially when Auto is requested).
  config_.backend = getActiveBackend();
  core_.setSleepTimeCode(config_.sample_interval);

  if (config_.startup.require_valid_part_id) {
    uint8_t part_id = 0;
    bool valid = false;
    for (uint8_t attempt = 0; attempt <= config_.startup.part_id_retries; ++attempt) {
      part_id = core_.readPartId();
      if (part_id != 0x00 && part_id != 0xFF) {
        valid = true;
        break;
      }
      delay(config_.startup.retry_delay_ms);
    }
    if (!valid) {
      return setStatus(StatusCode::InitFailed, status);
    }
  }

  // Prime compensation state once at startup with a combined conversion.
  const StatusCode prime_st = performOptionalPressureRefreshIfNeeded(true);
  if (prime_st != StatusCode::Ok) {
    return setStatus(prime_st, status);
  }

  initialized_ = true;
  return setStatus(StatusCode::Ok, status);
}

bool WF100DPZ::end(StatusCode* status) {
  core_.cancelNonBlocking();
  initialized_ = false;
  one_shot_active_ = false;
  one_shot_start_us_ = 0;
  active_mode_ = Mode::Combined;
  continuous_active_ = false;
  continuous_requested_mode_ = Mode::Combined;
  continuous_core_mode_ = WF100DPZCore::Mode::Combined;
  continuous_last_sample_us_ = 0;
  last_temp_refresh_ms_ = 0;
  return setStatus(StatusCode::Ok, status);
}

float WF100DPZ::readTemp(StatusCode* status) {
  const Sample s = read(Mode::TempOnly, status);
  if (last_status_ != StatusCode::Ok) {
    return NAN;
  }
  return s.temp_c;
}

float WF100DPZ::readPressure(StatusCode* status) {
  const Sample s = read(Mode::PressureOnly, status);
  if (last_status_ != StatusCode::Ok) {
    return NAN;
  }
  return s.pressure_kpa;
}

WF100DPZ::Sample WF100DPZ::readCombined(StatusCode* status) {
  return read(Mode::Combined, status);
}

WF100DPZ::Sample WF100DPZ::read(Mode mode, StatusCode* status) {
  Sample out;
  if (!initialized_) {
    setStatus(StatusCode::NotInitialized, status);
    return Sample{};
  }
  if (continuous_active_) {
    setStatus(StatusCode::Busy, status);
    return Sample{};
  }
  uint32_t wait_us = 0;
  bool drdy_hit = false;
  bool ok = false;

  out = Sample{};
  out.mode = mode;

  if (mode == Mode::TempOnly) {
    out.has_temp = true;
    ok = core_.measureTemp(out.temp_c, wait_us, drdy_hit);
  } else if (mode == Mode::PressureOnly) {
    const StatusCode refresh_status = performOptionalPressureRefreshIfNeeded(false);
    if (refresh_status != StatusCode::Ok) {
      setStatus(refresh_status, status);
      return Sample{};
    }
    out.has_pressure = true;
    ok = core_.measurePressure(out.pressure_kpa, wait_us, drdy_hit);
  } else if (mode == Mode::Combined) {
    out.has_temp = true;
    out.has_pressure = true;
    ok = core_.measureCombined(out.temp_c, out.pressure_kpa, wait_us, drdy_hit);
    if (ok) {
      last_temp_refresh_ms_ = millis();
    }
  } else {
    setStatus(StatusCode::InvalidArgument, status);
    return Sample{};
  }

  if (!ok) {
    setStatus(mapCoreFailureToStatus(mode), status);
    return Sample{};
  }

  fillSampleMetadata(out, mode, wait_us);
  setStatus(StatusCode::Ok, status);
  return out;
}

bool WF100DPZ::start(Mode mode, StatusCode* status) {
  if (!initialized_) {
    return setStatus(StatusCode::NotInitialized, status);
  }
  if (one_shot_active_ || continuous_active_) {
    return setStatus(StatusCode::Busy, status);
  }
  if (!core_.beginNonBlocking(toCoreMode(mode))) {
    return setStatus(mapCoreFailureToStatus(mode), status);
  }
  one_shot_active_ = true;
  one_shot_start_us_ = micros();
  active_mode_ = mode;
  return setStatus(StatusCode::Ok, status);
}

bool WF100DPZ::ready(StatusCode* status) {
  if (!initialized_) {
    setStatus(StatusCode::NotInitialized, status);
    return false;
  }
  if (!one_shot_active_) {
    setStatus(StatusCode::InvalidArgument, status);
    return false;
  }
  const bool is_ready = core_.nonBlockingReady();
  setStatus(StatusCode::Ok, status);
  return is_ready;
}

WF100DPZ::Sample WF100DPZ::getResult(StatusCode* status) {
  Sample out;
  if (!initialized_) {
    setStatus(StatusCode::NotInitialized, status);
    return Sample{};
  }
  if (!one_shot_active_) {
    setStatus(StatusCode::InvalidArgument, status);
    return Sample{};
  }

  WF100DPZCore::NonBlockingResult core_out;
  if (!core_.readNonBlocking(core_out)) {
    setStatus(StatusCode::Busy, status);
    return Sample{};
  }

  out = Sample{};
  out.mode = active_mode_;
  out.has_temp = core_out.has_temp;
  out.has_pressure = core_out.has_pressure;
  out.temp_c = core_out.temp_c;
  out.pressure_kpa = core_out.pressure_kpa;
  out.status_raw = core_out.status_raw;
  out.error_code = core_out.error_code;
  out.elapsed_us = micros() - one_shot_start_us_;
  out.timestamp_ms = millis();

  core_.cancelNonBlocking();
  one_shot_active_ = false;
  one_shot_start_us_ = 0;
  setStatus(StatusCode::Ok, status);
  return out;
}

void WF100DPZ::cancel() {
  core_.cancelNonBlocking();
  one_shot_active_ = false;
  one_shot_start_us_ = 0;
}

bool WF100DPZ::startContinuous(Mode mode, StatusCode* status) {
  if (!initialized_) {
    return setStatus(StatusCode::NotInitialized, status);
  }
  if (one_shot_active_ || continuous_active_) {
    return setStatus(StatusCode::Busy, status);
  }
  // Continuous-mode routing:
  // - sample_interval > 0: always internal SleepPeriodic
  // - sample_interval == 0 + Combined: still internal SleepPeriodic
  // - sample_interval == 0 + TempOnly/PressureOnly: true requested mode
  WF100DPZCore::Mode core_mode = toCoreMode(mode);
  if (config_.sample_interval > 0 || mode == Mode::Combined) {
    core_mode = WF100DPZCore::Mode::SleepPeriodic;
  }
  if (!core_.beginNonBlocking(core_mode)) {
    return setStatus(mapCoreFailureToStatus(mode), status);
  }

  continuous_active_ = true;
  continuous_requested_mode_ = mode;
  continuous_core_mode_ = core_mode;
  continuous_last_sample_us_ = micros();
  return setStatus(StatusCode::Ok, status);
}

bool WF100DPZ::continuousReady(StatusCode* status) {
  if (!initialized_) {
    setStatus(StatusCode::NotInitialized, status);
    return false;
  }
  if (!continuous_active_) {
    setStatus(StatusCode::InvalidArgument, status);
    return false;
  }
  const bool is_ready = core_.nonBlockingReady();
  setStatus(StatusCode::Ok, status);
  return is_ready;
}

WF100DPZ::Sample WF100DPZ::getContinuousResult(StatusCode* status) {
  Sample out;
  if (!initialized_) {
    setStatus(StatusCode::NotInitialized, status);
    return Sample{};
  }
  if (!continuous_active_) {
    setStatus(StatusCode::InvalidArgument, status);
    return Sample{};
  }

  WF100DPZCore::NonBlockingResult core_out;
  if (!core_.readNonBlocking(core_out)) {
    setStatus(StatusCode::Busy, status);
    return Sample{};
  }

  const uint32_t now_us = micros();
  out = Sample{};
  out.mode = continuous_requested_mode_;
  out.status_raw = core_out.status_raw;
  out.error_code = core_out.error_code;
  out.elapsed_us = now_us - continuous_last_sample_us_;
  out.timestamp_ms = millis();
  continuous_last_sample_us_ = now_us;

  if (continuous_core_mode_ == WF100DPZCore::Mode::SleepPeriodic) {
    if (continuous_requested_mode_ == Mode::TempOnly || continuous_requested_mode_ == Mode::Combined) {
      out.has_temp = true;
      out.temp_c = core_out.temp_c;
    }
    if (continuous_requested_mode_ == Mode::PressureOnly || continuous_requested_mode_ == Mode::Combined) {
      out.has_pressure = true;
      out.pressure_kpa = core_out.pressure_kpa;
    }
  } else {
    // When STC is disabled, preserve true mode semantics.
    out.has_temp = core_out.has_temp;
    out.has_pressure = core_out.has_pressure;
    out.temp_c = core_out.temp_c;
    out.pressure_kpa = core_out.pressure_kpa;
  }

  if (continuous_requested_mode_ == Mode::Combined) {
    last_temp_refresh_ms_ = millis();
  }

  // Non-STC continuous uses one-shot retrigger per sample.
  if (continuous_core_mode_ != WF100DPZCore::Mode::SleepPeriodic &&
      !core_.beginNonBlocking(continuous_core_mode_)) {
    stopContinuous();
    setStatus(mapCoreFailureToStatus(continuous_requested_mode_), status);
    return Sample{};
  }

  setStatus(StatusCode::Ok, status);
  return out;
}

void WF100DPZ::stopContinuous() {
  core_.cancelNonBlocking();
  continuous_active_ = false;
  continuous_requested_mode_ = Mode::Combined;
  continuous_core_mode_ = WF100DPZCore::Mode::Combined;
  continuous_last_sample_us_ = 0;
}

uint8_t WF100DPZ::readPartId(StatusCode* status) {
  if (!initialized_) {
    setStatus(StatusCode::NotInitialized, status);
    return 0;
  }
  const uint8_t part_id = core_.readPartId();
  setStatus(StatusCode::Ok, status);
  return part_id;
}

uint8_t WF100DPZ::readStatus(StatusCode* status) {
  if (!initialized_) {
    setStatus(StatusCode::NotInitialized, status);
    return 0;
  }
  const auto st = core_.readStatus();
  setStatus(StatusCode::Ok, status);
  return st.error_code;
}

bool WF100DPZ::setConfig(const Config& cfg, StatusCode* status) {
  if (!initialized_) {
    return setStatus(StatusCode::NotInitialized, status);
  }

  if (continuous_active_ || one_shot_active_) {
    return setStatus(StatusCode::Busy, status);
  }
  if (cfg.sample_interval > 0x0F) {
    return setStatus(StatusCode::InvalidArgument, status);
  }

  // Runtime backend/host switching is not supported while initialized.
  if (initialized_) {
    if (cfg.backend != config_.backend) {
      return setStatus(StatusCode::Unsupported, status);
    }
#if defined(ARDUINO_ARCH_ESP32)
    if (cfg.host != config_.host) {
      return setStatus(StatusCode::Unsupported, status);
    }
#endif
  }

  const Config old_cfg = config_;
  config_ = cfg;
#if !defined(ARDUINO_ARCH_ESP32)
  (void)old_cfg;
#endif

  // Keep core-level requested configuration synchronized.
#if defined(ARDUINO_ARCH_ESP32)
  core_.configureRequested(toCoreBackend(config_.backend), config_.spi_hz, config_.host);
#else
  core_.configureRequested(toCoreBackend(config_.backend), config_.spi_hz);
#endif
#if defined(ARDUINO_ARCH_ESP32)
  if (cfg.spi_hz != old_cfg.spi_hz) {
    if (!core_.setClockHz(cfg.spi_hz)) {
      config_ = old_cfg;
      core_.configureRequested(toCoreBackend(config_.backend), config_.spi_hz, config_.host);
      return setStatus(StatusCode::Unsupported, status);
    }
  }
#endif
  core_.setSleepTimeCode(config_.sample_interval);

  return setStatus(StatusCode::Ok, status);
}

bool WF100DPZ::setSampleInterval(uint8_t interval, StatusCode* status) {
  if (!initialized_) {
    return setStatus(StatusCode::NotInitialized, status);
  }
  if (continuous_active_ || one_shot_active_) {
    return setStatus(StatusCode::Busy, status);
  }
  if (interval > 0x0F) {
    return setStatus(StatusCode::InvalidArgument, status);
  }
  config_.sample_interval = interval;
  core_.setSleepTimeCode(config_.sample_interval);
  return setStatus(StatusCode::Ok, status);
}

uint8_t WF100DPZ::getSampleInterval() const {
  if (!initialized_) {
    return config_.sample_interval;
  }
  return core_.getSleepTimeCode();
}

WF100DPZ::Backend WF100DPZ::getActiveBackend() const {
  switch (core_.getActiveBackend()) {
    case WF100DPZCore::Backend::Auto:
      return Backend::Auto;
    case WF100DPZCore::Backend::BitBang:
      return Backend::BitBang;
    case WF100DPZCore::Backend::EspIdf3Wire:
      return Backend::EspIdf3Wire;
  }
  return Backend::Auto;
}

const char* WF100DPZ::statusString(StatusCode code) const {
  switch (code) {
    case StatusCode::Ok:
      return "ok";
    case StatusCode::NotInitialized:
      return "not initialized";
    case StatusCode::InvalidArgument:
      return "invalid argument";
    case StatusCode::Busy:
      return "busy";
    case StatusCode::Timeout:
      return "timeout";
    case StatusCode::TransportError:
      return "transport error";
    case StatusCode::Unsupported:
      return "unsupported";
    case StatusCode::InitFailed:
      return "initialization failed";
    case StatusCode::InternalError:
      return "internal error";
  }
  return "unknown";
}

const char* WF100DPZ::statusString() const { return statusString(last_status_); }



