// Copyright (C) 2023 The Qt Company Ltd.
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE

#include "LLMSuggestion.hpp"
#include <texteditor/texteditor.h>
#include <utils/stringutils.h>
#include <utils/tooltip/tooltip.h>

namespace QodeAssist {

static bool isClosingTail(const QString &s, int from)
{
    static const QString closeChars = QStringLiteral("(){}[];,");
    for (int i = from; i < s.size(); ++i) {
        const QChar c = s.at(i);
        if (!c.isSpace() && !closeChars.contains(c))
            return false;
    }
    return true;
}

int LLMSuggestion::calculateReplaceLength(const QString &suggestion, const QString &rightText)
{
    if (rightText.isEmpty())
        return 0;

    const int maxN = qMin(suggestion.size(), rightText.size());
    int lcp = 0;
    while (lcp < maxN && suggestion.at(lcp) == rightText.at(lcp))
        ++lcp;

    if (lcp > 0) {
        if (isClosingTail(rightText, lcp))
            return rightText.size();
        return lcp;
    }

    if (!isClosingTail(rightText, 0))
        return 0;

    static const QString closeChars = QStringLiteral("(){}[];,");
    int i = suggestion.size() - 1;
    while (i >= 0 && suggestion.at(i).isSpace())
        --i;
    if (i >= 0 && closeChars.contains(suggestion.at(i)) && rightText.contains(suggestion.at(i)))
        return rightText.size();

    return 0;
}

LLMSuggestion::LLMSuggestion(
    const QList<Data> &suggestions, QTextDocument *sourceDocument, int currentCompletion)
    : TextEditor::CyclicSuggestion(suggestions, sourceDocument, currentCompletion)
{
    const auto &data = suggestions[currentCompletion];

    int startPos = data.range.begin.toPositionInDocument(sourceDocument);

    startPos = qBound(0, startPos, sourceDocument->characterCount());

    QTextCursor cursor(sourceDocument);
    cursor.setPosition(startPos);
    QTextBlock block = cursor.block();
    QString blockText = block.text();

    int cursorPositionInBlock = cursor.positionInBlock();
    QString leftText = blockText.left(cursorPositionInBlock);
    QString rightText = blockText.mid(cursorPositionInBlock);

    QString suggestionText = data.text;

    if (!suggestionText.contains('\n')) {
        int replaceLength = calculateReplaceLength(suggestionText, rightText);
        QString remainingRightText = (replaceLength > 0) ? rightText.mid(replaceLength) : rightText;

        QString displayText = leftText + suggestionText + remainingRightText;
        replacementDocument()->setPlainText(displayText);
    } else {
        int firstLineEnd = suggestionText.indexOf('\n');
        QString firstLine = suggestionText.left(firstLineEnd);
        QString restOfCompletion = suggestionText.mid(firstLineEnd);

        int replaceLength = calculateReplaceLength(firstLine, rightText);
        QString remainingRightText = (replaceLength > 0) ? rightText.mid(replaceLength) : rightText;

        QString displayText = leftText + firstLine + remainingRightText + restOfCompletion;
        replacementDocument()->setPlainText(displayText);
    }
}

bool LLMSuggestion::applyWord(TextEditor::TextEditorWidget *widget)
{
    return applyPart(Word, widget);
}

bool LLMSuggestion::applyLine(TextEditor::TextEditorWidget *widget)
{
    return applyPart(Line, widget);
}

bool LLMSuggestion::applyPart(Part part, TextEditor::TextEditorWidget *widget)
{
    const auto &currentSuggestions = suggestions();
    const auto &currentData = currentSuggestions[currentSuggestion()];
    const Utils::Text::Range range = currentData.range;
    const QTextCursor cursor = range.begin.toTextCursor(sourceDocument());
    QTextCursor currentCursor = widget->textCursor();
    const QString text = currentData.text;

    const int startPos = currentCursor.positionInBlock() - cursor.positionInBlock()
                         + (cursor.selectionEnd() - cursor.selectionStart());

    int next = part == Word ? Utils::endOfNextWord(text, startPos) : text.indexOf('\n', startPos);

    if (next == -1) {
        if (part == Line) {
            next = text.length();
        } else {
            return apply();
        }
    }

    if (part == Line)
        ++next;

    QString subText = text.mid(startPos, next - startPos);

    if (subText.isEmpty()) {
        return false;
    }

    if (startPos == 0) {
        QTextBlock currentBlock = cursor.block();
        QString textAfterCursor = currentBlock.text().mid(cursor.positionInBlock());

        int replaceLength = calculateReplaceLength(text, textAfterCursor);

        if (replaceLength > 0) {
            currentCursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, replaceLength);
            currentCursor.removeSelectedText();
        }
    }

    if (!subText.contains('\n')) {
        currentCursor.insertText(subText);

        const QString remainingText = text.mid(next);
        if (!remainingText.isEmpty()) {
            QTextCursor newCursor = widget->textCursor();
            const Utils::Text::Position newStart = Utils::Text::Position::fromPositionInDocument(
                newCursor.document(), newCursor.position());
            const Utils::Text::Position
                newEnd{newStart.line, newStart.column + int(remainingText.length())};
            const Utils::Text::Range newRange{newStart, newEnd};
            const QList<Data> newSuggestion{{newRange, newStart, remainingText}};
            widget->insertSuggestion(
                std::make_unique<LLMSuggestion>(newSuggestion, widget->document(), 0));
        }
    } else {
        currentCursor.insertText(subText);

        if (const int seperatorPos = subText.lastIndexOf('\n'); seperatorPos >= 0) {
            const QString newCompletionText = text.mid(startPos + seperatorPos + 1);
            if (!newCompletionText.isEmpty()) {
                const Utils::Text::Position newStart{int(range.begin.line + subText.count('\n')), 0};
                const Utils::Text::Position newEnd{newStart.line, int(newCompletionText.length())};
                const Utils::Text::Range newRange{newStart, newEnd};
                const QList<Data> newSuggestion{{newRange, newEnd, newCompletionText}};
                widget->insertSuggestion(
                    std::make_unique<LLMSuggestion>(newSuggestion, widget->document(), 0));
            }
        }
    }

    return false;
}

bool LLMSuggestion::apply()
{
    const auto &currentSuggestions = suggestions();
    const auto &currentData = currentSuggestions[currentSuggestion()];
    const Utils::Text::Range range = currentData.range;
    const QTextCursor cursor = range.begin.toTextCursor(sourceDocument());
    QString text = currentData.text;

    QTextBlock currentBlock = cursor.block();
    QString textAfterCursor = currentBlock.text().mid(cursor.positionInBlock());

    QTextCursor editCursor = cursor;
    editCursor.beginEditBlock();

    int firstLineEnd = text.indexOf('\n');
    if (firstLineEnd != -1) {
        QString firstLine = text.left(firstLineEnd);
        QString restOfText = text.mid(firstLineEnd);

        int replaceLength = calculateReplaceLength(firstLine, textAfterCursor);

        if (replaceLength > 0) {
            editCursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, replaceLength);
            editCursor.removeSelectedText();
        }

        editCursor.insertText(firstLine + restOfText);
    } else {
        int replaceLength = calculateReplaceLength(text, textAfterCursor);

        if (replaceLength > 0) {
            editCursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, replaceLength);
            editCursor.removeSelectedText();
        }

        editCursor.insertText(text);
    }

    editCursor.endEditBlock();
    return true;
}

} // namespace QodeAssist

