// 20260401 Codex

#include <Arduino.h>
#include <esp_system.h>
#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include <U8g2lib.h>
#include <driver/gpio.h>

#include "tbeam_supreme_adapter.h"

#ifndef NODE_LABEL
#define NODE_LABEL "NODE"
#endif

#ifndef OLED_SDA
#define OLED_SDA 17
#endif

#ifndef OLED_SCL
#define OLED_SCL 18
#endif

static const uint32_t kSerialDelayMs = 1500;
static const uint32_t kPollIntervalMs = 200;
static const uint32_t kStartupQuietMs = 5000;
static const uint32_t kStartupReplayWindowMs = 20000;
static const uint32_t kStartupReplayPeriodMs = 2000;

static const uint32_t kFreqs[] = {
  400000,
  1000000,
  4000000,
  10000000
};

struct PinSnapshot {
  int cs = -1;
  int sck = -1;
  int miso = -1;
  int mosi = -1;
};

struct ProbeSummary {
  uint8_t ffCount = 0;
  uint8_t zeroCount = 0;
  uint8_t otherCount = 0;
  uint8_t firstBytes[8] = {0};
};

enum class DebugState : uint8_t {
  PMU_FAIL = 0,
  RAIL_OFF,
  BUS_FLOAT,
  BUS_LOW,
  BUS_CHATTER,
  SD_BEGIN_FAIL,
  CARD_NONE,
  FS_FAIL,
  MOUNT_OK
};

struct DebugSnapshot {
  DebugState state = DebugState::PMU_FAIL;
  bool pmuOk = false;
  bool railOn = false;
  float vbusV = -1.0f;
  float battV = -1.0f;
  PinSnapshot pins{};
  ProbeSummary probeH{};
  ProbeSummary probeF{};
  const char* mountBus = "none";
  uint32_t mountHz = 0;
  uint8_t cardType = CARD_NONE;
  uint64_t cardSizeMB = 0;
  bool rootOk = false;
};

static XPowersLibInterface* g_pmu = nullptr;
static U8G2_SH1106_128X64_NONAME_F_HW_I2C g_oled(U8G2_R0, U8X8_PIN_NONE);
static SPIClass g_spiH(HSPI);
static SPIClass g_spiF(FSPI);
static uint32_t g_sampleCount = 0;
static uint32_t g_bootMs = 0;
static uint32_t g_lastStartupReplayMs = 0;

static const char* resetReasonToString(esp_reset_reason_t reason) {
  switch (reason) {
    case ESP_RST_UNKNOWN:   return "UNKNOWN";
    case ESP_RST_POWERON:   return "POWERON";
    case ESP_RST_EXT:       return "EXT";
    case ESP_RST_SW:        return "SW";
    case ESP_RST_PANIC:     return "PANIC";
    case ESP_RST_INT_WDT:   return "INT_WDT";
    case ESP_RST_TASK_WDT:  return "TASK_WDT";
    case ESP_RST_WDT:       return "WDT";
    case ESP_RST_DEEPSLEEP: return "DEEPSLEEP";
    case ESP_RST_BROWNOUT:  return "BROWNOUT";
    case ESP_RST_SDIO:      return "SDIO";
    default:                return "OTHER";
  }
}

static const char* flashModeToString(FlashMode_t mode) {
  switch (mode) {
    case FM_QIO: return "QIO";
    case FM_QOUT: return "QOUT";
    case FM_DIO: return "DIO";
    case FM_DOUT: return "DOUT";
    case FM_FAST_READ: return "FAST";
    case FM_SLOW_READ: return "SLOW";
    default: return "UNKNOWN";
  }
}

static void printBoardIdentity() {
  uint64_t mac = ESP.getEfuseMac();
  uint32_t chipId = 0;
  for (int i = 0; i < 17; i += 8) {
    chipId |= ((mac >> (40 - i)) & 0xFF) << i;
  }

  Serial.println("BOARD IDENTITY");
  Serial.printf("chip_model=%s\r\n", ESP.getChipModel());
  Serial.printf("chip_revision=%u\r\n", (unsigned)ESP.getChipRevision());
  Serial.printf("chip_cores=%u\r\n", (unsigned)ESP.getChipCores());
  Serial.printf("sdk_version=%s\r\n", ESP.getSdkVersion());
  Serial.printf("cpu_mhz=%u\r\n", (unsigned)ESP.getCpuFreqMHz());
  Serial.printf("flash_size=%u\r\n", (unsigned)ESP.getFlashChipSize());
  Serial.printf("flash_speed=%u\r\n", (unsigned)ESP.getFlashChipSpeed());
  Serial.printf("flash_mode=%s\r\n", flashModeToString(ESP.getFlashChipMode()));
  Serial.printf("efuse_mac=%012llX\r\n", mac);
  Serial.printf("chip_id=%06lX\r\n", (unsigned long)chipId);
  Serial.printf("reset_reason=%s\r\n", resetReasonToString(esp_reset_reason()));
  Serial.printf("arduino_board=%s\r\n", ARDUINO_BOARD);
}

static void printStartupBanner() {
  Serial.println();
  Serial.println("STARTUP REPLAY");
  Serial.printf("uptime_ms=%lu\r\n", (unsigned long)(millis() - g_bootMs));
  printBoardIdentity();
}

static void forceSpiDeselected() {
  pinMode(tbeam_supreme::sdCs(), OUTPUT);
  digitalWrite(tbeam_supreme::sdCs(), HIGH);
  pinMode(tbeam_supreme::imuCs(), OUTPUT);
  digitalWrite(tbeam_supreme::imuCs(), HIGH);
}

static PinSnapshot readPins() {
  PinSnapshot s;
  s.cs = gpio_get_level((gpio_num_t)tbeam_supreme::sdCs());
  s.sck = gpio_get_level((gpio_num_t)tbeam_supreme::sdSck());
  s.miso = gpio_get_level((gpio_num_t)tbeam_supreme::sdMiso());
  s.mosi = gpio_get_level((gpio_num_t)tbeam_supreme::sdMosi());
  return s;
}

static void readPmu(DebugSnapshot& snap) {
  snap.pmuOk = (g_pmu != nullptr);
  if (!g_pmu) {
    return;
  }

  snap.railOn = g_pmu->isPowerChannelEnable(XPOWERS_BLDO1);
  snap.vbusV = g_pmu->getVbusVoltage() / 1000.0f;
  snap.battV = g_pmu->getBattVoltage() / 1000.0f;
}

static ProbeSummary runIdleProbe(SPIClass& bus) {
  ProbeSummary out;

  SD.end();
  bus.end();
  delay(2);
  forceSpiDeselected();

  bus.begin(
    tbeam_supreme::sdSck(),
    tbeam_supreme::sdMiso(),
    tbeam_supreme::sdMosi(),
    tbeam_supreme::sdCs()
  );

  digitalWrite(tbeam_supreme::sdCs(), HIGH);
  delay(1);
  for (int i = 0; i < 8; ++i) {
    uint8_t b = bus.transfer(0xFF);
    out.firstBytes[i] = b;
    if (b == 0xFF) out.ffCount++;
    else if (b == 0x00) out.zeroCount++;
    else out.otherCount++;
  }

  return out;
}

static const char* cardTypeToString(uint8_t type) {
  switch (type) {
    case CARD_MMC: return "MMC";
    case CARD_SD: return "SDSC";
    case CARD_SDHC: return "SDHC";
    default: return "NONE";
  }
}

static bool tryMount(SPIClass& bus,
                     const char* busName,
                     uint32_t hz,
                     DebugSnapshot& snap) {
  SD.end();
  bus.end();
  delay(2);
  forceSpiDeselected();

  bus.begin(
    tbeam_supreme::sdSck(),
    tbeam_supreme::sdMiso(),
    tbeam_supreme::sdMosi(),
    tbeam_supreme::sdCs()
  );

  digitalWrite(tbeam_supreme::sdCs(), HIGH);
  delay(1);
  for (int i = 0; i < 10; ++i) {
    bus.transfer(0xFF);
  }

  if (!SD.begin(tbeam_supreme::sdCs(), bus, hz)) {
    snap.state = DebugState::SD_BEGIN_FAIL;
    return false;
  }

  snap.cardType = SD.cardType();
  snap.mountBus = busName;
  snap.mountHz = hz;
  if (snap.cardType == CARD_NONE) {
    SD.end();
    snap.state = DebugState::CARD_NONE;
    return false;
  }

  snap.cardSizeMB = SD.cardSize() / (1024ULL * 1024ULL);

  File root = SD.open("/", FILE_READ);
  snap.rootOk = (bool)root;
  if (root) {
    root.close();
  }
  SD.end();

  snap.state = snap.rootOk ? DebugState::MOUNT_OK : DebugState::FS_FAIL;
  return snap.rootOk;
}

static DebugState classifyProbe(const ProbeSummary& probe) {
  if (probe.ffCount == 8) return DebugState::BUS_FLOAT;
  if (probe.zeroCount == 8) return DebugState::BUS_LOW;
  return DebugState::BUS_CHATTER;
}

static DebugSnapshot captureSnapshot() {
  DebugSnapshot snap;
  readPmu(snap);
  snap.pins = readPins();

  if (!snap.pmuOk) {
    snap.state = DebugState::PMU_FAIL;
    return snap;
  }

  if (!snap.railOn) {
    snap.state = DebugState::RAIL_OFF;
    return snap;
  }

  snap.probeH = runIdleProbe(g_spiH);
  snap.probeF = runIdleProbe(g_spiF);
  snap.state = classifyProbe(snap.probeH);

  for (size_t i = 0; i < (sizeof(kFreqs) / sizeof(kFreqs[0])); ++i) {
    if (tryMount(g_spiH, "HSPI", kFreqs[i], snap)) {
      return snap;
    }
  }

  for (size_t i = 0; i < (sizeof(kFreqs) / sizeof(kFreqs[0])); ++i) {
    if (tryMount(g_spiF, "FSPI", kFreqs[i], snap)) {
      return snap;
    }
  }

  return snap;
}

static const char* stateToString(DebugState state) {
  switch (state) {
    case DebugState::PMU_FAIL: return "PMU_FAIL";
    case DebugState::RAIL_OFF: return "RAIL_OFF";
    case DebugState::BUS_FLOAT: return "NO_RESP";
    case DebugState::BUS_LOW: return "BUS_LOW";
    case DebugState::BUS_CHATTER: return "BUS_CHAT";
    case DebugState::SD_BEGIN_FAIL: return "BEGIN_FAIL";
    case DebugState::CARD_NONE: return "CARD_NONE";
    case DebugState::FS_FAIL: return "FS_FAIL";
    case DebugState::MOUNT_OK: return "MOUNT_OK";
    default: return "UNKNOWN";
  }
}

static void printSnapshot(const DebugSnapshot& snap) {
  Serial.printf(
    "sample=%lu state=%s rail=%s vbus=%.2f batt=%.2f pins=%d/%d/%d/%d "
    "probeH(ff=%u z=%u o=%u %02X %02X %02X %02X) "
    "probeF(ff=%u z=%u o=%u %02X %02X %02X %02X) "
    "mount=%s@%lu type=%s size=%lluMB root=%s\r\n",
    (unsigned long)g_sampleCount,
    stateToString(snap.state),
    snap.railOn ? "ON" : "OFF",
    snap.vbusV,
    snap.battV,
    snap.pins.cs,
    snap.pins.sck,
    snap.pins.miso,
    snap.pins.mosi,
    (unsigned)snap.probeH.ffCount,
    (unsigned)snap.probeH.zeroCount,
    (unsigned)snap.probeH.otherCount,
    snap.probeH.firstBytes[0],
    snap.probeH.firstBytes[1],
    snap.probeH.firstBytes[2],
    snap.probeH.firstBytes[3],
    (unsigned)snap.probeF.ffCount,
    (unsigned)snap.probeF.zeroCount,
    (unsigned)snap.probeF.otherCount,
    snap.probeF.firstBytes[0],
    snap.probeF.firstBytes[1],
    snap.probeF.firstBytes[2],
    snap.probeF.firstBytes[3],
    snap.mountBus,
    (unsigned long)snap.mountHz,
    cardTypeToString(snap.cardType),
    snap.cardSizeMB,
    snap.rootOk ? "OK" : "FAIL"
  );
}

static void showSnapshot(const DebugSnapshot& snap) {
  char line1[24];
  char line2[24];
  char line3[24];
  char line4[24];
  char line5[24];

  snprintf(line1, sizeof(line1), "%s TF HWDBG", NODE_LABEL);
  snprintf(line2, sizeof(line2), "STATE %s", stateToString(snap.state));
  snprintf(line3, sizeof(line3), "H %u/%u/%u F %u/%u/%u",
           (unsigned)snap.probeH.ffCount,
           (unsigned)snap.probeH.zeroCount,
           (unsigned)snap.probeH.otherCount,
           (unsigned)snap.probeF.ffCount,
           (unsigned)snap.probeF.zeroCount,
           (unsigned)snap.probeF.otherCount);
  snprintf(line4, sizeof(line4), "%s %luk %s",
           snap.mountBus,
           (unsigned long)(snap.mountHz / 1000UL),
           cardTypeToString(snap.cardType));
  snprintf(line5, sizeof(line5), "P %d%d%d%d R%s %lu",
           snap.pins.cs,
           snap.pins.sck,
           snap.pins.miso,
           snap.pins.mosi,
           snap.railOn ? "1" : "0",
           (unsigned long)g_sampleCount);

  g_oled.clearBuffer();
  g_oled.setFont(u8g2_font_5x8_tf);
  g_oled.drawUTF8(0, 12, line1);
  g_oled.drawUTF8(0, 24, line2);
  g_oled.drawUTF8(0, 36, line3);
  g_oled.drawUTF8(0, 48, line4);
  g_oled.drawUTF8(0, 60, line5);
  g_oled.sendBuffer();
}

void setup() {
  Serial.begin(115200);
  g_bootMs = millis();
  delay(kSerialDelayMs);

  Wire.begin(OLED_SDA, OLED_SCL);
  g_oled.begin();
  g_oled.clearDisplay();

  tbeam_supreme::initPmuForPeripherals(g_pmu, &Serial);
  forceSpiDeselected();

  Serial.println();
  Serial.println("constantTFCard hardware debug");
  Serial.printf("Node: %s\r\n", NODE_LABEL);
  Serial.printf("Poll interval: %lu ms\r\n", (unsigned long)kPollIntervalMs);
  Serial.println("States: PMU_FAIL RAIL_OFF NO_RESP BUS_LOW BUS_CHAT BEGIN_FAIL CARD_NONE FS_FAIL MOUNT_OK");
  Serial.printf("Startup quiet delay: %lu ms\r\n", (unsigned long)kStartupQuietMs);
  Serial.printf("Startup replay window: %lu ms\r\n", (unsigned long)kStartupReplayWindowMs);

  g_oled.clearBuffer();
  g_oled.setFont(u8g2_font_5x8_tf);
  g_oled.drawUTF8(0, 12, "TF HWDBG");
  g_oled.drawUTF8(0, 24, "startup hold");
  g_oled.drawUTF8(0, 36, "attach monitor");
  g_oled.drawUTF8(0, 48, "waiting...");
  g_oled.sendBuffer();

  delay(kStartupQuietMs);
  printStartupBanner();
}

void loop() {
  static uint32_t lastPollMs = 0;

  uint32_t now = millis();
  if (now - lastPollMs < kPollIntervalMs) {
    delay(5);
    return;
  }

  lastPollMs = now;
  g_sampleCount++;

  DebugSnapshot snap = captureSnapshot();

  if (now - g_bootMs <= kStartupReplayWindowMs &&
      now - g_lastStartupReplayMs >= kStartupReplayPeriodMs) {
    g_lastStartupReplayMs = now;
    printStartupBanner();
  }

  printSnapshot(snap);
  showSnapshot(snap);
}
