/*
 * Fooyin
 * Copyright © 2024, Luke Taylor <luket@pm.me>
 *
 * Fooyin is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Fooyin is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Fooyin.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "searchdialog.h"

#include "playlist/playlistcontroller.h"
#include "playlist/playlistinteractor.h"
#include "playlist/playlistuicontroller.h"
#include "playlist/playlistview.h"
#include "playlist/playlistwidget.h"

#include <core/application.h>
#include <core/coresettings.h>
#include <gui/coverprovider.h>
#include <gui/guiconstants.h>
#include <gui/iconloader.h>
#include <utils/settings/settingsmanager.h>
#include <utils/signalthrottler.h>
#include <utils/utils.h>

#include <QAction>
#include <QDesktopServices>
#include <QJsonDocument>
#include <QJsonObject>
#include <QKeyEvent>
#include <QLineEdit>
#include <QMenu>
#include <QPushButton>
#include <QVBoxLayout>

using namespace std::chrono_literals;
using namespace Qt::StringLiterals;

constexpr auto AutoSelect    = "Searching/AutoSelect";
constexpr auto ShowAll       = "Searching/ShowAll";
constexpr auto PlaylistState = "Searching/PlaylistState";
constexpr auto LibraryState  = "Searching/LibraryState";

namespace Fooyin {
SearchDialog::SearchDialog(ActionManager* actionManager, PlaylistInteractor* playlistInteractor,
                           CoverProvider* coverProvider, Application* core, GuiStyleProvider* styleProvider,
                           TrackSelectionController* selectionController, Target target, QWidget* parent)
    : QDialog{parent}
    , m_target{target}
    , m_playlistInteractor{playlistInteractor}
    , m_settings{core->settingsManager()}
    , m_searchBar{new QLineEdit(this)}
    , m_view{m_target == Target::Library
                 ? PlaylistWidget::createDetachedLibrarySearch(actionManager, playlistInteractor, selectionController,
                                                               coverProvider, core, styleProvider, this)
                 : PlaylistWidget::createDetachedPlaylistSearch(actionManager, playlistInteractor, selectionController,
                                                                coverProvider, core, styleProvider, this)}
    , m_autoSelect{m_settings->fileValue(AutoSelect, false).toBool()}
    , m_showAll{m_settings->fileValue(ShowAll, false).toBool()}
{
    auto* layout = new QVBoxLayout(this);
    layout->setContentsMargins({});

    layout->addWidget(m_searchBar);
    layout->addWidget(m_view);

    auto* searchMenu = new QAction(tr("Options"), this);
    Gui::setThemeIcon(searchMenu, Constants::Icons::Options);
    QObject::connect(searchMenu, &QAction::triggered, this, &SearchDialog::showOptionsMenu);
    m_searchBar->addAction(searchMenu, QLineEdit::TrailingPosition);

    QObject::connect(m_view->view()->selectionModel(), &QItemSelectionModel::selectionChanged, this,
                     &SearchDialog::selectInPlaylist);
    QObject::connect(m_view->model(), &PlaylistModel::modelReset, this, [this]() {
        updateTitle();
        if(m_autoSelect && m_target == Target::Playlist) {
            m_view->view()->selectAll();
        }
    });
    QObject::connect(m_playlistInteractor->playlistController(), &PlaylistController::currentPlaylistChanged, this,
                     &SearchDialog::search);
    QObject::connect(m_playlistInteractor->playlistController(), &PlaylistController::currentPlaylistTracksChanged,
                     this, &SearchDialog::search);

    auto* throttler = new SignalThrottler(this);
    throttler->setTimeout(100ms);
    QObject::connect(m_searchBar, &QLineEdit::textChanged, throttler, &SignalThrottler::throttle);
    QObject::connect(throttler, &SignalThrottler::triggered, this, &SearchDialog::search);

    updateTitle();
    loadState();
    if(m_showAll) {
        search();
    }
}

void SearchDialog::done(int value)
{
    saveState();
    QDialog::done(value);
}

QSize SearchDialog::sizeHint() const
{
    return {800, 480};
}

void SearchDialog::setSearch(const QString& search)
{
    m_searchBar->setText(search);
    this->search();
}

void SearchDialog::keyPressEvent(QKeyEvent* event)
{
    if(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
        m_view->startPlayback();
    }

    QDialog::keyPressEvent(event);
}

void SearchDialog::search()
{
    m_view->searchEvent(
        {.text = m_searchBar->text(), .emptyMode = m_showAll ? EmptySearchMode::ShowAll : EmptySearchMode::Clear});
}

void SearchDialog::updateTitle()
{
    QString title = (m_target == Target::Library) ? tr("Search Library") : tr("Search Playlist");

    if(!m_showAll && m_searchBar->text().isEmpty()) {
        m_view->view()->setEmptyText(tr("Start typing to search"));
    }
    else {
        title += " ("_L1 + tr("%Ln result(s)", "", m_view->trackCount()) + ")"_L1;
        m_view->view()->setEmptyText(tr("No results"));
    }

    setWindowTitle(title);
}

void SearchDialog::showOptionsMenu()
{
    auto* menu = new QMenu(tr("Options"), this);
    menu->setAttribute(Qt::WA_DeleteOnClose);

    if(m_target == Target::Playlist) {
        auto* autoSelect = new QAction(tr("Auto-select on search"), menu);
        QObject::connect(autoSelect, &QAction::triggered, this, [this](const bool checked) {
            m_autoSelect = checked;
            m_settings->fileSet(AutoSelect, checked);
        });
        autoSelect->setCheckable(true);
        autoSelect->setChecked(m_autoSelect);
        menu->addAction(autoSelect);
    }

    auto* showAll = new QAction(tr("Show all when search is empty"), menu);
    QObject::connect(showAll, &QAction::triggered, this, [this](const bool checked) {
        m_showAll = checked;
        m_settings->fileSet(ShowAll, checked);
        search();
    });
    showAll->setCheckable(true);
    showAll->setChecked(m_showAll);
    menu->addAction(showAll);

    auto* searching = new QAction(tr("Help"), menu);
    QObject::connect(searching, &QAction::triggered, this,
                     []() { QDesktopServices::openUrl(u"https://docs.fooyin.org/en/latest/searching/basics.html"_s); });
    menu->addAction(searching);

    QStyleOptionFrame opt;
    opt.initFrom(m_searchBar);

    const QRect rect = m_searchBar->style()->subElementRect(QStyle::SE_LineEditContents, &opt, m_searchBar);
    const QPoint pos{rect.right() - 5, rect.center().y()};

    menu->popup(m_searchBar->mapToGlobal(pos));
}

void SearchDialog::selectInPlaylist()
{
    if(m_target != Target::Playlist) {
        return;
    }

    std::vector<int> trackIds;

    const auto selected = m_view->view()->selectionModel()->selectedIndexes();
    for(const QModelIndex& index : selected) {
        trackIds.emplace_back(index.data(PlaylistItem::Role::TrackId).toInt());
    }

    m_playlistInteractor->playlistController()->uiController()->selectTrackIds(trackIds);
}

void SearchDialog::saveState()
{
    QJsonObject layout;
    m_view->saveLayoutData(layout);
    layout["Geometry"_L1] = QString::fromUtf8(saveGeometry().toBase64());

    const QByteArray state = QJsonDocument{layout}.toJson(QJsonDocument::Compact).toBase64();

    if(m_target == Target::Playlist) {
        m_settings->fileSet(PlaylistState, state);
    }
    else if(m_target == Target::Library) {
        m_settings->fileSet(LibraryState, state);
    }
}

void SearchDialog::loadState()
{
    QByteArray state;

    if(m_target == Target::Playlist) {
        state = m_settings->fileValue(PlaylistState).toByteArray();
    }
    else if(m_target == Target::Library) {
        state = m_settings->fileValue(LibraryState).toByteArray();
    }

    if(!state.isEmpty()) {
        const QJsonObject layout = QJsonDocument::fromJson(QByteArray::fromBase64(state)).object();
        if(!layout.isEmpty()) {
            m_view->loadLayoutData(layout);

            if(layout.contains("Geometry"_L1)) {
                restoreGeometry(QByteArray::fromBase64(layout.value("Geometry"_L1).toString().toUtf8()));
            }
        }
    }

    m_view->finalise();
}
} // namespace Fooyin

#include "moc_searchdialog.cpp"
