#include <filesystem>

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

#include "types.hpp"

namespace stb {
  /**
   * @brief A class that handles loading and managing image data.
   *
   * This class uses the stb_image library to load images from the filesystem
   * and provides access to the image data, dimensions, and channel count.
   */
  class UniqueImage {
   public:
    /**
     * @brief Constructs a UniqueImage object and loads an image from the specified path.
     *
     * @param path The filesystem path to the image file to load.
     */
    UniqueImage(const std::filesystem::path& path) { load(path.string().c_str()); }

    // Deleted copy constructor to prevent copying.
    UniqueImage(const UniqueImage&) = delete;

    // Deleted copy assignment operator to prevent copying.
    fn operator=(const UniqueImage&)->UniqueImage& = delete;

    /**
     * @brief Move constructor for UniqueImage.
     *
     * @param other The UniqueImage object from which to move resources.
     *
     * Transfers ownership of resources from another UniqueImage object.
     */
    UniqueImage(UniqueImage&& other) noexcept
      : mData(other.mData), mWidth(other.mWidth), mHeight(other.mHeight), mChannels(other.mChannels) {
      other.mData = nullptr;
    }

    /**
     * @brief Move assignment operator for UniqueImage.
     *
     * @param other The UniqueImage object from which to move resources.
     * @return Reference to this object.
     *
     * Transfers ownership of resources from another UniqueImage object.
     */
    fn operator=(UniqueImage&& other) noexcept -> UniqueImage& {
      if (this != &other) {
        if (mData)
          stbi_image_free(mData);

        mData       = other.mData;
        mWidth      = other.mWidth;
        mHeight     = other.mHeight;
        mChannels   = other.mChannels;
        other.mData = nullptr;
      }
      return *this;
    }

    /**
     * @brief Destructor for UniqueImage.
     *
     * Frees the image data if it is allocated.
     */
    ~UniqueImage() {
      if (mData)
        stbi_image_free(mData);
    }

    /**
     * @brief Retrieves the image data.
     *
     * @return Pointer to the image data in memory.
     */
    [[nodiscard]] fn getData() const -> u8* { return mData; }

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

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

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

   private:
    u8* mData     = nullptr; ///< Pointer to the image data.
    i32 mWidth    = 0;       ///< Width of the image in pixels.
    i32 mHeight   = 0;       ///< Height of the image in pixels.
    i32 mChannels = 0;       ///< Number of channels in the image.

    /**
     * @brief Loads an image from a file.
     *
     * @param filename The name of the file from which to load the image.
     * @throws std::runtime_error If the image fails to load.
     */
    fn load(const char* filename) -> void {
      mData = stbi_load(filename, &mWidth, &mHeight, &mChannels, STBI_rgb_alpha);

      if (!mData)
        throw std::runtime_error("Failed to load image: " + string(stbi_failure_reason()));
    }
  };
}
