#include "CodeEditor.h"
#include "LineNumberArea.h"
#include "ColorIndicator.h"
#include "SignatureHelper.h"

#include "core/Settings.h"
#include "highlighter/HighlighterFactory.h"

#include <QPainter>
#include <QTextBlock>
#include <QPaintEvent>
#include <QResizeEvent>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QScrollBar>
#include <QFile>
#include <QTextStream>
#include <QFileInfo>
#include <QFileDialog>
#include <QMessageBox>

CodeEditor::CodeEditor(Settings *settings, QWidget *parent)
    : QPlainTextEdit(parent)
    , m_settings(settings)
{
    m_lineNumberArea  = new LineNumberArea(this);
    m_colorIndicator  = new ColorIndicator(this);
    m_signatureHelper = new SignatureHelper(this);
    setupEditor();

    connect(this, &CodeEditor::blockCountChanged,
            this, &CodeEditor::updateLineNumberAreaWidth);

    connect(this, &CodeEditor::updateRequest,
            this, &CodeEditor::updateLineNumberArea);

    connect(this, &CodeEditor::cursorPositionChanged,
            this, &CodeEditor::highlightCurrentLine);

    updateLineNumberAreaWidth(0);
    highlightCurrentLine();
}

CodeEditor::~CodeEditor() = default;

// ---------------------------------------------------------------------------
// Setup
// ---------------------------------------------------------------------------
void CodeEditor::setupEditor()
{
    applySettings();
    setLineWrapMode(QPlainTextEdit::NoWrap);
}

void CodeEditor::applySettings()
{
    setFont(m_settings->editorFont());

    const int tabStop = m_settings->tabSize();
    // Set tab stop width in pixels using font metrics
    QFontMetrics fm(m_settings->editorFont());
    setTabStopDistance(static_cast<qreal>(tabStop) * fm.horizontalAdvance(' '));
}

// ---------------------------------------------------------------------------
// File I/O
// ---------------------------------------------------------------------------
void CodeEditor::loadFile(const QString &filePath)
{
    QFile file(filePath);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QMessageBox::warning(this, tr("Open File"),
            tr("Cannot open file:\n%1").arg(filePath));
        return;
    }

    m_filePath = filePath;

    QTextStream in(&file);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    in.setEncoding(QStringConverter::Utf8);
#else
    in.setCodec("UTF-8");
#endif

    setPlainText(in.readAll());
    document()->setModified(false);

    installHighlighter(filePath);
}

QString CodeEditor::filePath() const
{
    return m_filePath;
}

bool CodeEditor::save()
{
    if (m_filePath.isEmpty())
    {
        return saveAs();
    }

    return writeToFile(m_filePath);
}

bool CodeEditor::saveAs()
{
    const QString path = QFileDialog::getSaveFileName(
        this,
        tr("Speichern unter"),
        m_filePath
    );

    if (path.isEmpty())
    {
        return false;
    }

    m_filePath = path;
    installHighlighter(m_filePath);
    return writeToFile(m_filePath);
}

bool CodeEditor::writeToFile(const QString &filePath)
{
    QFile file(filePath);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        QMessageBox::warning(this, tr("Speichern"),
            tr("Datei konnte nicht gespeichert werden:\n%1").arg(filePath));
        return false;
    }

    QTextStream out(&file);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    out.setEncoding(QStringConverter::Utf8);
#else
    out.setCodec("UTF-8");
#endif

    out << toPlainText();
    document()->setModified(false);
    emit fileSaved(filePath);
    return true;
}

void CodeEditor::installHighlighter(const QString &filePath)
{
    // Remove old highlighter first
    delete m_highlighter;
    m_highlighter = nullptr;

    m_highlighter = HighlighterFactory::createForFile(filePath, document());
}

bool CodeEditor::isModified() const
{
    return document()->isModified();
}

// ---------------------------------------------------------------------------
// Line number area
// ---------------------------------------------------------------------------
int CodeEditor::lineNumberAreaWidth() const
{
    int digits = 1;
    int max    = qMax(1, blockCount());
    while (max >= 10)
    {
        max /= 10;
        ++digits;
    }

    const int padding = 8;
    return fontMetrics().horizontalAdvance('9') * digits + padding * 2;
}

void CodeEditor::updateLineNumberAreaWidth(int /*newBlockCount*/)
{
    setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
}

void CodeEditor::updateLineNumberArea(const QRect &rect, int dy)
{
    if (dy != 0)
    {
        m_lineNumberArea->scroll(0, dy);
    }
    else
    {
        m_lineNumberArea->update(0, rect.y(), m_lineNumberArea->width(), rect.height());
    }

    if (rect.contains(viewport()->rect()))
    {
        updateLineNumberAreaWidth(0);
    }
}

void CodeEditor::paintEvent(QPaintEvent *event)
{
    // Zuerst den normalen Editor-Inhalt zeichnen
    QPlainTextEdit::paintEvent(event);

    // Einrück-Führungslinien
    const int tabSize = m_settings->tabSize();
    if (tabSize > 0)
    {
        QPainter painter(viewport());

        QColor guideColor = palette().color(QPalette::Text);
        guideColor.setAlpha(30);
        painter.setPen(QPen(guideColor, 1, Qt::SolidLine));

        const QFontMetrics fm(font());
        const int spaceWidth = fm.horizontalAdvance(' ');
        const int tabPixels  = tabSize * spaceWidth;

        if (tabPixels > 0)
        {
            int textOriginX = 0;
            {
                QTextBlock firstBlock = firstVisibleBlock();
                if (!firstBlock.isValid())
                {
                    firstBlock = document()->begin();
                }
                if (firstBlock.isValid())
                {
                    const QRectF blockRect = blockBoundingGeometry(firstBlock)
                                                 .translated(contentOffset());
                    const QTextLayout *layout = firstBlock.layout();
                    if (layout && layout->lineCount() > 0)
                    {
                        textOriginX = static_cast<int>(blockRect.left()
                                      + layout->lineAt(0).position().x());
                    }
                    else
                    {
                        textOriginX = static_cast<int>(blockRect.left());
                    }
                }
            }

            const int scrollX = horizontalScrollBar()->value();

            QTextBlock block  = firstVisibleBlock();
            const int  bottom = event->rect().bottom();

            while (block.isValid())
            {
                const QRectF blockRect = blockBoundingGeometry(block)
                                             .translated(contentOffset());
                if (blockRect.top() > bottom) { break; }

                if (block.isVisible())
                {
                    const QString text = block.text();
                    int indentSpaces = 0;
                    for (const QChar &ch : text)
                    {
                        if      (ch == ' ')  { ++indentSpaces; }
                        else if (ch == '\t') { indentSpaces = ((indentSpaces / tabSize) + 1) * tabSize; }
                        else                 { break; }
                    }

                    const int indentStops = indentSpaces / tabSize;
                    for (int stop = 1; stop <= indentStops; ++stop)
                    {
                        const int xPixel = textOriginX + stop * tabPixels - scrollX;
                        if (xPixel < lineNumberAreaWidth() || xPixel > viewport()->width())
                        {
                            continue;
                        }
                        painter.drawLine(xPixel,
                                         static_cast<int>(blockRect.top()),
                                         xPixel,
                                         static_cast<int>(blockRect.bottom()));
                    }
                }
                block = block.next();
            }
        }

        // Farbvorschau-Quadrate zeichnen
        m_colorIndicator->paint(painter, event);
    }
}

void CodeEditor::mousePressEvent(QMouseEvent *event)
{
    // Zuerst prüfen ob ein Farbquadrat geklickt wurde
    if (m_colorIndicator->handleMousePress(event))
    {
        return;
    }
    QPlainTextEdit::mousePressEvent(event);
}

void CodeEditor::resizeEvent(QResizeEvent *event)
{
    QPlainTextEdit::resizeEvent(event);

    const QRect cr = contentsRect();
    m_lineNumberArea->setGeometry(
        QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())
    );
}

void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
{
    QPainter painter(m_lineNumberArea);

    // Background
    const QColor bgColor = palette().color(QPalette::Window).darker(110);
    painter.fillRect(event->rect(), bgColor);

    const QColor lineNumColor = palette().color(QPalette::Mid);
    const QColor activeColor  = palette().color(QPalette::Text);

    const int currentLine = textCursor().blockNumber();

    QTextBlock block    = firstVisibleBlock();
    int blockNumber     = block.blockNumber();
    int top    = static_cast<int>(blockBoundingGeometry(block).translated(contentOffset()).top());
    int bottom = top + static_cast<int>(blockBoundingRect(block).height());

    while (block.isValid() && top <= event->rect().bottom())
    {
        if (block.isVisible() && bottom >= event->rect().top())
        {
            const QString number = QString::number(blockNumber + 1);
            painter.setPen(blockNumber == currentLine ? activeColor : lineNumColor);
            painter.drawText(
                0,
                top,
                m_lineNumberArea->width() - 4,
                fontMetrics().height(),
                Qt::AlignRight,
                number
            );
        }

        block   = block.next();
        top     = bottom;
        bottom  = top + static_cast<int>(blockBoundingRect(block).height());
        ++blockNumber;
    }
}

// ---------------------------------------------------------------------------
// Current line highlight
// ---------------------------------------------------------------------------
void CodeEditor::highlightCurrentLine()
{
    QList<QTextEdit::ExtraSelection> extraSelections;

    if (!isReadOnly())
    {
        QTextEdit::ExtraSelection selection;

        const QColor lineColor = palette().color(QPalette::AlternateBase);
        selection.format.setBackground(lineColor);
        selection.format.setProperty(QTextFormat::FullWidthSelection, true);
        selection.cursor = textCursor();
        selection.cursor.clearSelection();

        extraSelections.append(selection);
    }

    setExtraSelections(extraSelections);
}

// ---------------------------------------------------------------------------
// Key handling – Tab/Shift+Tab, Smart-Backspace, Auto-Indent
// ---------------------------------------------------------------------------
void CodeEditor::keyPressEvent(QKeyEvent *event)
{
    const int tabSize = m_settings->tabSize();

    // -----------------------------------------------------------------------
    // Shift+Tab: Einrückung zurückziehen
    // -----------------------------------------------------------------------
    if (event->key() == Qt::Key_Backtab ||
        (event->key() == Qt::Key_Tab && event->modifiers() & Qt::ShiftModifier))
    {
        QTextCursor cursor = textCursor();

        QTextBlock startBlock = document()->findBlock(cursor.selectionStart());
        QTextBlock endBlock   = document()->findBlock(cursor.selectionEnd());

        cursor.beginEditBlock();
        for (QTextBlock b = startBlock; b != endBlock.next(); b = b.next())
        {
            const QString lineText = b.text();
            int toRemove = 0;

            if (m_settings->useSpacesForTabs())
            {
                // Bis zu tabSize führende Leerzeichen entfernen
                for (int i = 0; i < tabSize && i < lineText.length(); ++i)
                {
                    if (lineText[i] == ' ')
                    {
                        ++toRemove;
                    }
                    else
                    {
                        break;
                    }
                }
            }
            else
            {
                // Einen führenden Tab entfernen
                if (!lineText.isEmpty() && lineText[0] == '\t')
                {
                    toRemove = 1;
                }
            }

            if (toRemove > 0)
            {
                QTextCursor lineCursor(b);
                lineCursor.movePosition(QTextCursor::StartOfBlock);
                lineCursor.movePosition(QTextCursor::Right,
                                        QTextCursor::KeepAnchor,
                                        toRemove);
                lineCursor.removeSelectedText();
            }
        }
        cursor.endEditBlock();
        return;
    }

    // -----------------------------------------------------------------------
    // Tab: Einrücken (Leerzeichen oder echter Tab)
    // -----------------------------------------------------------------------
    if (event->key() == Qt::Key_Tab)
    {
        QTextCursor cursor = textCursor();

        if (cursor.hasSelection())
        {
            // Mehrere Zeilen einrücken
            QTextBlock startBlock = document()->findBlock(cursor.selectionStart());
            QTextBlock endBlock   = document()->findBlock(cursor.selectionEnd());

            cursor.beginEditBlock();
            for (QTextBlock b = startBlock; b != endBlock.next(); b = b.next())
            {
                QTextCursor lineCursor(b);
                lineCursor.movePosition(QTextCursor::StartOfBlock);
                if (m_settings->useSpacesForTabs())
                {
                    lineCursor.insertText(QString(tabSize, ' '));
                }
                else
                {
                    lineCursor.insertText("\t");
                }
            }
            cursor.endEditBlock();
        }
        else
        {
            if (m_settings->useSpacesForTabs())
            {
                // Zum nächsten Tab-Stop auffüllen
                const int col         = cursor.columnNumber();
                const int spacesNeeded = tabSize - (col % tabSize);
                cursor.insertText(QString(spacesNeeded, ' '));
            }
            else
            {
                cursor.insertText("\t");
            }
        }
        return;
    }

    // -----------------------------------------------------------------------
    // Smart Backspace: springt zur vorherigen Einrückungsstufe
    // -----------------------------------------------------------------------
    if (event->key() == Qt::Key_Backspace
        && !textCursor().hasSelection()
        && m_settings->useSpacesForTabs())
    {
        QTextCursor cursor = textCursor();
        const int col = cursor.columnNumber();

        if (col > 0)
        {
            // Prüfen ob links vom Cursor nur Leerzeichen bis Zeilenbeginn stehen
            const QString lineText   = cursor.block().text();
            const QString leftOfCursor = lineText.left(col);
            const bool onlySpaces    = leftOfCursor.trimmed().isEmpty();

            if (onlySpaces && col > 0)
            {
                // Zur vorherigen Tab-Stop-Position springen
                const int targetCol  = ((col - 1) / tabSize) * tabSize;
                const int toDelete   = col - targetCol;

                cursor.movePosition(QTextCursor::Left,
                                    QTextCursor::KeepAnchor,
                                    toDelete);
                cursor.removeSelectedText();
                return;
            }
        }
    }

    // -----------------------------------------------------------------------
    // Enter / Return: Auto-Indent
    // -----------------------------------------------------------------------
    if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
    {
        QTextCursor cursor = textCursor();
        const QString currentLine = cursor.block().text();

        // Führende Leerzeichen der aktuellen Zeile zählen
        int leadingSpaces = 0;
        for (const QChar &ch : currentLine)
        {
            if (ch == ' ')
            {
                ++leadingSpaces;
            }
            else if (ch == '\t')
            {
                leadingSpaces += tabSize;
            }
            else
            {
                break;
            }
        }

        QPlainTextEdit::keyPressEvent(event);

        if (leadingSpaces > 0)
        {
            const QString indent = m_settings->useSpacesForTabs()
                ? QString(leadingSpaces, ' ')
                : QString(leadingSpaces / tabSize, '\t');
            textCursor().insertText(indent);
        }
        return;
    }

    QPlainTextEdit::keyPressEvent(event);
}

