#include "SearchPanel.h"
#include "CodeEditor.h"

#include <QTextCursor>
#include <QTextBlock>
#include <QRegularExpression>
#include <QMessageBox>
#include <QKeyEvent>
#include <QShortcut>

SearchPanel::SearchPanel(QWidget *parent)
    : QWidget(parent)
{
    setupUi();
    hide();
}

// ---------------------------------------------------------------------------
// UI
// ---------------------------------------------------------------------------
void SearchPanel::setupUi()
{
    m_grid = new QGridLayout(this);
    m_grid->setContentsMargins(6, 4, 6, 4);
    m_grid->setSpacing(4);

    // ---- Row 0: Suchen ----
    m_searchEdit = new QLineEdit(this);
    m_searchEdit->setPlaceholderText(tr("Suchen…"));
    m_searchEdit->setClearButtonEnabled(true);

    m_btnPrev  = new QPushButton(tr("▲"), this);
    m_btnNext  = new QPushButton(tr("▼"), this);
    m_btnPrev->setFixedWidth(28);
    m_btnNext->setFixedWidth(28);
    m_btnPrev->setToolTip(tr("Vorheriger Treffer  (Shift+F3)"));
    m_btnNext->setToolTip(tr("Nächster Treffer  (F3)"));

    m_matchLabel = new QLabel(this);
    m_matchLabel->setMinimumWidth(80);

    m_btnClose = new QPushButton(tr("✕"), this);
    m_btnClose->setFixedWidth(24);
    m_btnClose->setToolTip(tr("Schließen  (Esc)"));
    m_btnClose->setFlat(true);

    QHBoxLayout *searchRow = new QHBoxLayout();
    searchRow->addWidget(new QLabel(tr("Suchen:"), this));
    searchRow->addWidget(m_searchEdit, 1);
    searchRow->addWidget(m_btnPrev);
    searchRow->addWidget(m_btnNext);
    searchRow->addWidget(m_matchLabel);
    searchRow->addWidget(m_btnClose);
    m_grid->addLayout(searchRow, 0, 0);

    // ---- Row 1: Ersetzen ----
    m_replaceEdit = new QLineEdit(this);
    m_replaceEdit->setPlaceholderText(tr("Ersetzen durch…"));
    m_replaceEdit->setClearButtonEnabled(true);

    m_btnReplace          = new QPushButton(tr("Ersetzen"), this);
    m_btnReplaceAll       = new QPushButton(tr("Alle ersetzen"), this);
    m_btnReplaceSelection = new QPushButton(tr("In Auswahl ersetzen"), this);

    QHBoxLayout *replaceRow = new QHBoxLayout();
    replaceRow->addWidget(new QLabel(tr("Ersetzen:"), this));
    replaceRow->addWidget(m_replaceEdit, 1);
    replaceRow->addWidget(m_btnReplace);
    replaceRow->addWidget(m_btnReplaceAll);
    replaceRow->addWidget(m_btnReplaceSelection);
    m_grid->addLayout(replaceRow, 1, 0);

    // ---- Row 2: Optionen ----
    m_chkCase  = new QCheckBox(tr("Groß-/Kleinschreibung"), this);
    m_chkWord  = new QCheckBox(tr("Ganzes Wort"), this);
    m_chkRegex = new QCheckBox(tr("Regulärer Ausdruck"), this);

    QHBoxLayout *optRow = new QHBoxLayout();
    optRow->addWidget(m_chkCase);
    optRow->addWidget(m_chkWord);
    optRow->addWidget(m_chkRegex);
    optRow->addStretch();
    m_grid->addLayout(optRow, 2, 0);

    // ---- Connections ----
    connect(m_searchEdit, &QLineEdit::textChanged,
            this, &SearchPanel::onSearchTextChanged);

    connect(m_searchEdit, &QLineEdit::returnPressed,
            this, &SearchPanel::findNext);

    connect(m_btnNext,  &QPushButton::clicked, this, &SearchPanel::findNext);
    connect(m_btnPrev,  &QPushButton::clicked, this, &SearchPanel::findPrevious);

    connect(m_btnReplace,          &QPushButton::clicked, this, &SearchPanel::replaceCurrent);
    connect(m_btnReplaceAll,       &QPushButton::clicked, this, &SearchPanel::replaceAll);
    connect(m_btnReplaceSelection, &QPushButton::clicked, this, &SearchPanel::replaceInSelection);

    connect(m_btnClose, &QPushButton::clicked, this, &SearchPanel::onCloseClicked);

    connect(m_chkCase,  &QCheckBox::toggled, this, &SearchPanel::onOptionChanged);
    connect(m_chkWord,  &QCheckBox::toggled, this, &SearchPanel::onOptionChanged);
    connect(m_chkRegex, &QCheckBox::toggled, this, &SearchPanel::onOptionChanged);
}

// ---------------------------------------------------------------------------
// Public interface
// ---------------------------------------------------------------------------
void SearchPanel::setEditor(CodeEditor *editor)
{
    clearHighlights();
    m_editor = editor;
}

void SearchPanel::activate()
{
    show();
    m_searchEdit->setFocus();
    m_searchEdit->selectAll();

    // Pre-fill with selected text if short enough
    if (m_editor)
    {
        const QString sel = m_editor->textCursor().selectedText();
        if (!sel.isEmpty() && !sel.contains('\n') && sel.length() < 200)
        {
            m_searchEdit->setText(sel);
        }
    }

    updateMatchLabel();
}

// ---------------------------------------------------------------------------
// Find helpers
// ---------------------------------------------------------------------------
QTextDocument::FindFlags SearchPanel::buildFindFlags(bool backwards) const
{
    QTextDocument::FindFlags flags;
    if (backwards)              { flags |= QTextDocument::FindBackward;         }
    if (m_chkCase->isChecked()) { flags |= QTextDocument::FindCaseSensitively;  }
    if (m_chkWord->isChecked()) { flags |= QTextDocument::FindWholeWords;       }
    return flags;
}

bool SearchPanel::performFind(bool backwards)
{
    if (!m_editor || m_searchEdit->text().isEmpty())
    {
        return false;
    }

    const QTextDocument::FindFlags flags = buildFindFlags(backwards);
    bool found = false;

    if (m_chkRegex->isChecked())
    {
        QRegularExpression re(m_searchEdit->text());
        if (m_chkCase->isChecked())
        {
            re.setPatternOptions(QRegularExpression::NoPatternOption);
        }
        else
        {
            re.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
        }
        found = m_editor->find(re, flags);

        // Wrap around
        if (!found)
        {
            QTextCursor c = m_editor->textCursor();
            c.movePosition(backwards ? QTextCursor::End : QTextCursor::Start);
            m_editor->setTextCursor(c);
            found = m_editor->find(re, flags);
        }
    }
    else
    {
        found = m_editor->find(m_searchEdit->text(), flags);

        // Wrap around
        if (!found)
        {
            QTextCursor c = m_editor->textCursor();
            c.movePosition(backwards ? QTextCursor::End : QTextCursor::Start);
            m_editor->setTextCursor(c);
            found = m_editor->find(m_searchEdit->text(), flags);
        }
    }

    return found;
}

void SearchPanel::highlightAllMatches()
{
    if (!m_editor)
    {
        return;
    }

    QList<QTextEdit::ExtraSelection> extras;

    const QString needle = m_searchEdit->text();
    if (needle.isEmpty())
    {
        m_editor->setExtraSelections(extras);
        return;
    }

    QTextCharFormat fmt;
    fmt.setBackground(QColor("#3a3a00"));
    fmt.setForeground(QColor("#ffff80"));

    QTextDocument *doc = m_editor->document();
    QTextCursor   cursor(doc);

    const QTextDocument::FindFlags flags = buildFindFlags(false);

    while (true)
    {
        if (m_chkRegex->isChecked())
        {
            QRegularExpression re(needle);
            if (!m_chkCase->isChecked())
            {
                re.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
            }
            cursor = doc->find(re, cursor, flags);
        }
        else
        {
            cursor = doc->find(needle, cursor, flags);
        }

        if (cursor.isNull())
        {
            break;
        }

        QTextEdit::ExtraSelection sel;
        sel.cursor = cursor;
        sel.format = fmt;
        extras.append(sel);
    }

    m_editor->setExtraSelections(extras);
}

void SearchPanel::clearHighlights()
{
    if (m_editor)
    {
        m_editor->setExtraSelections({});
    }
}

void SearchPanel::updateMatchLabel()
{
    if (!m_editor || m_searchEdit->text().isEmpty())
    {
        m_matchLabel->setText(QString());
        return;
    }

    // Count total matches
    int count = 0;
    QTextDocument *doc = m_editor->document();
    QTextCursor   cursor(doc);
    const QTextDocument::FindFlags flags = buildFindFlags(false);
    const QString needle = m_searchEdit->text();

    while (true)
    {
        if (m_chkRegex->isChecked())
        {
            QRegularExpression re(needle);
            if (!m_chkCase->isChecked())
            {
                re.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
            }
            cursor = doc->find(re, cursor, flags);
        }
        else
        {
            cursor = doc->find(needle, cursor, flags);
        }

        if (cursor.isNull())
        {
            break;
        }
        ++count;
    }

    if (count == 0)
    {
        m_matchLabel->setText(tr("Kein Treffer"));
        m_matchLabel->setStyleSheet("color: #cc4444;");
    }
    else
    {
        m_matchLabel->setText(tr("%1 Treffer").arg(count));
        m_matchLabel->setStyleSheet(QString());
    }
}

// ---------------------------------------------------------------------------
// Public slots
// ---------------------------------------------------------------------------
void SearchPanel::findNext()
{
    performFind(false);
}

void SearchPanel::findPrevious()
{
    performFind(true);
}

void SearchPanel::replaceCurrent()
{
    if (!m_editor)
    {
        return;
    }

    QTextCursor cursor = m_editor->textCursor();

    // If current selection matches the search term, replace it
    // Otherwise just find the next occurrence first
    const bool hasMatch = !cursor.selectedText().isEmpty();
    if (!hasMatch)
    {
        performFind(false);
        return;
    }

    cursor.insertText(m_replaceEdit->text());

    // Move to next match
    performFind(false);
    updateMatchLabel();
    highlightAllMatches();
}

void SearchPanel::replaceAll()
{
    if (!m_editor || m_searchEdit->text().isEmpty())
    {
        return;
    }

    QTextDocument *doc = m_editor->document();
    QTextCursor   cursor(doc);
    cursor.beginEditBlock();

    int count = 0;
    const QTextDocument::FindFlags flags = buildFindFlags(false);
    const QString needle      = m_searchEdit->text();
    const QString replacement = m_replaceEdit->text();

    while (true)
    {
        if (m_chkRegex->isChecked())
        {
            QRegularExpression re(needle);
            if (!m_chkCase->isChecked())
            {
                re.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
            }
            cursor = doc->find(re, cursor, flags);
        }
        else
        {
            cursor = doc->find(needle, cursor, flags);
        }

        if (cursor.isNull())
        {
            break;
        }

        cursor.insertText(replacement);
        ++count;
    }

    cursor.endEditBlock();

    updateMatchLabel();
    clearHighlights();

    QMessageBox::information(this, tr("Alle ersetzen"),
        tr("%1 Ersetzung(en) durchgeführt.").arg(count));
}

void SearchPanel::replaceInSelection()
{
    if (!m_editor || m_searchEdit->text().isEmpty())
    {
        return;
    }

    QTextCursor selCursor = m_editor->textCursor();
    if (!selCursor.hasSelection())
    {
        QMessageBox::information(this, tr("In Auswahl ersetzen"),
            tr("Es ist kein Text ausgewählt."));
        return;
    }

    // Work only within the selected region
    const int selStart = selCursor.selectionStart();
    const int selEnd   = selCursor.selectionEnd();

    QTextDocument *doc = m_editor->document();
    QTextCursor   cursor(doc);
    cursor.setPosition(selStart);
    cursor.beginEditBlock();

    int count = 0;
    int offset = 0; // Replacement may be longer/shorter than search term
    const QTextDocument::FindFlags flags = buildFindFlags(false);
    const QString needle      = m_searchEdit->text();
    const QString replacement = m_replaceEdit->text();

    while (true)
    {
        if (m_chkRegex->isChecked())
        {
            QRegularExpression re(needle);
            if (!m_chkCase->isChecked())
            {
                re.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
            }
            cursor = doc->find(re, cursor, flags);
        }
        else
        {
            cursor = doc->find(needle, cursor, flags);
        }

        if (cursor.isNull())
        {
            break;
        }

        // Stop if we've left the original selection
        if (cursor.selectionEnd() > selEnd + offset)
        {
            break;
        }

        offset += replacement.length() - cursor.selectedText().length();
        cursor.insertText(replacement);
        ++count;
    }

    cursor.endEditBlock();

    updateMatchLabel();
    clearHighlights();

    QMessageBox::information(this, tr("In Auswahl ersetzen"),
        tr("%1 Ersetzung(en) in der Auswahl durchgeführt.").arg(count));
}

// ---------------------------------------------------------------------------
// Private slots
// ---------------------------------------------------------------------------
void SearchPanel::onSearchTextChanged(const QString &/*text*/)
{
    highlightAllMatches();
    updateMatchLabel();
}

void SearchPanel::onOptionChanged()
{
    highlightAllMatches();
    updateMatchLabel();
}

void SearchPanel::onCloseClicked()
{
    clearHighlights();
    m_matchLabel->setText(QString());
    hide();
    if (m_editor)
    {
        m_editor->setFocus();
    }
}
