/*
 * Fooyin
 * Copyright © 2023, 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 "playlistorganiser.h"

#include "dialog/autoplaylistdialog.h"
#include "playlist/playlistcontroller.h"
#include "playlist/playlistinteractor.h"
#include "playlistorganiserconfigwidget.h"
#include "playlistorganiserdelegate.h"
#include "playlistorganisermodel.h"

#include <core/library/musiclibrary.h>
#include <core/playlist/playlist.h>
#include <core/playlist/playlisthandler.h>
#include <core/track.h>
#include <gui/guiconstants.h>
#include <gui/guisettings.h>
#include <utils/actions/actionmanager.h>
#include <utils/actions/command.h>
#include <utils/actions/widgetcontext.h>
#include <utils/crypto.h>
#include <utils/settings/settingsmanager.h>
#include <utils/utils.h>

#include <QApplication>
#include <QContextMenuEvent>
#include <QFileInfo>
#include <QJsonObject>
#include <QMainWindow>
#include <QMenu>
#include <QMouseEvent>
#include <QTreeView>
#include <QVBoxLayout>

#include <algorithm>
#include <ranges>
#include <stack>

using namespace Qt::StringLiterals;

constexpr auto OrganiserModel                   = "PlaylistOrganiser/Model";
constexpr auto OrganiserState                   = "PlaylistOrganiser/State";
constexpr auto OrganiserLeftScript              = "PlaylistOrganiser/LeftScript";
constexpr auto OrganiserRightScript             = "PlaylistOrganiser/RightScript";
constexpr auto OrganiserPlayingTextColour       = "PlaylistOrganiser/PlayingTextColour";
constexpr auto OrganiserPlayingBackgroundColour = "PlaylistOrganiser/PlayingBackgroundColour";

namespace {
QByteArray saveExpandedState(QTreeView* view, QAbstractItemModel* model)
{
    QByteArray data;
    QDataStream stream{&data, QIODeviceBase::WriteOnly};
    stream.setVersion(QDataStream::Qt_6_0);

    std::stack<QModelIndex> indexesToSave;
    indexesToSave.emplace();

    while(!indexesToSave.empty()) {
        const QModelIndex index = indexesToSave.top();
        indexesToSave.pop();

        if(model->hasChildren(index)) {
            if(index.isValid()) {
                stream << view->isExpanded(index);
            }
            const int childCount = model->rowCount(index);
            for(int row{childCount - 1}; row >= 0; --row) {
                indexesToSave.push(model->index(row, 0, index));
            }
        }
    }

    data = qCompress(data, 9);

    return data;
}

void restoreExpandedState(QTreeView* view, QAbstractItemModel* model, QByteArray data)
{
    if(data.isEmpty()) {
        return;
    }

    data = qUncompress(data);

    QDataStream stream{&data, QIODeviceBase::ReadOnly};
    stream.setVersion(QDataStream::Qt_6_0);

    std::stack<QModelIndex> indexesToSave;
    indexesToSave.emplace();

    while(!indexesToSave.empty()) {
        const QModelIndex index = indexesToSave.top();
        indexesToSave.pop();

        if(model->hasChildren(index)) {
            if(index.isValid()) {
                bool expanded;
                stream >> expanded;
                view->setExpanded(index, expanded);
            }
            const int childCount = model->rowCount(index);
            for(int row{childCount - 1}; row >= 0; --row) {
                indexesToSave.push(model->index(row, 0, index));
            }
        }
    }
}

class OrganiserTreeView : public QTreeView
{
public:
    explicit OrganiserTreeView(QWidget* parent = nullptr)
        : QTreeView(parent)
    { }

protected:
    void mousePressEvent(QMouseEvent* event) override
    {
        if(event->button() > Qt::LeftButton) {
            event->ignore();
            return;
        }
        QTreeView::mousePressEvent(event);
    }
};

bool isPlaylistUrl(const QUrl& url)
{
    if(!url.isLocalFile()) {
        return false;
    }

    static const QStringList extensions = [] {
        QStringList values = Fooyin::Playlist::supportedPlaylistExtensions();
        std::ranges::transform(values, values.begin(), [](const QString& ext) { return ext.toLower(); });
        return values;
    }();

    const QString extension = QFileInfo(url.toLocalFile()).suffix().toLower();
    return extensions.contains(extension);
}

QString normaliseColour(const QString& colour)
{
    const QColor parsedColour{colour};
    return parsedColour.isValid() ? parsedColour.name(QColor::HexArgb) : QString{};
}

QColor defaultPlayingBackgroundColour()
{
    QColor colour = QApplication::palette().highlight().color();
    colour.setAlpha(90);
    return colour;
}

} // namespace

namespace Fooyin {
PlaylistOrganiser::PlaylistOrganiser(ActionManager* actionManager, PlaylistInteractor* playlistInteractor,
                                     SettingsManager* settings, QWidget* parent)
    : FyWidget{parent}
    , m_actionManager{actionManager}
    , m_settings{settings}
    , m_playlistInteractor{playlistInteractor}
    , m_playerController{playlistInteractor->playlistController()->playerController()}
    , m_organiserTree{new OrganiserTreeView(this)}
    , m_model{new PlaylistOrganiserModel(playlistInteractor->handler(), m_playerController)}
    , m_context{new WidgetContext(this, Context{Id{"Context.PlaylistOrganiser."}.append(Utils::generateUniqueHash())},
                                  this)}
    , m_removePlaylist{new QAction(tr("Remove"), this)}
    , m_removeCmd{actionManager->registerAction(m_removePlaylist, Constants::Actions::Remove, m_context->context())}
    , m_renamePlaylist{new QAction(tr("Rename"), this)}
    , m_renameCmd{actionManager->registerAction(m_renamePlaylist, Constants::Actions::Rename, m_context->context())}
    , m_newGroup{new QAction(tr("New group"), this)}
    , m_newGroupCmd{actionManager->registerAction(m_newGroup, "PlaylistOrganiser.NewGroup", m_context->context())}
    , m_newPlaylist{new QAction(tr("Create playlist"), this)}
    , m_newPlaylistCmd{actionManager->registerAction(m_newPlaylist, Constants::Actions::NewPlaylist,
                                                     m_context->context())}
    , m_newAutoPlaylist{new QAction(tr("Create autoplaylist"), this)}
    , m_newAutoPlaylistCmd{actionManager->registerAction(m_newAutoPlaylist, Constants::Actions::NewAutoPlaylist,
                                                         m_context->context())}
    , m_editAutoPlaylist{new QAction(tr("Edit autoplaylist"), this)}
    , m_editAutoPlaylistCmd{actionManager->registerAction(m_editAutoPlaylist, Constants::Actions::EditAutoPlaylist,
                                                          m_context->context())}

    , m_sortAllPlaylists{new QAction(tr("Sort all playlists"), this)}
    , m_sortAllPlaylistsCmd{actionManager->registerAction(m_sortAllPlaylists, "PlaylistOrganiser.SortAllPlaylists",
                                                          m_context->context())}

    , m_sortGroupPlaylists{new QAction(tr("Sort playlists in group"), this)}
    , m_sortGroupPlaylistsCmd{actionManager->registerAction(
          m_sortGroupPlaylists, "PlaylistOrganiser.SortGroupPlaylists", m_context->context())}
{
    auto* layout = new QVBoxLayout(this);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->addWidget(m_organiserTree);

    m_organiserTree->setHeaderHidden(true);
    m_organiserTree->setUniformRowHeights(false);
    m_organiserTree->setSelectionBehavior(QAbstractItemView::SelectRows);
    m_organiserTree->setSelectionMode(QAbstractItemView::ExtendedSelection);
    m_organiserTree->setDragEnabled(true);
    m_organiserTree->setAcceptDrops(true);
    m_organiserTree->setDragDropMode(QAbstractItemView::DragDrop);
    m_organiserTree->setDefaultDropAction(Qt::MoveAction);
    m_organiserTree->setDropIndicatorShown(true);
    m_organiserTree->setAllColumnsShowFocus(true);

    m_organiserTree->setModel(m_model);
    m_organiserTree->setItemDelegate(new PlaylistOrganiserDelegate(m_organiserTree));

    actionManager->addContextObject(m_context);

    const QStringList editCategory{tr("Edit")};
    const QStringList organiserCategory{tr("Playlist Organiser")};

    m_removeCmd->setCategories(editCategory);
    m_removeCmd->setDefaultShortcut(QKeySequence::Delete);

    m_renameCmd->setCategories(editCategory);
    m_renameCmd->setDefaultShortcut(Qt::Key_F2);
    m_renameCmd->setAttribute(ProxyAction::UpdateText);

    m_newGroupCmd->setCategories(organiserCategory);
    m_newGroup->setStatusTip(tr("Create a new empty group"));
    m_newGroupCmd->setAttribute(ProxyAction::UpdateText);
    m_newGroupCmd->setDefaultShortcut(QKeySequence::AddTab);

    m_sortAllPlaylistsCmd->setCategories(organiserCategory);
    m_sortAllPlaylists->setStatusTip(tr("Sort all playlists alphabetically"));
    m_sortGroupPlaylistsCmd->setCategories(organiserCategory);
    m_sortGroupPlaylists->setStatusTip(tr("Sort playlists in the selected group alphabetically"));

    const auto sortAndRestoreState = [this](auto&& sortFn) {
        const QByteArray state = saveExpandedState(m_organiserTree, m_model);
        sortFn();
        restoreExpandedState(m_organiserTree, m_model, state);
    };

    QAction::connect(m_newGroup, &QAction::triggered, this, [this]() { createGroup(actionIndex()); });
    QAction::connect(m_sortAllPlaylists, &QAction::triggered, this, [this, sortAndRestoreState]() {
        sortAndRestoreState([this]() { m_model->sortAllPlaylists(PlaylistOrganiserModel::SortOrder::Ascending); });
    });
    QAction::connect(m_sortGroupPlaylists, &QAction::triggered, this, [this, sortAndRestoreState]() {
        const auto indexes = actionIndexes();
        sortAndRestoreState(
            [this, indexes]() { m_model->sortGroupPlaylists(indexes, PlaylistOrganiserModel::SortOrder::Ascending); });
    });
    QObject::connect(m_removePlaylist, &QAction::triggered, this, [this]() { m_model->removeItems(actionIndexes()); });
    QObject::connect(m_renamePlaylist, &QAction::triggered, this, [this]() { m_organiserTree->edit(actionIndex()); });
    QObject::connect(m_newPlaylist, &QAction::triggered, this, [this]() { createPlaylist(actionIndex(), false); });
    QObject::connect(m_newAutoPlaylist, &QAction::triggered, this, [this]() { createPlaylist(actionIndex(), true); });
    QObject::connect(m_editAutoPlaylist, &QAction::triggered, this, [this]() { editAutoPlaylist(actionIndex()); });

    QObject::connect(m_model, &QAbstractItemModel::rowsMoved, this,
                     [this](const QModelIndex& /*source*/, int /*first*/, int /*last*/, const QModelIndex& target) {
                         if(target.isValid()) {
                             m_organiserTree->expand(target);
                         }
                     });
    QObject::connect(m_model, &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex& index) {
        if(index.isValid()) {
            m_organiserTree->expand(index);
        }
    });
    QObject::connect(m_model, &PlaylistOrganiserModel::filesDroppedOnPlaylist, this,
                     &PlaylistOrganiser::filesToPlaylist);
    QObject::connect(m_model, &PlaylistOrganiserModel::filesDroppedOnGroup, this, &PlaylistOrganiser::filesToGroup);
    QObject::connect(m_model, &PlaylistOrganiserModel::tracksDroppedOnPlaylist, this,
                     &PlaylistOrganiser::tracksToPlaylist);
    QObject::connect(m_model, &PlaylistOrganiserModel::tracksDroppedOnGroup, this, &PlaylistOrganiser::tracksToGroup);
    QObject::connect(m_organiserTree->selectionModel(), &QItemSelectionModel::selectionChanged, this,
                     [this]() { selectionChanged(); });

    QObject::connect(
        m_playlistInteractor->handler(), &PlaylistHandler::playlistAdded, this, [this](Playlist* playlist) {
            if(!m_creatingPlaylist) {
                QMetaObject::invokeMethod(
                    m_model, [this, playlist]() { m_model->playlistAdded(playlist); }, Qt::QueuedConnection);
            }
        });
    QObject::connect(m_playlistInteractor->handler(), &PlaylistHandler::playlistRemoved, m_model,
                     &PlaylistOrganiserModel::playlistRemoved);
    QObject::connect(m_playlistInteractor->handler(), &PlaylistHandler::playlistRenamed, m_model,
                     &PlaylistOrganiserModel::playlistRenamed);
    QObject::connect(
        m_playlistInteractor->playlistController(), &PlaylistController::currentPlaylistChanged, this,
        [this]() { QMetaObject::invokeMethod(m_model, [this]() { selectCurrentPlaylist(); }, Qt::QueuedConnection); });
    QObject::connect(m_playlistInteractor->playlistController(), &PlaylistController::playlistsLoaded, this,
                     [this]() { selectCurrentPlaylist(); });

    m_settings->subscribe<Settings::Gui::ResolvedAppStyle>(this, [this]() {
        applyConfig(m_config);
        m_organiserTree->doItemsLayout();
        m_organiserTree->viewport()->update();
    });

    if(m_model->restoreModel(m_settings->fileValue(OrganiserModel).toByteArray())) {
        const auto state = m_settings->fileValue(OrganiserState).toByteArray();
        restoreExpandedState(m_organiserTree, m_model, state);
        m_model->populateMissing();
    }
    else {
        m_model->populate();
    }

    m_config = defaultConfig();
    applyConfig(m_config);

    selectCurrentPlaylist();
}

PlaylistOrganiser::~PlaylistOrganiser()
{
    m_settings->fileSet(OrganiserModel, m_model->saveModel());
    m_settings->fileSet(OrganiserState, saveExpandedState(m_organiserTree, m_model));
}

QString PlaylistOrganiser::name() const
{
    return tr("Playlist Organiser");
}

QString PlaylistOrganiser::layoutName() const
{
    return u"PlaylistOrganiser"_s;
}

void PlaylistOrganiser::saveLayoutData(QJsonObject& layout)
{
    saveConfigToLayout(m_config, layout);
}

void PlaylistOrganiser::loadLayoutData(const QJsonObject& layout)
{
    applyConfig(configFromLayout(layout));
}

PlaylistOrganiser::ConfigData PlaylistOrganiser::factoryConfig() const
{
    return {
        .leftScript              = PlaylistOrganiserModel::defaultLeftDisplayScript(),
        .rightScript             = PlaylistOrganiserModel::defaultRightDisplayScript(),
        .playingTextColour       = {},
        .playingBackgroundColour = {},
    };
}

PlaylistOrganiser::ConfigData PlaylistOrganiser::defaultConfig() const
{
    auto config{factoryConfig()};

    config.leftScript  = m_settings->fileValue(OrganiserLeftScript, config.leftScript).toString();
    config.rightScript = m_settings->fileValue(OrganiserRightScript, config.rightScript).toString();

    config.playingTextColour = normaliseColour(m_settings->fileValue(OrganiserPlayingTextColour, {}).toString());
    config.playingBackgroundColour
        = normaliseColour(m_settings->fileValue(OrganiserPlayingBackgroundColour, {}).toString());

    return config;
}

const PlaylistOrganiser::ConfigData& PlaylistOrganiser::currentConfig() const
{
    return m_config;
}

void PlaylistOrganiser::saveDefaults(const ConfigData& config) const
{
    m_settings->fileSet(OrganiserLeftScript, config.leftScript);
    m_settings->fileSet(OrganiserRightScript, config.rightScript);
    m_settings->fileSet(OrganiserPlayingTextColour, normaliseColour(config.playingTextColour));
    m_settings->fileSet(OrganiserPlayingBackgroundColour, normaliseColour(config.playingBackgroundColour));
}

void PlaylistOrganiser::clearSavedDefaults() const
{
    m_settings->fileRemove(OrganiserLeftScript);
    m_settings->fileRemove(OrganiserRightScript);
    m_settings->fileRemove(OrganiserPlayingTextColour);
    m_settings->fileRemove(OrganiserPlayingBackgroundColour);
}

void PlaylistOrganiser::applyConfig(const ConfigData& config)
{
    m_config.leftScript              = config.leftScript;
    m_config.rightScript             = config.rightScript;
    m_config.playingTextColour       = normaliseColour(config.playingTextColour);
    m_config.playingBackgroundColour = normaliseColour(config.playingBackgroundColour);

    const QColor playingTextColour{m_config.playingTextColour};
    QColor playingBackgroundColour{m_config.playingBackgroundColour};
    if(!playingBackgroundColour.isValid()) {
        playingBackgroundColour = defaultPlayingBackgroundColour();
    }

    m_model->setDisplayScripts(m_config.leftScript, m_config.rightScript);
    m_model->setColours(playingTextColour, playingBackgroundColour);
}

void PlaylistOrganiser::contextMenuEvent(QContextMenuEvent* event)
{
    auto* menu = new QMenu(this);
    menu->setAttribute(Qt::WA_DeleteOnClose);
    Utils::forwardMenuStatusTips(menu);

    QObject::connect(menu, &QObject::destroyed, this, [this]() {
        m_contextMenuIndex = QPersistentModelIndex{};
        m_removePlaylist->setEnabled(true);
        m_renamePlaylist->setEnabled(true);
    });

    const QPoint point      = m_organiserTree->viewport()->mapFrom(this, event->pos());
    const QModelIndex index = m_organiserTree->indexAt(point);
    m_contextMenuIndex      = index;

    int playlistCount{0};
    int groupCount{0};

    const auto actionRows = actionIndexes();
    for(const auto& actionIndex : actionRows) {
        if(actionIndex.data(PlaylistOrganiserItem::ItemType).toInt() == PlaylistOrganiserItem::PlaylistItem) {
            ++playlistCount;
        }
        else {
            ++groupCount;
        }
    }

    if(playlistCount > 0 && groupCount == 0) {
        m_renamePlaylist->setStatusTip(tr("Rename the selected playlist"));
        m_removePlaylist->setStatusTip(tr("Remove the selected playlists"));
    }
    else if(groupCount > 0 && playlistCount == 0) {
        m_renamePlaylist->setStatusTip(tr("Rename the selected group"));
        m_removePlaylist->setStatusTip(tr("Remove the selected groups"));
    }
    else if(playlistCount > 0 && groupCount > 0) {
        m_removePlaylist->setStatusTip(tr("Remove the selected playlists and groups"));
    }

    m_removePlaylist->setEnabled(index.isValid());
    m_renamePlaylist->setEnabled(actionRows.size() == 1 && index.isValid());

    menu->addAction(m_newPlaylistCmd->action());
    menu->addAction(m_newAutoPlaylistCmd->action());
    menu->addAction(m_newGroupCmd->action());

    const auto removedPlaylists = m_playlistInteractor->handler()->removedPlaylists();
    if(!removedPlaylists.empty()) {
        auto* restoreMenu = new QMenu(tr("Restore deleted playlist"), menu);
        for(const auto* removedPlaylist : removedPlaylists) {
            const UId removedPlaylistId = removedPlaylist->id();
            auto* restoreAction         = new QAction(removedPlaylist->name(), restoreMenu);
            QObject::connect(restoreAction, &QAction::triggered, this, [this, removedPlaylistId]() {
                m_playlistInteractor->handler()->restorePlaylist(removedPlaylistId);
            });
            restoreMenu->addAction(restoreAction);
        }

        menu->addMenu(restoreMenu);
    }

    menu->addSeparator();

    menu->addAction(m_sortAllPlaylistsCmd->action());
    menu->addAction(m_sortGroupPlaylistsCmd->action());
    m_sortGroupPlaylists->setEnabled(groupCount == 1 && playlistCount == 0); // only enable if a group is selected

    menu->addSeparator();
    addConfigureAction(menu, false);

    if(index.data(PlaylistOrganiserItem::ItemType).toInt() == PlaylistOrganiserItem::PlaylistItem) {
        if(auto* savePlaylist = m_actionManager->command(Constants::Actions::SavePlaylist)) {
            menu->addSeparator();
            menu->addAction(savePlaylist->action());
        }
    }

    menu->addSeparator();

    if(playlistCount == 1) {
        if(auto* playlist = actionIndex().data(PlaylistOrganiserItem::PlaylistData).value<Playlist*>()) {
            if(playlist->isAutoPlaylist()) {
                menu->addAction(m_editAutoPlaylistCmd->action());
            }
        }
    }

    menu->addAction(m_renameCmd->action());
    menu->addAction(m_removeCmd->action());

    menu->popup(event->globalPos());
}

QModelIndex PlaylistOrganiser::actionIndex() const
{
    if(m_contextMenuIndex.isValid()) {
        return m_contextMenuIndex;
    }

    const auto indexes = actionIndexes();
    return indexes.empty() ? QModelIndex{} : indexes.front();
}

QModelIndexList PlaylistOrganiser::actionIndexes() const
{
    if(m_contextMenuIndex.isValid()) {
        return {m_contextMenuIndex};
    }

    return m_organiserTree->selectionModel()->selectedIndexes();
}

void PlaylistOrganiser::selectionChanged()
{
    const QModelIndexList selectedIndexes = m_organiserTree->selectionModel()->selectedIndexes();
    if(selectedIndexes.empty()) {
        return;
    }

    const QModelIndex firstIndex = selectedIndexes.front();
    if(firstIndex.data(PlaylistOrganiserItem::ItemType).toInt() != PlaylistOrganiserItem::PlaylistItem) {
        return;
    }

    auto* playlist       = firstIndex.data(PlaylistOrganiserItem::PlaylistData).value<Playlist*>();
    const UId playlistId = playlist->id();

    if(std::exchange(m_currentPlaylistId, playlistId) != playlistId) {
        m_playlistInteractor->playlistController()->changeCurrentPlaylist(playlist);
    }
}

void PlaylistOrganiser::selectCurrentPlaylist()
{
    auto* playlist = m_playlistInteractor->playlistController()->currentPlaylist();
    if(!playlist) {
        return;
    }

    const UId playlistId = playlist->id();
    if(std::exchange(m_currentPlaylistId, playlistId) != playlistId) {
        const QModelIndex index = m_model->indexForPlaylist(playlist);
        if(index.isValid()) {
            m_organiserTree->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
        }
    }
}

void PlaylistOrganiser::createGroup(const QModelIndex& index) const
{
    QModelIndex parent{index};
    if(parent.data(PlaylistOrganiserItem::ItemType).toInt() == PlaylistOrganiserItem::PlaylistItem) {
        parent = parent.parent();
    }
    const QModelIndex groupIndex = m_model->createGroup(parent);
    m_organiserTree->edit(groupIndex);
}

void PlaylistOrganiser::createPlaylist(const QModelIndex& index, bool autoPlaylist)
{
    m_creatingPlaylist = true;

    const auto addToModel = [this, index](Playlist* playlist) {
        QModelIndex parent{index};
        if(parent.data(PlaylistOrganiserItem::ItemType).toInt() == PlaylistOrganiserItem::PlaylistItem) {
            parent = parent.parent();
        }
        return m_model->createPlaylist(playlist, parent);
    };

    if(autoPlaylist) {
        auto* autoDialog = new AutoPlaylistDialog(Utils::getMainWindow());
        autoDialog->setAttribute(Qt::WA_DeleteOnClose);
        QObject::connect(autoDialog, &QDialog::finished, this, [this]() { m_creatingPlaylist = false; });
        QObject::connect(
            autoDialog, &AutoPlaylistDialog::playlistEdited, this,
            [this, addToModel](const QString& name, const QString& query, const QString& sortQuery, bool forceSorted) {
                if(auto* playlist
                   = m_playlistInteractor->handler()->createNewAutoPlaylist(name, query, sortQuery, forceSorted)) {
                    addToModel(playlist);
                }
            });
        autoDialog->show();
        return;
    }

    if(auto* playlist = m_playlistInteractor->handler()->createEmptyPlaylist()) {
        const QModelIndex playlistIndex = addToModel(playlist);
        m_organiserTree->edit(playlistIndex);
    }

    m_creatingPlaylist = false;
}

void PlaylistOrganiser::editAutoPlaylist(const QModelIndex& index)
{
    if(!index.isValid()) {
        return;
    }

    if(auto* playlist = index.data(PlaylistOrganiserItem::PlaylistData).value<Playlist*>()) {
        if(playlist->isAutoPlaylist()) {
            auto* autoDialog
                = new AutoPlaylistDialog(m_playlistInteractor->handler(), playlist, Utils::getMainWindow());
            autoDialog->setAttribute(Qt::WA_DeleteOnClose);
            autoDialog->show();
        }
    }
}

void PlaylistOrganiser::filesToPlaylist(const QList<QUrl>& urls, const UId& id)
{
    if(urls.empty()) {
        return;
    }

    m_playlistInteractor->filesToPlaylist(urls, id);
}

void PlaylistOrganiser::filesToGroup(const QList<QUrl>& urls, const QString& group, int index)
{
    if(urls.empty()) {
        return;
    }

    filesToGroupOrdered(urls, group, index);
}

void PlaylistOrganiser::filesToGroupOrdered(const QList<QUrl>& urls, const QString& group, int index)
{
    if(urls.empty()) {
        return;
    }

    const QUrl& firstUrl  = urls.constFirst();
    const int insertIndex = index < 0 ? -1 : index;
    QList remainingUrls{urls};

    if(isPlaylistUrl(firstUrl)) {
        remainingUrls.pop_front();

        const QString name = QFileInfo{firstUrl.toLocalFile()}.completeBaseName();
        m_playlistInteractor->playlistFilesToTracks(
            {firstUrl}, [this, group, name, insertIndex, remainingUrls](const TrackList& tracks) {
                int nextIndex = insertIndex;

                const QSignalBlocker block{m_playlistInteractor->handler()};
                if(auto* playlist = m_playlistInteractor->handler()->createNewPlaylist(name, tracks)) {
                    m_model->playlistInserted(playlist, group, insertIndex);
                    if(insertIndex >= 0) {
                        nextIndex = insertIndex + 1;
                    }
                }

                filesToGroupOrdered(remainingUrls, group, nextIndex);
            });
        return;
    }

    QList<QUrl> trackUrls;
    while(!remainingUrls.empty() && !isPlaylistUrl(remainingUrls.constFirst())) {
        trackUrls.append(remainingUrls.constFirst());
        remainingUrls.pop_front();
    }

    m_playlistInteractor->filesToTracks(trackUrls, [this, group, insertIndex, remainingUrls](const TrackList& tracks) {
        int nextIndex{insertIndex};

        const QSignalBlocker block{m_playlistInteractor->handler()};
        const QString name = Track::findCommonField(tracks);

        if(auto* playlist = m_playlistInteractor->handler()->createNewPlaylist(name, tracks)) {
            m_model->playlistInserted(playlist, group, insertIndex);
            if(insertIndex >= 0) {
                nextIndex = insertIndex + 1;
            }
        }

        filesToGroupOrdered(remainingUrls, group, nextIndex);
    });
}

void PlaylistOrganiser::importPlaylists(const QList<QUrl>& urls, const QString& group, int index)
{
    if(urls.empty()) {
        return;
    }

    const QUrl& url       = urls.constFirst();
    const QString name    = QFileInfo{url.toLocalFile()}.completeBaseName();
    const int insertIndex = index < 0 ? -1 : index;

    QList remainingUrls{urls};
    remainingUrls.pop_front();

    m_playlistInteractor->playlistFilesToTracks(
        {url}, [this, group, name, insertIndex, remainingUrls](const TrackList& tracks) {
            int nextIndex = insertIndex;

            const QSignalBlocker block{m_playlistInteractor->handler()};
            if(auto* playlist = m_playlistInteractor->handler()->createNewPlaylist(name, tracks)) {
                m_model->playlistInserted(playlist, group, insertIndex);
                if(insertIndex >= 0) {
                    nextIndex = insertIndex + 1;
                }
            }

            importPlaylists(remainingUrls, group, nextIndex);
        });
}

void PlaylistOrganiser::tracksToPlaylist(const std::vector<int>& trackIds, const UId& id)
{
    const auto tracks = m_playlistInteractor->library()->tracksForIds(trackIds);
    if(tracks.empty()) {
        return;
    }

    if(m_playlistInteractor->handler()->playlistById(id)) {
        m_playlistInteractor->handler()->appendToPlaylist(id, tracks);
    }
}

void PlaylistOrganiser::tracksToGroup(const std::vector<int>& trackIds, const QString& group, int index)
{
    const auto tracks = m_playlistInteractor->library()->tracksForIds(trackIds);
    if(tracks.empty()) {
        return;
    }

    const QSignalBlocker block{m_playlistInteractor->handler()};

    const QString name = Track::findCommonField(tracks);
    if(auto* playlist = m_playlistInteractor->handler()->createNewPlaylist(name, tracks)) {
        m_model->playlistInserted(playlist, group, index);
    }
}

PlaylistOrganiser::ConfigData PlaylistOrganiser::configFromLayout(const QJsonObject& layout) const
{
    ConfigData config{defaultConfig()};

    if(layout.contains("LeftScript"_L1)) {
        config.leftScript = layout.value("LeftScript"_L1).toString();
    }
    if(layout.contains("RightScript"_L1)) {
        config.rightScript = layout.value("RightScript"_L1).toString();
    }
    if(layout.contains("PlayingTextColour"_L1)) {
        config.playingTextColour = normaliseColour(layout.value("PlayingTextColour"_L1).toString());
    }
    if(layout.contains("PlayingBackgroundColour"_L1)) {
        config.playingBackgroundColour = normaliseColour(layout.value("PlayingBackgroundColour"_L1).toString());
    }

    return config;
}

void PlaylistOrganiser::saveConfigToLayout(const ConfigData& config, QJsonObject& layout)
{
    layout["LeftScript"_L1]              = config.leftScript;
    layout["RightScript"_L1]             = config.rightScript;
    layout["PlayingTextColour"_L1]       = normaliseColour(config.playingTextColour);
    layout["PlayingBackgroundColour"_L1] = normaliseColour(config.playingBackgroundColour);
}

void PlaylistOrganiser::openConfigDialog()
{
    showConfigDialog(new PlaylistOrganiserConfigDialog(this, this));
}
} // namespace Fooyin

#include "moc_playlistorganiser.cpp"
