/**
 * @file unique_image.hpp
 * @brief Provides RAII wrapper for image loading and management.
 *
 * This file implements a RAII-compliant image handling class that uses stb_image
 * for loading various image formats. It ensures proper resource management and
 * provides a safe interface for image data access.
 */

#include <filesystem>
#include <fmt/format.h>

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

#include "types.hpp"

namespace stb {
  /**
   * @brief RAII wrapper for image data loaded via stb_image.
   *
   * This class provides safe resource management for loaded images, ensuring proper
   * cleanup of image data. It supports move semantics but prevents copying to maintain
   * single ownership of image resources.
   */
  class UniqueImage {
   public:
    /**
     * @brief Constructs a UniqueImage by loading from file.
     *
     * @param path Filesystem path to the image file.
     * @throws std::runtime_error If image loading fails.
     *
     * Automatically loads the image data from the specified file using stb_image.
     * The image data is stored in RGBA format with 8 bits per channel.
     */
    UniqueImage(const std::filesystem::path& path) { load(path.string().c_str()); }

    // Prevent copying to maintain single ownership
    UniqueImage(const UniqueImage&)                = delete;
    fn operator=(const UniqueImage&)->UniqueImage& = delete;

    /**
     * @brief Move constructor for transferring image ownership.
     *
     * @param other Source UniqueImage to move from.
     *
     * Transfers ownership of image data from another UniqueImage instance,
     * leaving the source in a valid but empty state.
     */
    UniqueImage(UniqueImage&& other) noexcept
      : mData(other.mData),
        mWidth(static_cast<i32>(other.mWidth)),
        mHeight(static_cast<i32>(other.mHeight)),
        mChannels(static_cast<i32>(other.mChannels)) {
      other.mData = nullptr;
    }

    /**
     * @brief Move assignment operator for transferring image ownership.
     *
     * @param other Source UniqueImage to move from.
     * @return Reference to this object.
     *
     * Safely transfers ownership of image data, ensuring proper cleanup of
     * existing resources before the transfer.
     */
    fn operator=(UniqueImage&& other) noexcept -> UniqueImage& {
      if (this != &other) {
        if (mData)
          stbi_image_free(mData);

        mData       = other.mData;
        mWidth      = static_cast<i32>(other.mWidth);
        mHeight     = static_cast<i32>(other.mHeight);
        mChannels   = static_cast<i32>(other.mChannels);
        other.mData = nullptr;
      }
      return *this;
    }

    /**
     * @brief Destructor that ensures proper cleanup of image resources.
     *
     * Automatically frees the image data using stb_image_free when the
     * object goes out of scope.
     */
    ~UniqueImage() {
      if (mData)
        stbi_image_free(mData);
    }

    /**
     * @brief Get raw pointer to image data.
     * @return Pointer to the raw image data in memory.
     *
     * The data is stored in row-major order with either RGB or RGBA format,
     * depending on the source image.
     */
    [[nodiscard]] fn getData() const -> u8* { return mData; }

    /**
     * @brief Get image width in pixels.
     * @return Width of the image.
     */
    [[nodiscard]] fn getWidth() const -> i32 { return mWidth; }

    /**
     * @brief Get image height in pixels.
     * @return Height of the image.
     */
    [[nodiscard]] fn getHeight() const -> i32 { return mHeight; }

    /**
     * @brief Get number of color channels.
     * @return Number of channels (e.g., 3 for RGB, 4 for RGBA).
     */
    [[nodiscard]] fn getChannels() const -> i32 { return mChannels; }

   private:
    /**
     * @brief Internal helper function to load image data.
     *
     * @param path Path to the image file.
     * @throws std::runtime_error If image loading fails.
     *
     * Uses stb_image to load the image data, automatically detecting the
     * format and number of channels from the file.
     */
    fn load(const char* path) -> void {
      mData = stbi_load(path, &mWidth, &mHeight, &mChannels, STBI_rgb_alpha);
      if (!mData)
        throw std::runtime_error(fmt::format("Failed to load texture image: {}", path));
    }

    u8* mData     = nullptr; ///< Raw image data in memory
    i32 mWidth    = 0;       ///< Image width in pixels
    i32 mHeight   = 0;       ///< Image height in pixels
    i32 mChannels = 0;       ///< Number of color channels
  };
}
