/*
  SPDX-FileCopyrightText: 2025-2026 Laurent Montel <montel@kde.org>

  SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "textautogeneratelistview.h"
#include "config-textautogeneratetext.h"
#include "core/models/textautogeneratemessagesmodel.h"
#include "core/textautogeneratemanager.h"
#include "core/textautogeneratesearchmessagesettings.h"
#include "delegate/textautogeneratelistviewdelegate.h"
#include "textautogeneratemessagewaitingansweranimation.h"
#include "textautogenerateselectedmessagebackgroundanimation.h"
#include "textautogeneratetextwidget_animation_debug.h"
#include <KLocalizedString>
#include <QMouseEvent>
#include <QScrollBar>

using namespace TextAutoGenerateText;
TextAutoGenerateListView::TextAutoGenerateListView(TextAutoGenerateText::TextAutoGenerateManager *manager, QWidget *parent)
    : TextAutoGenerateBaseListView(manager, parent)
{
    auto delegate = new TextAutoGenerateListViewDelegate(manager, this);
    mDelegate = delegate;
    setItemDelegate(mDelegate);
    if (mManager) {
        connect(mManager, &TextAutoGenerateText::TextAutoGenerateManager::currentChatIdChanged, this, &TextAutoGenerateListView::slotCurrentChatIdChanged);
        connect(mManager, &TextAutoGenerateText::TextAutoGenerateManager::showArchiveChanged, this, [this, delegate]() {
            delegate->setShowArchive(mManager->showArchived());
        });
        connect(mManager, &TextAutoGenerateText::TextAutoGenerateManager::showMessageId, this, &TextAutoGenerateListView::goToMessageId);
        connect(mManager, &TextAutoGenerateText::TextAutoGenerateManager::chatInProgressChanged, this, [delegate](bool inProgress) {
            delegate->setInProgress(inProgress);
        });
        connect(delegate, &TextAutoGenerateListViewDelegate::insertBlockCode, mManager, &TextAutoGenerateText::TextAutoGenerateManager::insertBlockCode);
    }

    connect(delegate, &TextAutoGenerateListViewDelegate::updateView, this, &TextAutoGenerateListView::slotUpdateView);
    connect(delegate, &TextAutoGenerateListViewDelegate::updateColors, this, &TextAutoGenerateListView::slotUpdateColors);

    connect(delegate, &TextAutoGenerateListViewDelegate::editMessage, this, &TextAutoGenerateListView::slotEditMessage);
    connect(delegate, &TextAutoGenerateListViewDelegate::copyMessage, this, &TextAutoGenerateListView::slotCopyMessage);
    connect(delegate, &TextAutoGenerateListViewDelegate::cancelRequested, this, &TextAutoGenerateListView::slotCancelRequested);
    connect(delegate, &TextAutoGenerateListViewDelegate::refreshRequested, this, &TextAutoGenerateListView::slotRefreshRequested);
    connect(delegate, &TextAutoGenerateListViewDelegate::removeMessage, this, &TextAutoGenerateListView::slotRemoveRequested);
    connect(delegate, &TextAutoGenerateListViewDelegate::forkRequested, this, &TextAutoGenerateListView::slotForkRequested);
#if HAVE_KTEXTADDONS_TEXT_TO_SPEECH_SUPPORT
    connect(delegate, &TextAutoGenerateListViewDelegate::textToSpeechRequested, this, &TextAutoGenerateListView::slotTextToSpeechRequested);
    connect(delegate, &TextAutoGenerateListViewDelegate::stopTextToSpeechRequested, this, &TextAutoGenerateListView::slotStopTextToSpeechRequested);
#endif
}

TextAutoGenerateListView::~TextAutoGenerateListView() = default;

void TextAutoGenerateListView::slotUpdateView()
{
    viewport()->update();
}

void TextAutoGenerateListView::slotUpdateColors()
{
    auto model = mManager->messagesModelFromChatId(mManager->currentChatId());
    if (model) {
        model->refreshMessageColors();
        clearDocumentCache();
    }
}

void TextAutoGenerateListView::slotEditMessage(const QModelIndex &index)
{
    clearEditingMode();
    auto model = mManager->messagesModelFromChatId(mManager->currentChatId());
    model->setData(index, true, TextAutoGenerateMessagesModel::EditingRole);

    const QByteArray anwserUuid = index.data(TextAutoGenerateMessagesModel::AnswerUuidRole).toByteArray();
    mMessageIdBeingEdited = index.data(TextAutoGenerateMessagesModel::UuidRole).toByteArray();
    const QModelIndex anwseridx = model->indexForUuid(anwserUuid);
    const QList<QByteArray> tools = anwseridx.data(TextAutoGenerateMessagesModel::ToolsRole).value<QList<QByteArray>>();
    Q_EMIT editMessageRequested(index, tools);
}

void TextAutoGenerateListView::clearEditingMode()
{
    if (mMessageIdBeingEdited.isEmpty()) {
        return;
    }
    // Remove old mark as editing
    auto model = mManager->messagesModelFromChatId(mManager->currentChatId());

    const QModelIndex index = model->indexForUuid(mMessageIdBeingEdited);
    if (index.isValid()) {
        model->setData(index, false, TextAutoGenerateMessagesModel::EditingRole);
    }
}

void TextAutoGenerateListView::slotCancelRequested(const QModelIndex &index)
{
    const QByteArray uuid = index.data(TextAutoGenerateMessagesModel::UuidRole).toByteArray();
    if (!uuid.isEmpty()) {
        if (mManager->cancelRequest(mManager->currentChatId(), index)) {
            Q_EMIT cancelRequested(uuid);
        }
    }
}

void TextAutoGenerateListView::slotForkRequested(const QModelIndex &index)
{
    const QByteArray uuid = index.data(TextAutoGenerateMessagesModel::UuidRole).toByteArray();
    if (!uuid.isEmpty()) {
        const QByteArray chatId = mManager->currentChatId();
        mManager->forkDiscussionUntilMessage(chatId, uuid);
    }
}

void TextAutoGenerateListView::slotRemoveRequested(const QModelIndex &index)
{
    const QByteArray uuid = index.data(TextAutoGenerateMessagesModel::UuidRole).toByteArray();
    if (!uuid.isEmpty()) {
        const QByteArray chatId = mManager->currentChatId();
        mManager->removeMessage(chatId, uuid);
    }
}

void TextAutoGenerateListView::slotRefreshRequested(const QModelIndex &index)
{
    const QByteArray uuid = index.data(TextAutoGenerateMessagesModel::UuidRole).toByteArray();

    if (!uuid.isEmpty()) {
        const QList<QByteArray> tools = index.data(TextAutoGenerateMessagesModel::ToolsRole).value<QList<QByteArray>>();
        const QByteArray chatId = mManager->currentChatId();
        const QModelIndex indexAnswer = mManager->refreshAnswer(chatId, uuid);
        if (indexAnswer.isValid()) {
            Q_EMIT refreshAnswerRequested(chatId, indexAnswer, tools, {}); // TODO use attachmentList !
        }
    }
}

void TextAutoGenerateListView::handleMouseEvent(QMouseEvent *event)
{
    const QPersistentModelIndex index = indexAt(event->pos());
    if (index.isValid()) {
        // When the cursor hovers another message, hide/show the reaction icon accordingly
        if (mCurrentIndex != index) {
            if (mCurrentIndex.isValid()) {
                auto lastModel = const_cast<QAbstractItemModel *>(mCurrentIndex.model());
                lastModel->setData(mCurrentIndex, false, TextAutoGenerateMessagesModel::MouseHoverRole);
            }
            mCurrentIndex = index;
            auto model = const_cast<QAbstractItemModel *>(mCurrentIndex.model());
            model->setData(mCurrentIndex, true, TextAutoGenerateMessagesModel::MouseHoverRole);
        }
        QStyleOptionViewItem options = listViewOptions();
        options.rect = visualRect(mCurrentIndex);
        if (mouseEvent(event, options, mCurrentIndex)) {
            update(mCurrentIndex);
        }
    }
}

void TextAutoGenerateListView::leaveEvent(QEvent *event)
{
    if (mCurrentIndex.isValid()) {
        auto lastModel = const_cast<QAbstractItemModel *>(mCurrentIndex.model());
        lastModel->setData(mCurrentIndex, false, TextAutoGenerateMessagesModel::MouseHoverRole);
        mCurrentIndex = QPersistentModelIndex();
    }
    QListView::leaveEvent(event);
}

QString TextAutoGenerateListView::originalMessage(const QModelIndex &index) const
{
    return index.data(TextAutoGenerateMessagesModel::OriginalMessageRole).toString();
}

void TextAutoGenerateListView::handleKeyPressEvent(QKeyEvent *ev)
{
    const int key = ev->key();
    if (key == Qt::Key_Up || key == Qt::Key_Down || key == Qt::Key_PageDown || key == Qt::Key_PageUp) {
        // QListView/QAIV PageUp/PageDown moves the current item, first inside visible bounds
        // before it triggers scrolling around. Let's just let the scrollarea handle it,
        // since we don't show the current item.
        QAbstractScrollArea::keyPressEvent(ev);
        ev->accept();
    } else if (ev->modifiers() & Qt::ControlModifier) {
        if (key == Qt::Key_Home) {
            scrollToTop();
            ev->accept();
        } else if (key == Qt::Key_End) {
            scrollToBottom();
            ev->accept();
        }
    }
}

void TextAutoGenerateListView::goToMessageId(const QByteArray &uuid)
{
    auto model = mManager->messagesModelFromChatId(mManager->currentChatId());
    if (model) {
        const QModelIndex idx = model->indexForUuid(uuid);
        if (idx.isValid()) {
            scrollTo(idx);
        }
    }
}

void TextAutoGenerateListView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint)
{
    QListView::scrollTo(index, hint);
    addSelectedMessageBackgroundAnimation(index);
}

void TextAutoGenerateListView::editingFinished(const QByteArray &uuid)
{
    auto model = mManager->messagesModelFromChatId(mManager->currentChatId());
    if (model) {
        const QModelIndex idx = model->indexForUuid(uuid);
        if (idx.isValid()) {
            auto lastModel = const_cast<QAbstractItemModel *>(idx.model());
            lastModel->setData(idx, false, TextAutoGenerateMessagesModel::EditingRole);
            mMessageIdBeingEdited.clear();
        }
    }
}

void TextAutoGenerateListView::slotCurrentChatIdChanged()
{
    mCurrentModel = mManager->messagesModelFromChatId(mManager->currentChatId());
    setModel(mCurrentModel);
    static_cast<TextAutoGenerateListViewDelegate *>(mDelegate)->setInProgress(mManager->chatInProgress(mManager->currentChatId()));
}

void TextAutoGenerateListView::setSearchText(const QString &str)
{
    if (mCurrentModel) {
        const int numberOfSearchStringFound = mCurrentModel->setSearchText(str);
        clearDocumentCache();
        Q_EMIT mManager->numberOfSearchStringChanged(numberOfSearchStringFound);
    }
}

void TextAutoGenerateListView::slotFindNext()
{
    if (mManager && mCurrentModel) {
        mCurrentModel->searchMessageSettings()->next();
    }
}

void TextAutoGenerateListView::slotFindPrev()
{
    if (mManager && mCurrentModel) {
        mCurrentModel->searchMessageSettings()->previous();
    }
}

void TextAutoGenerateListView::addWaitingAnswerAnimation(const QModelIndex &index)
{
    auto animation = new TextAutoGenerateMessageWaitingAnswerAnimation(mManager->currentChatId(), mManager, this);
    animation->setModelIndex(index);
    const QMetaObject::Connection valueChangeConnection =
        connect(animation, &TextAutoGenerateMessageWaitingAnswerAnimation::valueChanged, this, [this, animation]() {
            qCDebug(TEXTAUTOGENERATETEXT_WIDGET_ANIMATION_LOG) << "TextAutoGenerateMessageWaitingAnswerAnimation value changed";
            static_cast<TextAutoGenerateListViewDelegate *>(mDelegate)->needUpdateWaitingAnswerAnimation(animation->modelIndex(), animation->scaleOpacities());
            update(animation->modelIndex());
        });
    connect(animation, &TextAutoGenerateMessageWaitingAnswerAnimation::waitingAnswerDone, this, [this, index, valueChangeConnection]() {
        static_cast<TextAutoGenerateListViewDelegate *>(mDelegate)->removeNeedUpdateWaitingAnswerAnimation(index);
        disconnect(valueChangeConnection);
        qCDebug(TEXTAUTOGENERATETEXT_WIDGET_ANIMATION_LOG) << "TextAutoGenerateMessageWaitingAnswerAnimation end";
        update(index);
    });
    animation->start();
}

void TextAutoGenerateListView::addSelectedMessageBackgroundAnimation(const QModelIndex &index)
{
    auto animation = new TextAutoGenerateSelectedMessageBackgroundAnimation(this);
    animation->setModelIndex(index);
    connect(animation, &TextAutoGenerateSelectedMessageBackgroundAnimation::backgroundColorChanged, this, [this, animation]() {
        qCDebug(TEXTAUTOGENERATETEXT_WIDGET_ANIMATION_LOG) << "TextAutoGenerateSelectedMessageBackgroundAnimation start";
        static_cast<TextAutoGenerateListViewDelegate *>(mDelegate)->needUpdateIndexBackground(animation->modelIndex(), animation->backgroundColor());
        update(animation->modelIndex());
    });
    connect(animation, &TextAutoGenerateSelectedMessageBackgroundAnimation::animationFinished, this, [this, animation]() {
        qCDebug(TEXTAUTOGENERATETEXT_WIDGET_ANIMATION_LOG) << "TextAutoGenerateSelectedMessageBackgroundAnimation end";
        static_cast<TextAutoGenerateListViewDelegate *>(mDelegate)->removeNeedUpdateIndexBackground(animation->modelIndex());
        update(animation->modelIndex());
    });
    animation->start();
}

void TextAutoGenerateListView::setModel(QAbstractItemModel *newModel)
{
    clearEditingMode();
    const QAbstractItemModel *oldModel = model();
    if (oldModel) {
        disconnect(oldModel, nullptr, this, nullptr);
        if (mCurrentModel && mCurrentModel->searchMessageSettings()) {
            disconnect(mCurrentModel->searchMessageSettings(), nullptr, this, nullptr);
        }

        mDelegate->clearSelection();
    }
    QListView::setModel(newModel);
    if (newModel) {
        connect(newModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &TextAutoGenerateListView::checkIfAtBottom);
        connect(newModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &TextAutoGenerateListView::checkIfAtBottom);
        connect(newModel, &QAbstractItemModel::modelAboutToBeReset, this, &TextAutoGenerateListView::checkIfAtBottom);
        // Connect to rangeChanged rather than rowsInserted/rowsRemoved/modelReset.
        // This way it also catches the case of an item changing height (e.g. after async image loading)
        connect(verticalScrollBar(), &QScrollBar::rangeChanged, this, &TextAutoGenerateListView::maybeScrollToBottom);

        connect(newModel, &QAbstractItemModel::dataChanged, this, [this](const QModelIndex &topLeft, const QModelIndex &, const QList<int> &roles) {
            if (roles.contains(TextAutoGenerateMessagesModel::MessageHtmlGeneratedRole) || roles.contains(TextAutoGenerateMessagesModel::FinishedRole)) {
                const QByteArray uuid = topLeft.data(TextAutoGenerateMessagesModel::UuidRole).toByteArray();
                if (!uuid.isEmpty()) {
                    mDelegate->removeMessageCache(uuid);
                }
                if (roles.contains(TextAutoGenerateMessagesModel::FinishedRole)) {
                    const bool inProgress = !topLeft.data(TextAutoGenerateMessagesModel::FinishedRole).toBool();
                    if (inProgress) {
                        addWaitingAnswerAnimation(topLeft);
                    }
                }
            }
        });
        connect(mCurrentModel->searchMessageSettings(),
                &TextAutoGenerateSearchMessageSettings::refreshMessage,
                this,
                &TextAutoGenerateListView::slotRefreshMessage);

        connect(mCurrentModel->searchMessageSettings(),
                &TextAutoGenerateSearchMessageSettings::updateNextPreviousButtons,
                this,
                &TextAutoGenerateListView::updateNextPreviousButtons);
        scrollToBottom();
    }
}

void TextAutoGenerateListView::slotRefreshMessage(const QByteArray &currentIdentifier, const QByteArray &previousIdentifier, int index)
{
    // qDebug() << "TextAutoGenerateListView::slotRefreshMessage " << currentIdentifier << "index  " << index;
    if (previousIdentifier != currentIdentifier) {
        mCurrentModel->regenerateHtmlMessage(previousIdentifier, -1); // Clear
    }
    mCurrentModel->regenerateHtmlMessage(currentIdentifier, index);
    const QModelIndex modelIndex = mCurrentModel->indexForUuid(currentIdentifier);
    if (modelIndex.isValid()) {
        scrollTo(modelIndex);
    }
}

#include "moc_textautogeneratelistview.cpp"
