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

  SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "ollamacommonmodelavailableinfosdelegate.h"

#include "autogeneratetext_ollamacommon_debug.h"
#include "ollamacommonmodelavailableinfosmodel.h"
#include "plugincommonmodelinfosdelegateutils.h"
#include "widgets/view/textautogeneratedelegateutils.h"
#include "widgets/view/textautogeneratelistviewtextselection.h"
#include <QAbstractTextDocumentLayout>
#include <QDesktopServices>
#include <QPainter>

using namespace Qt::Literals::StringLiterals;
OllamaCommonModelAvailableInfosDelegate::OllamaCommonModelAvailableInfosDelegate(QListView *view)
    : TextAutoGenerateText::TextAutoGenerateListViewBaseDelegate(view)
{
}

OllamaCommonModelAvailableInfosDelegate::~OllamaCommonModelAvailableInfosDelegate() = default;

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

    const int margin = PluginCommonModelInfosDelegateUtils::basicMargin();
    painter->save();
    painter->setRenderHint(QPainter::Antialiasing);
    if (option.state & QStyle::State_Selected) {
        painter->setBrush(option.palette.brush(QPalette::Highlight));
    }

    const int offset = static_cast<int>(static_cast<double>(margin) / 2.0);
    painter->drawRoundedRect(option.rect.adjusted(offset, offset, -offset, -offset),
                             PluginCommonModelInfosDelegateUtils::rectRoundValue(),
                             PluginCommonModelInfosDelegateUtils::rectRoundValue());

    const OllamaCommonModelAvailableInfosDelegate::ModelInfoLayout layout = doLayout(option, index);
    if (layout.textRect.isValid()) {
        draw(painter, layout, index, option);
    }
    drawCatergories(painter, layout, index, option);
    painter->restore();
}

void OllamaCommonModelAvailableInfosDelegate::drawCatergories(QPainter *painter,
                                                              const OllamaCommonModelAvailableInfosDelegate::ModelInfoLayout &layout,
                                                              [[maybe_unused]] const QModelIndex &index,
                                                              [[maybe_unused]] const QStyleOptionViewItem &option) const
{
    for (const auto &cat : layout.categoriesLayout) {
        painter->drawRoundedRect(cat.categoryRect,
                                 PluginCommonModelInfosDelegateUtils::rectRoundValue(),
                                 PluginCommonModelInfosDelegateUtils::rectRoundValue());
        painter->drawText(cat.categoryRect.translated(PluginCommonModelInfosDelegateUtils::categoryMarginText(), 0), cat.categoryString);
    }
}

void OllamaCommonModelAvailableInfosDelegate::draw(QPainter *painter,
                                                   const OllamaCommonModelAvailableInfosDelegate::ModelInfoLayout &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 =
            TextAutoGenerateText::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();
}

bool OllamaCommonModelAvailableInfosDelegate::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 OllamaCommonModelAvailableInfosDelegate::ModelInfoLayout layout = doLayout(option, index);
        if (handleMouseEvent(mev, layout.textRect, 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 OllamaCommonModelAvailableInfosDelegate::ModelInfoLayout layout = doLayout(option, index);
            if (handleMouseEvent(mev, layout.textRect, option, index)) {
                return true;
            }
        }
    }
    return false;
}

OllamaCommonModelAvailableInfosDelegate::ModelInfoLayout OllamaCommonModelAvailableInfosDelegate::doLayout(const QStyleOptionViewItem &option,
                                                                                                           const QModelIndex &index) const
{
    OllamaCommonModelAvailableInfosDelegate::ModelInfoLayout layout;
    const int maxWidth = qMax(30, option.rect.width());
    const QSize textSize = documentSizeHint(index, maxWidth, option, &layout.baseLine);
    const QRect usableRect = option.rect;

    layout.textRect = QRect(PluginCommonModelInfosDelegateUtils::textMargin(),
                            usableRect.top() + PluginCommonModelInfosDelegateUtils::textMargin(),
                            maxWidth - 2 * PluginCommonModelInfosDelegateUtils::textMargin(),
                            textSize.height() + 2 * PluginCommonModelInfosDelegateUtils::textMargin());

    const QStringList categoriesName = index.data(OllamaCommonModelAvailableInfosModel::CategoriesName).toStringList();
    const QFontMetricsF emojiFontMetrics(option.font);
    int offsetX = 0;
    int offsetY = layout.textRect.bottom() - 10;
    int categoryWith = 0;
    layout.categoryRect = QRect(layout.textRect.x() + offsetX, offsetY, layout.textRect.width(), 20);
    for (const QString &cat : categoriesName) {
        CategoryLayout catLayout;
        catLayout.categoryString = cat;
        const qreal categoryWidthText = emojiFontMetrics.horizontalAdvance(catLayout.categoryString);
        catLayout.categoryRect = QRectF(layout.textRect.x() + offsetX,
                                        offsetY,
                                        categoryWidthText + PluginCommonModelInfosDelegateUtils::categoryMarginText() * 2,
                                        20); // TODO
        offsetX += catLayout.categoryRect.width() + PluginCommonModelInfosDelegateUtils::categoryOffset();
        layout.categoriesLayout.append(catLayout);
        categoryWith +=
            categoryWidthText + PluginCommonModelInfosDelegateUtils::categoryMarginText() * 2 + PluginCommonModelInfosDelegateUtils::categoryOffset() * 3;
        if (categoryWith >= layout.textRect.width()) {
            offsetX = 0;
            offsetY += 30;
            categoryWith = 0;
            layout.categoryRect.setBottom(layout.categoryRect.bottom() + 40); // TODO fix me hardcoded values
        }
    }
    return layout;
}

bool OllamaCommonModelAvailableInfosDelegate::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();
    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(AUTOGENERATETEXT_OLLAMACOMMON_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(AUTOGENERATETEXT_OLLAMACOMMON_LOG) << "released";
        // const OllamaCommonModelAvailableInfosDelegate::ModelInfoLayout layout = doLayout(option, index);
        TextAutoGenerateText::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()) {
                    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(AUTOGENERATETEXT_OLLAMACOMMON_LOG) << "double-clicked at pos" << charPos;
                if (charPos == -1) {
                    return false;
                }
                mTextSelection->selectWordUnderCursor(index, charPos);
                return true;
            }
        }
        break;
    default:
        break;
    }
    return false;
}

QSize OllamaCommonModelAvailableInfosDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    const QByteArray modelName = index.data(OllamaCommonModelAvailableInfosModel::ModelName).toByteArray();
    auto it = mSizeHintCache.find(modelName);
    if (it != mSizeHintCache.end()) {
        const QSize result = it->value;
        qCDebug(AUTOGENERATETEXT_OLLAMACOMMON_LOG) << "ApplicationsSettingsDelegate: SizeHint found in cache: " << result;
        return result;
    }

    // Note: option.rect in this method is huge (as big as the viewport)
    const OllamaCommonModelAvailableInfosDelegate::ModelInfoLayout layout = doLayout(option, index);
    const QSize size = {option.rect.width() - 2 * static_cast<int>(PluginCommonModelInfosDelegateUtils::textMargin()),
                        layout.textRect.height() + static_cast<int>(PluginCommonModelInfosDelegateUtils::textMargin())
                            + PluginCommonModelInfosDelegateUtils::spacingBetweenTextAndCategories() + layout.categoryRect.height()};
    if (!size.isEmpty()) {
        mSizeHintCache.insert(modelName, size);
    }
    return size;
}

QTextDocument *OllamaCommonModelAvailableInfosDelegate::documentForIndex(const QModelIndex &index, int width) const
{
    Q_ASSERT(index.isValid());
    const QByteArray modelName = index.data(OllamaCommonModelAvailableInfosModel::ModelName).toByteArray();
    Q_ASSERT(!modelName.isEmpty());
    auto it = mDocumentCache.find(modelName);
    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(OllamaCommonModelAvailableInfosModel::Description).toString();
    if (text.isEmpty()) {
        return nullptr;
    }
    auto doc = createTextDocument(u"<b>%1</b><br/>"_s.arg(QString::fromLatin1(modelName)) + text, width);
    auto ret = doc.get();
    mDocumentCache.insert(modelName, std::move(doc));
    return ret;
}

#include "moc_ollamacommonmodelavailableinfosdelegate.cpp"
