#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(DatabaseManager& db, BookList& bookList)
  : m_db(db),
    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);
  }
  .toast-box {
    background-color: @theme_bg_color;
    border-radius: 8px;
    padding: 8px 12px;
    margin: 12px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
  }
  .toast-box.toast-error {
    background-color: #c01c28;
    color: white;
  }
  .toast-box.toast-success {
    background-color: #26a269;
    color: white;
  }

  )CSS");

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

  // Build the main views
  buildPlaceholder();
  buildToast();

  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_bookDetails = std::make_unique<BookDetails>(m_db, m_bookList);
  m_bookDetails->signalBookRemoved().connect([this]() {
    // Navigate back to shelf when displayed book is removed
    m_stack.set_visible_child("shelf");
  });
  m_stack.add(*m_bookDetails, "details");
  m_stack.add(m_noResults, "noresults");

  m_stack.property_visible_child().signal_changed().connect([this]() {
    auto visible_child = m_stack.get_visible_child_name();
    m_backButton.set_visible(visible_child == "details");
    m_searchButton.set_visible(visible_child != "details");
    m_addBookButton.set_visible(visible_child != "details");
  });

  // Use overlay to show toast on top of content
  m_overlay.set_child(m_stack);
  m_overlay.add_overlay(m_toastRevealer);

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

  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_backButton.set_icon_name("go-previous-symbolic");
  m_backButton.set_has_frame(false);
  m_backButton.add_css_class("flat");
  m_backButton.set_tooltip_text("Back");
  m_backButton.signal_clicked().connect([this]() {
    m_stack.set_visible_child("shelf");
    // Restore search filter when returning from details
    if (!m_lastQuery.empty()) {
      m_shelf->setFilter(m_lastQuery);
      updateVisibleView();
    }
  });
  m_headerBar.pack_start(m_backButton);

  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::buildToast() {
  m_toastRevealer.set_transition_type(Gtk::RevealerTransitionType::SLIDE_UP);
  m_toastRevealer.set_transition_duration(200);
  m_toastRevealer.set_halign(Gtk::Align::CENTER);
  m_toastRevealer.set_valign(Gtk::Align::END);
  m_toastRevealer.set_reveal_child(false);

  m_toastBox.set_spacing(8);
  m_toastBox.add_css_class("toast-box");

  m_toastLabel.set_hexpand(true);

  m_toastCloseButton.set_icon_name("window-close-symbolic");
  m_toastCloseButton.add_css_class("flat");
  m_toastCloseButton.add_css_class("circular");
  m_toastCloseButton.signal_clicked().connect(sigc::mem_fun(*this, &BibliothecaWindow::hideToast));

  m_toastBox.append(m_toastLabel);
  m_toastBox.append(m_toastCloseButton);
  m_toastRevealer.set_child(m_toastBox);
}

void BibliothecaWindow::showToast(const Glib::ustring& message, bool is_error) {
  // Cancel any pending hide timeout
  if (m_toastTimeout.connected()) {
    m_toastTimeout.disconnect();
  }

  m_toastLabel.set_text(message);

  // Update styling
  m_toastBox.remove_css_class("toast-error");
  m_toastBox.remove_css_class("toast-success");
  m_toastBox.add_css_class(is_error ? "toast-error" : "toast-success");

  m_toastRevealer.set_reveal_child(true);

  // Auto-hide after 4 seconds
  m_toastTimeout = Glib::signal_timeout().connect([this]() {
    hideToast();
    return false; // Don't repeat
  }, 4000);
}

void BibliothecaWindow::hideToast() {
  if (m_toastTimeout.connected()) {
    m_toastTimeout.disconnect();
  }
  m_toastRevealer.set_reveal_child(false);
}

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()) {
    // Only clear search when explicitly closing search mode (not when navigating)
    // Check if we're on the shelf view - only clear if user is closing search there
    if (m_stack.get_visible_child_name() == "shelf" ||
        m_stack.get_visible_child_name() == "noresults") {
      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) {
        // User cancelled or error
        if (e.code() != 2) { // 2 = GTK_DIALOG_ERROR_DISMISSED
          showToast("Failed to open file dialog", true);
        }
        return;
      }
      if (files.empty()) return;

      // Hash and create Book objects off the GTK thread
      std::thread([this, files = std::move(files)]() {
        struct ImportedBook {
          Book book;
          bool is_duplicate;
        };
        std::vector<ImportedBook> imported;
        std::vector<std::string> errors;
        imported.reserve(files.size());

        for (const auto& f : files) {
          try {
            const auto path = f->get_path();
            const auto id = sha256_file(path);

            // Check if this book already exists (by hash)
            bool is_duplicate = m_bookList.findById(id).has_value();

            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), is_duplicate});
          } catch (const std::exception& e) {
            errors.push_back(Glib::path_get_basename(f->get_path()) + ": " + e.what());
            std::cerr << "Import failed: " << e.what() << "\n";
          }
        }

        // Push to model on the GTK main loop
        Glib::signal_idle().connect_once([this, imported = std::move(imported), errors = std::move(errors)]() mutable {
          std::vector<Book> books_to_add;
          size_t new_count = 0;
          size_t duplicate_count = 0;

          for (auto& item : imported) {
            if (item.is_duplicate) {
              duplicate_count++;
            } else {
              new_count++;
            }
            books_to_add.push_back(std::move(item.book));
          }

          if (!books_to_add.empty()) {
            m_bookList.upsertMany(books_to_add);
            updateVisibleView();
          }

          // Show toast with import results
          const size_t error_count = errors.size();

          if (error_count == 0 && new_count > 0 && duplicate_count == 0) {
            showToast(Glib::ustring::compose("Added %1 book%2", new_count, new_count == 1 ? "" : "s"), false);
          } else if (error_count == 0 && new_count == 0 && duplicate_count > 0) {
            showToast(Glib::ustring::compose("%1 book%2 already in library", duplicate_count, duplicate_count == 1 ? "" : "s"), false);
          } else if (error_count == 0 && new_count > 0 && duplicate_count > 0) {
            showToast(Glib::ustring::compose("Added %1 new, %2 duplicate%3", new_count, duplicate_count, duplicate_count == 1 ? "" : "s"), false);
          } else if (new_count == 0 && duplicate_count == 0 && error_count > 0) {
            showToast(Glib::ustring::compose("Failed to import %1 file%2", error_count, error_count == 1 ? "" : "s"), true);
          } else if ((new_count > 0 || duplicate_count > 0) && error_count > 0) {
            showToast(Glib::ustring::compose("Added %1, %2 failed", new_count + duplicate_count, error_count), true);
          }
        });
      }).detach();
    }
  );
}

void BibliothecaWindow::onBookActivated(const Book& book) {
  m_bookDetails->set_book(&book);
  m_stack.set_visible_child("details");
}
