#include "BookTile.hpp"
#include <gtkmm/stylecontext.h>
#include <gtkmm/cssprovider.h>
#include <gdkmm/display.h>
#include <gtkmm/icontheme.h>
#include <gtkmm/eventcontrollermotion.h>
#include <gdkmm/memorytexture.h>
#include <glibmm/bytes.h>
#include <algorithm>
#include <map>
#include <vector>

namespace {

// Maximum number of cached placeholder textures to prevent unbounded growth
constexpr size_t kMaxPlaceholderCacheSize = 8;

Glib::RefPtr<Gdk::Texture> make_placeholder_texture(int width, int height) {
  using Key = std::pair<int, int>;
  static std::map<Key, Glib::RefPtr<Gdk::Texture>> cache;
  const Key key{width, height};

  if (auto it = cache.find(key); it != cache.end())
    return it->second;

  // Limit cache size - remove oldest entry if full
  if (cache.size() >= kMaxPlaceholderCacheSize) {
    cache.erase(cache.begin());
  }

  const int stride = width * 4;
  std::vector<guint8> pixels(static_cast<std::size_t>(stride) * height);

  auto put_pixel = [&](int x, int y, guint8 r, guint8 g, guint8 b, guint8 a = 255) {
    const auto idx = static_cast<std::size_t>(y) * stride + static_cast<std::size_t>(x) * 4;
    pixels[idx + 0] = r;
    pixels[idx + 1] = g;
    pixels[idx + 2] = b;
    pixels[idx + 3] = a;
  };

  const guint8 bg_r = 0x2e, bg_g = 0x34, bg_b = 0x40;
  const guint8 spine_r = 0x5e, spine_g = 0x81, spine_b = 0xac;
  const guint8 band_r = 0xb4, band_g = 0x88, band_b = 0xea;

  for (int y = 0; y < height; ++y) {
    for (int x = 0; x < width; ++x) {
      guint8 r = bg_r, g = bg_g, b = bg_b;

      if (x < width / 6) {
        r = spine_r; g = spine_g; b = spine_b;
      } else if (x > width / 2 && x < width / 2 + width / 6) {
        r = band_r; g = band_g; b = band_b;
      }

      if (y < height / 12 || y > height - height / 12) {
        r = static_cast<guint8>(std::min<int>(r + 20, 255));
        g = static_cast<guint8>(std::min<int>(g + 20, 255));
        b = static_cast<guint8>(std::min<int>(b + 20, 255));
      }

      put_pixel(x, y, r, g, b);
    }
  }

  auto bytes = Glib::Bytes::create(pixels.data(), pixels.size());
  auto texture = Gdk::MemoryTexture::create(width, height, Gdk::MemoryTexture::Format::R8G8B8A8, bytes, stride);
  cache[key] = texture;
  return texture;
}

} // namespace

BookTile::BookTile(const Book& book, int coverSize)
: Gtk::Box(Gtk::Orientation::VERTICAL),
  m_book(book),
  m_coverSize(coverSize) {
  set_spacing(4);
  set_margin_top(4);
  set_margin_bottom(4);
  set_margin_start(4);
  set_margin_end(4);

  // Fixed width so FlowBox knows how to pack columns
  // Add some padding to account for margins
  set_size_request(m_coverSize + 10, -1);
  set_hexpand(false);
  set_halign(Gtk::Align::CENTER);
  
  add_css_class("book-tile");

  m_cover.set_content_fit(Gtk::ContentFit::SCALE_DOWN);
  m_cover.set_can_shrink(true);
  m_cover.set_size_request(m_coverSize, m_coverSize);
  m_cover.set_halign(Gtk::Align::CENTER);

  // clamp title
  m_title.set_wrap(true);
  m_title.set_wrap_mode(Pango::WrapMode::WORD_CHAR);
  m_title.set_xalign(0.5);
  m_title.set_width_chars(15);
  m_title.set_max_width_chars(18);
  m_title.set_lines(2);
  m_title.set_ellipsize(Pango::EllipsizeMode::END);

  m_hover = Gtk::EventControllerMotion::create();
  m_hover->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
  m_hover->signal_enter().connect([this](double, double) {
    add_css_class("book-tile-hover");
  });
  m_hover->signal_leave().connect([this]() {
    remove_css_class("book-tile-hover");
  });
  add_controller(m_hover);

  // Click to "activate"
  m_click = Gtk::GestureClick::create();
  m_click->set_button(0);
  m_click->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
  m_click->signal_released().connect([this](int, double, double){
    onActivated();
  });
  add_controller(m_click);

  append(m_cover);
  append(m_title);

  rebuild();
}


void BookTile::setBook(const Book& book) {
  m_book = book;
  rebuild();
}

void BookTile::setSelected(bool selected) {
  if (m_selected == selected)
    return;

  m_selected = selected;
  if (selected)
    add_css_class("book-tile-selected");
  else
    remove_css_class("book-tile-selected");
}

void BookTile::rebuild() {
  if (!m_book.cover()) {
    const_cast<Book&>(m_book).load_cover_from_disk();
  }

  auto tex = m_book.cover();
  if (!tex) {
    tex = make_placeholder_texture(m_coverSize, m_coverSize);
  }

  m_cover.set_paintable(tex);

  m_cover.set_size_request(m_coverSize, m_coverSize);
  m_title.set_text(m_book.title().empty()
                   ? Glib::path_get_basename(m_book.filePath())
                   : m_book.title());
}

void BookTile::onActivated() {
  m_signalActivated.emit(m_book);
}
