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

  SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "textautogeneratelistviewdelegate.h"
#include "config-textautogeneratetext.h"
#include "textautogeneratetextglobalconfig.h"

#include "core/models/textautogeneratemessagesmodel.h"
#include "textautogeneratetextwidget_debug.h"
#include "widgets/view/delegate/textautogenerateattachmentdelegatehelperbase.h"
#include "widgets/view/textautogeneratelistviewtextselection.h"
#include <KIconLoader>
#include <KLocalizedString>
#include <KMessageBox>
#include <QAbstractTextDocumentLayout>
#include <QClipboard>
#include <QDesktopServices>
#include <QDrag>
#include <QListView>
#include <QMimeData>
#include <QPainter>
#include <QToolTip>
#include <TextUtils/TextUtilsBlockCodeManager>
#include <TextUtils/TextUtilsColorsAndMessageViewStyle>
#include <TextUtils/TextUtilsSyntaxHighlighter>

using namespace Qt::Literals::StringLiterals;
using namespace TextAutoGenerateText;
TextAutoGenerateListViewDelegate::TextAutoGenerateListViewDelegate(TextAutoGenerateText::TextAutoGenerateManager *manager, QListView *view)
    : TextAutoGenerateListViewMessageBaseDelegate{manager, view}
    , mEditedIcon(QIcon::fromTheme(u"document-edit"_s))
    , mCopyIcon(QIcon::fromTheme(u"edit-copy"_s))
    , mCancelIcon(QIcon::fromTheme(u"dialog-cancel"_s))
    , mRefreshIcon(QIcon::fromTheme(u"view-refresh"_s))
    , mInformationIcon(QIcon::fromTheme(u"info"_s))
    , mRemoveIcon(QIcon::fromTheme(u"edit-delete"_s))
    , mTextToSpeechIcon(QIcon::fromTheme(u"player-volume"_s))
    , mForkIcon(QIcon::fromTheme(u"state-fork"_s))
{
    connect(&TextUtils::TextUtilsColorsAndMessageViewStyle::self(),
            &TextUtils::TextUtilsColorsAndMessageViewStyle::needToUpdateColors,
            this,
            &TextAutoGenerateListViewDelegate::slotUpdateColors);
    slotUpdateColors();
}

TextAutoGenerateListViewDelegate::~TextAutoGenerateListViewDelegate() = default;

void TextAutoGenerateListViewDelegate::slotUpdateColors()
{
    const KColorScheme scheme = TextUtils::TextUtilsColorsAndMessageViewStyle::self().schemeView();
    mEditingColorMode = scheme.foreground(KColorScheme::NegativeText).color();
    // KIconLoader::global()->setCustomPalette(mListView->palette());
    // KIconLoader::global()->resetPalette();
    Q_EMIT updateColors();
}

bool TextAutoGenerateListViewDelegate::showFork() const
{
    return mShowFork;
}

void TextAutoGenerateListViewDelegate::setShowFork(bool newShowFork)
{
    if (mShowFork != newShowFork) {
        mShowFork = newShowFork;
        Q_EMIT updateView();
    }
}

void TextAutoGenerateListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    painter->save();
    drawBackground(painter, option, index);
    painter->restore();

    const TextAutoGenerateMessage *message = index.data(TextAutoGenerateMessagesModel::MessagePointer).value<TextAutoGenerateMessage *>();

    const MessageLayout layout = doLayout(option, index);
    if (layout.textRect.isValid()) {
        painter->save();
        const TextAutoGenerateMessage::Sender sender = message->sender();
        const bool isUser = (sender == TextAutoGenerateMessage::Sender::User);
        painter->setPen(QPen(Qt::NoPen));

        auto it = std::find_if(mIndexBackgroundColorList.cbegin(), mIndexBackgroundColorList.cend(), [index](const IndexBackgroundColor &key) {
            return key.index == index;
        });
        QColor messageBackgroundColor;
        if (it != mIndexBackgroundColorList.cend()) {
            messageBackgroundColor = it->color;
        }
        if (index.data(TextAutoGenerateMessagesModel::EditingRole).toBool()) {
            messageBackgroundColor = mEditingColorMode;
        }

        if (messageBackgroundColor.isValid() && messageBackgroundColor != QColor(Qt::transparent)) {
            painter->setBrush(QBrush(messageBackgroundColor));
        } else {
            painter->setBrush(
                QBrush(isUser ? option.palette.color(QPalette::Inactive, QPalette::Midlight) : option.palette.color(QPalette::Active, QPalette::Mid)));
        }
        painter->setRenderHint(QPainter::Antialiasing);
        if (message->textToSpeechInProgress()) {
            QPen pen;
            pen.setWidth(3);
            pen.setColor(Qt::blue);
            painter->setPen(pen);
        }
        painter->drawRoundedRect(
            QRect(layout.decoRect.topLeft(), QSize(layout.decoRect.width(), layout.decoRect.height() - TextAutoGenerateDelegateUtils::spacingText() - 5)),
            TextAutoGenerateDelegateUtils::roundRectValue(),
            TextAutoGenerateDelegateUtils::roundRectValue());
        painter->restore();
        draw(painter, layout, index, option);
    }

    if (auto att = message->messageAttachments(); att) {
        int i = 0;
        const auto attachments = att->messageAttachments();
        for (const auto &attachment : attachments) {
            const TextAutoGenerateAttachmentDelegateHelperBase *helper = attachmentsHelper(attachment);
            helper->draw(attachment, painter, layout.attachmentsRectList.isEmpty() ? QRect{} : layout.attachmentsRectList.at(i), index, option);
            i++;
        }
    }

    drawDateAndIcons(painter, index, option, layout);
    drawInProgressIndicator(painter, index, option, layout);
    /*
    painter->save();
    painter->setPen(QPen(Qt::green));
    painter->drawRect(layout.decoRect);
    painter->restore();
    */
}

void TextAutoGenerateListViewDelegate::draw(QPainter *painter, const MessageLayout &layout, const QModelIndex &index, const QStyleOptionViewItem &option) const
{
    const QRect rect = layout.textRect;
    auto *doc = documentForIndex(index, rect.width());
    if (!doc) {
        return;
    }
    painter->save();
    painter->translate(rect.left(), rect.top());
    const QRect clip(0, 0, rect.width(), rect.height());

    QAbstractTextDocumentLayout::PaintContext ctx;
    if (mTextSelection) {
        const QList<QAbstractTextDocumentLayout::Selection> selections = TextAutoGenerateDelegateUtils::selection(mTextSelection, doc, index, option);
        // Same as pDoc->drawContents(painter, clip) but we also set selections
        ctx.selections = selections;
        if (clip.isValid()) {
            painter->setClipRect(clip);
            ctx.clip = clip;
        }
    }
    doc->documentLayout()->draw(painter, ctx);
    painter->restore();
}

void TextAutoGenerateListViewDelegate::drawInProgressIndicator(QPainter *painter,
                                                               const QModelIndex &index,
                                                               [[maybe_unused]] const QStyleOptionViewItem &option,
                                                               const MessageLayout &layout) const
{
    auto it = std::find_if(mIndexScaleAndOpacitiesList.cbegin(),
                           mIndexScaleAndOpacitiesList.cend(),
                           [index](const TextAutoGenerateDelegateUtils::IndexScaleAndOpacities &key) {
                               return key.index == index;
                           });
    if (it == mIndexScaleAndOpacitiesList.cend()) {
        return;
    }
    const auto scaleAndOpacities = (*it).scaleAndOpacities;
    painter->save();
    painter->setRenderHint(QPainter::Antialiasing);

    const int dotSize = 5;
    const int spacing = TextAutoGenerateDelegateUtils::iconSpacing();

    for (int i = 0; i < scaleAndOpacities.count(); ++i) {
        const TextAutoGenerateText::TextAutoGenerateMessageWaitingAnswerAnimation::ScaleAndOpacity value = scaleAndOpacities.value(i);
        painter->setOpacity(value.opacity);
        // qDebug() << " value " << value;
        painter->save();
        painter->translate(layout.inProgressRect.x() + spacing + i * (dotSize + spacing), layout.inProgressRect.top() + layout.inProgressRect.height() / 2);
        painter->rotate(45);
        painter->scale(value.scale, value.scale);
        painter->setBrush(Qt::black);
        painter->drawEllipse(-dotSize / 2, -dotSize / 2, dotSize, dotSize);
        painter->restore();
    }
    painter->restore();
}

void TextAutoGenerateListViewDelegate::drawDateAndIcons(QPainter *painter,
                                                        const QModelIndex &index,
                                                        const QStyleOptionViewItem &option,
                                                        const MessageLayout &layout) const
{
    const bool isMouseOver = index.data(TextAutoGenerateMessagesModel::MouseHoverRole).toBool();
    if (layout.dateSize.isValid()) {
        const QPen origPen = painter->pen();
        const qreal margin = TextAutoGenerateDelegateUtils::leftLLMIndent();
        const QString dateStr = index.data(TextAutoGenerateMessagesModel::DateTimeStrRole).toString();

        /*
        // qDebug() << " draw date" << dateAreaRect << layout.decoRect;
        painter->save();
        painter->setPen(QPen(Qt::yellow));
        painter->drawRect(dateAreaRect);
        painter->restore();
        */

        const QRect dateTextRect = QStyle::alignedRect(Qt::LayoutDirectionAuto, Qt::AlignCenter, layout.dateSize, layout.dateAreaRect);
        QColor lightColor(painter->pen().color());
        lightColor.setAlpha(60);
        painter->setPen(lightColor);
        painter->drawText(dateTextRect, dateStr);
        // qDebug() << " dateTextRect" << dateTextRect;
        const int lineY = (layout.dateAreaRect.top() + layout.dateAreaRect.bottom()) / 2;
        painter->drawLine(layout.dateAreaRect.left(), lineY, dateTextRect.left() - margin, lineY);
        int iconSizeAreaWidth = 0;
        if (isMouseOver) {
            int offset = 0;
#if HAVE_KTEXTADDONS_TEXT_TO_SPEECH_SUPPORT
            offset = TextAutoGenerateText::TextAutogenerateTextGlobalConfig::self()->enableTextToSpeech() ? 1 : 0;
#endif

            iconSizeAreaWidth = (3 + offset) * (buttonIconSize(option) + TextAutoGenerateDelegateUtils::iconSpacing());
        }
        painter->drawLine(dateTextRect.right() + margin, lineY, layout.dateAreaRect.right() - iconSizeAreaWidth, lineY);
        painter->setPen(origPen);
    }

    if (isMouseOver) {
        const bool waitingAnswer = index.data(TextAutoGenerateMessagesModel::WaitingAnswerRole).toBool();
        if (layout.editedIconRect.isValid() && !waitingAnswer && !mShowArchive && !mInProgress) {
            mEditedIcon.paint(painter, layout.editedIconRect);
        }
        if (layout.copyIconRect.isValid()) {
            mCopyIcon.paint(painter, layout.copyIconRect);
        }
        if (layout.removeIconRect.isValid()) {
            mRemoveIcon.paint(painter, layout.removeIconRect);
        }
#if HAVE_KTEXTADDONS_TEXT_TO_SPEECH_SUPPORT
        if (TextAutoGenerateText::TextAutogenerateTextGlobalConfig::self()->enableTextToSpeech() && layout.textToSpeechIconRect.isValid()) {
            mTextToSpeechIcon.paint(painter, layout.textToSpeechIconRect);
        }
#endif
        if (layout.cancelIconRect.isValid()) {
            mCancelIcon.paint(painter, layout.cancelIconRect);
        }
        if (layout.refreshIconRect.isValid() && !waitingAnswer && !mShowArchive && !mInProgress) {
            mRefreshIcon.paint(painter, layout.refreshIconRect);
        }
        if (layout.infoIconRect.isValid() && !waitingAnswer) {
            mInformationIcon.paint(painter, layout.infoIconRect);
        }
        // TODO disable it in quick answer
        if (layout.forkIconRect.isValid() && !waitingAnswer && mShowFork) {
            mForkIcon.paint(painter, layout.forkIconRect);
        }
    }
}

QSize TextAutoGenerateListViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    const QByteArray uuid = index.data(TextAutoGenerateMessagesModel::UuidRole).toByteArray();
    auto it = mSizeHintCache.find(uuid);
    if (it != mSizeHintCache.end()) {
        const QSize result = it->value;
        qCDebug(TEXTAUTOGENERATETEXT_WIDGET_LOG) << "TextAutoGenerateListViewDelegate: SizeHint found in cache: " << result;
        return result;
    }

    const TextAutoGenerateListViewDelegate::MessageLayout layout = doLayout(option, index);

    int additionalHeight = 0;
    // A little bit of margin below the very last item, it just looks better
    if (index.row() == index.model()->rowCount() - 1) {
        additionalHeight += 4;
    }

    const QSize size = {layout.decoRect.width(), layout.decoRect.height() + additionalHeight + (layout.dateSize.isValid() ? layout.dateSize.height() : 16)};
    if (!size.isEmpty()) {
        mSizeHintCache.insert(uuid, size);
    }
    return size;
}

int TextAutoGenerateListViewDelegate::buttonIconSize(const QStyleOptionViewItem &option) const
{
    return option.widget->style()->pixelMetric(QStyle::PM_ButtonIconSize);
}

QString TextAutoGenerateListViewDelegate::urlAt(const QModelIndex &index, QPoint pos) const
{
    QStyleOptionViewItem option;
    option.initFrom(mListView);
    option.widget = mListView;
    option.rect = mListView->visualRect(index);
    const MessageLayout layout = doLayout(option, index);
    const QPoint relativePos = pos - layout.textRect.topLeft();
    auto document = documentForIndex(index, layout.textRect.width());
    if (!document) {
        return {};
    }
    return document->documentLayout()->anchorAt(relativePos);
}

TextAutoGenerateListViewDelegate::MessageLayout TextAutoGenerateListViewDelegate::doLayout(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    const int iconSize = buttonIconSize(option);
    TextAutoGenerateListViewDelegate::MessageLayout layout;
    QRect usableRect = option.rect;
    const TextAutoGenerateMessage::Sender sender = index.data(TextAutoGenerateMessagesModel::SenderRole).value<TextAutoGenerateMessage::Sender>();
    const bool isUser = (sender == TextAutoGenerateMessage::Sender::User);
    const int indent = isUser ? TextAutoGenerateDelegateUtils::leftUserIndent() : TextAutoGenerateDelegateUtils::leftLLMIndent();
    if (!isUser) {
        const QString dateStr = index.data(TextAutoGenerateMessagesModel::DateTimeStrRole).toString();
        layout.dateSize = option.fontMetrics.size(Qt::TextSingleLine, dateStr);
        usableRect.setBottom(usableRect.bottom() + layout.dateSize.height());
    } else {
        usableRect.setBottom(usableRect.bottom() + iconSize);
    }
    const TextAutoGenerateMessage *message = index.data(TextAutoGenerateMessagesModel::MessagePointer).value<TextAutoGenerateMessage *>();
    int maxWidth = qMax(30, option.rect.width() - indent - TextAutoGenerateDelegateUtils::rightIndent());
    const QSize textSize = documentSizeHint(index, maxWidth, option, &layout.baseLine);
    int attachmentWidth = textSize.width();
    if (message->messageAttachments() && !message->messageAttachments()->isEmpty()) {
        const auto attachments = message->messageAttachments()->messageAttachments();
        for (const TextAutoGenerateAttachment &msgAttach : attachments) {
            const TextAutoGenerateAttachmentDelegateHelperBase *helper = attachmentsHelper(msgAttach);
            attachmentWidth = qMax(attachmentWidth, helper ? helper->sizeHint(msgAttach, index, maxWidth, option).width() : 0);
        }
    }

    if (isUser) {
        maxWidth = qMax(30, attachmentWidth + TextAutoGenerateDelegateUtils::rightIndent() + TextAutoGenerateDelegateUtils::marginText());
        layout.textRect = QRect(option.rect.width() - maxWidth,
                                usableRect.top() + TextAutoGenerateDelegateUtils::spacingText() * 2,
                                maxWidth - TextAutoGenerateDelegateUtils::marginText() * 2,
                                textSize.height() + TextAutoGenerateDelegateUtils::spacingText() * 2);

        layout.decoRect = QRect(layout.textRect.x() - TextAutoGenerateDelegateUtils::rightIndent(),
                                usableRect.top() + TextAutoGenerateDelegateUtils::spacingText(),
                                maxWidth,
                                layout.textRect.height() + TextAutoGenerateDelegateUtils::spacingText() * 3);

    } else {
        layout.textRect = QRect(indent + TextAutoGenerateDelegateUtils::marginText(),
                                usableRect.top() + TextAutoGenerateDelegateUtils::spacingText() * 2,
                                maxWidth - TextAutoGenerateDelegateUtils::marginText() * 2,
                                textSize.height() + TextAutoGenerateDelegateUtils::spacingText() * 2);

        layout.decoRect = QRect(indent,
                                usableRect.top() + TextAutoGenerateDelegateUtils::spacingText(),
                                maxWidth,
                                layout.textRect.height() + TextAutoGenerateDelegateUtils::spacingText() * 3);
    }

    if (message->messageAttachments() && !message->messageAttachments()->isEmpty()) {
        const auto attachments = message->messageAttachments()->messageAttachments();
        QSize attachmentsSize;
        const int offsetSpacing = TextAutoGenerateDelegateUtils::spacingText();
        int topAttachment = layout.textRect.y() + offsetSpacing;
        for (const TextAutoGenerateAttachment &msgAttach : attachments) {
            const TextAutoGenerateAttachmentDelegateHelperBase *helper = attachmentsHelper(msgAttach);
            if (attachmentsSize.isEmpty()) {
                attachmentsSize = helper ? helper->sizeHint(msgAttach, index, maxWidth, option) : QSize(0, 0);
                layout.attachmentsRectList.append(QRect(layout.textRect.x() - TextAutoGenerateDelegateUtils::rightIndent(),
                                                        topAttachment,
                                                        attachmentsSize.width(),
                                                        attachmentsSize.height()));
                topAttachment += attachmentsSize.height();
            } else {
                topAttachment += offsetSpacing;
                const QSize attSize = helper ? helper->sizeHint(msgAttach, index, maxWidth, option) : QSize(0, 0);
                attachmentsSize = QSize(qMax(attachmentsSize.width(), attSize.width()), attSize.height() + attachmentsSize.height());
                layout.attachmentsRectList.append(
                    QRect(layout.textRect.x() - TextAutoGenerateDelegateUtils::rightIndent(), topAttachment, attSize.width(), attSize.height()));
                topAttachment += attSize.height();
            }
        }
        layout.decoRect.setHeight(layout.decoRect.height() + attachmentsSize.height() + attachments.count() * offsetSpacing);
    }

    layout.dateAreaRect = QRect(layout.decoRect.x(),
                                layout.decoRect.y() + layout.decoRect.height() - TextAutoGenerateDelegateUtils::spacingText() / 2,
                                layout.decoRect.width(),
                                qMax(layout.dateSize.height(), iconSize)); // the whole row

    int positionIcon = layout.decoRect.right() - iconSize;
    const int lineY = layout.dateAreaRect.center().y() - (iconSize / 2);
    const bool waitingAnswer = index.data(TextAutoGenerateMessagesModel::WaitingAnswerRole).toBool();
    if (isUser && !waitingAnswer && !mShowArchive && !mInProgress) {
        layout.removeIconRect = QRect(positionIcon, lineY, iconSize, iconSize);
        positionIcon -= iconSize + TextAutoGenerateDelegateUtils::iconSpacing();

        layout.editedIconRect = QRect(positionIcon, lineY, iconSize, iconSize);
        positionIcon -= iconSize + TextAutoGenerateDelegateUtils::iconSpacing();
    } else {
        const bool inProgress = index.data(TextAutoGenerateMessagesModel::FinishedRole).toBool();
        if (!inProgress) {
            layout.cancelIconRect = QRect(positionIcon, lineY, iconSize, iconSize);
            positionIcon -= iconSize + TextAutoGenerateDelegateUtils::iconSpacing();
        }
    }
    layout.copyIconRect = QRect(positionIcon, lineY, iconSize, iconSize);

    if (!isUser && !mInProgress) {
        positionIcon -= iconSize + TextAutoGenerateDelegateUtils::iconSpacing();
        layout.refreshIconRect = QRect(positionIcon, lineY, iconSize, iconSize);

        layout.infoIconRect = QRect(layout.textRect.topRight().x(), layout.textRect.topRight().y(), iconSize, iconSize);
    }
    positionIcon -= iconSize + TextAutoGenerateDelegateUtils::iconSpacing();
    layout.forkIconRect = QRect(positionIcon, lineY, iconSize, iconSize);
#if HAVE_KTEXTADDONS_TEXT_TO_SPEECH_SUPPORT
    if (TextAutoGenerateText::TextAutogenerateTextGlobalConfig::self()->enableTextToSpeech()) {
        positionIcon -= iconSize + TextAutoGenerateDelegateUtils::iconSpacing();
        layout.textToSpeechIconRect = QRect(positionIcon, lineY, iconSize, iconSize);
    }
#endif

    const QSize progressIndicatorSize = TextAutoGenerateDelegateUtils::progressIndicatorSize();
    layout.inProgressRect = QRect(layout.textRect.topRight().x() - progressIndicatorSize.width(),
                                  layout.textRect.y() + layout.textRect.height() - progressIndicatorSize.height(),
                                  progressIndicatorSize.width(),
                                  progressIndicatorSize.height());

    return layout;
}

bool TextAutoGenerateListViewDelegate::mouseEvent(QEvent *event, const QStyleOptionViewItem &option, const QModelIndex &index)
{
    const QEvent::Type eventType = event->type();
    if (eventType == QEvent::MouseButtonRelease) {
        auto mev = static_cast<QMouseEvent *>(event);
        const TextAutoGenerateListViewDelegate::MessageLayout layout = doLayout(option, index);
        if (handleMouseEvent(mev, layout.decoRect.united(layout.dateAreaRect), layout.textRect, option, index)) {
            return true;
        }
        const TextAutoGenerateMessage *message = index.data(TextAutoGenerateMessagesModel::MessagePointer).value<TextAutoGenerateMessage *>();
        if (message->messageAttachments()) {
            const auto attachments = message->messageAttachments()->messageAttachments();
            int i = 0;
            for (const TextAutoGenerateAttachment &att : attachments) {
                TextAutoGenerateAttachmentDelegateHelperBase *helper = attachmentsHelper(att);
                if (helper && helper->handleMouseEvent(att, mev, layout.attachmentsRectList.at(i), option, index)) {
                    return true;
                }
                ++i;
            }
        }
    } else if (eventType == QEvent::MouseButtonPress || eventType == QEvent::MouseMove || eventType == QEvent::MouseButtonDblClick) {
        auto mev = static_cast<QMouseEvent *>(event);
        if (mev->buttons() & Qt::LeftButton) {
            const TextAutoGenerateListViewDelegate::MessageLayout layout = doLayout(option, index);
            if (handleMouseEvent(mev, layout.decoRect, layout.textRect, option, index)) {
                return true;
            }
        }
    }
    return false;
}

bool TextAutoGenerateListViewDelegate::helpEvent(QHelpEvent *helpEvent, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index)
{
    if (!index.isValid()) {
        return false;
    }
    if (helpEvent->type() == QEvent::ToolTip) {
        const TextAutoGenerateListViewDelegate::MessageLayout layout = doLayout(option, index);
        const QPoint helpEventPos{helpEvent->pos()};
        if (layout.textRect.contains(helpEventPos)) {
            const auto *doc = documentForIndex(index, layout.textRect.width());
            if (!doc) {
                return false;
            }

            const QPoint pos = helpEvent->pos() - layout.textRect.topLeft();
            QString formattedTooltip;
            if (TextAutoGenerateDelegateUtils::generateToolTip(doc, pos, formattedTooltip)) {
                QToolTip::showText(helpEvent->globalPos(), formattedTooltip, view);
                return true;
            }
            return true;
        }
        if (layout.editedIconRect.contains(helpEventPos)) {
            QToolTip::showText(helpEvent->globalPos(), i18nc("@info:tooltip", "Edit…"), view);
            return true;
        }
        if (layout.removeIconRect.contains(helpEventPos)) {
            QToolTip::showText(helpEvent->globalPos(), i18nc("@info:tooltip", "Remove"), view);
            return true;
        }
        if (layout.textToSpeechIconRect.contains(helpEventPos)) {
            const bool textToSpeechInProgress = index.data(TextAutoGenerateMessagesModel::TextToSpeechInProgressRole).toBool();
            QToolTip::showText(helpEvent->globalPos(), textToSpeechInProgress ? i18nc("@info:tooltip", "Stop") : i18nc("@info:tooltip", "Speak"), view);
            return true;
        }
        if (layout.copyIconRect.contains(helpEventPos)) {
            QToolTip::showText(helpEvent->globalPos(), i18nc("@info:tooltip", "Copy"), view);
            return true;
        }
        if (layout.cancelIconRect.contains(helpEventPos)) {
            QToolTip::showText(helpEvent->globalPos(), i18nc("@info:tooltip", "Cancel"), view);
            return true;
        }
        if (layout.refreshIconRect.contains(helpEventPos)) {
            QToolTip::showText(helpEvent->globalPos(), i18nc("@info:tooltip", "Refresh"), view);
            return true;
        }
        if (layout.forkIconRect.contains(helpEventPos)) {
            QToolTip::showText(helpEvent->globalPos(), i18nc("@info:tooltip", "Fork"), view);
            return true;
        }
        if (layout.infoIconRect.contains(helpEventPos)) {
            const QString modelInfo = index.data(TextAutoGenerateMessagesModel::ModelInfoRole).toString();
            QToolTip::showText(helpEvent->globalPos(), modelInfo, view);
            return true;
        }
    }
    return false;
}

QTextDocument *TextAutoGenerateListViewDelegate::documentForIndex(const QModelIndex &index, int width) const
{
    Q_ASSERT(index.isValid());
    const QByteArray uuid = index.data(TextAutoGenerateMessagesModel::UuidRole).toByteArray();
    Q_ASSERT(!uuid.isEmpty());
    auto it = mDocumentCache.find(uuid);
    if (it != mDocumentCache.end()) {
        auto ret = it->value.get();
        if (width != -1 && !qFuzzyCompare(ret->textWidth(), width)) {
            ret->setTextWidth(width);
        }
        return ret;
    }

    const QString text = index.data(TextAutoGenerateMessagesModel::MessageHtmlGeneratedRole).toString();
    if (text.isEmpty()) {
        return nullptr;
    }
    auto doc = createTextDocument(text, width);
    auto ret = doc.get();
    mDocumentCache.insert(uuid, std::move(doc));
    return ret;
}

bool TextAutoGenerateListViewDelegate::maybeStartDrag(QMouseEvent *event, const QStyleOptionViewItem &option, const QModelIndex &index)
{
    const TextAutoGenerateListViewDelegate::MessageLayout layout = doLayout(option, index);
    if (maybeStartDrag(event, layout.textRect, option, index)) {
        return true;
    }
    return false;
}

bool TextAutoGenerateListViewDelegate::maybeStartDrag(QMouseEvent *mouseEvent, QRect messageRect, const QStyleOptionViewItem &option, const QModelIndex &index)
{
    if (!mTextSelection->mightStartDrag()) {
        return false;
    }
    if (mTextSelection->hasSelection()) {
        const QPoint pos = mouseEvent->pos() - messageRect.topLeft();
        const auto *doc = documentForIndex(index, messageRect.width());
        const int charPos = doc->documentLayout()->hitTest(pos, Qt::FuzzyHit);
        if (charPos != -1 && mTextSelection->contains(index, charPos)) {
            auto mimeData = new QMimeData;
            mimeData->setHtml(mTextSelection->selectedText(TextAutoGenerateListViewTextSelection::Format::Html));
            mimeData->setText(mTextSelection->selectedText(TextAutoGenerateListViewTextSelection::Format::Text));
            auto drag = new QDrag(const_cast<QWidget *>(option.widget));
            drag->setMimeData(mimeData);
            drag->exec(Qt::CopyAction);
            mTextSelection->setMightStartDrag(false); // don't clear selection on release
            return true;
        }
    }
    return false;
}
bool TextAutoGenerateListViewDelegate::handleMouseEvent(QMouseEvent *mouseEvent,
                                                        QRect decoRect,
                                                        QRect textRect,
                                                        const QStyleOptionViewItem &option,
                                                        const QModelIndex &index)
{
    if (!decoRect.contains(mouseEvent->pos())) {
        return false;
    }

    const QPoint pos = mouseEvent->pos() - textRect.topLeft();

    const QEvent::Type eventType = mouseEvent->type();

    // Text selection
    switch (eventType) {
    case QEvent::MouseButtonPress:
        mTextSelection->setMightStartDrag(false);
        if (const auto *doc = documentForIndex(index, textRect.width())) {
            const int charPos = doc->documentLayout()->hitTest(pos, Qt::FuzzyHit);
            qCDebug(TEXTAUTOGENERATETEXT_WIDGET_LOG) << "pressed at pos" << charPos;
            if (charPos == -1) {
                return false;
            }
            if (mTextSelection->contains(index, charPos) && doc->documentLayout()->hitTest(pos, Qt::ExactHit) != -1) {
                mTextSelection->setMightStartDrag(true);
                return true;
            }

            // QWidgetTextControl also has code to support selectBlockOnTripleClick, shift to extend selection
            // (look there if you want to add these things)

            mTextSelection->setTextSelectionStart(index, charPos);
            return true;
        } else {
            mTextSelection->clear();
        }
        break;
    case QEvent::MouseMove:
        if (!mTextSelection->mightStartDrag()) {
            if (const auto *doc = documentForIndex(index, textRect.width())) {
                const int charPos = doc->documentLayout()->hitTest(pos, Qt::FuzzyHit);
                if (charPos != -1) {
                    // QWidgetTextControl also has code to support isPreediting()/commitPreedit(), selectBlockOnTripleClick
                    mTextSelection->setTextSelectionEnd(index, charPos);
                    return true;
                }
            }
        }
        break;
    case QEvent::MouseButtonRelease: {
        qCDebug(TEXTAUTOGENERATETEXT_WIDGET_LOG) << "released";
        const TextAutoGenerateListViewDelegate::MessageLayout layout = doLayout(option, index);
        TextAutoGenerateDelegateUtils::setClipboardSelection(mTextSelection);
        // Clicks on links
        if (!mTextSelection->hasSelection()) {
            if (const auto *doc = documentForIndex(index, textRect.width())) {
                const QString link = doc->documentLayout()->anchorAt(pos);
                if (!link.isEmpty()) {
                    if (link.startsWith(TextUtils::TextUtilsSyntaxHighlighter::copyHref())) {
                        QString identifier = link;
                        identifier.remove(TextUtils::TextUtilsSyntaxHighlighter::copyHref());
                        // qDebug() << "identifier**************************** " << identifier;
                        const QString blockCodeStr = TextUtils::TextUtilsBlockCodeManager::self()->blockCode(identifier);
                        QClipboard *clipboard = QGuiApplication::clipboard();
                        clipboard->setText(blockCodeStr, QClipboard::Clipboard);
                        clipboard->setText(blockCodeStr, QClipboard::Selection);
                        KMessageBox::information(mListView, i18n("Block Code copied."), i18nc("@title", "Copy Block Code"));
                    } else if (link.startsWith(TextUtils::TextUtilsSyntaxHighlighter::insertBlockTextHref())) {
                        QString identifier = link;
                        identifier.remove(TextUtils::TextUtilsSyntaxHighlighter::insertBlockTextHref());
                        const QString blockCodeStr = TextUtils::TextUtilsBlockCodeManager::self()->blockCode(identifier);
                        Q_EMIT insertBlockCode(blockCodeStr);
                    } else {
                        QDesktopServices::openUrl(QUrl(link));
                    }
                    return true;
                }
            }
        } else if (mTextSelection->mightStartDrag()) {
            // clicked into selection, didn't start drag, clear it (like kwrite and QTextEdit)
            mTextSelection->clear();
        }
        if (layout.editedIconRect.contains(mouseEvent->pos())) {
            Q_EMIT editMessage(index);
            return true;
        } else if (layout.removeIconRect.contains(mouseEvent->pos())) {
            Q_EMIT removeMessage(index);
            return true;
        } else if (layout.copyIconRect.contains(mouseEvent->pos())) {
            Q_EMIT copyMessage(index);
            return true;
        } else if (layout.cancelIconRect.contains(mouseEvent->pos())) {
            Q_EMIT cancelRequested(index);
            return true;
        } else if (layout.refreshIconRect.contains(mouseEvent->pos())) {
            Q_EMIT refreshRequested(index);
            return true;
        } else if (layout.forkIconRect.contains(mouseEvent->pos())) {
            Q_EMIT forkRequested(index);
            return true;
        } else if (layout.textToSpeechIconRect.contains(mouseEvent->pos())) {
            const bool textToSpeechInProgress = index.data(TextAutoGenerateMessagesModel::TextToSpeechInProgressRole).toBool();
            if (textToSpeechInProgress) {
                Q_EMIT stopTextToSpeechRequested(index);
            } else {
                Q_EMIT textToSpeechRequested(index);
            }
            return true;
        }
        // don't return true here, we need to send mouse release events to other helpers (ex: click on image)
        break;
    }
    case QEvent::MouseButtonDblClick:
        if (!mTextSelection->hasSelection()) {
            if (const auto *doc = documentForIndex(index, textRect.width())) {
                const int charPos = doc->documentLayout()->hitTest(pos, Qt::FuzzyHit);
                qCDebug(TEXTAUTOGENERATETEXT_WIDGET_LOG) << "double-clicked at pos" << charPos;
                if (charPos == -1) {
                    return false;
                }
                mTextSelection->selectWordUnderCursor(index, charPos);
                return true;
            }
        }
        break;
    default:
        break;
    }

    return false;
}

void TextAutoGenerateListViewDelegate::needUpdateIndexBackground(const QPersistentModelIndex &index, const QColor &color)
{
    removeNeedUpdateIndexBackground(index);
    const IndexBackgroundColor back{.index = index, .color = color};
    mIndexBackgroundColorList.append(back);
}

void TextAutoGenerateListViewDelegate::removeNeedUpdateIndexBackground(const QPersistentModelIndex &index)
{
    auto it = std::find_if(mIndexBackgroundColorList.cbegin(), mIndexBackgroundColorList.cend(), [index](const IndexBackgroundColor &key) {
        return key.index == index;
    });
    if (it != mIndexBackgroundColorList.cend()) {
        mIndexBackgroundColorList.erase(it);
    }
}

void TextAutoGenerateListViewDelegate::needUpdateWaitingAnswerAnimation(
    const QPersistentModelIndex &index,
    const QList<TextAutoGenerateMessageWaitingAnswerAnimation::ScaleAndOpacity> &scaleAndOpacities)
{
    removeNeedUpdateWaitingAnswerAnimation(index);
    const TextAutoGenerateDelegateUtils::IndexScaleAndOpacities back{.index = index, .scaleAndOpacities = scaleAndOpacities};
    mIndexScaleAndOpacitiesList.append(back);
}

void TextAutoGenerateListViewDelegate::removeNeedUpdateWaitingAnswerAnimation(const QPersistentModelIndex &index)
{
    auto it = std::find_if(mIndexScaleAndOpacitiesList.cbegin(),
                           mIndexScaleAndOpacitiesList.cend(),
                           [index](const TextAutoGenerateDelegateUtils::IndexScaleAndOpacities &key) {
                               return key.index == index;
                           });
    if (it != mIndexScaleAndOpacitiesList.cend()) {
        mIndexScaleAndOpacitiesList.erase(it);
    }
}

void TextAutoGenerateListViewDelegate::setShowArchive(bool archive)
{
    if (mShowArchive != archive) {
        mShowArchive = archive;
        Q_EMIT updateView();
    }
}

void TextAutoGenerateListViewDelegate::setInProgress(bool newInProgress)
{
    if (mInProgress != newInProgress) {
        mInProgress = newInProgress;
        Q_EMIT updateView();
    }
}

#include "moc_textautogeneratelistviewdelegate.cpp"
