#include "ColorIndicator.h"
#include "CodeEditor.h"

#include <QPainter>
#include <QPaintEvent>
#include <QMouseEvent>
#include <QColorDialog>
#include <QTextBlock>
#include <QTextCursor>
#include <QTextDocument>
#include <QScrollBar>
#include <QHash>

// ---------------------------------------------------------------------------
// Kombinierter Regex — erfasst alle CSS-Farbformate in einer Runde
// ---------------------------------------------------------------------------
const QRegularExpression ColorIndicator::s_colorRegex(
    // #rgb / #rrggbb / #rrggbbaa
    R"(#(?:[0-9A-Fa-f]{8}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})(?=[^0-9A-Fa-f]|$))"
    R"(|rgba?\s*\([^)]+\))"
    R"(|hsla?\s*\([^)]+\))",
    QRegularExpression::CaseInsensitiveOption
);

// ---------------------------------------------------------------------------
// Konstruktor
// ---------------------------------------------------------------------------
ColorIndicator::ColorIndicator(CodeEditor *editor)
    : QObject(editor)
    , m_editor(editor)
{
    // Cache invalidieren und Viewport neu zeichnen wenn sich der Text ändert
    connect(m_editor->document(), &QTextDocument::contentsChanged,
            this, [this]()
    {
        m_cacheFirstBlock = -1;
        m_cacheLastBlock  = -1;
        m_editor->viewport()->update();
    });

    // Auch beim Scrollen neu zeichnen (Cache bleibt gültig, nur Position ändert sich)
    connect(m_editor->verticalScrollBar(), &QScrollBar::valueChanged,
            this, [this]()
    {
        m_cacheFirstBlock = -1;
        m_cacheLastBlock  = -1;
    });
}

// ---------------------------------------------------------------------------
// Paint – wird aus CodeEditor::paintEvent aufgerufen
// ---------------------------------------------------------------------------
void ColorIndicator::paint(QPainter &painter, QPaintEvent *event)
{
    rebuildCache();

    const int squareSize = m_editor->fontMetrics().height() - 4;
    const int radius     = 2;

    for (const ColorMatch &m : m_cache)
    {
        if (!event->rect().intersects(m.rect))
        {
            continue;
        }

        // Rahmen
        painter.setPen(QColor(0, 0, 0, 80));
        painter.setBrush(m.color);
        painter.drawRoundedRect(m.rect, radius, radius);

        // Schachbrettmuster als Hintergrund für transparente Farben
        if (m.color.alpha() < 255)
        {
            const int half = squareSize / 2;
            painter.setPen(Qt::NoPen);
            painter.setBrush(QColor(180, 180, 180));
            painter.drawRect(m.rect.x(),        m.rect.y(),        half, half);
            painter.drawRect(m.rect.x() + half, m.rect.y() + half, half, half);
            painter.setBrush(m.color);
            painter.drawRoundedRect(m.rect, radius, radius);
        }
    }
}

// ---------------------------------------------------------------------------
// Mouse – wird aus CodeEditor::mousePressEvent aufgerufen
// ---------------------------------------------------------------------------
bool ColorIndicator::handleMousePress(QMouseEvent *event)
{
    for (const ColorMatch &m : m_cache)
    {
        if (!m.rect.contains(event->pos()))
        {
            continue;
        }

        // Farb-Dialog öffnen
        QColorDialog dlg(m.color, m_editor);
        dlg.setOption(QColorDialog::ShowAlphaChannel, true);
        dlg.setWindowTitle(QObject::tr("Farbe wählen"));

        if (dlg.exec() != QDialog::Accepted)
        {
            return true;
        }

        const QColor newColor = dlg.selectedColor();

        // Ursprünglichen Farbwert im Dokument ersetzen
        QTextBlock block = m_editor->document()->findBlockByNumber(m.blockNumber);
        if (!block.isValid())
        {
            return true;
        }

        // Neuen Farbwert als Hex-String formatieren
        QString newValue;
        if (newColor.alpha() < 255)
        {
            newValue = newColor.name(QColor::HexArgb); // #aarrggbb
            // CSS erwartet #rrggbbaa — Bytes umstellen
            // Qt liefert #aarrggbb, CSS will #rrggbbaa
            newValue = QString("#%1%2%3%4")
                .arg(newColor.red(),   2, 16, QChar('0'))
                .arg(newColor.green(), 2, 16, QChar('0'))
                .arg(newColor.blue(),  2, 16, QChar('0'))
                .arg(newColor.alpha(), 2, 16, QChar('0'));
        }
        else
        {
            newValue = newColor.name(QColor::HexRgb); // #rrggbb
        }

        QTextCursor cursor(block);
        cursor.setPosition(block.position() + m.posInBlock);
        cursor.setPosition(block.position() + m.posInBlock + m.length,
                           QTextCursor::KeepAnchor);
        cursor.insertText(newValue);

        // Cache invalidieren
        m_cache.clear();
        m_cacheFirstBlock = -1;
        m_cacheLastBlock  = -1;

        return true;
    }

    return false;
}

// ---------------------------------------------------------------------------
// Cache aufbauen – nur für sichtbare Blöcke
// ---------------------------------------------------------------------------
void ColorIndicator::rebuildCache()
{
    QTextBlock firstVisible = m_editor->firstVisibleBlockPublic();
    const int  firstNum     = firstVisible.blockNumber();

    // Letzten sichtbaren Block bestimmen
    int lastNum = firstNum;
    {
        QTextBlock b   = firstVisible;
        const int  bot = m_editor->viewport()->height();
        while (b.isValid())
        {
            const QRectF r = m_editor->blockBoundingGeometryPublic(b)
                                 .translated(m_editor->contentOffsetPublic());
            if (r.top() > bot)
            {
                break;
            }
            lastNum = b.blockNumber();
            b = b.next();
        }
    }

    // Cache noch aktuell?
    if (firstNum == m_cacheFirstBlock && lastNum == m_cacheLastBlock)
    {
        return;
    }

    m_cache.clear();
    m_cacheFirstBlock = firstNum;
    m_cacheLastBlock  = lastNum;

    const int squareSize = m_editor->fontMetrics().height() - 4;
    const int scrollX    = m_editor->horizontalScrollBar()->value();

    QTextBlock block = firstVisible;
    while (block.isValid() && block.blockNumber() <= lastNum)
    {
        const QRectF blockRect = m_editor->blockBoundingGeometryPublic(block)
                                     .translated(m_editor->contentOffsetPublic());

        const QList<ColorMatch> found = findColorsInBlock(block.text(),
                                                           block.blockNumber());
        for (ColorMatch m : found)
        {
            // X-Position des Farbwerts im Viewport berechnen
            const QTextLayout *layout = block.layout();
            if (!layout || layout->lineCount() == 0)
            {
                continue;
            }

            const QTextLine line  = layout->lineAt(0);
            // Position nach dem Ende des Farbwerts
            const qreal endCharX  = line.cursorToX(m.posInBlock + m.length);
            const int   x         = static_cast<int>(blockRect.left() + endCharX)
                                    - scrollX + 3;

            if (x + squareSize > m_editor->viewport()->width())
            {
                continue;
            }

            const int y = static_cast<int>(blockRect.top())
                        + (static_cast<int>(blockRect.height()) - squareSize) / 2;

            m.rect = QRect(x, y, squareSize, squareSize);
            m_cache.append(m);
        }

        block = block.next();
    }
}

// ---------------------------------------------------------------------------
// Farbwerte in einer Zeile suchen
// ---------------------------------------------------------------------------
QList<ColorIndicator::ColorMatch> ColorIndicator::findColorsInBlock(
    const QString &text, int blockNumber) const
{
    QList<ColorMatch> result;

    QRegularExpressionMatchIterator it = s_colorRegex.globalMatch(text);
    while (it.hasNext())
    {
        QRegularExpressionMatch match = it.next();
        const QString token = match.captured(0);
        const QColor  color = parseColor(token);

        if (!color.isValid())
        {
            continue;
        }

        ColorMatch m;
        m.blockNumber = blockNumber;
        m.posInBlock  = static_cast<int>(match.capturedStart());
        m.length      = static_cast<int>(match.capturedLength());
        m.color       = color;
        result.append(m);
    }

    return result;
}

// ---------------------------------------------------------------------------
// Farb-Parser
// ---------------------------------------------------------------------------
QColor ColorIndicator::parseColor(const QString &token)
{
    const QString t = token.trimmed();

    if (t.startsWith('#'))                                        { return parseHex(t);   }
    if (t.startsWith("rgba", Qt::CaseInsensitive))                { return parseRgba(t);  }
    if (t.startsWith("rgb",  Qt::CaseInsensitive))                { return parseRgb(t);   }
    if (t.startsWith("hsla", Qt::CaseInsensitive))                { return parseHsla(t);  }
    if (t.startsWith("hsl",  Qt::CaseInsensitive))                { return parseHsl(t);   }

    return QColor();
}

QColor ColorIndicator::parseHex(const QString &s)
{
    // #rgb → #rrggbb
    if (s.length() == 4)
    {
        return QColor(QString("#%1%1%2%2%3%3")
            .arg(s[1]).arg(s[2]).arg(s[3]));
    }
    // #rrggbb
    if (s.length() == 7)
    {
        return QColor(s);
    }
    // #rrggbbaa (CSS) → Qt braucht #aarrggbb
    if (s.length() == 9)
    {
        const QString rr = s.mid(1, 2);
        const QString gg = s.mid(3, 2);
        const QString bb = s.mid(5, 2);
        const QString aa = s.mid(7, 2);
        return QColor(QString("#%1%2%3%4").arg(aa, rr, gg, bb));
    }
    return QColor();
}

QColor ColorIndicator::parseRgb(const QString &s)
{
    // rgb(r, g, b)
    static const QRegularExpression re(
        R"(rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\))",
        QRegularExpression::CaseInsensitiveOption);
    QRegularExpressionMatch m = re.match(s);
    if (!m.hasMatch()) { return QColor(); }
    return QColor(m.captured(1).toInt(),
                  m.captured(2).toInt(),
                  m.captured(3).toInt());
}

QColor ColorIndicator::parseRgba(const QString &s)
{
    // rgba(r, g, b, a)  — a ist 0.0–1.0
    static const QRegularExpression re(
        R"(rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([0-9.]+)\s*\))",
        QRegularExpression::CaseInsensitiveOption);
    QRegularExpressionMatch m = re.match(s);
    if (!m.hasMatch()) { return QColor(); }
    return QColor(m.captured(1).toInt(),
                  m.captured(2).toInt(),
                  m.captured(3).toInt(),
                  qRound(m.captured(4).toDouble() * 255.0));
}

QColor ColorIndicator::parseHsl(const QString &s)
{
    // hsl(h, s%, l%)
    static const QRegularExpression re(
        R"(hsl\s*\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\))",
        QRegularExpression::CaseInsensitiveOption);
    QRegularExpressionMatch m = re.match(s);
    if (!m.hasMatch()) { return QColor(); }
    QColor c;
    c.setHsl(m.captured(1).toInt(),
             qRound(m.captured(2).toInt() * 2.55),
             qRound(m.captured(3).toInt() * 2.55));
    return c;
}

QColor ColorIndicator::parseHsla(const QString &s)
{
    // hsla(h, s%, l%, a)
    static const QRegularExpression re(
        R"(hsla\s*\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([0-9.]+)\s*\))",
        QRegularExpression::CaseInsensitiveOption);
    QRegularExpressionMatch m = re.match(s);
    if (!m.hasMatch()) { return QColor(); }
    QColor c;
    c.setHsl(m.captured(1).toInt(),
             qRound(m.captured(2).toInt() * 2.55),
             qRound(m.captured(3).toInt() * 2.55),
             qRound(m.captured(4).toDouble() * 255.0));
    return c;
}



