#include "infrared.h"

#include <cinttypes>

#include "esphome/core/log.h"

#ifdef USE_API
#include "esphome/components/api/api_server.h"
#endif

namespace esphome::infrared {

static const char *const TAG = "infrared";

// ========== InfraredCall ==========

InfraredCall &InfraredCall::set_carrier_frequency(uint32_t frequency) {
  this->carrier_frequency_ = frequency;
  return *this;
}

InfraredCall &InfraredCall::set_raw_timings(const std::vector<int32_t> &timings) {
  this->raw_timings_ = &timings;
  this->packed_data_ = nullptr;
  this->base64url_ptr_ = nullptr;
  return *this;
}

InfraredCall &InfraredCall::set_raw_timings_base64url(const std::string &base64url) {
  this->base64url_ptr_ = &base64url;
  this->raw_timings_ = nullptr;
  this->packed_data_ = nullptr;
  return *this;
}

InfraredCall &InfraredCall::set_raw_timings_packed(const uint8_t *data, uint16_t length, uint16_t count) {
  this->packed_data_ = data;
  this->packed_length_ = length;
  this->packed_count_ = count;
  this->raw_timings_ = nullptr;
  this->base64url_ptr_ = nullptr;
  return *this;
}

InfraredCall &InfraredCall::set_repeat_count(uint32_t count) {
  this->repeat_count_ = count;
  return *this;
}

void InfraredCall::perform() {
  if (this->parent_ != nullptr) {
    this->parent_->control(*this);
  }
}

// ========== Infrared ==========

void Infrared::setup() {
  // Set up traits based on configuration
  this->traits_.set_supports_transmitter(this->has_transmitter());
  this->traits_.set_supports_receiver(this->has_receiver());

  // Register as listener for received IR data
  if (this->receiver_ != nullptr) {
    this->receiver_->register_listener(this);
  }
}

void Infrared::dump_config() {
  ESP_LOGCONFIG(TAG,
                "Infrared '%s'\n"
                "  Supports Transmitter: %s\n"
                "  Supports Receiver: %s",
                this->get_name().c_str(), YESNO(this->traits_.get_supports_transmitter()),
                YESNO(this->traits_.get_supports_receiver()));
}

InfraredCall Infrared::make_call() { return InfraredCall(this); }

void Infrared::control(const InfraredCall &call) {
  if (this->transmitter_ == nullptr) {
    ESP_LOGW(TAG, "No transmitter configured");
    return;
  }

  if (!call.has_raw_timings()) {
    ESP_LOGE(TAG, "No raw timings provided");
    return;
  }

  // Create transmit data object
  auto transmit_call = this->transmitter_->transmit();
  auto *transmit_data = transmit_call.get_data();

  // Set carrier frequency
  auto freq = call.get_carrier_frequency();
  if (freq.has_value()) {
    transmit_data->set_carrier_frequency(*freq);
  }

  // Set timings based on format
  if (call.is_packed()) {
    // Zero-copy from packed protobuf data
    transmit_data->set_data_from_packed_sint32(call.get_packed_data(), call.get_packed_length(),
                                               call.get_packed_count());
    ESP_LOGD(TAG, "Transmitting packed raw timings: count=%" PRIu16 ", repeat=%" PRIu32, call.get_packed_count(),
             call.get_repeat_count());
  } else if (call.is_base64url()) {
    // Decode base64url (URL-safe) into transmit buffer
    if (!transmit_data->set_data_from_base64url(call.get_base64url_data())) {
      ESP_LOGE(TAG, "Invalid base64url data");
      return;
    }
    // Sanity check: validate timing values are within reasonable bounds
    constexpr int32_t max_timing_us = 500000;  // 500ms absolute max
    for (int32_t timing : transmit_data->get_data()) {
      int32_t abs_timing = timing < 0 ? -timing : timing;
      if (abs_timing > max_timing_us) {
        ESP_LOGE(TAG, "Invalid timing value: %" PRId32 " µs (max %" PRId32 ")", timing, max_timing_us);
        return;
      }
    }
    ESP_LOGD(TAG, "Transmitting base64url raw timings: count=%zu, repeat=%" PRIu32, transmit_data->get_data().size(),
             call.get_repeat_count());
  } else {
    // From vector (lambdas/automations)
    transmit_data->set_data(call.get_raw_timings());
    ESP_LOGD(TAG, "Transmitting raw timings: count=%zu, repeat=%" PRIu32, call.get_raw_timings().size(),
             call.get_repeat_count());
  }

  // Set repeat count
  if (call.get_repeat_count() > 0) {
    transmit_call.set_send_times(call.get_repeat_count());
  }

  // Perform transmission
  transmit_call.perform();
}

uint32_t Infrared::get_capability_flags() const {
  uint32_t flags = 0;

  // Add transmit/receive capability based on traits
  if (this->traits_.get_supports_transmitter())
    flags |= InfraredCapability::CAPABILITY_TRANSMITTER;
  if (this->traits_.get_supports_receiver())
    flags |= InfraredCapability::CAPABILITY_RECEIVER;

  return flags;
}

bool Infrared::on_receive(remote_base::RemoteReceiveData data) {
  // Forward received IR data to API server
#if defined(USE_API) && defined(USE_IR_RF)
  if (api::global_api_server != nullptr) {
#ifdef USE_DEVICES
    uint32_t device_id = this->get_device_id();
#else
    uint32_t device_id = 0;
#endif
    api::global_api_server->send_infrared_rf_receive_event(device_id, this->get_object_id_hash(), &data.get_raw_data());
  }
#endif
  return false;  // Don't consume the event, allow other listeners to process it
}

}  // namespace esphome::infrared
