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

  SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "textautogeneratesearchlistviewdelegate.h"
#include "core/models/textautogeneratesearchmessagesmodel.h"
#include "core/textautogeneratesearchmessageutils.h"
#include "textautogeneratetextwidget_debug.h"
#include "widgets/view/textautogeneratedelegateutils.h"
#include "widgets/view/textautogeneratelistviewtextselection.h"
#include <QAbstractTextDocumentLayout>
#include <QDesktopServices>
#include <QListView>
#include <QPainter>

using namespace TextAutoGenerateText;
TextAutoGenerateSearchListViewDelegate::TextAutoGenerateSearchListViewDelegate(TextAutoGenerateText::TextAutoGenerateManager *manager, QListView *view)
    : TextAutoGenerateListViewMessageBaseDelegate{manager, view}
{
}

TextAutoGenerateSearchListViewDelegate::~TextAutoGenerateSearchListViewDelegate() = default;

QTextDocument *TextAutoGenerateSearchListViewDelegate::documentForIndex(const QModelIndex &index, int width) const
{
    Q_ASSERT(index.isValid());
    const QByteArray uuid = index.data(TextAutoGenerateSearchMessagesModel::MessageUuid).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(TextAutoGenerateSearchMessagesModel::PreviewText).toString();
    if (text.isEmpty()) {
        return nullptr;
    }
    auto doc = createTextDocument(text, width);
    auto ret = doc.get();
    mDocumentCache.insert(uuid, std::move(doc));
    return ret;
}

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

    const MessageLayout layout = doLayout(option, index);
    if (layout.textRect.isValid()) {
        painter->save();
        painter->setPen(Qt::NoPen);
        painter->setBrush(QBrush(option.palette.color(QPalette::Active, QPalette::Mid)));
        painter->setRenderHint(QPainter::Antialiasing);
        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);
    }
    /*
    painter->save();
    painter->setPen(QPen(Qt::green));
    painter->drawRect(layout.decoRect);
    painter->restore();
    */
}

void TextAutoGenerateSearchListViewDelegate::draw(QPainter *painter,
                                                  const MessageLayout &layout,
                                                  const QModelIndex &index,
                                                  const QStyleOptionViewItem &option) 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();
}

QSize TextAutoGenerateSearchListViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    const QByteArray uuid = index.data(TextAutoGenerateSearchMessagesModel::MessageUuid).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 TextAutoGenerateSearchListViewDelegate::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};
    if (!size.isEmpty()) {
        mSizeHintCache.insert(uuid, size);
    }
    return size;
}

TextAutoGenerateSearchListViewDelegate::MessageLayout TextAutoGenerateSearchListViewDelegate::doLayout(const QStyleOptionViewItem &option,
                                                                                                       const QModelIndex &index) const
{
    TextAutoGenerateSearchListViewDelegate::MessageLayout layout;
    const QRect usableRect = option.rect;
    const int indent = TextAutoGenerateDelegateUtils::leftLLMIndent();
    int maxWidth = qMax(30, option.rect.width() - indent - TextAutoGenerateDelegateUtils::rightIndent());
    const QSize textSize = documentSizeHint(index, maxWidth, option, &layout.baseLine);

    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);
    return layout;
}

bool TextAutoGenerateSearchListViewDelegate::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 TextAutoGenerateSearchListViewDelegate::MessageLayout layout = doLayout(option, index);
        if (handleMouseEvent(mev, layout.decoRect, option, index)) {
            return true;
        }
    } else if (eventType == QEvent::MouseButtonPress || eventType == QEvent::MouseMove || eventType == QEvent::MouseButtonDblClick) {
        auto mev = static_cast<QMouseEvent *>(event);
        if (mev->buttons() & Qt::LeftButton) {
            const TextAutoGenerateSearchListViewDelegate::MessageLayout layout = doLayout(option, index);
            if (handleMouseEvent(mev, layout.decoRect, option, index)) {
                return true;
            }
        }
    }
    return false;
}

bool TextAutoGenerateSearchListViewDelegate::handleMouseEvent(QMouseEvent *mouseEvent,
                                                              QRect messageRect,
                                                              [[maybe_unused]] const QStyleOptionViewItem &option,
                                                              const QModelIndex &index)
{
    if (!messageRect.contains(mouseEvent->pos())) {
        return false;
    }

    const QPoint pos =
        mouseEvent->pos() - messageRect.topLeft() + QPoint(-TextAutoGenerateDelegateUtils::spacingText() * 2, -TextAutoGenerateDelegateUtils::spacingText());

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

    // Text selection
    switch (eventType) {
    case QEvent::MouseButtonPress:
        mTextSelection->setMightStartDrag(false);
        if (const auto *doc = documentForIndex(index, messageRect.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, messageRect.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";
        TextAutoGenerateDelegateUtils::setClipboardSelection(mTextSelection);
        // Clicks on links
        if (!mTextSelection->hasSelection()) {
            if (const auto *doc = documentForIndex(index, messageRect.width())) {
                const QString link = doc->documentLayout()->anchorAt(pos);
                if (!link.isEmpty()) {
                    if (link.startsWith(TextAutoGenerateSearchMessageUtils::scheme())) {
                        Q_EMIT goToMessage(link);
                    } 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();
        }
        // 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, messageRect.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;
}

#include "moc_textautogeneratesearchlistviewdelegate.cpp"
