#include "DeadCodeAnalyzer.h"

#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QTextStream>
#include <QRegularExpression>
#include <QSet>
#include <QFileInfo>

DeadCodeAnalyzer::DeadCodeAnalyzer(QObject *parent)
    : QObject(parent)
{
    m_scanner = new FunctionScanner(this);
}

QList<DeadCodeAnalyzer::DeadFunction> DeadCodeAnalyzer::analyze(
    const QString     &projectRoot,
    const QStringList &extensions,
    const QStringList &excludeDirs) const
{
    // -----------------------------------------------------------------------
    // Schritt 1: Alle Dateien sammeln (mit Exclude-Filter)
    // -----------------------------------------------------------------------
    QStringList allFiles;
    QDirIterator it(projectRoot, extensions, QDir::Files,
                    QDirIterator::Subdirectories);

    while (it.hasNext())
    {
        const QString path = it.next();

        // Exclude-Verzeichnisse prüfen
        bool excluded = false;
        if (!excludeDirs.isEmpty())
        {
            const QStringList parts = path.split('/');
            for (const QString &excl : excludeDirs)
            {
                if (parts.contains(excl.trimmed(), Qt::CaseInsensitive))
                {
                    excluded = true;
                    break;
                }
            }
        }

        if (!excluded)
        {
            allFiles.append(path);
        }
    }

    const int totalFiles = allFiles.size();

    // -----------------------------------------------------------------------
    // Schritt 2: Alle Funktionsdefinitionen aus nicht-excluded Dateien
    // -----------------------------------------------------------------------
    QHash<QString, FunctionScanner::FunctionInfo> definitions;

    for (const QString &path : allFiles)
    {
        const QList<FunctionScanner::FunctionInfo> funcs = m_scanner->scanFile(path);
        for (const FunctionScanner::FunctionInfo &func : funcs)
        {
            if (func.name.startsWith("__"))
            {
                continue;
            }
            const QString key = func.name.toLower();
            if (!definitions.contains(key))
            {
                definitions.insert(key, func);
            }
        }
    }

    if (definitions.isEmpty())
    {
        return {};
    }

    // -----------------------------------------------------------------------
    // Schritt 3: Alle Dateien EINMAL lesen, alle Aufrufe sammeln
    // -----------------------------------------------------------------------
    QSet<QString> calledFunctions;

    static const QRegularExpression identifierRegex(
        R"((?:->|::|\b)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\s*\()"
        R"(|['"]([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)['"])"
    );

    static const QRegularExpression defLineRegex(
        R"(\bfunction\s+([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*))"
    );

    int filesScanned = 0;

    for (const QString &filePath : allFiles)
    {
        ++filesScanned;

        // Fortschritt signalisieren
        emit progressUpdate(filesScanned, totalFiles,
                            QFileInfo(filePath).fileName());

        QFile file(filePath);
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
        {
            continue;
        }

        QTextStream stream(&file);
        stream.setEncoding(QStringConverter::Utf8);

        while (!stream.atEnd())
        {
            const QString line    = stream.readLine();
            const QString trimmed = line.trimmed();

            if (trimmed.startsWith("//") ||
                trimmed.startsWith("*")  ||
                trimmed.startsWith("#")  ||
                trimmed.startsWith("/*"))
            {
                continue;
            }

            // Definitionen auf dieser Zeile nicht als Aufruf werten
            QSet<QString> definedOnThisLine;
            {
                QRegularExpressionMatchIterator dit = defLineRegex.globalMatch(line);
                while (dit.hasNext())
                {
                    definedOnThisLine.insert(dit.next().captured(1).toLower());
                }
            }

            QRegularExpressionMatchIterator mit = identifierRegex.globalMatch(line);
            while (mit.hasNext())
            {
                const QRegularExpressionMatch match = mit.next();
                const QString name = match.captured(1).isEmpty()
                                     ? match.captured(2).toLower()
                                     : match.captured(1).toLower();

                if (name.isEmpty() || definedOnThisLine.contains(name))
                {
                    continue;
                }

                if (definitions.contains(name))
                {
                    calledFunctions.insert(name);
                }
            }
        }
    }

    // -----------------------------------------------------------------------
    // Schritt 4: Nicht aufgerufene Funktionen zurückgeben
    // -----------------------------------------------------------------------
    QList<DeadFunction> dead;

    for (auto it2 = definitions.begin(); it2 != definitions.end(); ++it2)
    {
        if (!calledFunctions.contains(it2.key()))
        {
            const FunctionScanner::FunctionInfo &func = it2.value();
            DeadFunction d;
            d.info   = func;
            d.reason = func.className.isEmpty()
                ? QObject::tr("Globale Funktion — kein Aufruf gefunden")
                : QObject::tr("Methode von %1 — kein Aufruf gefunden")
                      .arg(func.className);
            dead.append(d);
        }
    }

    return dead;
}
