// SMASHR: a big red Wi-Fi button that buys one preset Solana coin per press.
// The device never holds a wallet key. It signs press events with a device secret (HMAC-SHA256);
// the SMASHR server checks limits and executes the Jupiter swap from your capped button wallet.
//
// Board: Seeed Studio XIAO ESP32S3, Arduino core for ESP32 3.x
// Libraries: Adafruit NeoPixel, ArduinoJson 7
// Wiring and BOM: docs/hardware/WIRING.md and docs/hardware/BOM.md

#include <WiFi.h>
#include "config.h"
#include "led.h"
#include "api.h"
#include "store.h"
#include "portal.h"
#include "ota.h"

static String deviceId, deviceSecret;
static uint32_t pressSeq = 0;
static uint32_t lastBeat = 0;

// ---------- debounced button ----------
struct Debounce {
  bool stable = false;      // true = pressed
  bool lastRead = false;
  uint32_t changedAt = 0;
  uint32_t pressedAt = 0;
  // returns +1 on press edge, -1 on release edge, 0 otherwise
  int update() {
    bool r = digitalRead(PIN_BUTTON) == LOW;
    uint32_t now = millis();
    if (r != lastRead) { lastRead = r; changedAt = now; }
    if (now - changedAt >= DEBOUNCE_MS && r != stable) {
      stable = r;
      if (r) { pressedAt = now; return +1; }
      return -1;
    }
    return 0;
  }
  uint32_t heldFor() const { return stable ? millis() - pressedAt : 0; }
  // after a blocking request: start counting the hold from now, so a slow swap never looks like a 10 s hold
  void resync() {
    bool r = digitalRead(PIN_BUTTON) == LOW;
    stable = lastRead = r; changedAt = pressedAt = millis();
  }
} btn;

static bool connectWifi() {
  String ssid, pass;
  if (!store::loadWifi(ssid, pass)) return false;
  WiFi.mode(WIFI_STA);
  WiFi.setAutoReconnect(true);
  WiFi.begin(ssid.c_str(), pass.c_str());
  led::set(LED_CONNECTING);
  uint32_t t0 = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - t0 < WIFI_CONNECT_MS) delay(50);
  return WiFi.status() == WL_CONNECTED;
}

static void factoryReset() {
  led::set(LED_RESET_WARN);
  delay(1200);
  store::wipe();
  WiFi.disconnect(true, true);
  ESP.restart();
}

static void doPress() {
  if (WiFi.status() != WL_CONNECTED) { led::settle(LED_ERROR, 1800, LED_OFFLINE); return; }
  led::set(LED_SENDING);
  PressResult r = api::press(++pressSeq);
  Serial.printf("[press] #%u -> %s %s\n", pressSeq, r.code.c_str(), r.reason.c_str());
  if (r.code == "ok") led::settle(LED_OK, 2600, LED_IDLE);
  else if (r.code == "cooldown") led::settle(LED_COOLDOWN, min<uint32_t>(r.retryIn, 30) * 1000 + 200, LED_IDLE);
  else if (r.code == "insufficient") led::settle(LED_INSUFFICIENT, 2700, LED_IDLE);
  else if (r.reason == "unpaired") { store::saveIdentity("", ""); led::settle(LED_ERROR, 3000, LED_PORTAL); delay(3000); ESP.restart(); }
  else led::settle(LED_ERROR, 2000, LED_IDLE);
}

static void heartbeat() {
  String url, version;
  if (!api::ping(url, version)) return;
  if (url.length() && version != FW_VERSION) {
    Serial.printf("[ota] %s -> %s\n", FW_VERSION, version.c_str());
    ota::pull(url);
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(PIN_BUTTON, INPUT_PULLUP);
  led::begin();
  led::set(LED_BOOT);
  delay(400);

  // Hold the button while plugging in to enter setup.
  uint32_t t0 = millis();
  bool held = digitalRead(PIN_BUTTON) == LOW;
  while (held && millis() - t0 < SETUP_HOLD_MS) { held = digitalRead(PIN_BUTTON) == LOW; delay(10); }

  bool paired = store::loadIdentity(deviceId, deviceSecret);
  if (held || !paired || !connectWifi()) {
    if (!held && paired) {
      // Known network is down: keep retrying in the background instead of wiping anything.
      led::set(LED_OFFLINE);
    } else {
      portal::run(); // reboots when done
    }
  }

  api::setIdentity(deviceId, deviceSecret);
  configTime(0, 0, "pool.ntp.org", "time.google.com");
  ota::beginLan(String("sendit-") + deviceId);
  if (WiFi.status() == WL_CONNECTED) { led::settle(LED_OK, 900, LED_IDLE); heartbeat(); }
  lastBeat = millis();
}

void loop() {
  int edge = btn.update();
  if (edge == +1) {
    // feedback first, network second: the click and the comet start before HTTPS
    doPress();
    btn.resync();
  } else if (edge == -1 && led::current() == LED_RESET_WARN) {
    led::set(LED_IDLE); // let go in time: nothing is wiped
  }
  if (btn.heldFor() > RESET_HOLD_MS) factoryReset();
  else if (btn.heldFor() > RESET_HOLD_MS - 3000 && led::current() != LED_RESET_WARN) led::set(LED_RESET_WARN);

  if (WiFi.status() != WL_CONNECTED) {
    if (led::current() == LED_IDLE) led::set(LED_OFFLINE);
  } else if (led::current() == LED_OFFLINE) {
    led::set(LED_IDLE);
  }

  if (millis() - lastBeat > HEARTBEAT_MS) { lastBeat = millis(); heartbeat(); }
  ota::loopLan();
  delay(2);
}
