#include "led.h"
#include "config.h"
#include <Adafruit_NeoPixel.h>

// Choreography matches the web (lib/led.ts) so the virtual button and the hardware say the same things.
namespace {
  Adafruit_NeoPixel ring(LED_COUNT, PIN_LED_DATA, NEO_GRB + NEO_KHZ800);
  volatile LedState state = LED_BOOT;
  volatile LedState fallback = LED_IDLE;
  volatile uint32_t since = 0, holdUntil = 0, drainMs = 3000;
  portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

  struct RGB { uint8_t r, g, b; };
  const RGB RED{255, 40, 18}, GREEN{20, 230, 90}, AMBER{255, 140, 0}, WHITE{255, 255, 255}, BLUE{40, 90, 255};

  void px(int i, RGB c, float k) {
    if (k < 0) k = 0; if (k > 1) k = 1;
    ring.setPixelColor(i, ring.Color(c.r * k, c.g * k, c.b * k));
  }
  void fill(RGB c, float k) { for (int i = 0; i < LED_COUNT; i++) px(i, c, k); }

  void frame(LedState s, float t) {
    switch (s) {
      case LED_OFF: fill(RED, 0); break;
      case LED_BOOT: { int lit = min(LED_COUNT, (int)(t * 40)); for (int i = 0; i < LED_COUNT; i++) px(i, WHITE, i < lit ? 0.5f : 0); break; }
      case LED_PORTAL: { float b = 0.25f + 0.25f * sinf(t * 3.0f); for (int i = 0; i < LED_COUNT; i++) px(i, AMBER, (i % 2) ? b : 0.05f); break; }
      case LED_CONNECTING: case LED_SENDING: {
        float head = fmodf(t * (s == LED_SENDING ? 26.0f : 12.0f), LED_COUNT);
        for (int i = 0; i < LED_COUNT; i++) { float d = fmodf(head - i + LED_COUNT, LED_COUNT); px(i, s == LED_SENDING ? WHITE : BLUE, d < 7 ? powf(1 - d / 7, 1.6f) : 0.03f); }
        break;
      }
      case LED_IDLE: fill(RED, 0.16f + 0.10f * (0.5f + 0.5f * sinf(t * 1.6f))); break;
      case LED_OK: { int lit = min(LED_COUNT, (int)(t * 56)); float fade = t > 1.5f ? max(0.0f, 1 - (t - 1.5f) / 0.9f) : 1; for (int i = 0; i < LED_COUNT; i++) px(i, GREEN, i < lit ? fade : 0); break; }
      case LED_COOLDOWN: { float left = max(0.0f, 1 - t / (drainMs / 1000.0f)) * LED_COUNT; for (int i = 0; i < LED_COUNT; i++) px(i, AMBER, i < left ? 0.85f : 0.04f); break; }
      case LED_INSUFFICIENT: { float p = fmodf(t, 1.3f); bool on = p < 0.16f || (p > 0.32f && p < 0.48f); fill(RED, on ? 1 : 0.03f); break; }
      case LED_ERROR: fill(RED, t < 0.5f ? (((int)(t * 18)) % 2 ? 0.4f : 1) : 1); break;
      case LED_OFFLINE: fill(RED, 0.05f + 0.2f * (0.5f + 0.5f * sinf(t * 0.8f))); break;
      case LED_RESET_WARN: fill(((int)(t * 6)) % 2 ? RED : WHITE, 0.6f); break;
    }
  }

  void task(void*) {
    for (;;) {
      uint32_t now = millis();
      portENTER_CRITICAL(&mux);
      if (holdUntil && now >= holdUntil) { state = fallback; since = now; holdUntil = 0; }
      LedState s = state; uint32_t t0 = since;
      portEXIT_CRITICAL(&mux);
      frame(s, (now - t0) / 1000.0f);
      ring.show();
      vTaskDelay(pdMS_TO_TICKS(16));
    }
  }
}

namespace led {
  void begin() {
    ring.begin();
    ring.setBrightness(LED_MAX_BRIGHT);
    ring.clear(); ring.show();
    since = millis();
    xTaskCreatePinnedToCore(task, "led", 4096, nullptr, 2, nullptr, 0);
  }
  void set(LedState s, uint32_t durationMs) {
    portENTER_CRITICAL(&mux);
    state = s; since = millis(); holdUntil = 0;
    if (durationMs) drainMs = durationMs;
    portEXIT_CRITICAL(&mux);
  }
  void settle(LedState s, uint32_t holdMs, LedState after) {
    portENTER_CRITICAL(&mux);
    state = s; since = millis(); fallback = after; holdUntil = millis() + holdMs;
    if (s == LED_COOLDOWN) drainMs = holdMs;
    portEXIT_CRITICAL(&mux);
  }
  LedState current() { return state; }
}
