#include "BibliothecaWindow.hpp"
#include "BookImport.hpp"

#include <gtkmm/filedialog.h>
#include <gtkmm/filefilter.h>
#include <giomm/file.h>
#include <giomm/liststore.h>
#include <giomm/asyncresult.h>
#include <glibmm/main.h>
#include <glibmm/markup.h>

#include <thread>
#include <iostream>

BibliothecaWindow::BibliothecaWindow(BookList& bookList)
  : m_bookList(bookList) {

  set_title("Bibliotheca");
  set_default_size(900, 650);

  buildHeaderBar();
  set_titlebar(m_headerBar);

  auto css = Gtk::CssProvider::create();
  css->load_from_data(R"CSS(
  gridview {
    min-width: 280px;
  }
  gridviewchild {
    background-color: transparent;
  }
  gridviewchild:selected {
    background-color: transparent;
    box-shadow: none;
  }
  gridviewchild > listitem,
  gridviewchild > listitem > * {
    background-color: transparent;
    border: none;
  }
  gridviewchild:selected > listitem,
  gridviewchild:selected > listitem > * {
    background-color: transparent;
    box-shadow: none;
    border: none;
  }
  .book-tile {
    padding: 4px;
    border-radius: 10px;
    transition: all 120ms ease-in-out;
    min-width: 160px;
    border: 2px solid transparent;
    background-color: transparent;
  }
  .book-tile.book-tile-hover {
    border: 2px solid rgba(120, 180, 255, 0.55);
    border-radius: 16px;
    background-color: rgba(120, 180, 255, 0.12);
  }

  )CSS");

  Gtk::StyleContext::add_provider_for_display(
      Gdk::Display::get_default(), css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

  // Build the main views
  buildPlaceholder();

  m_noResults.set_orientation(Gtk::Orientation::VERTICAL);
  m_noResults.set_spacing(12);
  m_noResults.set_halign(Gtk::Align::CENTER);
  m_noResults.set_valign(Gtk::Align::CENTER);
  m_noResultsLabel.set_wrap(true);
  m_noResultsLabel.set_xalign(0.5);
  m_noResultsLabel.set_use_markup(true);
  m_noResults.append(m_noResultsLabel);

  m_shelf = std::make_unique<BookShelf>(m_bookList, /*coverSize*/ 180);
  m_shelf->signalBookActivated().connect(sigc::mem_fun(*this, &BibliothecaWindow::onBookActivated));

  m_searchEntry.set_placeholder_text("Search your library…");
  m_searchEntry.signal_changed().connect(sigc::mem_fun(*this, &BibliothecaWindow::onSearchChanged));
  m_searchEntry.signal_stop_search().connect([this]() {
    m_searchBar.property_search_mode_enabled() = false;
  });

  m_searchBar.set_child(m_searchEntry);
  m_searchBar.set_show_close_button(true);
  m_searchBar.connect_entry(m_searchEntry);

  m_stack.set_transition_type(Gtk::StackTransitionType::CROSSFADE);
  m_stack.set_transition_duration(150);
  m_stack.set_hexpand(true);
  m_stack.set_vexpand(true);
  m_stack.add(m_placeholder, "empty");
  m_stack.add(*m_shelf, "shelf");
  m_stack.add(m_noResults, "noresults");

  m_mainBox.set_spacing(0);
  m_mainBox.append(m_searchBar);
  m_mainBox.append(m_stack);

  set_child(m_mainBox);

  m_searchBar.set_key_capture_widget(*this);
  m_searchBar.property_search_mode_enabled().signal_changed().connect(sigc::mem_fun(*this, &BibliothecaWindow::onSearchModeChanged));

  // Start with placeholder or shelf depending on DB contents
  updateVisibleView();

  // If the list resets (e.g., first app load), choose the right view
  m_bookList.signalReset().connect([this] { updateVisibleView(); });
  m_bookList.signalBookAdded().connect([this](const Book&) { updateVisibleView(); });
  m_bookList.signalBookUpdated().connect([this](const Book&) { updateVisibleView(); });
  m_bookList.signalBookRemoved().connect([this](const std::string&) { updateVisibleView(); });
  m_bookList.signalBulkChanged().connect([this] { updateVisibleView(); });
}

void BibliothecaWindow::buildHeaderBar() {
  m_searchButton.set_icon_name("system-search-symbolic");
  m_searchButton.set_has_frame(false);
  m_searchButton.add_css_class("flat");
  m_searchButton.set_tooltip_text("Search (Ctrl+F)");
  m_searchButton.signal_toggled().connect(sigc::mem_fun(*this, &BibliothecaWindow::onSearchToggle));
  m_headerBar.pack_start(m_searchButton);

  // Add (+) button on the right
  m_addBookButton.set_icon_name("list-add-symbolic");
  m_addBookButton.set_has_frame(false);
  m_addBookButton.set_tooltip_text("Add books to your library");
  m_addBookButton.signal_clicked().connect(sigc::mem_fun(*this, &BibliothecaWindow::onAddBookClicked));

  m_headerBar.pack_end(m_addBookButton);
}

void BibliothecaWindow::buildPlaceholder() {
  // Centered vertical box
  m_placeholder.set_valign(Gtk::Align::CENTER);
  m_placeholder.set_halign(Gtk::Align::CENTER);
  m_placeholder.set_spacing(12);
  m_placeholder.set_margin_top(24);
  m_placeholder.set_margin_bottom(24);
  m_placeholder.set_margin_start(24);
  m_placeholder.set_margin_end(24);

  // Icon (symbolic; safe across themes)
  m_placeholderIcon.set_from_icon_name("folder-open-symbolic");
  m_placeholderIcon.set_pixel_size(64);

  // Text
  m_placeholderLabel.set_use_markup(true);
  m_placeholderLabel.set_wrap(true);
  m_placeholderLabel.set_xalign(0.5);
  m_placeholderLabel.set_markup(
    "<span size='large' weight='bold'>Your Library is Empty</span>\n"
    "<small>Click the ‘+’ button to add your first book.</small>"
  );

  m_placeholder.append(m_placeholderIcon);
  m_placeholder.append(m_placeholderLabel);
}

void BibliothecaWindow::showPlaceholderIfEmpty() {
  updateVisibleView();
}

void BibliothecaWindow::updateVisibleView() {
  const auto query = m_searchEntry.get_text();
  if (!query.empty()) {
    if (m_shelf->empty()) {
      auto escaped = Glib::Markup::escape_text(query);
      m_noResultsLabel.set_markup(
        Glib::ustring::compose(
          "<span size='large' weight='bold'>No matches</span>\n"
          "<small>No books match “%1”.</small>", escaped));
      m_stack.set_visible_child("noresults");
    } else {
      m_stack.set_visible_child("shelf");
    }
    return;
  }

  if (m_bookList.size() == 0)
    m_stack.set_visible_child("empty");
  else
    m_stack.set_visible_child("shelf");
}

void BibliothecaWindow::onSearchChanged() {
  const auto text = m_searchEntry.get_text();
  m_lastQuery = text.raw();
  m_shelf->setFilter(m_lastQuery);
  updateVisibleView();
}

void BibliothecaWindow::onSearchToggle() {
  const bool active = m_searchButton.get_active();
  if (m_searchBar.property_search_mode_enabled().get_value() != active)
    m_searchBar.property_search_mode_enabled() = active;
  if (active) {
    m_searchEntry.grab_focus();
    m_searchEntry.select_region(0, -1);
  }
}

void BibliothecaWindow::onSearchModeChanged() {
  const bool enabled = m_searchBar.property_search_mode_enabled().get_value();
  if (m_searchButton.get_active() != enabled)
    m_searchButton.set_active(enabled);
  if (enabled) {
    m_searchEntry.grab_focus();
    m_searchEntry.select_region(0, -1);
  } else if (!m_searchEntry.get_text().empty()) {
    m_searchEntry.set_text("");
  }
}

void BibliothecaWindow::onAddBookClicked() {
  auto dialog = Gtk::FileDialog::create();
  dialog->set_title("Add books");

  // Build filters via a Gio::ListStore (gtkmm-4.18 API)
  auto epub = Gtk::FileFilter::create();
  epub->set_name("EPUB books");
  epub->add_suffix("epub");

  auto pdf = Gtk::FileFilter::create();
  pdf->set_name("PDF books");
  pdf->add_suffix("pdf");

  auto any = Gtk::FileFilter::create();
  any->set_name("All files");
  any->add_pattern("*");

  auto filterStore = Gio::ListStore<Gtk::FileFilter>::create();
  filterStore->append(epub);
  filterStore->append(pdf);
  filterStore->append(any);
  dialog->set_filters(filterStore);

  // Let user pick multiple files
  dialog->open_multiple(
    *this,
    [this, dialog](const Glib::RefPtr<Gio::AsyncResult>& res) {
      std::vector<Glib::RefPtr<Gio::File>> files;
      try {
        files = dialog->open_multiple_finish(res);
      } catch (const Glib::Error& e) {
        std::cerr << "File dialog error: " << e.what() << "\n";
        return;
      }
      if (files.empty()) return;

      // Hash and create Book objects off the GTK thread
      std::thread([this, files = std::move(files)]() {
        std::vector<Book> imported;
        imported.reserve(files.size());

        for (const auto& f : files) {
          try {
            const auto path = f->get_path();
            const auto id = sha256_file(path);
            auto resOpt   = import_book_assets(path, id);  // may throw -> catch already present
            ImportResult res = resOpt.value_or(ImportResult{});

            // Build the Book with extracted metadata
            Book b(
              id,
              !res.title.empty()  ? res.title  : Glib::path_get_basename(path),
              res.author,
              path,
              res.coverPngPath
            );
            // Preload cover into texture (or defer to BookTile which already calls load_cover_from_disk)
            b.load_cover_from_disk();

            imported.push_back(std::move(b));
          } catch (const std::exception& e) {
            std::cerr << "Import failed: " << e.what() << "\n";
          }
        }

        // Push to model on the GTK main loop
        Glib::signal_idle().connect_once([this, books = std::move(imported)]() mutable {
          if (!books.empty()) {
            m_bookList.upsertMany(books);
            updateVisibleView();
          }
        });
      }).detach();
    }
  );
}

void BibliothecaWindow::onBookActivated(const Book& book) {
  const auto& path = book.filePath();
  if (path.empty()) {
    std::cerr << "Activated book has no file path: " << book.title() << "\n";
    return;
  }
  try {
    auto file = Gio::File::create_for_path(path);
    if (!file->query_exists()) {
      std::cerr << "Book file is missing: " << path << "\n";
      return;
    }
    // Ask the desktop to open the file with the user's default application.
    const auto uri = Glib::filename_to_uri(path);
    Gio::AppInfo::launch_default_for_uri(uri);
  } catch (const Glib::Error& e) {
    std::cerr << "Failed to open book '" << book.title() << "': "
              << e.what() << "\n";
  }
}
