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

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

#include "autogeneratetext_ollama_debug.h"
#include "ollamacommonmodelinfosdelegateutils.h"
#include "ollamamodelinstalledinfosmodel.h"
#include "widgets/view/textautogeneratedelegateutils.h"
#include "widgets/view/textautogeneratelistviewtextselection.h"
#include <QAbstractTextDocumentLayout>
#include <QDesktopServices>
#include <QPainter>

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

OllamaModelInstalledInfosDelegate::~OllamaModelInstalledInfosDelegate() = default;

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

    const int margin = OllamaCommonModelInfosDelegateUtils::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),
                             OllamaCommonModelInfosDelegateUtils::rectRoundValue(),
                             OllamaCommonModelInfosDelegateUtils::rectRoundValue());

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

void OllamaModelInstalledInfosDelegate::draw(QPainter *painter,
                                             const OllamaModelInstalledInfosDelegate::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 OllamaModelInstalledInfosDelegate::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 OllamaModelInstalledInfosDelegate::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 OllamaModelInstalledInfosDelegate::ModelInfoLayout layout = doLayout(option, index);
            if (handleMouseEvent(mev, layout.textRect, option, index)) {
                return true;
            }
        }
    }
    return false;
}

bool OllamaModelInstalledInfosDelegate::helpEvent(QHelpEvent *helpEvent,
                                                  [[maybe_unused]] QAbstractItemView *view,
                                                  [[maybe_unused]] const QStyleOptionViewItem &option,
                                                  const QModelIndex &index)
{
    if (!index.isValid()) {
        return false;
    }
    if (helpEvent->type() == QEvent::ToolTip) {
        // TODO
    }
    return false;
}

OllamaModelInstalledInfosDelegate::ModelInfoLayout OllamaModelInstalledInfosDelegate::doLayout(const QStyleOptionViewItem &option,
                                                                                               const QModelIndex &index) const
{
    OllamaModelInstalledInfosDelegate::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(OllamaCommonModelInfosDelegateUtils::textMargin(),
                            usableRect.top() + OllamaCommonModelInfosDelegateUtils::textMargin(),
                            maxWidth - 2 * OllamaCommonModelInfosDelegateUtils::textMargin(),
                            textSize.height() + 2 * OllamaCommonModelInfosDelegateUtils::textMargin());
    return layout;
}

bool OllamaModelInstalledInfosDelegate::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_OLLAMA_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_OLLAMA_LOG) << "released";
        // const OllamaModelInstalledInfosDelegate::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_OLLAMA_LOG) << "double-clicked at pos" << charPos;
                if (charPos == -1) {
                    return false;
                }
                mTextSelection->selectWordUnderCursor(index, charPos);
                return true;
            }
        }
        break;
    default:
        break;
    }
    return false;
}

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

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

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

#include "moc_ollamamodelinstalledinfosdelegate.cpp"
