#pragma once
#include "esphome/core/color.h"

namespace esphome::runtime_image {

enum DecodeError : int {
  DECODE_ERROR_INVALID_TYPE = -1,
  DECODE_ERROR_UNSUPPORTED_FORMAT = -2,
  DECODE_ERROR_OUT_OF_MEMORY = -3,
  DECODE_ERROR_INTERNAL_DECODER_ERROR = -4,
};

constexpr const char *decode_error_to_string(int error) {
  switch (error) {
    case DECODE_ERROR_INVALID_TYPE:
      return "Invalid type";
    case DECODE_ERROR_UNSUPPORTED_FORMAT:
      return "Unsupported format";
    case DECODE_ERROR_OUT_OF_MEMORY:
      return "Out of memory";
    case DECODE_ERROR_INTERNAL_DECODER_ERROR:
      return "Internal decoder error";
    default:
      return "Unknown error";
  }
}

class RuntimeImage;

/**
 * @brief Class to abstract decoding different image formats.
 */
class ImageDecoder {
 public:
  /**
   * @brief Construct a new Image Decoder object
   *
   * @param image The RuntimeImage to decode the stream into.
   */
  ImageDecoder(RuntimeImage *image) : image_(image) {}
  virtual ~ImageDecoder() = default;

  /**
   * @brief Initialize the decoder.
   *
   * @param expected_size Hint about the expected data size (0 if unknown).
   * @return int          Returns 0 on success, a {@see DecodeError} value in case of an error.
   */
  virtual int prepare(size_t expected_size) {
    this->expected_size_ = expected_size;
    return 0;
  }

  /**
   * @brief Decode a part of the image. It will try reading from the buffer.
   * There is no guarantee that the whole available buffer will be read/decoded;
   * the method will return the amount of bytes actually decoded, so that the
   * unread content can be moved to the beginning.
   *
   * @param buffer The buffer to read from.
   * @param size   The maximum amount of bytes that can be read from the buffer.
   * @return int   The amount of bytes read. It can be 0 if the buffer does not have enough content to meaningfully
   *               decode anything, or negative in case of a decoding error.
   */
  virtual int decode(uint8_t *buffer, size_t size) = 0;

  /**
   * @brief Request the image to be resized once the actual dimensions are known.
   * Called by the callback functions, to be able to access the parent Image class.
   *
   * @param width The image's width.
   * @param height The image's height.
   * @return true if the image was resized, false otherwise.
   */
  bool set_size(int width, int height);

  /**
   * @brief Fill a rectangle on the display_buffer using the defined color.
   * Will check the given coordinates for out-of-bounds, and clip the rectangle accordingly.
   * In case of binary displays, the color will be converted to binary as well.
   * Called by the callback functions, to be able to access the parent Image class.
   *
   * @param x The left-most coordinate of the rectangle.
   * @param y The top-most coordinate of the rectangle.
   * @param w The width of the rectangle.
   * @param h The height of the rectangle.
   * @param color The fill color
   */
  void draw(int x, int y, int w, int h, const Color &color);

  /**
   * @brief Check if the decoder has finished processing.
   *
   * This should be overridden by decoders that can detect completion
   * based on format-specific markers rather than byte counts.
   */
  virtual bool is_finished() const {
    if (this->expected_size_ > 0) {
      return this->decoded_bytes_ >= this->expected_size_;
    }
    // If size is unknown, derived classes should override this
    return false;
  }

 protected:
  RuntimeImage *image_;
  size_t expected_size_ = 0;  // Expected data size (0 if unknown)
  size_t decoded_bytes_ = 0;  // Bytes processed so far
  double x_scale_ = 1.0;
  double y_scale_ = 1.0;
};

}  // namespace esphome::runtime_image
