/*
    SPDX-FileCopyrightText: 2026 Alexander Lohnau <alexander.lohnau@kde.org>
    SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "modernize-list-initialization.h"
#include "QtUtils.h"
#include "TypeUtils.h"
#include "Utils.h"
#include "clang/AST/ParentMap.h"

#include <algorithm>
#include <clang/AST/AST.h>

using namespace clang;

void ModernizeListInitialization::VisitStmt(clang::Stmt *stmt)
{
    if (auto expr = dyn_cast<DeclStmt>(stmt)) {
        if (auto varDecl = dyn_cast<VarDecl>(expr->getSingleDecl()); varDecl && varDecl->hasInit()) {
            if (auto *initExpr = dyn_cast<ExprWithCleanups>(varDecl->getInit())) {
                const std::string name = initExpr->getType().getUnqualifiedType().getAsString();
                if (name == "QStringList" || name.starts_with("QList")) {
                    if (auto constructExpr = dyn_cast<CXXConstructExpr>(initExpr->getSubExpr()); constructExpr && constructExpr->getNumArgs() > 0) {
                        auto exprArg = constructExpr->getArg(0)->IgnoreImplicit();
                        // Resolve any ParenExpr that might be in between
                        if (auto parenExpr = dyn_cast<ParenExpr>(exprArg)) {
                            exprArg = parenExpr->getSubExpr();
                        }

                        auto operatorCall = dyn_cast<CXXOperatorCallExpr>(exprArg);
                        if (operatorCall && operatorCall->getOperator() == OverloadedOperatorKind::OO_LessLess) {
                            SourceLocation afterName = Lexer::getLocForEndOfToken(varDecl->getLocation(), 0, sm(), lo());
                            checkOperatorCallListInitialization(SourceRange(afterName, initExpr->getEndLoc()), operatorCall);
                            m_alreadyCheckedOperatorCalls.push_back(operatorCall);
                        }
                    }
                }
            }
        }
    }

    if (auto *topLevelOperatorCall = dyn_cast<CXXOperatorCallExpr>(stmt);
        topLevelOperatorCall && topLevelOperatorCall->getOperator() == OverloadedOperatorKind::OO_LessLess) {
        if (std::ranges::find(m_alreadyCheckedOperatorCalls, topLevelOperatorCall) != m_alreadyCheckedOperatorCalls.end()) {
            return;
        }
        std::string name = topLevelOperatorCall->getType().getAsString();
        if (name != "QStringList" && !name.starts_with("QList")) {
            return;
        }
        auto parent = m_context->parentMap->getParent(topLevelOperatorCall);
        if (!isa<ImplicitCastExpr>(parent)) {
            return;
        }
        checkOperatorCallListInitialization({parent->getBeginLoc(), topLevelOperatorCall->getEndLoc()}, topLevelOperatorCall);
    }
}

void ModernizeListInitialization::checkOperatorCallListInitialization(clang::SourceRange fixitSourceRange, clang::CXXOperatorCallExpr *operatorCall)
{
    std::vector<std::string> replacementTexts;
    auto opCall = operatorCall;
    while (opCall && opCall->getNumArgs() > 0) {
        auto firstArg = opCall->getArg(1);
        std::string sourceText = getSourceText(firstArg).str();
#if LLVM_VERSION_MAJOR < 20
        auto token = Lexer::findNextToken(firstArg->getEndLoc(), sm(), lo());
#else
        auto token = Lexer::findNextToken(firstArg->getEndLoc(), sm(), lo(), true);
#endif
        if (token.has_value() && token.value().is(tok::TokenKind::comment)) {
            std::string comment = getSourceText(CharSourceRange::getTokenRange(token->getLocation())).str();
            if (comment.starts_with("//")) {
                sourceText += ", " + comment + "\n";
            } else {
                sourceText += " " + comment + ", ";
            }
        } else {
            sourceText += ", ";
        }
        replacementTexts.push_back(sourceText);

        if (auto *materializeTemp = dyn_cast<MaterializeTemporaryExpr>(opCall->getArg(0))) {
            if (auto *bindTemp = dyn_cast<CXXBindTemporaryExpr>(materializeTemp->getSubExpr())) {
                if (isa<CallExpr>(bindTemp->getSubExpr())) {
                    return; // abort since we have a function that produced the initial list
                }
            }
        }
        opCall = dyn_cast<CXXOperatorCallExpr>(opCall->getArg(0));
    }

    if (replacementTexts.empty()) {
        return;
    }

    std::reverse(replacementTexts.begin(), replacementTexts.end());
    const auto replacementTextMultiline = std::ranges::find_if(replacementTexts, [](std::string text) {
        return text.ends_with("\n");
    });
    if (replacementTextMultiline == replacementTexts.end()) {
        auto &text = replacementTexts.back();
        if (text.ends_with(", ")) {
            // Replace comma with just a space
            text.replace(text.size() - 2, 2, " ");
        }
    }

    std::string replacementText = "{";
    for (auto str : replacementTexts) {
        replacementText += str;
    }
    if (replacementText[replacementText.length() - 1] == '\n') {
        replacementText += "}";
    } else {
        replacementText[replacementText.length() - 1] = '}';
    }

    std::vector<FixItHint> fixes{FixItHint::CreateReplacement(fixitSourceRange, replacementText)};
    emitWarning(fixitSourceRange.getBegin(), "Use initializer-list syntax", fixes);
}
