#include <benchmark/benchmark.h>

#include "esphome/components/light/light_output.h"
#include "esphome/components/light/light_state.h"

// Gamma 2.8 forward LUT generated by the light component's Python codegen
// (see tests/benchmarks/components/light/__init__.py which calls generate_gamma_table())
extern const uint16_t bench_gamma_2_8_fwd[256];

namespace esphome::benchmarks {

// Inner iteration count to amortize CodSpeed instrumentation overhead.
static constexpr int kInnerIterations = 2000;

// Minimal LightOutput for benchmarking — no real hardware interaction.
class BenchLightOutput : public light::LightOutput {
 public:
  light::LightTraits get_traits() override { return this->traits_; }
  void write_state(light::LightState * /*state*/) override {}

  light::LightTraits traits_;
};

// Test subclass to access protected configure_entity_() for benchmark setup.
class TestLightState : public light::LightState {
 public:
  using LightState::LightState;
  void configure(const char *name) { this->configure_entity_(name, 0x12345678, 0); }
};

// Helper to create a configured RGBWW light state for benchmarks.
// Note: setup() is not called (no preferences backend), so save_remote_values_()
// is effectively a no-op. This benchmarks the call/validation path, not persistence.
static void setup_rgbww_light(BenchLightOutput &output, TestLightState &light) {
  output.traits_.set_supported_color_modes({light::ColorMode::RGB_COLD_WARM_WHITE});
  output.traits_.set_min_mireds(153.0f);
  output.traits_.set_max_mireds(500.0f);
  light.configure("test_light");
  light.set_default_transition_length(0);
  light.set_gamma_correct(2.8f);
  light.set_gamma_table(bench_gamma_2_8_fwd);
  light.set_restore_mode(light::LIGHT_ALWAYS_OFF);
}

// --- LightCall::perform() with instant RGB color change (Home Assistant API path) ---
// Measures the full call path: validation, set_immediately_, publish, and save.
// HA sends color_mode explicitly since API 1.6.

static void LightCall_RGBInstant(benchmark::State &state) {
  BenchLightOutput output;
  TestLightState light(&output);
  setup_rgbww_light(output, light);

  // Turn on first so subsequent calls are color changes
  light.make_call().set_state(true).set_brightness(1.0f).set_color_brightness(1.0f).set_transition_length(0).perform();

  for (auto _ : state) {
    for (int i = 0; i < kInnerIterations; i++) {
      float v = static_cast<float>(i % 256) / 255.0f;
      light.make_call()
          .set_color_mode(light::ColorMode::RGB_COLD_WARM_WHITE)
          .set_red(v)
          .set_green(1.0f - v)
          .set_blue(v * 0.5f)
          .set_transition_length(0)
          .perform();
    }
    benchmark::DoNotOptimize(light.remote_values);
  }
  state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(LightCall_RGBInstant);

// --- LightCall::perform() turn on/off cycle (Home Assistant API path) ---
// HA sends color_mode explicitly since API 1.6, skipping compute_color_mode_().

static void LightCall_ToggleOnOff(benchmark::State &state) {
  BenchLightOutput output;
  TestLightState light(&output);
  setup_rgbww_light(output, light);

  for (auto _ : state) {
    for (int i = 0; i < kInnerIterations; i++) {
      light.make_call()
          .set_state(i % 2 == 0)
          .set_color_mode(light::ColorMode::RGB_COLD_WARM_WHITE)
          .set_transition_length(0)
          .perform();
    }
    benchmark::DoNotOptimize(light.remote_values);
  }
  state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(LightCall_ToggleOnOff);

// --- LightCall::perform() turn on/off via MQTT ---
// MQTT never sends color_mode, so compute_color_mode_() runs every call.

static void LightCall_ToggleOnOff_MQTT(benchmark::State &state) {
  BenchLightOutput output;
  TestLightState light(&output);
  setup_rgbww_light(output, light);

  for (auto _ : state) {
    for (int i = 0; i < kInnerIterations; i++) {
      light.make_call().set_state(i % 2 == 0).set_transition_length(0).perform();
    }
    benchmark::DoNotOptimize(light.remote_values);
  }
  state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(LightCall_ToggleOnOff_MQTT);

// --- LightCall::perform() with color temperature via MQTT ---
// Exercises the transform_parameters_() path that converts color_temperature
// to cold/warm white fractions. MQTT never sends color_mode, so this also
// hits compute_color_mode_() every call. Modern HA avoids this path entirely
// by converting color temp to CW/WW client-side.

static void LightCall_ColorTemperature_MQTT(benchmark::State &state) {
  BenchLightOutput output;
  TestLightState light(&output);
  setup_rgbww_light(output, light);

  light.make_call().set_state(true).set_brightness(1.0f).set_transition_length(0).perform();

  for (auto _ : state) {
    for (int i = 0; i < kInnerIterations; i++) {
      // Sweep through color temperature range
      float ct = 153.0f + static_cast<float>(i % 348);
      light.make_call().set_color_temperature(ct).set_transition_length(0).perform();
    }
    benchmark::DoNotOptimize(light.remote_values);
  }
  state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(LightCall_ColorTemperature_MQTT);

// --- LightCall::perform() with 1s transition (Home Assistant API path) ---
// Exercises start_transition_() which allocates a LightTransformer.
// This is the default HA path when transition_length > 0.

static void LightCall_Transition(benchmark::State &state) {
  BenchLightOutput output;
  TestLightState light(&output);
  setup_rgbww_light(output, light);

  light.make_call().set_state(true).set_brightness(1.0f).set_transition_length(0).perform();

  for (auto _ : state) {
    for (int i = 0; i < kInnerIterations; i++) {
      float v = static_cast<float>(i % 256) / 255.0f;
      light.make_call()
          .set_color_mode(light::ColorMode::RGB_COLD_WARM_WHITE)
          .set_red(v)
          .set_green(1.0f - v)
          .set_blue(v * 0.5f)
          .set_transition_length(1000)
          .perform();
    }
    benchmark::DoNotOptimize(light.remote_values);
  }
  state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(LightCall_Transition);

// --- LightCall::perform() with cold/warm white (Home Assistant API path) ---
// Mirrors what modern HA sends: explicit color_mode with direct cold_white
// and warm_white values. HA converts color temp to CW/WW client-side for
// CWWW lights (API >= 1.6), so this is the primary HA path.

static void LightCall_ColdWarmWhite(benchmark::State &state) {
  BenchLightOutput output;
  TestLightState light(&output);
  setup_rgbww_light(output, light);

  light.make_call().set_state(true).set_brightness(1.0f).set_transition_length(0).perform();

  for (auto _ : state) {
    for (int i = 0; i < kInnerIterations; i++) {
      float frac = static_cast<float>(i % 256) / 255.0f;
      light.make_call()
          .set_color_mode(light::ColorMode::RGB_COLD_WARM_WHITE)
          .set_cold_white(1.0f - frac)
          .set_warm_white(frac)
          .set_transition_length(0)
          .perform();
    }
    benchmark::DoNotOptimize(light.remote_values);
  }
  state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(LightCall_ColdWarmWhite);

// --- LightState::publish_state() with a remote values listener ---
// Measures listener notification overhead.

static void LightPublish_WithListener(benchmark::State &state) {
  BenchLightOutput output;
  TestLightState light(&output);
  setup_rgbww_light(output, light);

  struct TestListener : public light::LightRemoteValuesListener {
    void on_light_remote_values_update() override { count_++; }
    uint64_t count_{0};
  } listener;
  light.add_remote_values_listener(&listener);

  for (auto _ : state) {
    for (int i = 0; i < kInnerIterations; i++) {
      light.publish_state();
    }
    benchmark::DoNotOptimize(listener.count_);
  }
  state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(LightPublish_WithListener);

// --- current_values_as_rgbww output conversion with gamma LUT ---
// Measures the output conversion path that real light drivers call
// from write_state() to get hardware PWM values, including gamma
// table lookups via the LUT generated by Python codegen.

static void LightOutput_RGBWW(benchmark::State &state) {
  BenchLightOutput output;
  TestLightState light(&output);
  setup_rgbww_light(output, light);

  light.make_call()
      .set_state(true)
      .set_brightness(0.8f)
      .set_color_brightness(0.6f)
      .set_red(1.0f)
      .set_green(0.5f)
      .set_blue(0.2f)
      .set_cold_white(0.7f)
      .set_warm_white(0.3f)
      .set_transition_length(0)
      .perform();

  float r, g, b, cw, ww;
  for (auto _ : state) {
    for (int i = 0; i < kInnerIterations; i++) {
      light.current_values_as_rgbww(&r, &g, &b, &cw, &ww);
    }
    benchmark::DoNotOptimize(r);
    benchmark::DoNotOptimize(cw);
  }
  state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(LightOutput_RGBWW);

}  // namespace esphome::benchmarks
