/*
 * ESPHome helper for OWON B35T/B35T+ BLE meter on M5Stack Core 1.
 * Parser is based on the standalone Arduino sketch by Reaper7
 * (Beerware license, Revision 42) and Dean Cording's owonb35 notes.
 */
#pragma once

#include <cmath>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
#include "esp_heap_caps.h"
#include "esp_system.h"
#include "esphome/core/helpers.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/log.h"
#include "esphome/components/display/display.h"

namespace owon_b35t {

using esphome::Color;
using esphome::display::Display;

static const char *const TAG = "owon_b35t";
static const char *const POWER_TAG = "core2_power";
static constexpr uint8_t AXP192_ADDR = 0x34;
static esphome::i2c::I2CDevice axp192;
static bool axp192_ready = false;

static bool axp_write(uint8_t reg, uint8_t value) {
  if (!axp192_ready) return false;
  bool ok = axp192.write_byte(reg, value);
  if (!ok) ESP_LOGW(POWER_TAG, "AXP192 write reg 0x%02X failed", reg);
  return ok;
}

static bool axp_read(uint8_t reg, uint8_t *value) {
  if (!axp192_ready) return false;
  bool ok = axp192.read_byte(reg, value);
  if (!ok) ESP_LOGW(POWER_TAG, "AXP192 read reg 0x%02X failed", reg);
  return ok;
}

static void axp_update(uint8_t reg, uint8_t clear_mask, uint8_t set_mask) {
  uint8_t value = 0;
  if (!axp_read(reg, &value)) return;
  value = (value & ~clear_mask) | set_mask;
  axp_write(reg, value);
}

static uint8_t axp_dc_voltage_data(uint16_t millivolts) {
  if (millivolts < 700) millivolts = 700;
  if (millivolts > 3500) millivolts = 3500;
  return static_cast<uint8_t>((millivolts - 700) / 25) & 0x7F;
}

static uint8_t axp_ldo_voltage_data(uint16_t millivolts) {
  if (millivolts < 1800) millivolts = 1800;
  if (millivolts > 3300) millivolts = 3300;
  return static_cast<uint8_t>((millivolts - 1800) / 100) & 0x0F;
}

static void core2_axp192_set_lcd_voltage(uint16_t millivolts) {
  uint8_t value = 0;
  axp_read(0x27, &value);
  axp_write(0x27, (value & 0x80) | axp_dc_voltage_data(millivolts));  // DCDC3, LCD backlight
}

static void core2_axp192_set_backlight(float brightness) {
  if (brightness <= 0.0f) {
    axp_update(0x12, 0x02, 0x00);  // DCDC3 off
    return;
  }
  if (brightness > 1.0f) brightness = 1.0f;
  uint16_t millivolts = static_cast<uint16_t>(2400 + brightness * 900);
  core2_axp192_set_lcd_voltage(millivolts);
  axp_update(0x12, 0x00, 0x02);  // DCDC3 on
}

static void core2_axp192_init(esphome::i2c::I2CBus *bus) {
  axp192.set_i2c_bus(bus);
  axp192.set_i2c_address(AXP192_ADDR);
  axp192_ready = true;
  ESP_LOGI(POWER_TAG, "Initializing M5Stack Core2 AXP192 LCD power");
  axp_update(0x30, 0xF9, 0x02);  // Disable VBUS current limit, preserve bit 2.
  axp_update(0x92, 0x07, 0x00);  // GPIO1 open-drain output.
  axp_update(0x93, 0x07, 0x00);  // GPIO2 open-drain output.
  axp_write(0x35, 0xA2);         // RTC battery charging.

  uint8_t value = 0;
  axp_read(0x26, &value);
  axp_write(0x26, (value & 0x80) | axp_dc_voltage_data(3350));  // DCDC1 ESP32 VDD.
  core2_axp192_set_lcd_voltage(2800);

  uint8_t ldo2 = axp_ldo_voltage_data(3300);
  uint8_t ldo3 = axp_ldo_voltage_data(2000);
  axp_write(0x28, (ldo2 << 4) | ldo3);  // LDO2 LCD logic/SD, LDO3 vibrator.
  axp_update(0x12, 0x00, 0x07);         // Enable DCDC1, DCDC3, LDO2.
  axp_write(0x82, 0xFF);                // ADCs on.
  axp_update(0x95, 0x8D, 0x84);         // GPIO4 setup, as M5Core2 library does.
  axp_write(0x36, 0x4C);                // Power key timing.

  // LCD reset through AXP192 GPIO4.
  axp_update(0x96, 0x02, 0x00);
  delay(100);
  axp_update(0x96, 0x00, 0x02);
  delay(100);

  core2_axp192_set_backlight(1.0f);
}

static const uint8_t ACCU_BMP[32] = {
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b11111111, 0b11111110,
    0b10000000, 0b00000010,
    0b10000000, 0b00000011,
    0b10000000, 0b00000011,
    0b10000000, 0b00000011,
    0b10000000, 0b00000011,
    0b10000000, 0b00000011,
    0b10000000, 0b00000011,
    0b10000000, 0b00000010,
    0b11111111, 0b11111110,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
};

static const uint8_t BLE_BMP[32] = {
    0b00000001, 0b10000000,
    0b00000001, 0b11000000,
    0b00010001, 0b01100000,
    0b00011001, 0b00110000,
    0b00001101, 0b00011000,
    0b00000111, 0b00110000,
    0b00000011, 0b01100000,
    0b00000001, 0b11000000,
    0b00000001, 0b11000000,
    0b00000011, 0b01100000,
    0b00000111, 0b00110000,
    0b00001101, 0b00011000,
    0b00011001, 0b00110000,
    0b00010001, 0b01100000,
    0b00000001, 0b11000000,
    0b00000001, 0b10000000,
};

static const uint8_t DIODE_BMP[32] = {
    0b00001000, 0b00011000,
    0b00001100, 0b00011000,
    0b00001110, 0b00011000,
    0b00001111, 0b00011000,
    0b00001111, 0b10011000,
    0b00001111, 0b11011000,
    0b00001111, 0b11111000,
    0b11111111, 0b11111111,
    0b11111111, 0b11111111,
    0b00001111, 0b11111000,
    0b00001111, 0b11011000,
    0b00001111, 0b10011000,
    0b00001111, 0b00011000,
    0b00001110, 0b00011000,
    0b00001100, 0b00011000,
    0b00001000, 0b00011000,
};

static const uint8_t BUZZ_BMP[32] = {
    0b00000000, 0b11000000,
    0b00000001, 0b11000000,
    0b00000011, 0b11000001,
    0b00000111, 0b11000001,
    0b00001111, 0b11000101,
    0b11111111, 0b11000101,
    0b11111111, 0b11010101,
    0b11111111, 0b11010101,
    0b11111111, 0b11010101,
    0b11111111, 0b11010101,
    0b11111111, 0b11000101,
    0b00001111, 0b11000101,
    0b00000111, 0b11000001,
    0b00000011, 0b11000001,
    0b00000001, 0b11000000,
    0b00000000, 0b11000000,
};

class Meter {
 public:
  static constexpr uint8_t REGPLUSMINUS = 0x00;
  static constexpr uint8_t FLAGPLUS = 0b00101011;
  static constexpr uint8_t FLAGMINUS = 0b00101101;
  static constexpr uint8_t REGDIG1 = 0x01;
  static constexpr uint8_t REGDIG2 = 0x02;
  static constexpr uint8_t REGDIG3 = 0x03;
  static constexpr uint8_t REGDIG4 = 0x04;
  static constexpr uint8_t REGPOINT = 0x06;
  static constexpr uint8_t FLAGPOINT0 = 0b00110000;
  static constexpr uint8_t FLAGPOINT1 = 0b00110001;
  static constexpr uint8_t FLAGPOINT2 = 0b00110010;
  static constexpr uint8_t FLAGPOINT3 = 0b00110100;
  static constexpr uint8_t REGMODE = 0x07;
  static constexpr uint8_t FLAGMODEHOLD = 0b00000010;
  static constexpr uint8_t FLAGMODEREL = 0b00000100;
  static constexpr uint8_t FLAGMODEAC = 0b00001000;
  static constexpr uint8_t FLAGMODEDC = 0b00010000;
  static constexpr uint8_t FLAGMODEAUTO = 0b00100000;
  static constexpr uint8_t REGMINMAX = 0x08;
  static constexpr uint8_t FLAGMIN = 0b00010000;
  static constexpr uint8_t FLAGMAX = 0b00100000;
  static constexpr uint8_t REGSCALE = 0x09;
  static constexpr uint8_t FLAGSCALEDUTY = 0b00000010;
  static constexpr uint8_t FLAGSCALEDIODE = 0b00000100;
  static constexpr uint8_t FLAGSCALEBUZZ = 0b00001000;
  static constexpr uint8_t FLAGSCALEMEGA = 0b00010000;
  static constexpr uint8_t FLAGSCALEKILO = 0b00100000;
  static constexpr uint8_t FLAGSCALEMILLI = 0b01000000;
  static constexpr uint8_t FLAGSCALEMICRO = 0b10000000;
  static constexpr uint8_t REGUNIT = 0x0a;
  static constexpr uint8_t FLAGUNITFAHR = 0b00000001;
  static constexpr uint8_t FLAGUNITGRAD = 0b00000010;
  static constexpr uint8_t FLAGUNITNF = 0b00000100;
  static constexpr uint8_t FLAGUNITHZ = 0b00001000;
  static constexpr uint8_t FLAGUNITHFE = 0b00010000;
  static constexpr uint8_t FLAGUNITOHM = 0b00100000;
  static constexpr uint8_t FLAGUNITAMP = 0b01000000;
  static constexpr uint8_t FLAGUNITVOLT = 0b10000000;

  bool connected{false};
  bool write_available{false};
  bool is_plus{false};
  bool low_battery{false};
  bool overload{false};
  bool has_reading{false};
  uint8_t selected_button{1};
  uint32_t last_notify_ms{0};

  bool handle_notify(const std::vector<uint8_t> &data) {
    if (data.size() > sizeof(this->raw_))
      return false;
    if (data.size() == 6 && data[1] >= 0xF0) {
      memset(this->raw_, 0, sizeof(this->raw_));
      memcpy(this->raw_, data.data(), data.size());
      this->is_plus = true;
      this->parse_plus_();
    } else if (data.size() == 14 && data[12] == 0x0D && data[13] == 0x0A) {
      memset(this->value_, 0, sizeof(this->value_));
      memcpy(this->value_, data.data(), data.size());
      this->is_plus = false;
    } else {
      ESP_LOGW(TAG, "Ignoring unexpected OWON frame length=%u", static_cast<unsigned>(data.size()));
      return false;
    }

    this->overload = memcmp(this->value_, OVERLOAD_FRAME, sizeof(OVERLOAD_FRAME)) == 0;
    this->display_value = this->calc_display_value_();
    this->base_value = this->calc_base_value_();
    this->has_reading = true;
    this->last_notify_ms = millis();
    return true;
  }

  void on_connect() {
    this->connected = true;
    this->write_available = true;
  }

  void on_disconnect() {
    this->connected = false;
    this->write_available = false;
  }

  float value() const { return this->display_value; }
  float value_base() const { return this->base_value; }
  bool negative() const { return (this->value_[REGPLUSMINUS] & FLAGMINUS) == FLAGMINUS; }
  bool auto_range() const { return (this->value_[REGMODE] & FLAGMODEAUTO) == FLAGMODEAUTO; }
  bool hold() const { return (this->value_[REGMODE] & FLAGMODEHOLD) == FLAGMODEHOLD; }
  bool relative() const { return (this->value_[REGMODE] & FLAGMODEREL) == FLAGMODEREL; }
  bool ac() const { return (this->value_[REGMODE] & FLAGMODEAC) == FLAGMODEAC; }
  bool dc() const { return (this->value_[REGMODE] & FLAGMODEDC) == FLAGMODEDC; }
  bool min_mode() const { return (this->value_[REGMINMAX] & FLAGMIN) == FLAGMIN; }
  bool max_mode() const { return (this->value_[REGMINMAX] & FLAGMAX) == FLAGMAX; }
  bool diode() const { return (this->value_[REGSCALE] & FLAGSCALEDIODE) == FLAGSCALEDIODE; }
  bool continuity() const { return (this->value_[REGSCALE] & FLAGSCALEBUZZ) == FLAGSCALEBUZZ; }

  const char *unit() const {
    switch (this->value_[REGUNIT]) {
      case FLAGUNITFAHR: return "°F";
      case FLAGUNITGRAD: return "°C";
      case FLAGUNITNF: return "nF";
      case FLAGUNITHZ: return "Hz";
      case FLAGUNITHFE: return "hFE";
      case FLAGUNITOHM: return "Ω";
      case FLAGUNITAMP: return "A";
      case FLAGUNITVOLT: return "V";
      default: return "";
    }
  }

  const char *scale() const {
    if ((this->value_[REGSCALE] & FLAGSCALEDUTY) == FLAGSCALEDUTY) return "%";
    if ((this->value_[REGSCALE] & FLAGSCALEMEGA) == FLAGSCALEMEGA) return "M";
    if ((this->value_[REGSCALE] & FLAGSCALEKILO) == FLAGSCALEKILO) return "k";
    if ((this->value_[REGSCALE] & FLAGSCALEMILLI) == FLAGSCALEMILLI) return "m";
    if ((this->value_[REGSCALE] & FLAGSCALEMICRO) == FLAGSCALEMICRO) return "µ";
    return "";
  }

  std::string mode_text() const {
    std::string out;
    if (this->dc()) out += "DC ";
    if (this->ac()) out += "AC ";
    if (this->auto_range()) out += "AUTO ";
    if (this->hold()) out += "HOLD ";
    if (this->relative()) out += "REL ";
    if (this->min_mode()) out += "MIN ";
    if (this->max_mode()) out += "MAX ";
    if (this->diode()) out += "DIODE ";
    if (this->continuity()) out += "CONT ";
    if (!out.empty()) out.pop_back();
    return out;
  }

  std::string reading_text() const {
    if (!this->connected) return "Disconnected";
    if (!this->has_reading) return "Waiting for data";
    if (this->overload) return "OL " + std::string(this->scale()) + this->unit();
    char buf[48];
    snprintf(buf, sizeof(buf), "%s%.4g %s%s", this->negative() ? "-" : "", std::fabs(this->display_value), this->scale(), this->unit());
    return std::string(buf);
  }

  enum Kind { KIND_OTHER, KIND_VOLTAGE, KIND_CURRENT, KIND_RESISTANCE, KIND_FREQUENCY, KIND_CAPACITANCE, KIND_TEMP_C, KIND_TEMP_F, KIND_DUTY };
  Kind kind() const {
    if ((this->value_[REGSCALE] & FLAGSCALEDUTY) == FLAGSCALEDUTY) return KIND_DUTY;
    switch (this->value_[REGUNIT]) {
      case FLAGUNITVOLT: return KIND_VOLTAGE;
      case FLAGUNITAMP: return KIND_CURRENT;
      case FLAGUNITOHM: return KIND_RESISTANCE;
      case FLAGUNITHZ: return KIND_FREQUENCY;
      case FLAGUNITNF: return KIND_CAPACITANCE;
      case FLAGUNITGRAD: return KIND_TEMP_C;
      case FLAGUNITFAHR: return KIND_TEMP_F;
      default: return KIND_OTHER;
    }
  }

  const char *selected_button_name() const {
    static const char *const names[] = {"SELECT", "RANGE", "HLD/LIG", "REL/BT", "HZ/DUTY", "MAX/MIN"};
    uint8_t index = this->selected_button;
    if (index < 1) index = 1;
    if (index > 6) index = 6;
    return names[index - 1];
  }

  void previous_button() {
    if (this->selected_button > 1) this->selected_button--;
  }
  void next_button() {
    if (this->selected_button < 6) this->selected_button++;
  }

  void render(Display &it, esphome::display::BaseFont *font) {
    const Color bg(0, 0, 0);
    const Color fg(210, 210, 210);
    // Chosen to map to a neutral dark gray in the RGB332 8-bit display palette.
    const Color inactive(80, 80, 80);
    const Color yellow(255, 220, 0);
    const Color blue(0, 80, 255);
    const Color cyan(0, 255, 255);
    const Color magenta(255, 0, 255);
    const Color red(255, 0, 0);
    const Color green(0, 220, 0);
    const Color orange(255, 165, 0);

    it.fill(bg);
    bool status_active = this->connected && this->has_reading;
    this->draw_icon_(it, 12, 8, 16, 16, ACCU_BMP, status_active ? (this->low_battery ? red : green) : inactive);
    this->draw_icon_(it, 46, 8, 16, 16, BLE_BMP, this->connected ? blue : inactive);
    this->label_(it, font, 86, 8, "AUTO", status_active && this->auto_range() ? fg : inactive);
    this->label_(it, font, 138, 8, "MAX", status_active && this->max_mode() ? red : inactive);
    this->label_(it, font, 178, 8, "MIN", status_active && this->min_mode() ? green : inactive);
    this->label_(it, font, 218, 8, "HOLD", status_active && this->hold() ? blue : inactive);
    this->label_(it, font, 270, 8, "REL", status_active && this->relative() ? Color(128, 128, 0) : inactive);

    this->label_(it, font, 8, 66, "DC", status_active && this->dc() ? cyan : inactive);
    this->label_(it, font, 8, 102, "AC", status_active && this->ac() ? magenta : inactive);

    if (!this->connected) {
      this->draw_digits_(it, "----", false, inactive);
      it.print(160, 148, font, inactive, esphome::display::TextAlign::CENTER, "scan/connect");
    } else if (!this->has_reading) {
      this->draw_digits_(it, "8888", false, inactive);
      it.print(160, 148, font, inactive, esphome::display::TextAlign::CENTER, "waiting");
    } else if (this->overload) {
      this->draw_digits_(it, " OL ", false, fg);
    } else {
      char d[5];
      d[0] = this->digit_char_(REGDIG1);
      d[1] = this->digit_char_(REGDIG2);
      d[2] = this->digit_char_(REGDIG3);
      d[3] = this->digit_char_(REGDIG4);
      d[4] = 0;
      this->draw_digits_(it, d, this->negative(), fg);
      this->draw_decimal_points_(it, fg);
    }

    if (status_active) {
      std::string unit_line = std::string(this->scale()) + this->unit();
      it.print(270, 140, font, yellow, esphome::display::TextAlign::CENTER, unit_line.c_str());
    }

    bool bargraph_active = status_active && !this->overload;
    this->draw_bargraph_(it, bargraph_active ? this->digits_from_buffer_() : 0, bargraph_active);
    this->draw_icon_(it, 300, 148, 16, 16, DIODE_BMP, status_active && this->diode() ? magenta : inactive);
    this->draw_icon_(it, 300, 174, 16, 16, BUZZ_BMP, status_active && this->continuity() ? orange : inactive);

    it.filled_rectangle(34, 212, 40, 24, this->write_available ? fg : inactive);
    it.filled_rectangle(108, 212, 100, 24, this->write_available ? fg : inactive);
    it.filled_rectangle(242, 212, 40, 24, this->write_available ? fg : inactive);
    it.print(54, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, "<");
    it.print(158, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, this->selected_button_name());
    it.print(262, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, ">");
  }

 private:
  uint8_t raw_[14]{};
  uint8_t value_[14]{};
  float display_value{NAN};
  float base_value{NAN};
  static constexpr uint8_t OVERLOAD_FRAME[5] = {0x2B, 0x3F, 0x30, 0x3A, 0x3F};

  uint16_t digits_from_buffer_() const {
    uint16_t out = 0;
    if (this->value_[REGDIG1] >= '0' && this->value_[REGDIG1] <= '9') out += (this->value_[REGDIG1] - '0') * 1000;
    if (this->value_[REGDIG2] >= '0' && this->value_[REGDIG2] <= '9') out += (this->value_[REGDIG2] - '0') * 100;
    if (this->value_[REGDIG3] >= '0' && this->value_[REGDIG3] <= '9') out += (this->value_[REGDIG3] - '0') * 10;
    if (this->value_[REGDIG4] >= '0' && this->value_[REGDIG4] <= '9') out += (this->value_[REGDIG4] - '0');
    return out;
  }

  float calc_display_value_() const {
    if (this->overload) return NAN;

    if (this->is_plus) {
      uint16_t pair1 = static_cast<uint16_t>(this->raw_[0]) | (static_cast<uint16_t>(this->raw_[1]) << 8);
      uint8_t decimal = pair1 & 0x07;
      if (decimal >= 7) return NAN;

      uint16_t pair3 = static_cast<uint16_t>(this->raw_[4]) | (static_cast<uint16_t>(this->raw_[5]) << 8);
      bool negative = pair3 >= 0x7FFF;
      uint16_t digits = negative ? (pair3 & 0x7FFF) : pair3;
      float v = static_cast<float>(digits) / std::pow(10.0f, decimal);
      return negative ? -v : v;
    }

    uint8_t decimal = 0;
    switch (this->value_[REGPOINT] & 0x07) {
      case 0b001: decimal = 1; break;
      case 0b010: decimal = 2; break;
      case 0b100: decimal = 3; break;
      default: break;
    }
    float v = static_cast<float>(this->digits_from_buffer_()) / std::pow(10.0f, decimal);
    return this->negative() ? -v : v;
  }

  float calc_base_value_() const {
    if (std::isnan(this->display_value)) return NAN;
    if (this->value_[REGUNIT] == FLAGUNITNF) return this->display_value * 1e-9f;
    if ((this->value_[REGSCALE] & FLAGSCALEMEGA) == FLAGSCALEMEGA) return this->display_value * 1e6f;
    if ((this->value_[REGSCALE] & FLAGSCALEKILO) == FLAGSCALEKILO) return this->display_value * 1e3f;
    if ((this->value_[REGSCALE] & FLAGSCALEMILLI) == FLAGSCALEMILLI) return this->display_value * 1e-3f;
    if ((this->value_[REGSCALE] & FLAGSCALEMICRO) == FLAGSCALEMICRO) return this->display_value * 1e-6f;
    return this->display_value;
  }

  void parse_plus_() {
    memset(this->value_, 0, sizeof(this->value_));
    this->value_[5] = 0x20;
    this->value_[12] = 0x0D;
    this->value_[13] = 0x0A;

    uint16_t pair1 = static_cast<uint16_t>(this->raw_[0]) | (static_cast<uint16_t>(this->raw_[1]) << 8);
    uint8_t function = (pair1 >> 6) & 0x0F;
    uint8_t scale = (pair1 >> 3) & 0x07;
    uint8_t decimal = pair1 & 0x07;

    switch (decimal) {
      case 0: this->value_[REGPOINT] = FLAGPOINT0; break;
      case 1: this->value_[REGPOINT] = FLAGPOINT3; break;
      case 2: this->value_[REGPOINT] = FLAGPOINT2; break;
      case 3: this->value_[REGPOINT] = FLAGPOINT1; break;
      default: break;
    }

    switch (function) {
      case 0: this->value_[REGUNIT] |= FLAGUNITVOLT; this->value_[REGMODE] |= FLAGMODEDC; break;
      case 1: this->value_[REGUNIT] |= FLAGUNITVOLT; this->value_[REGMODE] |= FLAGMODEAC; break;
      case 2: this->value_[REGUNIT] |= FLAGUNITAMP; this->value_[REGMODE] |= FLAGMODEDC; break;
      case 3: this->value_[REGUNIT] |= FLAGUNITAMP; this->value_[REGMODE] |= FLAGMODEAC; break;
      case 4: this->value_[REGUNIT] |= FLAGUNITOHM; break;
      case 5: this->value_[REGUNIT] |= FLAGUNITNF; break;
      case 6: this->value_[REGUNIT] |= FLAGUNITHZ; break;
      case 7: this->value_[REGSCALE] |= FLAGSCALEDUTY; break;
      case 8: this->value_[REGUNIT] |= FLAGUNITGRAD; break;
      case 9: this->value_[REGUNIT] |= FLAGUNITFAHR; break;
      case 10: this->value_[REGSCALE] |= FLAGSCALEDIODE; break;
      case 11: this->value_[REGSCALE] |= FLAGSCALEBUZZ; break;
      case 12: this->value_[REGUNIT] |= FLAGUNITHFE; break;
      default: break;
    }

    switch (scale) {
      case 2: this->value_[REGSCALE] |= FLAGSCALEMICRO; break;
      case 3: this->value_[REGSCALE] |= FLAGSCALEMILLI; break;
      case 5: this->value_[REGSCALE] |= FLAGSCALEKILO; break;
      case 6: this->value_[REGSCALE] |= FLAGSCALEMEGA; break;
      default: break;
    }

    uint16_t pair2 = static_cast<uint16_t>(this->raw_[2]) | (static_cast<uint16_t>(this->raw_[3]) << 8);
    if (pair2 & (1 << 0)) this->value_[REGMODE] |= FLAGMODEHOLD;
    if (pair2 & (1 << 1)) this->value_[REGMODE] |= FLAGMODEREL;
    if (pair2 & (1 << 2)) this->value_[REGMODE] |= FLAGMODEAUTO;
    this->low_battery = (pair2 & (1 << 3)) != 0;
    if (pair2 & (1 << 4)) this->value_[REGMINMAX] |= FLAGMIN;
    if (pair2 & (1 << 5)) this->value_[REGMINMAX] |= FLAGMAX;

    uint16_t pair3 = static_cast<uint16_t>(this->raw_[4]) | (static_cast<uint16_t>(this->raw_[5]) << 8);
    if (decimal < 7) {
      uint16_t digits = pair3;
      if (pair3 < 0x7FFF) {
        this->value_[REGPLUSMINUS] = FLAGPLUS;
      } else {
        this->value_[REGPLUSMINUS] = FLAGMINUS;
        digits = pair3 & 0x7FFF;
      }
      this->value_[REGDIG1] = '0' + ((digits / 1000) % 10);
      this->value_[REGDIG2] = '0' + ((digits / 100) % 10);
      this->value_[REGDIG3] = '0' + ((digits / 10) % 10);
      this->value_[REGDIG4] = '0' + (digits % 10);
    } else {
      memcpy(this->value_, OVERLOAD_FRAME, sizeof(OVERLOAD_FRAME));
    }
  }

  char digit_char_(uint8_t reg) const {
    uint8_t c = this->value_[reg];
    return (c >= '0' && c <= '9') ? static_cast<char>(c) : ' ';
  }

  void label_(Display &it, esphome::display::BaseFont *font, int x, int y, const char *text, Color color) {
    it.print(x, y, font, color, esphome::display::TextAlign::TOP_LEFT, text);
  }

  void draw_digits_(Display &it, const char *text, bool negative, Color color) {
    if (negative) this->draw_segment_(it, 8, 88, 26, 9, true, color);
    constexpr int digit_x = 40;
    constexpr int digit_y = 35;
    constexpr int digit_w = 50;
    constexpr int digit_h = 88;
    constexpr int digit_distance = 64;
    for (int i = 0; i < 4; i++) {
      this->draw_seven_segment_(it, digit_x + i * digit_distance, digit_y, digit_w, digit_h, text[i], color);
    }
  }

  void draw_decimal_points_(Display &it, Color color) {
    uint8_t p = this->value_[REGPOINT];
    if ((p & FLAGPOINT1) == FLAGPOINT1) it.filled_rectangle(95, 117, 8, 10, color);
    if ((p & FLAGPOINT2) == FLAGPOINT2) it.filled_rectangle(159, 117, 8, 10, color);
    if ((p & FLAGPOINT3) == FLAGPOINT3) it.filled_rectangle(223, 117, 8, 10, color);
  }

  void draw_segment_(Display &it, int x, int y, int w, int h, bool horizontal, Color color) {
    if (horizontal) {
      int cap = h / 2;
      it.filled_rectangle(x + cap, y, w - 2 * cap, h +1, color);
      it.filled_triangle(x, y + cap, x + cap, y, x + cap, y + h, color);
      it.filled_triangle(x + w, y + cap, x + w - cap, y, x + w - cap, y + h, color);
    } else {
      int cap = w / 2;
      it.filled_rectangle(x, y + cap, w +1, h - 2 * cap, color);
      it.filled_triangle(x + cap, y, x, y + cap, x + w, y + cap, color);
      it.filled_triangle(x + cap, y + h, x, y + h - cap, x + w, y + h - cap, color);
    }
  }

  void draw_icon_(Display &it, int x, int y, int w, int h, const uint8_t *data, Color color) {
    for (int row = 0; row < h; row++) {
      for (int col = 0; col < w; col++) {
        uint8_t byte = data[row * ((w + 7) / 8) + (col / 8)];
        if ((byte & (0x80 >> (col % 8))) != 0) {
          it.draw_pixel_at(x + col, y + row, color);
        }
      }
    }
  }

  void draw_seven_segment_(Display &it, int x, int y, int w, int h, char ch, Color color) {
    bool a=false,b=false,c=false,d=false,e=false,f=false,g=false;
    switch (ch) {
      case '0': a=b=c=d=e=f=true; break;
      case '1': b=c=true; break;
      case '2': a=b=d=e=g=true; break;
      case '3': a=b=c=d=g=true; break;
      case '4': b=c=f=g=true; break;
      case '5': a=c=d=f=g=true; break;
      case '6': a=c=d=e=f=g=true; break;
      case '7': a=b=c=true; break;
      case '8': a=b=c=d=e=f=g=true; break;
      case '9': a=b=c=d=f=g=true; break;
      case 'O': a=b=c=d=e=f=true; break;
      case 'L': d=e=f=true; break;
      case '-': g=true; break;
      default: break;
    }
    int t = 10;
    int half = h / 2;
    int gap = 1;

    int top_y = y;
    int mid_y = y + half - t / 2;
    int bot_y = y + h - t;

    int upper_v_y = top_y + t + gap;
    int upper_v_h = mid_y - gap - upper_v_y;
    int lower_v_y = mid_y + t + gap;
    int lower_v_h = bot_y - gap - lower_v_y;

    if (a) this->draw_segment_(it, x + t / 2, top_y, w - t, t, true, color);
    if (b && upper_v_h > 0) this->draw_segment_(it, x + w - t, upper_v_y, t, upper_v_h, false, color);
    if (c && lower_v_h > 0) this->draw_segment_(it, x + w - t, lower_v_y, t, lower_v_h, false, color);
    if (d) this->draw_segment_(it, x + t / 2, bot_y, w - t, t, true, color);
    if (e && lower_v_h > 0) this->draw_segment_(it, x, lower_v_y, t, lower_v_h, false, color);
    if (f && upper_v_h > 0) this->draw_segment_(it, x, upper_v_y, t, upper_v_h, false, color);
    if (g) this->draw_segment_(it, x + t / 2, mid_y, w - t, t, true, color);
  }

  void draw_bargraph_(Display &it, uint16_t digits, bool active) {
    const Color fg(255, 255, 255);
    const Color inactive(80, 80, 80);
    uint16_t mapped = active ? static_cast<uint16_t>(digits * 240 / 6000) : 0;
    if (mapped > 240) mapped = 240;
    for (uint16_t i = 0; i <= 240; i += 4) {
      Color col = (active && i <= mapped) ? fg : inactive;
      int h = (i % 40 == 0) ? 20 : ((i % 20 == 0) ? 15 : 10);
      it.vertical_line(40 + i, 185 - h, h, col);
    }
    it.horizontal_line(35, 185, 250, inactive);
  }
};

}  // namespace owon_b35t

static owon_b35t::Meter owon_meter;
