#include "visual_database_display_widget.h"

#include "../../../client/settings/cache_settings.h"
#include "../../../filters/filter_tree_model.h"
#include "../../../filters/syntax_help.h"
#include "../../pixel_map_generator.h"
#include "../cards/card_info_picture_with_text_overlay_widget.h"
#include "../deck_editor/card_database_view.h"
#include "../utility/custom_line_edit.h"
#include "visual_database_display_color_filter_widget.h"
#include "visual_database_display_filter_save_load_widget.h"
#include "visual_database_display_main_type_filter_widget.h"
#include "visual_database_display_name_filter_widget.h"
#include "visual_database_display_set_filter_widget.h"
#include "visual_database_display_sub_type_filter_widget.h"

#include <QHeaderView>
#include <QScrollBar>
#include <QStyledItemDelegate>
#include <libcockatrice/card/card_info_comparator.h>
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h>
#include <utility>

VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent,
                                                         CardDatabaseModel *database_model,
                                                         DeckListModel *deckListModel)
    : QWidget(parent)
{
    debounceTimer = new QTimer(this);
    debounceTimer->setSingleShot(true); // Ensure it only fires once after the timeout

    connect(debounceTimer, &QTimer::timeout, this, &VisualDatabaseDisplayWidget::onSearchModelChanged);

    // Create display model
    databaseDisplayModel = new CardDatabaseDisplayModel(this);
    databaseDisplayModel->setObjectName("databaseDisplayModel");
    databaseDisplayModel->setSourceModel(database_model);
    databaseDisplayModel->setFilterKeyColumn(0);

    cards = new QList<ExactCard>;
    connect(databaseDisplayModel, &CardDatabaseDisplayModel::modelDirty, this,
            &VisualDatabaseDisplayWidget::modelDirty);

    // Set up main layout and widgets
    setMinimumSize(0, 0);
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    mainLayout = new QVBoxLayout(this);
    setLayout(mainLayout);
    mainLayout->setSpacing(1);
    mainLayout->setContentsMargins(0, 0, 0, 0);

    flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarPolicy::ScrollBarAsNeeded);
    cardSizeWidget = new CardSizeWidget(this, flowWidget, SettingsCache::instance().getVisualDatabaseDisplayCardSize());
    connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(),
            &SettingsCache::setVisualDatabaseDisplayCardSize);

    searchContainer = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);

    searchEdit = new SearchLineEdit();
    searchEdit->setObjectName("searchEdit");
    searchEdit->setPlaceholderText(tr("Search by card name (or search expressions)"));
    searchEdit->setClearButtonEnabled(true);
    searchEdit->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition);
    auto help = searchEdit->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition);
    connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(searchEdit); });

    setFocusProxy(searchEdit);
    setFocusPolicy(Qt::ClickFocus);

    displayModeButton = new QPushButton(tr("Visual"), this);
    displayModeButton->setCheckable(true); // Toggle button

    connect(displayModeButton, &QPushButton::toggled, this, &VisualDatabaseDisplayWidget::onDisplayModeChanged);

    displayModeButton->setChecked(false); // Start in Visual mode

    filterModel = new FilterTreeModel();
    filterModel->setObjectName("filterModel");

    connect(searchEdit, &SearchLineEdit::textChanged, databaseDisplayModel, &CardDatabaseDisplayModel::setStringFilter);

    databaseView = new CardDatabaseView(this, databaseDisplayModel);
    databaseView->setObjectName("databaseView");
    databaseView->setFocusProxy(searchEdit);
    databaseView->setItemDelegate(nullptr);
    databaseView->setVisible(false);

    searchEdit->setTreeView(databaseView);
    searchEdit->installEventFilter(databaseView->getKeySignals());

    connect(databaseView, &CardDatabaseView::cardChanged, this, &VisualDatabaseDisplayWidget::onSelectedCardChanged);
    connect(databaseView, &CardDatabaseView::cardAdded, this, &VisualDatabaseDisplayWidget::actAddCard);
    connect(databaseView, &CardDatabaseView::cardDecremented, this, &VisualDatabaseDisplayWidget::actDecrementCard);
    connect(databaseView, &CardDatabaseView::edhrecClicked, this, &VisualDatabaseDisplayWidget::edhrecRequested);
    connect(databaseView, &CardDatabaseView::selectPrintingClicked, this,
            &VisualDatabaseDisplayWidget::printingSelectorRequested);
    connect(databaseView, &CardDatabaseView::relatedCardClicked, this,
            &VisualDatabaseDisplayWidget::onRelatedCardClicked);

    colorFilterWidget = new VisualDatabaseDisplayColorFilterWidget(this, filterModel);

    filterContainer = new VisualDatabaseDisplayFilterToolbarWidget(this, deckListModel);

    clearFilterWidget = new QToolButton();
    clearFilterWidget->setFixedSize(32, 32);
    clearFilterWidget->setIcon(QPixmap("theme:icons/delete"));
    connect(clearFilterWidget, &QToolButton::clicked, this, [this] {
        filterModel->blockSignals(true);
        filterModel->filterTree()->blockSignals(true);
        filterModel->clear();
        filterModel->blockSignals(false);
        filterModel->filterTree()->blockSignals(false);
        emit filterModel->filterTree()->changed();
        emit filterModel->layoutChanged();
    });

    databaseLoadIndicator = new QLabel(this);
    databaseLoadIndicator->setAlignment(Qt::AlignCenter);

    mainLayout->addWidget(databaseLoadIndicator);

    if (CardDatabaseManager::getInstance()->getLoadStatus() != LoadStatus::Ok) {
        connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this,
                &VisualDatabaseDisplayWidget::initialize);
        filterContainer->setVisible(false);
    } else {
        initialize();
        databaseLoadIndicator->setVisible(false);
    }

    QScrollBar *scrollBar = flowWidget->scrollArea->verticalScrollBar();
    connect(scrollBar, &QScrollBar::valueChanged, [this](int /*value*/) { loadCurrentPage(); });
    retranslateUi();
}

void VisualDatabaseDisplayWidget::initialize()
{
    databaseLoadIndicator->setVisible(false);

    filterContainer->initialize();
    filterContainer->setVisible(true);

    searchContainer->addWidget(colorFilterWidget);
    searchContainer->addWidget(clearFilterWidget);
    searchContainer->addWidget(searchEdit);
    searchContainer->addWidget(displayModeButton);

    mainLayout->addWidget(searchContainer);

    mainLayout->addWidget(filterContainer);

    mainLayout->addWidget(databaseView);

    mainLayout->addWidget(flowWidget);

    mainLayout->addWidget(cardSizeWidget);

    databaseDisplayModel->setFilterTree(filterModel->filterTree());

    connect(filterModel, &FilterTreeModel::layoutChanged, this, &VisualDatabaseDisplayWidget::onSearchModelChanged);

    loadCardsTimer = new QTimer(this);
    loadCardsTimer->setSingleShot(true); // Ensure it only fires once after the timeout

    connect(loadCardsTimer, &QTimer::timeout, this, [this]() { loadCurrentPage(); });
    loadCardsTimer->start(5000);

    retranslateUi();
}

void VisualDatabaseDisplayWidget::retranslateUi()
{
    databaseLoadIndicator->setText(tr("Loading database ..."));
    clearFilterWidget->setToolTip(tr("Clear all filters"));
}

void VisualDatabaseDisplayWidget::highlightAllSearchEdit()
{
    searchEdit->setSelection(0, searchEdit->text().length());
}

void VisualDatabaseDisplayWidget::resizeEvent(QResizeEvent *event)
{
    QWidget::resizeEvent(event);
    loadCurrentPage();
}

void VisualDatabaseDisplayWidget::onDisplayModeChanged(bool checked)
{
    if (checked) {
        // Table mode
        displayModeButton->setText(tr("Table"));
        flowWidget->setVisible(false);
        cardSizeWidget->setVisible(false);
        databaseView->setItemDelegate(new QStyledItemDelegate(databaseView));
        databaseView->setVisible(true);
    } else {
        // Visual mode
        displayModeButton->setText(tr("Visual"));
        flowWidget->setVisible(true);
        cardSizeWidget->setVisible(true);
        databaseView->setVisible(false);
        populateCards();
    }
}

void VisualDatabaseDisplayWidget::onClick(QMouseEvent *event, const ExactCard &card)
{
    emit cardClickedDatabaseDisplay(event, card);
}

void VisualDatabaseDisplayWidget::onHover(const ExactCard &hoveredCard)
{
    emit cardHoveredDatabaseDisplay(hoveredCard);
}

void VisualDatabaseDisplayWidget::addCardToDisplay(const ExactCard &cardToAdd)
{
    cards->append(cardToAdd);
    auto *display = new CardInfoPictureWithTextOverlayWidget(flowWidget, false);
    display->setScaleFactor(cardSizeWidget->getSlider()->value());
    display->setCard(cardToAdd);
    flowWidget->addWidget(display);
    connect(display, &CardInfoPictureWithTextOverlayWidget::cardClicked, this, &VisualDatabaseDisplayWidget::onClick);
    connect(display, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this, &VisualDatabaseDisplayWidget::onHover);
    connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, display, &CardInfoPictureWidget::setScaleFactor);
}

bool VisualDatabaseDisplayWidget::isVisualDisplayMode() const
{
    return !displayModeButton->isChecked();
}

void VisualDatabaseDisplayWidget::onSearchModelChanged()
{
    if (flowWidget->isVisible()) {
        // Clear the current page and prepare for new data
        flowWidget->clearLayout(); // Clear existing cards
        cards->clear();            // Clear the card list
        // Reset scrollbar position to the top after loading new cards
        QScrollBar *scrollBar = flowWidget->scrollArea->verticalScrollBar();
        scrollBar->setValue(0); // Reset scrollbar to top

        currentPage = 0;
        loadCurrentPage();
        qCDebug(VisualDatabaseDisplayLog) << "Search model changed";
    }
}

void VisualDatabaseDisplayWidget::onSelectedCardChanged(const QString &cardName)
{
    emit cardHoveredDatabaseDisplay(CardDatabaseManager::query()->getPreferredCard(cardName));
}

void VisualDatabaseDisplayWidget::actAddCard(const QString &cardName, const QString &zoneName)
{
    highlightAllSearchEdit();
    ExactCard exactCard = CardDatabaseManager::query()->getPreferredCard(cardName);
    emit cardAdded(exactCard, zoneName);
}

void VisualDatabaseDisplayWidget::actDecrementCard(const QString &cardName, const QString &zoneName)
{
    ExactCard exactCard = CardDatabaseManager::query()->getPreferredCard(cardName);
    emit cardDecremented(exactCard, zoneName);
}

void VisualDatabaseDisplayWidget::onRelatedCardClicked(const QString &relatedCard)
{
    ExactCard exactCard = CardDatabaseManager::query()->guessCard({relatedCard});
    emit cardInfoRequested(exactCard);
}

bool VisualDatabaseDisplayWidget::nearEndOfPage() const
{
    if (!flowWidget->isVisible()) {
        return false;
    }

    QScrollBar *scrollBar = flowWidget->scrollArea->verticalScrollBar();
    if (scrollBar->value() + scrollBar->pageStep() * 2 < scrollBar->maximum()) {
        return false; // there is at least two pages of space to scroll remaining
    }

    return true;
}

void VisualDatabaseDisplayWidget::loadCurrentPage()
{
    // Ensure only the initial page is loaded
    if (currentPage == 0) {
        // Only load the first page initially
        qCDebug(VisualDatabaseDisplayLog) << "Loading the first page";
        populateCards();
    } else if (nearEndOfPage()) {
        // If not the first page, just load the next page and append to the flow widget
        loadNextPage();
    }
}

void VisualDatabaseDisplayWidget::populateCards()
{
    cards->clear();
    loadNextPage();
}

void VisualDatabaseDisplayWidget::loadNextPage()
{
    // Calculate the start and end indices for the next page
    int rowCount = databaseDisplayModel->rowCount();
    int start = currentPage * cardsPerPage;
    int end = qMin(start + cardsPerPage, rowCount);

    // Load more cards if we are at the end of the current list and can fetch more
    if (end >= rowCount && databaseDisplayModel->canFetchMore(QModelIndex())) {
        databaseDisplayModel->fetchMore(QModelIndex());
    }

    // Load the next page of cards and add them to the flow widget
    flowWidget->setUpdatesEnabled(false);
    loadPage(start, end);
    flowWidget->setUpdatesEnabled(true);
}

void VisualDatabaseDisplayWidget::loadPage(int start, int end)
{
    QList<const CardFilter *> setFilters = filterModel->getFiltersOfType(CardFilter::AttrSet);
    for (int row = start; row < end; ++row) {
        qCDebug(VisualDatabaseDisplayLog) << "Adding " << row;
        QModelIndex index = databaseDisplayModel->index(row, CardDatabaseModel::NameColumn);
        QVariant name = databaseDisplayModel->data(index, Qt::DisplayRole);
        qCDebug(VisualDatabaseDisplayLog) << name.toString();

        if (CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(name.toString())) {
            if (!setFilters.empty()) {
                SetToPrintingsMap setMap = info->getSets();
                for (const CardFilter *setFilter : setFilters) {
                    if (setMap.contains(setFilter->term())) {
                        for (PrintingInfo printing : setMap[setFilter->term()]) {
                            addCardToDisplay(ExactCard(info, printing));
                        }
                    }
                }
            } else {
                addCardToDisplay(CardDatabaseManager::query()->getPreferredCard(info));
            }
        } else {
            qCDebug(VisualDatabaseDisplayLog) << "Card not found in database!";
        }
    }
    currentPage++;
}

void VisualDatabaseDisplayWidget::modelDirty() const
{
    debounceTimer->start(debounceTime);
}

void VisualDatabaseDisplayWidget::sortCardList(const QStringList &properties,
                                               Qt::SortOrder order = Qt::AscendingOrder) const
{
    CardInfoComparator comparator(properties, order);
    std::sort(cards->begin(), cards->end(), [comparator](const ExactCard &a, const ExactCard &b) {
        return comparator(a.getCardPtr(), b.getCardPtr());
    });
}

void VisualDatabaseDisplayWidget::databaseDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
    (void)topLeft;
    (void)bottomRight;
    qCDebug(VisualDatabaseDisplayLog) << "Database Data changed";
}
