#include "WF100DPZCore.h"

#include <math.h>

namespace wf100dpz_detail {

uint8_t BuildCmd(uint8_t measurement, uint8_t sleep_time, bool start) {
  const uint8_t sco = start ? 1u : 0u;
  return (uint8_t)(((sleep_time & 0x0Fu) << 4) | (sco << 3) | (measurement & 0x07u));
}

int32_t DecodeSigned24(uint8_t msb, uint8_t csb, uint8_t lsb) {
  const uint32_t u = ((uint32_t)msb << 16) | ((uint32_t)csb << 8) | (uint32_t)lsb;
  return (u >= 0x800000u) ? (int32_t)(u - 0x1000000u) : (int32_t)u;
}

float DecodeTempC(uint8_t t_msb_u8, uint8_t t_lsb_u8) {
  const int16_t msb = (t_msb_u8 >= 0x80) ? (int16_t)t_msb_u8 - 256 : (int16_t)t_msb_u8;
  return -1.2f + (float)msb + (float)t_lsb_u8 * 0.004f;
}

float DecodePressureKpa(uint8_t p_msb, uint8_t p_csb, uint8_t p_lsb) {
  const int32_t s = DecodeSigned24(p_msb, p_csb, p_lsb);
  const float f_dat = (float)s / 8388608.0f;
  return f_dat * 250.0f + 25.0f;
}

template <typename ReadStatusFn>
bool WaitReadyCommon(ReadStatusFn&& read_status, uint32_t timeout_ms, uint32_t poll_us,
                     uint32_t& elapsed_us, bool& drdy_hit) {
  const uint32_t t_start_us = micros();
  const uint32_t t0_ms = millis();
  while ((millis() - t0_ms) < timeout_ms) {
    if (read_status().drdy) {
      elapsed_us = micros() - t_start_us;
      drdy_hit = true;
      return true;
    }
    delayMicroseconds(poll_us);
  }
  elapsed_us = micros() - t_start_us;
  drdy_hit = false;
  return false;
}

template <typename ReadStatusFn>
bool WaitForDrdyStateCommon(ReadStatusFn&& read_status, uint32_t timeout_ms, uint32_t poll_us,
                            bool high, uint32_t& detect_us) {
  const uint32_t t0_ms = millis();
  while ((millis() - t0_ms) < timeout_ms) {
    if (read_status().drdy == high) {
      detect_us = micros();
      return true;
    }
    delayMicroseconds(poll_us);
  }
  detect_us = micros();
  return false;
}

}  // namespace wf100dpz_detail

WF100DPZCoreBitBang::WF100DPZCoreBitBang(int pinCsb, int pinSda, int pinClk)
    : pin_csb_(pinCsb), pin_sda_(pinSda), pin_clk_(pinClk) {}

bool WF100DPZCoreBitBang::begin() {
  pinMode(pin_csb_, OUTPUT);
  pinMode(pin_clk_, OUTPUT);
  SDAOut();

  csHigh();
  clkLow();
  SDAWrite(false);

  // Best-effort; readback can be non-sticky on this part.
  writeReg(kRegSpiCtrl, 0x01);
  delay(2);
  return true;
}

void WF100DPZCoreBitBang::setSleepTimeCode(uint8_t code) { sleep_time_code_ = code & 0x0F; }
uint8_t WF100DPZCoreBitBang::getSleepTimeCode() const { return sleep_time_code_; }

uint8_t WF100DPZCoreBitBang::readPartId() { return readReg(kRegPartId); }

WF100DPZCoreBitBang::Status WF100DPZCoreBitBang::readStatus() {
  Status s;
  s.raw = readReg(kRegStatus);
  s.drdy = (s.raw & kStatusDrdyMask) != 0;
  s.error_code = (uint8_t)((s.raw & kStatusErrorCodeMask) >> kStatusErrorCodeShift);
  return s;
}

WF100DPZCoreBitBang::Snapshot WF100DPZCoreBitBang::readSnapshot() {
  Snapshot snap;
  snap.spi_ctrl = readReg(kRegSpiCtrl);
  snap.part_id = readReg(kRegPartId);
  snap.status = readReg(kRegStatus);
  snap.cmd = readReg(kRegCmd);
  return snap;
}

uint8_t WF100DPZCoreBitBang::readSpiCtrl() { return readReg(kRegSpiCtrl); }

void WF100DPZCoreBitBang::trigger(Mode mode) {
  const uint8_t sleep = (mode == Mode::SleepPeriodic) ? sleep_time_code_ : 0;
  const uint8_t cmd = wf100dpz_detail::BuildCmd((uint8_t)mode, sleep, true);
  writeReg(kRegCmd, cmd);
}

bool WF100DPZCoreBitBang::waitReady(uint32_t& elapsed_us, bool& drdy_hit) {
  return wf100dpz_detail::WaitReadyCommon([this]() { return readStatus(); }, kDrdyTimeoutMs,
                                          kDrdyPollUs, elapsed_us, drdy_hit);
}

bool WF100DPZCoreBitBang::waitForDrdyState(bool high, uint32_t timeout_ms, uint32_t& detect_us) {
  return wf100dpz_detail::WaitForDrdyStateCommon([this]() { return readStatus(); }, timeout_ms,
                                                 kDrdyPollUs, high, detect_us);
}

bool WF100DPZCoreBitBang::measureTemp(float& temp_c, uint32_t& wait_us, bool& drdy_hit) {
  trigger(Mode::TempOnly);
  if (!waitReady(wait_us, drdy_hit)) {
    return false;
  }

  uint8_t tm, tl;
  readTempRegs(tm, tl);
  temp_c = wf100dpz_detail::DecodeTempC(tm, tl);
  return true;
}

bool WF100DPZCoreBitBang::measurePressure(float& pressure_kpa, uint32_t& wait_us, bool& drdy_hit) {
  trigger(Mode::PressureOnly);
  if (!waitReady(wait_us, drdy_hit)) {
    return false;
  }

  uint8_t pm, pc, pl;
  readPressureRegs(pm, pc, pl);
  pressure_kpa = wf100dpz_detail::DecodePressureKpa(pm, pc, pl);
  return true;
}

bool WF100DPZCoreBitBang::measureCombined(float& temp_c, float& pressure_kpa, uint32_t& wait_us,
                                      bool& drdy_hit) {
  trigger(Mode::Combined);
  if (!waitReady(wait_us, drdy_hit)) {
    return false;
  }

  uint8_t tm, tl, pm, pc, pl;
  readTempRegs(tm, tl);
  readPressureRegs(pm, pc, pl);

  temp_c = wf100dpz_detail::DecodeTempC(tm, tl);
  pressure_kpa = wf100dpz_detail::DecodePressureKpa(pm, pc, pl);
  return true;
}

bool WF100DPZCoreBitBang::readLatestCombined(float& temp_c, float& pressure_kpa, uint32_t& wait_us,
                                         bool& drdy_hit) {
  if (!waitReady(wait_us, drdy_hit)) {
    return false;
  }

  uint8_t tm, tl, pm, pc, pl;
  readTempRegs(tm, tl);
  readPressureRegs(pm, pc, pl);

  temp_c = wf100dpz_detail::DecodeTempC(tm, tl);
  pressure_kpa = wf100dpz_detail::DecodePressureKpa(pm, pc, pl);
  return true;
}

bool WF100DPZCoreBitBang::dataReady() { return readStatus().drdy; }

bool WF100DPZCoreBitBang::readLatestTempNow(float& temp_c) {
  uint8_t tm = 0;
  uint8_t tl = 0;
  readTempRegs(tm, tl);
  temp_c = wf100dpz_detail::DecodeTempC(tm, tl);
  return true;
}

bool WF100DPZCoreBitBang::readLatestPressureNow(float& pressure_kpa) {
  uint8_t pm = 0;
  uint8_t pc = 0;
  uint8_t pl = 0;
  readPressureRegs(pm, pc, pl);
  pressure_kpa = wf100dpz_detail::DecodePressureKpa(pm, pc, pl);
  return true;
}

bool WF100DPZCoreBitBang::readLatestCombinedNow(float& temp_c, float& pressure_kpa) {
  uint8_t tm = 0;
  uint8_t tl = 0;
  uint8_t pm = 0;
  uint8_t pc = 0;
  uint8_t pl = 0;
  readTempRegs(tm, tl);
  readPressureRegs(pm, pc, pl);
  temp_c = wf100dpz_detail::DecodeTempC(tm, tl);
  pressure_kpa = wf100dpz_detail::DecodePressureKpa(pm, pc, pl);
  return true;
}

void WF100DPZCoreBitBang::SDAOut() { pinMode(pin_sda_, OUTPUT); }
void WF100DPZCoreBitBang::SDAIn() { pinMode(pin_sda_, INPUT); }
void WF100DPZCoreBitBang::csLow() { digitalWrite(pin_csb_, LOW); }
void WF100DPZCoreBitBang::csHigh() { digitalWrite(pin_csb_, HIGH); }
void WF100DPZCoreBitBang::clkLow() { digitalWrite(pin_clk_, LOW); }
void WF100DPZCoreBitBang::clkHigh() { digitalWrite(pin_clk_, HIGH); }
void WF100DPZCoreBitBang::SDAWrite(bool v) { digitalWrite(pin_sda_, v ? HIGH : LOW); }
bool WF100DPZCoreBitBang::SDARead() { return digitalRead(pin_sda_) == HIGH; }
void WF100DPZCoreBitBang::halfclkDelay() { delayMicroseconds(kHalfclkUs); }

void WF100DPZCoreBitBang::spiWriteByte(uint8_t b) {
  for (int i = 0; i < 8; i++) {
    SDAWrite((b & 0x80) != 0);
    halfclkDelay();
    clkHigh();
    halfclkDelay();
    clkLow();
    halfclkDelay();
    b <<= 1;
  }
}

uint8_t WF100DPZCoreBitBang::spiReadByte() {
  uint8_t v = 0;
  for (int i = 0; i < 8; i++) {
    v <<= 1;
    clkHigh();
    halfclkDelay();
    if (SDARead()) {
      v |= 0x01;
    }
    clkLow();
    halfclkDelay();
  }
  return v;
}

void WF100DPZCoreBitBang::writeReg(uint8_t addr, uint8_t value) {
  csLow();
  SDAOut();
  spiWriteByte(kCmdWrite);
  spiWriteByte(addr);
  spiWriteByte(value);
  csHigh();
}

uint8_t WF100DPZCoreBitBang::readReg(uint8_t addr) {
  csLow();
  SDAOut();
  spiWriteByte(kCmdRead);
  spiWriteByte(addr);
  SDAIn();
  const uint8_t v = spiReadByte();
  csHigh();
  SDAOut();
  return v;
}

void WF100DPZCoreBitBang::readTempRegs(uint8_t& t_msb, uint8_t& t_lsb) {
  t_msb = readReg(kRegTempMsb);
  t_lsb = readReg(kRegTempLsb);
}

void WF100DPZCoreBitBang::readPressureRegs(uint8_t& p_msb, uint8_t& p_csb, uint8_t& p_lsb) {
  p_msb = readReg(kRegDataMsb);
  p_csb = readReg(kRegDataCsb);
  p_lsb = readReg(kRegDataLsb);
}

#if defined(ARDUINO_ARCH_ESP32)
WF100DPZCoreEspIdf3Wire::WF100DPZCoreEspIdf3Wire(int pinCsb, int pinSda, int pinClk,
                                         spi_host_device_t host, uint32_t spi_hz)
    : pin_csb_(pinCsb), pin_sda_(pinSda), pin_clk_(pinClk), host_(host), spi_hz_(spi_hz) {}

bool WF100DPZCoreEspIdf3Wire::begin() {
  pinMode(pin_csb_, OUTPUT);
  digitalWrite(pin_csb_, HIGH);

  spi_bus_config_t buscfg = {};
  buscfg.mosi_io_num = pin_sda_;
  buscfg.miso_io_num = -1;
  buscfg.sclk_io_num = pin_clk_;
  buscfg.quadwp_io_num = -1;
  buscfg.quadhd_io_num = -1;
  buscfg.max_transfer_sz = 4;

  const esp_err_t bus_err = spi_bus_initialize(host_, &buscfg, SPI_DMA_DISABLED);
  if (bus_err != ESP_OK && bus_err != ESP_ERR_INVALID_STATE) {
    return false;
  }

  if (!addDevice(spi_hz_)) {
    return false;
  }

  // Best-effort; readback can be non-sticky on this part.
  if (!writeReg(kRegSpiCtrl, 0x01)) {
    return false;
  }
  delay(2);
  return true;
}

void WF100DPZCoreEspIdf3Wire::configure(spi_host_device_t host, uint32_t spi_hz) {
  host_ = host;
  spi_hz_ = spi_hz;
}

bool WF100DPZCoreEspIdf3Wire::setClockHz(uint32_t spi_hz) {
  if (dev_ != nullptr) {
    spi_bus_remove_device(dev_);
    dev_ = nullptr;
  }
  spi_hz_ = spi_hz;
  return addDevice(spi_hz_);
}

uint32_t WF100DPZCoreEspIdf3Wire::getClockHz() const { return spi_hz_; }

void WF100DPZCoreEspIdf3Wire::setSleepTimeCode(uint8_t code) { sleep_time_code_ = code & 0x0F; }
uint8_t WF100DPZCoreEspIdf3Wire::getSleepTimeCode() const { return sleep_time_code_; }

uint8_t WF100DPZCoreEspIdf3Wire::readPartId() { return readReg(kRegPartId); }

WF100DPZCoreEspIdf3Wire::Status WF100DPZCoreEspIdf3Wire::readStatus() {
  Status s;
  s.raw = readReg(kRegStatus);
  s.drdy = (s.raw & kStatusDrdyMask) != 0;
  s.error_code = (uint8_t)((s.raw & kStatusErrorCodeMask) >> kStatusErrorCodeShift);
  return s;
}

WF100DPZCoreEspIdf3Wire::Snapshot WF100DPZCoreEspIdf3Wire::readSnapshot() {
  Snapshot snap;
  snap.spi_ctrl = readReg(kRegSpiCtrl);
  snap.part_id = readReg(kRegPartId);
  snap.status = readReg(kRegStatus);
  snap.cmd = readReg(kRegCmd);
  return snap;
}

uint8_t WF100DPZCoreEspIdf3Wire::readSpiCtrl() { return readReg(kRegSpiCtrl); }

void WF100DPZCoreEspIdf3Wire::trigger(Mode mode) {
  const uint8_t sleep = (mode == Mode::SleepPeriodic) ? sleep_time_code_ : 0;
  const uint8_t cmd = wf100dpz_detail::BuildCmd((uint8_t)mode, sleep, true);
  last_cmd_write_ok_ = writeReg(kRegCmd, cmd);
}

bool WF100DPZCoreEspIdf3Wire::waitReady(uint32_t& elapsed_us, bool& drdy_hit) {
  if (!last_cmd_write_ok_) {
    elapsed_us = 0;
    drdy_hit = false;
    return false;
  }
  return wf100dpz_detail::WaitReadyCommon([this]() { return readStatus(); }, kDrdyTimeoutMs,
                                          kDrdyPollUs, elapsed_us, drdy_hit);
}

bool WF100DPZCoreEspIdf3Wire::waitForDrdyState(bool high, uint32_t timeout_ms, uint32_t& detect_us) {
  return wf100dpz_detail::WaitForDrdyStateCommon([this]() { return readStatus(); }, timeout_ms,
                                                 kDrdyPollUs, high, detect_us);
}

bool WF100DPZCoreEspIdf3Wire::measureTemp(float& temp_c, uint32_t& wait_us, bool& drdy_hit) {
  trigger(Mode::TempOnly);
  if (!waitReady(wait_us, drdy_hit)) {
    return false;
  }

  uint8_t tm, tl;
  readTempRegs(tm, tl);
  temp_c = wf100dpz_detail::DecodeTempC(tm, tl);
  return true;
}

bool WF100DPZCoreEspIdf3Wire::measurePressure(float& pressure_kpa, uint32_t& wait_us, bool& drdy_hit) {
  trigger(Mode::PressureOnly);
  if (!waitReady(wait_us, drdy_hit)) {
    return false;
  }

  uint8_t pm, pc, pl;
  readPressureRegs(pm, pc, pl);
  pressure_kpa = wf100dpz_detail::DecodePressureKpa(pm, pc, pl);
  return true;
}

bool WF100DPZCoreEspIdf3Wire::measureCombined(float& temp_c, float& pressure_kpa, uint32_t& wait_us,
                                          bool& drdy_hit) {
  trigger(Mode::Combined);
  if (!waitReady(wait_us, drdy_hit)) {
    return false;
  }

  uint8_t tm, tl, pm, pc, pl;
  readTempRegs(tm, tl);
  readPressureRegs(pm, pc, pl);

  temp_c = wf100dpz_detail::DecodeTempC(tm, tl);
  pressure_kpa = wf100dpz_detail::DecodePressureKpa(pm, pc, pl);
  return true;
}

bool WF100DPZCoreEspIdf3Wire::readLatestCombined(float& temp_c, float& pressure_kpa, uint32_t& wait_us,
                                             bool& drdy_hit) {
  if (!waitReady(wait_us, drdy_hit)) {
    return false;
  }

  uint8_t tm, tl, pm, pc, pl;
  readTempRegs(tm, tl);
  readPressureRegs(pm, pc, pl);

  temp_c = wf100dpz_detail::DecodeTempC(tm, tl);
  pressure_kpa = wf100dpz_detail::DecodePressureKpa(pm, pc, pl);
  return true;
}

bool WF100DPZCoreEspIdf3Wire::dataReady() { return readStatus().drdy; }

bool WF100DPZCoreEspIdf3Wire::readLatestTempNow(float& temp_c) {
  uint8_t tm = 0;
  uint8_t tl = 0;
  readTempRegs(tm, tl);
  temp_c = wf100dpz_detail::DecodeTempC(tm, tl);
  return true;
}

bool WF100DPZCoreEspIdf3Wire::readLatestPressureNow(float& pressure_kpa) {
  uint8_t pm = 0;
  uint8_t pc = 0;
  uint8_t pl = 0;
  readPressureRegs(pm, pc, pl);
  pressure_kpa = wf100dpz_detail::DecodePressureKpa(pm, pc, pl);
  return true;
}

bool WF100DPZCoreEspIdf3Wire::readLatestCombinedNow(float& temp_c, float& pressure_kpa) {
  uint8_t tm = 0;
  uint8_t tl = 0;
  uint8_t pm = 0;
  uint8_t pc = 0;
  uint8_t pl = 0;
  readTempRegs(tm, tl);
  readPressureRegs(pm, pc, pl);
  temp_c = wf100dpz_detail::DecodeTempC(tm, tl);
  pressure_kpa = wf100dpz_detail::DecodePressureKpa(pm, pc, pl);
  return true;
}

bool WF100DPZCoreEspIdf3Wire::addDevice(uint32_t spi_hz) {
  spi_device_interface_config_t devcfg = {};
  devcfg.clock_speed_hz = (int)spi_hz;
  devcfg.mode = 0;
  devcfg.spics_io_num = -1;
  devcfg.queue_size = 1;
  devcfg.flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_3WIRE;
  return spi_bus_add_device(host_, &devcfg, &dev_) == ESP_OK;
}

void WF100DPZCoreEspIdf3Wire::csLow() { digitalWrite(pin_csb_, LOW); }
void WF100DPZCoreEspIdf3Wire::csHigh() { digitalWrite(pin_csb_, HIGH); }

bool WF100DPZCoreEspIdf3Wire::writeReg(uint8_t addr, uint8_t value) {
  if (dev_ == nullptr) return false;
  uint8_t tx_data[3] = {kCmdWrite, addr, value};
  spi_transaction_t t = {};
  t.length = 24;
  t.tx_buffer = tx_data;
  csLow();
  const esp_err_t err = spi_device_polling_transmit(dev_, &t);
  csHigh();
  return err == ESP_OK;
}

uint8_t WF100DPZCoreEspIdf3Wire::readReg(uint8_t addr) {
  if (dev_ == nullptr) return 0;
  if (spi_device_acquire_bus(dev_, portMAX_DELAY) != ESP_OK) {
    return 0;
  }

  uint8_t tx_hdr[2] = {kCmdRead, addr};
  spi_transaction_t t_tx = {};
  t_tx.length = 16;
  t_tx.tx_buffer = tx_hdr;

  uint8_t rx_data = 0;
  spi_transaction_t t_rx = {};
  t_rx.length = 0;
  t_rx.rxlength = 8;
  t_rx.flags = SPI_TRANS_USE_RXDATA;

  csLow();
  const esp_err_t e1 = spi_device_polling_transmit(dev_, &t_tx);
  // Give the controller one short turn-around slot before reading shared data line.
  delayMicroseconds(1);
  const esp_err_t e2 = spi_device_polling_transmit(dev_, &t_rx);
  csHigh();

  spi_device_release_bus(dev_);
  if (e1 != ESP_OK || e2 != ESP_OK) {
    return 0;
  }
  rx_data = t_rx.rx_data[0];
  return rx_data;
}

void WF100DPZCoreEspIdf3Wire::readTempRegs(uint8_t& t_msb, uint8_t& t_lsb) {
  t_msb = readReg(kRegTempMsb);
  t_lsb = readReg(kRegTempLsb);
}

void WF100DPZCoreEspIdf3Wire::readPressureRegs(uint8_t& p_msb, uint8_t& p_csb, uint8_t& p_lsb) {
  p_msb = readReg(kRegDataMsb);
  p_csb = readReg(kRegDataCsb);
  p_lsb = readReg(kRegDataLsb);
}
#endif

#if defined(ARDUINO_ARCH_ESP32)
WF100DPZCore::WF100DPZCore(int pinCsb, int pinSda, int pinClk, Backend backend, uint32_t spi_hz,
                   spi_host_device_t host)
    : requested_backend_(backend),
      bitbang_(pinCsb, pinSda, pinClk),
      espidf_(pinCsb, pinSda, pinClk, host, spi_hz) {}
#else
WF100DPZCore::WF100DPZCore(int pinCsb, int pinSda, int pinClk, Backend backend, uint32_t)
    : requested_backend_(backend), bitbang_(pinCsb, pinSda, pinClk) {}
#endif

bool WF100DPZCore::begin() {
  last_error_ = Error::None;
  clearNonBlocking();
#if defined(ARDUINO_ARCH_ESP32)
  if (!ensureLock()) {
    last_error_ = Error::BackendInitFailed;
    return false;
  }
  if (!lockApi()) {
    last_error_ = Error::BackendInitFailed;
    return false;
  }
  auto done = [this](bool ok, Error err, Backend fallback_backend) {
    if (!ok) {
      last_error_ = err;
      active_backend_ = fallback_backend;
    }
    unlockApi();
    return ok;
  };

  if (requested_backend_ == Backend::Auto) {
    // Prefer ESP-IDF backend on ESP32 and fall back to bit-bang if needed.
    if (espidf_.begin()) {
      active_backend_ = Backend::EspIdf3Wire;
      return done(true, Error::None, active_backend_);
    }
    if (bitbang_.begin()) {
      active_backend_ = Backend::BitBang;
      return done(true, Error::None, active_backend_);
    }
    return done(false, Error::BackendInitFailed, Backend::Auto);
  }

  if (requested_backend_ == Backend::EspIdf3Wire) {
    active_backend_ = Backend::EspIdf3Wire;
    if (!espidf_.begin()) {
      return done(false, Error::BackendInitFailed, Backend::Auto);
    }
    return done(true, Error::None, active_backend_);
  }

  if (requested_backend_ == Backend::BitBang) {
    active_backend_ = Backend::BitBang;
    if (!bitbang_.begin()) {
      return done(false, Error::BackendInitFailed, Backend::Auto);
    }
    return done(true, Error::None, active_backend_);
  }

  return done(false, Error::UnsupportedBackendOnPlatform, Backend::Auto);
#else
  if (requested_backend_ == Backend::EspIdf3Wire) {
    last_error_ = Error::UnsupportedBackendOnPlatform;
    active_backend_ = Backend::Auto;
    return false;
  }

  active_backend_ = Backend::BitBang;
  if (!bitbang_.begin()) {
    last_error_ = Error::BackendInitFailed;
    active_backend_ = Backend::Auto;
    return false;
  }
  return true;
#endif
}

const char* WF100DPZCore::lastErrorString() const {
  switch (last_error_) {
    case Error::None:
      return "none";
    case Error::UnsupportedBackendOnPlatform:
      return "unsupported backend on this platform";
    case Error::BackendInitFailed:
      return "backend initialization failed";
    case Error::BackendNotInitialized:
      return "backend not initialized (call begin)";
  }
  return "unknown";
}

bool WF100DPZCore::ensureInitialized() {
  if (active_backend_ == Backend::BitBang
#if defined(ARDUINO_ARCH_ESP32)
      || active_backend_ == Backend::EspIdf3Wire
#endif
  ) {
    return true;
  }
  last_error_ = Error::BackendNotInitialized;
  return false;
}

void WF100DPZCore::clearNonBlocking() {
  nb_state_ = NonBlockingState::Idle;
  nb_mode_ = Mode::Combined;
}

#if defined(ARDUINO_ARCH_ESP32)
bool WF100DPZCore::ensureLock() {
  if (api_lock_ != nullptr) {
    return true;
  }
  api_lock_ = xSemaphoreCreateMutex();
  return api_lock_ != nullptr;
}

bool WF100DPZCore::lockApi() const {
  if (api_lock_ == nullptr) {
    return true;
  }
  return xSemaphoreTake(api_lock_, portMAX_DELAY) == pdTRUE;
}

void WF100DPZCore::unlockApi() const {
  if (api_lock_ != nullptr) {
    xSemaphoreGive(api_lock_);
  }
}
#endif

void WF100DPZCore::setSleepTimeCode(uint8_t code) {
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return;
#endif
  if (!ensureInitialized()) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return;
  }
  clearNonBlocking();
  if (active_backend_ == Backend::BitBang) {
    bitbang_.setSleepTimeCode(code);
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return;
  }
#if defined(ARDUINO_ARCH_ESP32)
  espidf_.setSleepTimeCode(code);
  unlockApi();
#endif
}

uint8_t WF100DPZCore::getSleepTimeCode() const {
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return 0;
#endif
  if (active_backend_ == Backend::BitBang) {
    const uint8_t out = bitbang_.getSleepTimeCode();
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return out;
  }
#if defined(ARDUINO_ARCH_ESP32)
  if (active_backend_ == Backend::EspIdf3Wire) {
    const uint8_t out = espidf_.getSleepTimeCode();
    unlockApi();
    return out;
  }
  unlockApi();
#endif
  return 0;
}

uint8_t WF100DPZCore::readPartId() {
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return 0;
#endif
  if (!ensureInitialized()) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return 0;
  }
  if (active_backend_ == Backend::BitBang) {
    const uint8_t out = bitbang_.readPartId();
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return out;
  }
#if defined(ARDUINO_ARCH_ESP32)
  const uint8_t out = espidf_.readPartId();
  unlockApi();
  return out;
#else
  return 0;
#endif
}

WF100DPZCore::Status WF100DPZCore::readStatus() {
  Status out{};
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return out;
#endif
  if (!ensureInitialized()) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return out;
  }
  if (active_backend_ == Backend::BitBang) {
    out = bitbang_.readStatus();
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return out;
  }
#if defined(ARDUINO_ARCH_ESP32)
  const auto s = espidf_.readStatus();
  out.raw = s.raw;
  out.drdy = s.drdy;
  out.error_code = s.error_code;
  unlockApi();
#endif
  return out;
}

WF100DPZCore::Snapshot WF100DPZCore::readSnapshot() {
  Snapshot out{};
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return out;
#endif
  if (!ensureInitialized()) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return out;
  }
  if (active_backend_ == Backend::BitBang) {
    out = bitbang_.readSnapshot();
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return out;
  }
#if defined(ARDUINO_ARCH_ESP32)
  const auto s = espidf_.readSnapshot();
  out.spi_ctrl = s.spi_ctrl;
  out.part_id = s.part_id;
  out.status = s.status;
  out.cmd = s.cmd;
  unlockApi();
#endif
  return out;
}

uint8_t WF100DPZCore::readSpiCtrl() {
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return 0;
#endif
  if (!ensureInitialized()) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return 0;
  }
  if (active_backend_ == Backend::BitBang) {
    const uint8_t out = bitbang_.readSpiCtrl();
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return out;
  }
#if defined(ARDUINO_ARCH_ESP32)
  const uint8_t out = espidf_.readSpiCtrl();
  unlockApi();
  return out;
#else
  return 0;
#endif
}

void WF100DPZCore::trigger(Mode mode) {
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return;
#endif
  if (!ensureInitialized()) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return;
  }
  clearNonBlocking();
  if (active_backend_ == Backend::BitBang) {
    bitbang_.trigger(mode);
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return;
  }
#if defined(ARDUINO_ARCH_ESP32)
  espidf_.trigger(toEspMode(mode));
  unlockApi();
#endif
}

bool WF100DPZCore::waitReady(uint32_t& elapsed_us, bool& drdy_hit) {
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return false;
#endif
  if (!ensureInitialized()) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return false;
  }
  if (active_backend_ == Backend::BitBang) {
    const bool out = bitbang_.waitReady(elapsed_us, drdy_hit);
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return out;
  }
#if defined(ARDUINO_ARCH_ESP32)
  const bool out = espidf_.waitReady(elapsed_us, drdy_hit);
  unlockApi();
  return out;
#else
  return false;
#endif
}

bool WF100DPZCore::waitForDrdyState(bool high, uint32_t timeout_ms, uint32_t& detect_us) {
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return false;
#endif
  if (!ensureInitialized()) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return false;
  }
  if (active_backend_ == Backend::BitBang) {
    const bool out = bitbang_.waitForDrdyState(high, timeout_ms, detect_us);
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return out;
  }
#if defined(ARDUINO_ARCH_ESP32)
  const bool out = espidf_.waitForDrdyState(high, timeout_ms, detect_us);
  unlockApi();
  return out;
#else
  return false;
#endif
}

bool WF100DPZCore::measureTemp(float& temp_c, uint32_t& wait_us, bool& drdy_hit) {
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return false;
#endif
  if (!ensureInitialized()) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return false;
  }
  clearNonBlocking();
  if (active_backend_ == Backend::BitBang) {
    const bool out = bitbang_.measureTemp(temp_c, wait_us, drdy_hit);
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return out;
  }
#if defined(ARDUINO_ARCH_ESP32)
  const bool out = espidf_.measureTemp(temp_c, wait_us, drdy_hit);
  unlockApi();
  return out;
#else
  return false;
#endif
}

bool WF100DPZCore::measurePressure(float& pressure_kpa, uint32_t& wait_us, bool& drdy_hit) {
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return false;
#endif
  if (!ensureInitialized()) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return false;
  }
  clearNonBlocking();
  if (active_backend_ == Backend::BitBang) {
    const bool out = bitbang_.measurePressure(pressure_kpa, wait_us, drdy_hit);
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return out;
  }
#if defined(ARDUINO_ARCH_ESP32)
  const bool out = espidf_.measurePressure(pressure_kpa, wait_us, drdy_hit);
  unlockApi();
  return out;
#else
  return false;
#endif
}

bool WF100DPZCore::measureCombined(float& temp_c, float& pressure_kpa, uint32_t& wait_us, bool& drdy_hit) {
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return false;
#endif
  if (!ensureInitialized()) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return false;
  }
  clearNonBlocking();
  if (active_backend_ == Backend::BitBang) {
    const bool out = bitbang_.measureCombined(temp_c, pressure_kpa, wait_us, drdy_hit);
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return out;
  }
#if defined(ARDUINO_ARCH_ESP32)
  const bool out = espidf_.measureCombined(temp_c, pressure_kpa, wait_us, drdy_hit);
  unlockApi();
  return out;
#else
  return false;
#endif
}

bool WF100DPZCore::readLatestCombined(float& temp_c, float& pressure_kpa, uint32_t& wait_us, bool& drdy_hit) {
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return false;
#endif
  if (!ensureInitialized()) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return false;
  }
  clearNonBlocking();
  if (active_backend_ == Backend::BitBang) {
    const bool out = bitbang_.readLatestCombined(temp_c, pressure_kpa, wait_us, drdy_hit);
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return out;
  }
#if defined(ARDUINO_ARCH_ESP32)
  const bool out = espidf_.readLatestCombined(temp_c, pressure_kpa, wait_us, drdy_hit);
  unlockApi();
  return out;
#else
  return false;
#endif
}

bool WF100DPZCore::beginNonBlocking(Mode mode) {
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return false;
#endif
  if (!ensureInitialized()) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return false;
  }
  if (nb_state_ == NonBlockingState::Pending) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return false;
  }

  if (active_backend_ == Backend::BitBang) {
    bitbang_.trigger(toBitBangMode(mode));
    nb_mode_ = mode;
    nb_state_ = NonBlockingState::Pending;
    last_error_ = Error::None;
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return true;
  }
#if defined(ARDUINO_ARCH_ESP32)
  if (active_backend_ == Backend::EspIdf3Wire) {
    espidf_.trigger(toEspMode(mode));
    nb_mode_ = mode;
    nb_state_ = NonBlockingState::Pending;
    last_error_ = Error::None;
    unlockApi();
    return true;
  }
  unlockApi();
#endif
  return false;
}

bool WF100DPZCore::nonBlockingReady() {
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return false;
#endif
  if (nb_state_ != NonBlockingState::Pending) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return false;
  }
  if (!ensureInitialized()) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return false;
  }
  if (active_backend_ == Backend::BitBang) {
    const bool out = bitbang_.dataReady();
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return out;
  }
#if defined(ARDUINO_ARCH_ESP32)
  if (active_backend_ == Backend::EspIdf3Wire) {
    const bool out = espidf_.dataReady();
    unlockApi();
    return out;
  }
  unlockApi();
#endif
  return false;
}

bool WF100DPZCore::readNonBlocking(NonBlockingResult& out) {
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return false;
#endif
  if (!ensureInitialized()) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return false;
  }
  if (nb_state_ != NonBlockingState::Pending) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return false;
  }

  bool ready = true;
  if (nb_mode_ != Mode::SleepPeriodic) {
    ready = false;
    if (active_backend_ == Backend::BitBang) {
      ready = bitbang_.dataReady();
    }
#if defined(ARDUINO_ARCH_ESP32)
    else if (active_backend_ == Backend::EspIdf3Wire) {
      ready = espidf_.dataReady();
    }
#endif
  }
  if (!ready) {
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return false;
  }

  out = NonBlockingResult{};
  out.mode = nb_mode_;
  bool ok = false;
  switch (nb_mode_) {
    case Mode::TempOnly:
      out.has_temp = true;
      if (active_backend_ == Backend::BitBang) {
        ok = bitbang_.readLatestTempNow(out.temp_c);
      }
#if defined(ARDUINO_ARCH_ESP32)
      else if (active_backend_ == Backend::EspIdf3Wire) {
        ok = espidf_.readLatestTempNow(out.temp_c);
      }
#endif
      break;
    case Mode::PressureOnly:
      out.has_pressure = true;
      if (active_backend_ == Backend::BitBang) {
        ok = bitbang_.readLatestPressureNow(out.pressure_kpa);
      }
#if defined(ARDUINO_ARCH_ESP32)
      else if (active_backend_ == Backend::EspIdf3Wire) {
        ok = espidf_.readLatestPressureNow(out.pressure_kpa);
      }
#endif
      break;
    case Mode::Combined:
    case Mode::SleepPeriodic:
      out.has_temp = true;
      out.has_pressure = true;
      if (active_backend_ == Backend::BitBang) {
        ok = bitbang_.readLatestCombinedNow(out.temp_c, out.pressure_kpa);
      }
#if defined(ARDUINO_ARCH_ESP32)
      else if (active_backend_ == Backend::EspIdf3Wire) {
        ok = espidf_.readLatestCombinedNow(out.temp_c, out.pressure_kpa);
      }
#endif
      break;
  }

  if (ok) {
    Status s{};
    if (active_backend_ == Backend::BitBang) {
      s = bitbang_.readStatus();
    }
#if defined(ARDUINO_ARCH_ESP32)
    else if (active_backend_ == Backend::EspIdf3Wire) {
      const auto esp_s = espidf_.readStatus();
      s.raw = esp_s.raw;
      s.drdy = esp_s.drdy;
      s.error_code = esp_s.error_code;
    }
#endif
    out.status_raw = s.raw;
    out.error_code = s.error_code;
    if (nb_mode_ != Mode::SleepPeriodic) {
      clearNonBlocking();
    }
  }

#if defined(ARDUINO_ARCH_ESP32)
  unlockApi();
#endif
  return ok;
}

void WF100DPZCore::cancelNonBlocking() {
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return;
#endif
  clearNonBlocking();
#if defined(ARDUINO_ARCH_ESP32)
  unlockApi();
#endif
}

#if defined(ARDUINO_ARCH_ESP32)
bool WF100DPZCore::setClockHz(uint32_t spi_hz) {
#if defined(ARDUINO_ARCH_ESP32)
  if (!lockApi()) return false;
#endif
  if (!ensureInitialized()) {
    unlockApi();
    return false;
  }
  if (active_backend_ != Backend::EspIdf3Wire) {
    last_error_ = Error::UnsupportedBackendOnPlatform;
#if defined(ARDUINO_ARCH_ESP32)
    unlockApi();
#endif
    return false;
  }
  const bool out = espidf_.setClockHz(spi_hz);
  unlockApi();
  return out;
}

uint32_t WF100DPZCore::getClockHz() const {
  if (!lockApi()) return 0;
  if (active_backend_ == Backend::EspIdf3Wire) {
    const uint32_t out = espidf_.getClockHz();
    unlockApi();
    return out;
  }
  unlockApi();
  return 0;
}

void WF100DPZCore::configureRequested(Backend backend, uint32_t spi_hz, spi_host_device_t host) {
  requested_backend_ = backend;
  espidf_.configure(host, spi_hz);
}
#endif

#if !defined(ARDUINO_ARCH_ESP32)
void WF100DPZCore::configureRequested(Backend backend, uint32_t) {
  requested_backend_ = backend;
}
#endif




