#include "SyntaxHighlighter.h"
#include <QTextDocument>

// ===========================================================================
// SyntaxHighlighter – Basis
// ===========================================================================
SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent)
    : QSyntaxHighlighter(parent)
{
}

void SyntaxHighlighter::highlightBlock(const QString &text)
{
    for (const HighlightRule &rule : m_rules)
    {
        QRegularExpressionMatchIterator it = rule.pattern.globalMatch(text);
        while (it.hasNext())
        {
            QRegularExpressionMatch match = it.next();
            setFormat(
                static_cast<int>(match.capturedStart()),
                static_cast<int>(match.capturedLength()),
                rule.format
            );
        }
    }

    if (!m_hasMultiLineComment)
    {
        return;
    }

    setCurrentBlockState(0);

    int startIndex = 0;
    if (previousBlockState() != 1)
    {
        QRegularExpressionMatch m = m_commentStartExpression.match(text);
        startIndex = m.hasMatch() ? static_cast<int>(m.capturedStart()) : -1;
    }

    while (startIndex >= 0)
    {
        QRegularExpressionMatch endMatch = m_commentEndExpression.match(text, startIndex);
        int commentLength = 0;

        if (endMatch.hasMatch())
        {
            commentLength = static_cast<int>(endMatch.capturedStart())
                          - startIndex
                          + static_cast<int>(endMatch.capturedLength());
        }
        else
        {
            setCurrentBlockState(1);
            commentLength = text.length() - startIndex;
        }

        setFormat(startIndex, commentLength, m_multiLineCommentFormat);

        if (!endMatch.hasMatch())
        {
            break;
        }

        QRegularExpressionMatch nextStart =
            m_commentStartExpression.match(text, startIndex + commentLength);
        startIndex = nextStart.hasMatch()
                   ? static_cast<int>(nextStart.capturedStart())
                   : -1;
    }
}

// ===========================================================================
// CppHighlighter
// ===========================================================================
CppHighlighter::CppHighlighter(QTextDocument *parent)
    : SyntaxHighlighter(parent)
{
    m_hasMultiLineComment = true;

    QTextCharFormat keywordFormat;
    keywordFormat.setForeground(QColor("#569CD6"));
    keywordFormat.setFontWeight(QFont::Bold);

    const QStringList keywords = {
        "alignas","alignof","and","and_eq","asm","auto","bitand","bitor",
        "bool","break","case","catch","char","char8_t","char16_t","char32_t",
        "class","compl","concept","const","consteval","constexpr","constinit",
        "const_cast","continue","co_await","co_return","co_yield","decltype",
        "default","delete","do","double","dynamic_cast","else","enum",
        "explicit","export","extern","false","float","for","friend","goto",
        "if","inline","int","long","mutable","namespace","new","noexcept",
        "not","not_eq","nullptr","operator","or","or_eq","private","protected",
        "public","register","reinterpret_cast","requires","return","short",
        "signed","sizeof","static","static_assert","static_cast","struct",
        "switch","template","this","thread_local","throw","true","try",
        "typedef","typeid","typename","union","unsigned","using","virtual",
        "void","volatile","wchar_t","while","xor","xor_eq","override","final"
    };

    for (const QString &kw : keywords)
    {
        HighlightRule rule;
        rule.pattern = QRegularExpression(QString("\\b%1\\b").arg(kw));
        rule.format  = keywordFormat;
        m_rules.append(rule);
    }

    QTextCharFormat preprocFormat;
    preprocFormat.setForeground(QColor("#C586C0"));
    { HighlightRule r; r.pattern = QRegularExpression("^\\s*#\\s*\\w+"); r.format = preprocFormat; m_rules.append(r); }

    QTextCharFormat stringFormat;
    stringFormat.setForeground(QColor("#CE9178"));
    { HighlightRule r; r.pattern = QRegularExpression(R"("(?:[^"\\]|\\.)*")"); r.format = stringFormat; m_rules.append(r); }
    { HighlightRule r; r.pattern = QRegularExpression(R"('(?:[^'\\]|\\.)*')"); r.format = stringFormat; m_rules.append(r); }

    QTextCharFormat numberFormat;
    numberFormat.setForeground(QColor("#B5CEA8"));
    { HighlightRule r; r.pattern = QRegularExpression(R"(\b(0[xX][0-9A-Fa-f]+[uUlL]*|[0-9]+\.?[0-9]*([eE][+-]?[0-9]+)?[fFlLuU]*)\b)"); r.format = numberFormat; m_rules.append(r); }

    QTextCharFormat commentFormat;
    commentFormat.setForeground(QColor("#6A9955"));
    commentFormat.setFontItalic(true);
    { HighlightRule r; r.pattern = QRegularExpression("//[^\n]*"); r.format = commentFormat; m_rules.append(r); }

    m_multiLineCommentFormat  = commentFormat;
    m_commentStartExpression  = QRegularExpression(R"(/\*)");
    m_commentEndExpression    = QRegularExpression(R"(\*/)");
}

void CppHighlighter::highlightBlock(const QString &text)
{
    SyntaxHighlighter::highlightBlock(text);
}

// ===========================================================================
// CssHighlighter
// ===========================================================================
CssHighlighter::CssHighlighter(QTextDocument *parent)
    : SyntaxHighlighter(parent)
{
    m_hasMultiLineComment = true;

    // Selektoren: .klasse  #id  element  ::pseudo  :pseudo
    QTextCharFormat selectorFormat;
    selectorFormat.setForeground(QColor("#D7BA7D"));
    { HighlightRule r; r.pattern = QRegularExpression(R"([.#]?[\w-]+\s*(?=\s*[,{]))"); r.format = selectorFormat; m_rules.append(r); }
    { HighlightRule r; r.pattern = QRegularExpression(R"(:{1,2}[\w-]+)"); r.format = selectorFormat; m_rules.append(r); }

    // Eigenschaften (property:)
    QTextCharFormat propFormat;
    propFormat.setForeground(QColor("#9CDCFE"));
    { HighlightRule r; r.pattern = QRegularExpression(R"([\w-]+\s*(?=:))"); r.format = propFormat; m_rules.append(r); }

    // Werte – Farben #hex
    QTextCharFormat colorFormat;
    colorFormat.setForeground(QColor("#CE9178"));
    { HighlightRule r; r.pattern = QRegularExpression(R"(#[0-9A-Fa-f]{3,8}\b)"); r.format = colorFormat; m_rules.append(r); }

    // Zahlen + Einheiten
    QTextCharFormat numberFormat;
    numberFormat.setForeground(QColor("#B5CEA8"));
    { HighlightRule r; r.pattern = QRegularExpression(R"(\b\d+\.?\d*(px|em|rem|%|vh|vw|pt|cm|mm|s|ms)?\b)"); r.format = numberFormat; m_rules.append(r); }

    // Strings
    QTextCharFormat stringFormat;
    stringFormat.setForeground(QColor("#CE9178"));
    { HighlightRule r; r.pattern = QRegularExpression(R"("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')"); r.format = stringFormat; m_rules.append(r); }

    // !important
    QTextCharFormat importantFormat;
    importantFormat.setForeground(QColor("#F44747"));
    importantFormat.setFontWeight(QFont::Bold);
    { HighlightRule r; r.pattern = QRegularExpression(R"(!important)"); r.format = importantFormat; m_rules.append(r); }

    // @-Regeln
    QTextCharFormat atFormat;
    atFormat.setForeground(QColor("#C586C0"));
    { HighlightRule r; r.pattern = QRegularExpression(R"(@[\w-]+)"); r.format = atFormat; m_rules.append(r); }

    QTextCharFormat commentFormat;
    commentFormat.setForeground(QColor("#6A9955"));
    commentFormat.setFontItalic(true);
    m_multiLineCommentFormat = commentFormat;
    m_commentStartExpression = QRegularExpression(R"(/\*)");
    m_commentEndExpression   = QRegularExpression(R"(\*/)");
}

void CssHighlighter::highlightBlock(const QString &text)
{
    SyntaxHighlighter::highlightBlock(text);
}

// ===========================================================================
// HtmlHighlighter
// ===========================================================================
HtmlHighlighter::HtmlHighlighter(QTextDocument *parent)
    : SyntaxHighlighter(parent)
{
    // Tag-Namen  <div  </div  />
    QTextCharFormat tagFormat;
    tagFormat.setForeground(QColor("#569CD6"));
    { HighlightRule r; r.pattern = QRegularExpression(R"(</?[\w:-]+)"); r.format = tagFormat; m_rules.append(r); }
    { HighlightRule r; r.pattern = QRegularExpression(R"(/?>)"); r.format = tagFormat; m_rules.append(r); }

    // Attribute  name=
    QTextCharFormat attrFormat;
    attrFormat.setForeground(QColor("#9CDCFE"));
    { HighlightRule r; r.pattern = QRegularExpression(R"(\b[\w:-]+=)"); r.format = attrFormat; m_rules.append(r); }

    // Attributwerte  "wert"  'wert'
    QTextCharFormat valueFormat;
    valueFormat.setForeground(QColor("#CE9178"));
    { HighlightRule r; r.pattern = QRegularExpression(R"("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')"); r.format = valueFormat; m_rules.append(r); }

    // DOCTYPE
    QTextCharFormat doctypeFormat;
    doctypeFormat.setForeground(QColor("#808080"));
    { HighlightRule r; r.pattern = QRegularExpression(R"(<!DOCTYPE[^>]*>)", QRegularExpression::CaseInsensitiveOption); r.format = doctypeFormat; m_rules.append(r); }

    // Entities  &amp;  &#123;
    QTextCharFormat entityFormat;
    entityFormat.setForeground(QColor("#D7BA7D"));
    { HighlightRule r; r.pattern = QRegularExpression(R"(&(?:#\d+|#x[0-9A-Fa-f]+|[\w]+);)"); r.format = entityFormat; m_rules.append(r); }

    // Kommentare  <!-- ... -->  (mehrzeilig)
    QTextCharFormat commentFormat;
    commentFormat.setForeground(QColor("#6A9955"));
    commentFormat.setFontItalic(true);
    m_multiLineCommentFormat = commentFormat;
    m_commentStartExpression = QRegularExpression("<!--");
    m_commentEndExpression   = QRegularExpression("-->");
    m_hasMultiLineComment    = true;
}

void HtmlHighlighter::highlightBlock(const QString &text)
{
    SyntaxHighlighter::highlightBlock(text);
}

// ===========================================================================
// PhpHighlighter
// ===========================================================================
PhpHighlighter::PhpHighlighter(QTextDocument *parent)
    : SyntaxHighlighter(parent)
{
    // ---- HTML-Regeln (Basis, für den Teil außerhalb von <?php ?>) ----

    QTextCharFormat tagFormat;
    tagFormat.setForeground(QColor("#569CD6"));
    { HighlightRule r; r.pattern = QRegularExpression(R"(</?[\w:-]+)"); r.format = tagFormat; m_rules.append(r); }
    { HighlightRule r; r.pattern = QRegularExpression(R"(/?>)"); r.format = tagFormat; m_rules.append(r); }

    QTextCharFormat attrFormat;
    attrFormat.setForeground(QColor("#9CDCFE"));
    { HighlightRule r; r.pattern = QRegularExpression(R"(\b[\w:-]+=)"); r.format = attrFormat; m_rules.append(r); }

    QTextCharFormat valueFormat;
    valueFormat.setForeground(QColor("#CE9178"));
    { HighlightRule r; r.pattern = QRegularExpression(R"("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')"); r.format = valueFormat; m_rules.append(r); }

    QTextCharFormat entityFormat;
    entityFormat.setForeground(QColor("#D7BA7D"));
    { HighlightRule r; r.pattern = QRegularExpression(R"(&(?:#\d+|#x[0-9A-Fa-f]+|[\w]+);)"); r.format = entityFormat; m_rules.append(r); }

    // HTML-Kommentare
    QTextCharFormat htmlCommentFormat;
    htmlCommentFormat.setForeground(QColor("#6A9955"));
    htmlCommentFormat.setFontItalic(true);
    m_multiLineCommentFormat = htmlCommentFormat;
    m_commentStartExpression = QRegularExpression("<!--");
    m_commentEndExpression   = QRegularExpression("-->");
    m_hasMultiLineComment    = true;

    // ---- PHP-Tags hervorheben ----
    m_phpTagFormat.setForeground(QColor("#C586C0"));
    m_phpTagFormat.setFontWeight(QFont::Bold);

    // ---- PHP-spezifische Regeln ----
    m_phpStringFormat.setForeground(QColor("#CE9178"));
    m_phpCommentFormat.setForeground(QColor("#6A9955"));
    m_phpCommentFormat.setFontItalic(true);

    // Keywords
    QTextCharFormat kwFormat;
    kwFormat.setForeground(QColor("#569CD6"));
    kwFormat.setFontWeight(QFont::Bold);

    const QStringList phpKeywords = {
        "abstract","and","array","as","break","callable","case","catch",
        "class","clone","const","continue","declare","default","die","do",
        "echo","else","elseif","empty","enddeclare","endfor","endforeach",
        "endif","endswitch","endwhile","enum","extends","final","finally",
        "fn","for","foreach","function","global","goto","if","implements",
        "include","include_once","instanceof","insteadof","interface",
        "isset","list","match","namespace","new","or","print","private",
        "protected","public","readonly","require","require_once","return",
        "static","switch","throw","trait","try","unset","use","var",
        "while","xor","yield","null","true","false","NULL","TRUE","FALSE"
    };

    for (const QString &kw : phpKeywords)
    {
        HighlightRule r;
        r.pattern = QRegularExpression(QString("\\b%1\\b").arg(kw));
        r.format  = kwFormat;
        m_phpRules.append(r);
    }

    // Variablen $var
    QTextCharFormat varFormat;
    varFormat.setForeground(QColor("#9CDCFE"));
    { HighlightRule r; r.pattern = QRegularExpression(R"(\$[\w]+)"); r.format = varFormat; m_phpRules.append(r); }

    // Strings
    { HighlightRule r; r.pattern = QRegularExpression(R"("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')"); r.format = m_phpStringFormat; m_phpRules.append(r); }

    // Zahlen
    QTextCharFormat numFormat;
    numFormat.setForeground(QColor("#B5CEA8"));
    { HighlightRule r; r.pattern = QRegularExpression(R"(\b\d+\.?\d*\b)"); r.format = numFormat; m_phpRules.append(r); }

    // Einzeilige Kommentare
    { HighlightRule r; r.pattern = QRegularExpression(R"((//|#)[^\n]*)"); r.format = m_phpCommentFormat; m_phpRules.append(r); }

    // Eingebaute Funktionen (Auswahl der häufigsten)
    QTextCharFormat builtinFormat;
    builtinFormat.setForeground(QColor("#DCDCAA"));
    const QStringList builtins = {
        "array_map","array_filter","array_keys","array_values","array_merge",
        "array_push","array_pop","array_shift","array_slice","array_splice",
        "count","strlen","substr","strpos","strtolower","strtoupper","trim",
        "ltrim","rtrim","explode","implode","str_replace","preg_match",
        "preg_replace","sprintf","printf","print_r","var_dump","isset",
        "empty","unset","intval","floatval","strval","is_array","is_string",
        "is_int","is_float","is_null","is_bool","is_numeric","date","time",
        "mktime","json_encode","json_decode","header","session_start",
        "htmlspecialchars","htmlentities","strip_tags","nl2br","round",
        "floor","ceil","abs","min","max","rand","in_array","array_key_exists",
        "sort","rsort","usort","ksort","krsort","ob_start","ob_get_clean"
    };

    for (const QString &fn : builtins)
    {
        HighlightRule r;
        r.pattern = QRegularExpression(QString("\\b%1\\b").arg(fn));
        r.format  = builtinFormat;
        m_phpRules.append(r);
    }
}

void PhpHighlighter::highlightPhpRange(const QString &text, int start, int length)
{
    if (length <= 0)
    {
        return;
    }

    const QString phpText = text.mid(start, length);

    for (const HighlightRule &rule : m_phpRules)
    {
        QRegularExpressionMatchIterator it = rule.pattern.globalMatch(phpText);
        while (it.hasNext())
        {
            QRegularExpressionMatch match = it.next();
            setFormat(
                start + static_cast<int>(match.capturedStart()),
                static_cast<int>(match.capturedLength()),
                rule.format
            );
        }
    }

    // /* */ Block-Kommentare innerhalb des PHP-Bereichs mehrzeilig behandeln.
    // Block-Zustand 3 = wir sind mitten in einem /* ... */ PHP-Kommentar.
    static const QRegularExpression blockOpen(R"(/\*)");
    static const QRegularExpression blockClose(R"(\*/)");

    int searchFrom = 0;

    // Waren wir bereits in einem Block-Kommentar?
    if (previousBlockState() == 3)
    {
        QRegularExpressionMatch closeMatch = blockClose.match(phpText, 0);
        if (closeMatch.hasMatch())
        {
            const int end = static_cast<int>(closeMatch.capturedStart())
                          + static_cast<int>(closeMatch.capturedLength());
            setFormat(start, end, m_phpCommentFormat);
            searchFrom = end;
            // Block-Kommentar geschlossen — Zustand wird weiter unten gesetzt
        }
        else
        {
            // Gesamter Bereich ist noch Kommentar
            setFormat(start, length, m_phpCommentFormat);
            setCurrentBlockState(3);
            return;
        }
    }

    // Neue /* ... */ Kommentare innerhalb dieses PHP-Bereichs suchen
    while (searchFrom < phpText.length())
    {
        QRegularExpressionMatch openMatch = blockOpen.match(phpText, searchFrom);
        if (!openMatch.hasMatch())
        {
            break;
        }

        const int openPos = static_cast<int>(openMatch.capturedStart());
        QRegularExpressionMatch closeMatch = blockClose.match(phpText, openPos + 2);

        if (closeMatch.hasMatch())
        {
            const int closeEnd = static_cast<int>(closeMatch.capturedStart())
                               + static_cast<int>(closeMatch.capturedLength());
            setFormat(start + openPos, closeEnd - openPos, m_phpCommentFormat);
            searchFrom = closeEnd;
        }
        else
        {
            // Kein schließendes */ gefunden — geht über Zeilenende
            setFormat(start + openPos, length - openPos, m_phpCommentFormat);
            setCurrentBlockState(3);
            return;
        }
    }
}

void PhpHighlighter::highlightBlock(const QString &text)
{
    // Zuerst HTML-Basis-Regeln auf den gesamten Text anwenden
    SyntaxHighlighter::highlightBlock(text);

    // Block-Zustände:
    //   0 = HTML-Modus
    //   2 = innerhalb PHP-Block (kein /* */ Kommentar)
    //   3 = innerhalb PHP /* */ Block-Kommentar
    setCurrentBlockState(0);

    static const QRegularExpression phpOpen(R"(<\?(?:php|=)?\s?)",
        QRegularExpression::CaseInsensitiveOption);
    static const QRegularExpression phpClose(R"(\?>)");

    int pos = 0;

    if (previousBlockState() == 2 || previousBlockState() == 3)
    {
        // Wir befinden uns bereits in einem PHP-Block (ggf. in einem Kommentar)
        QRegularExpressionMatch closeMatch = phpClose.match(text, 0);
        if (closeMatch.hasMatch())
        {
            const int end = static_cast<int>(closeMatch.capturedStart())
                          + static_cast<int>(closeMatch.capturedLength());
            highlightPhpRange(text, 0, end);
            setFormat(static_cast<int>(closeMatch.capturedStart()),
                      static_cast<int>(closeMatch.capturedLength()),
                      m_phpTagFormat);
            pos = end;
            setCurrentBlockState(0);
        }
        else
        {
            highlightPhpRange(text, 0, text.length());
            // highlightPhpRange setzt den Zustand auf 3 falls nötig,
            // sonst behalten wir 2 (offener PHP-Block ohne Kommentar)
            if (currentBlockState() != 3)
            {
                setCurrentBlockState(2);
            }
            return;
        }
    }

    while (pos < text.length())
    {
        QRegularExpressionMatch openMatch = phpOpen.match(text, pos);
        if (!openMatch.hasMatch())
        {
            break;
        }

        const int openStart = static_cast<int>(openMatch.capturedStart());
        const int openEnd   = openStart + static_cast<int>(openMatch.capturedLength());

        setFormat(openStart, static_cast<int>(openMatch.capturedLength()), m_phpTagFormat);

        QRegularExpressionMatch closeMatch = phpClose.match(text, openEnd);
        if (closeMatch.hasMatch())
        {
            const int closeStart = static_cast<int>(closeMatch.capturedStart());
            const int closeEnd   = closeStart + static_cast<int>(closeMatch.capturedLength());

            highlightPhpRange(text, openEnd, closeStart - openEnd);
            setFormat(closeStart, static_cast<int>(closeMatch.capturedLength()), m_phpTagFormat);

            pos = closeEnd;
            setCurrentBlockState(0);
        }
        else
        {
            highlightPhpRange(text, openEnd, text.length() - openEnd);
            if (currentBlockState() != 3)
            {
                setCurrentBlockState(2);
            }
            return;
        }
    }
}
