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

#include "modernize-overloaded-connects.h"
#include "PreProcessorVisitor.h"
#include "QtUtils.h"
#include "TypeUtils.h"
#include "Utils.h"

#include <clang/AST/AST.h>

using namespace clang;

void ModernizeOverloadedConnects::VisitStmt(clang::Stmt *stmt)
{
    auto *call = dyn_cast<CallExpr>(stmt);
    if (!call) {
        return;
    }

    FunctionDecl *func = call->getDirectCallee();
    if (!func || !clazy::connectHasPMFStyle(func)) {
        return;
    }
    const std::string funcName = trimQtNamespace(func->getQualifiedNameAsString());
    const int numArgs = call->getNumArgs();
    if (funcName == "QObject::connect") {
        checkConnectArg(call, 1);
        checkConnectArg(call, numArgs > 3 ? 3 : 2);
    } else if (funcName == "QTimer::singleShot") {
        checkConnectArg(call, numArgs - 1);
    } else if (funcName == "QMenu::addAction" || funcName == "QWidget::addAction") {
        const PreProcessorVisitor *preProcessorVisitor = m_context->preprocessorVisitor;
        const int numArgToCheck = !preProcessorVisitor || preProcessorVisitor->qtVersion() >= 60000 ? numArgs - 1 : numArgs - 2;
        checkConnectArg(call, numArgToCheck);
    }
}

void ModernizeOverloadedConnects::checkConnectArg(CallExpr *call, int numArgToCheck)
{
    CXXMethodDecl *pmfFromConnect = clazy::pmfFromConnect(call, numArgToCheck, m_context->qtNamespace());
    if (!pmfFromConnect) {
        return;
    }

    const std::string pmfMethodName = pmfFromConnect->getNameAsString();
    bool isMethodOverloaded = false;
    for (CXXMethodDecl *method : pmfFromConnect->getParent()->methods()) {
        if (!isMethodOverloaded && method != pmfFromConnect) {
            isMethodOverloaded = pmfMethodName == method->getNameAsString();
        }
    }

    const auto maybeEmitWarn = [this](clang::SourceLocation loc, std::string error, const std::vector<FixItHint> &fixits) {
        if (clazy::isUIFile(loc, sm())) {
            return; // Do not warn for any autogenerated code!
        }
        emitWarning(loc, error, fixits);
    };

    Expr *argToCheck = call->getArg(numArgToCheck)->IgnoreImplicit();
    if (auto operatorCall = dyn_cast<CXXOperatorCallExpr>(argToCheck); operatorCall && operatorCall->getNumArgs() > 1 && !isMethodOverloaded) {
        if (auto expr = dyn_cast<DeclRefExpr>(operatorCall->getArg(0)->IgnoreImplicit())) {
            if (expr->getType().getUnqualifiedType().getAsString().starts_with("QOverload")) {
                std::vector<FixItHint> fixits{
                    FixItHint::CreateRemoval(SourceRange(operatorCall->getBeginLoc(), operatorCall->getArg(1)->getBeginLoc().getLocWithOffset(-1))),
                    FixItHint::CreateRemoval(SourceRange(operatorCall->getEndLoc(), operatorCall->getEndLoc())),
                };
                maybeEmitWarn(call->getBeginLoc(), "Unneeded qOverload", fixits);
            }
        }
    }
    if (auto callExpr = dyn_cast<CallExpr>(argToCheck)) {
        if (auto calleeDecl = dyn_cast<CXXMethodDecl>(callExpr->getDirectCallee())) {
            if (calleeDecl->getNameAsString() == "of" && calleeDecl->getParent()->getNameAsString() == "QNonConstOverload") {
                if (isMethodOverloaded) {
                    std::vector<FixItHint> fixits{
                        FixItHint::CreateReplacement(SourceRange(callExpr->getBeginLoc(), callExpr->getBeginLoc().getLocWithOffset(1)), "qOverload"),
                        FixItHint::CreateRemoval(SourceRange(callExpr->getArg(0)->getBeginLoc().getLocWithOffset(-5), // Remove ::of part from source code
                                                             callExpr->getArg(0)->getBeginLoc().getLocWithOffset(-2))),
                    };
                    maybeEmitWarn(call->getBeginLoc(), "Use more concise qOverload", fixits);
                } else {
                    std::vector<FixItHint> fixits{
                        FixItHint::CreateRemoval(SourceRange(callExpr->getBeginLoc(), callExpr->getArg(0)->getBeginLoc().getLocWithOffset(-1))),
                        FixItHint::CreateRemoval(SourceRange(callExpr->getEndLoc(), callExpr->getEndLoc())),
                    };
                    maybeEmitWarn(call->getBeginLoc(), "Unneeded QOverload::of", fixits);
                }
            }
        }
    }
    if (auto castExpr = dyn_cast<CXXStaticCastExpr>(argToCheck)) {
        auto subExpr = castExpr->getSubExpr();
        if (isMethodOverloaded) {
            std::string signalOverloadParams;
            for (auto p : pmfFromConnect->parameters()) {
                if (!signalOverloadParams.empty()) {
                    signalOverloadParams += ", ";
                }
                signalOverloadParams += p->getType().getAsString();
            }
            std::vector<FixItHint> fixits{
                FixItHint::CreateReplacement(SourceRange(castExpr->getBeginLoc(), subExpr->getBeginLoc().getLocWithOffset(-2)),
                                             "qOverload<" + signalOverloadParams + ">"),
            };
            maybeEmitWarn(call->getBeginLoc(), "Unneeded QOverload::of", fixits);
        } else {
            std::vector<FixItHint> fixits{
                FixItHint::CreateRemoval(SourceRange(castExpr->getBeginLoc(), subExpr->getBeginLoc().getLocWithOffset(-1))),
                FixItHint::CreateRemoval(SourceRange(castExpr->getEndLoc(), castExpr->getEndLoc())),
            };
            maybeEmitWarn(call->getBeginLoc(), "Unneeded QOverload::of", fixits);
        }
    }
}
