#ifdef _WIN32
#include <windows.h>
#endif

#include <QApplication>
#include <QTranslator>
#include <QLocale>
#include "MainWindow.h"

int main(int argc, char *argv[]) {
#ifdef _WIN32
    FreeConsole();
#endif
    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "1");
    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1");
    SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK);
    QApplication app(argc, argv);
    #ifdef _WIN32
    app.setAttribute(Qt::AA_EnableHighDpiScaling, true);
    app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
    #endif
    QTranslator translator;
    QString locale = QLocale::system().name();
    QString langCode = locale.section('_', 0, 0);
    QString translationsPath = QCoreApplication::applicationDirPath();
    if (translator.load(translationsPath + "/app_" + langCode + ".qm")) {
        app.installTranslator(&translator);
    }
    QString inputFile;
    if (argc >= 2) {
        inputFile = QString::fromLocal8Bit(argv[1]);
    }
    MainWindow w;
    if (!inputFile.isEmpty()) {
        if (inputFile.toLower().endsWith(".pdf")) {
            w.show();
            w.openPdfFile(inputFile);
        } else if (inputFile.toLower().endsWith(".snpkg")) {
            w.importNotebookFromFile(inputFile);
            w.show();
        } else {
            w.show();
    }
    } else {
    w.show();
    }
    return app.exec();
}

class MarkdownWindowManager {
    Q_OBJECT

signals:
    void zoomChanged(int newZoom);
    void panChanged(int panX, int panY);
    void touchGestureEnded();
    void ropeSelectionCompleted(const QPoint &position);
    void pdfLinkClicked(int targetPage);
    void pdfTextSelected(const QString &text);
    void pdfLoaded();
    void markdownSelectionModeChanged(bool enabled);
  public:
    explicit InkCanvas(QWidget *parent = nullptr) {

    // Set theme-aware default pen color
    MainWindow *mainWindow = qobject_cast<MainWindow*>(parent);
    if (mainWindow) {
        penColor = mainWindow->getDefaultPenColor();
    }
    setAttribute(Qt::WA_StaticContents);
    setTabletTracking(true);
    setAttribute(Qt::WA_AcceptTouchEvents);  // Enable touch events

    // Enable immediate updates for smoother animation
    setAttribute(Qt::WA_OpaquePaintEvent);
    setAttribute(Qt::WA_NoSystemBackground);

    // Detect screen resolution and set canvas size
    QScreen *screen = QGuiApplication::primaryScreen();
    if (screen) {
        QSize logicalSize = screen->availableGeometry().size() * 0.89;
        setMaximumSize(logicalSize); // Optional
        setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
    } else {
        setFixedSize(1920, 1080); // Fallback size
    }

    initializeBuffer();
    QCache<int, QPixmap> pdfCache(10);
    pdfCache.setMaxCost(10);  // ✅ Ensures the cache holds at most 5 pages
    // No need to set auto-delete, QCache will handle deletion automatically

    // Initialize PDF text selection throttling timer (60 FPS = ~16.67ms)
    pdfTextSelectionTimer = new QTimer(this);
    pdfTextSelectionTimer->setSingleShot(true);
    pdfTextSelectionTimer->setInterval(16); // ~60 FPS
    connect(pdfTextSelectionTimer, &QTimer::timeout, this, &InkCanvas::processPendingTextSelection);

    // Initialize PDF cache timer (will be created on-demand)
    pdfCacheTimer = nullptr;
    currentCachedPage = -1;

    // Initialize note page cache system
    noteCache.setMaxCost(15); // Cache up to 15 note pages (more than PDF since they're smaller)
    noteCacheTimer = nullptr;
    currentCachedNotePage = -1;

    // Initialize markdown manager
    markdownManager = new MarkdownWindowManager(this, this);

    // Connect pan/zoom signals to update markdown window positions
    connect(this, &InkCanvas::panChanged, markdownManager, &MarkdownWindowManager::updateAllWindowPositions);
    connect(this, &InkCanvas::zoomChanged, markdownManager, &MarkdownWindowManager::updateAllWindowPositions);
}
    virtual ~InkCanvas() {
    // Cleanup if needed
}
    void startBenchmark() {
    benchmarking = true;
    processedTimestamps.clear();
    benchmarkTimer.start();
}
    void stopBenchmark() {
    benchmarking = false;
}
    int getProcessedRate() {
    qint64 now = benchmarkTimer.elapsed();
    while (!processedTimestamps.empty() && now - processedTimestamps.front() > 1000) {
        processedTimestamps.pop_front();
    }
    return static_cast<int>(processedTimestamps.size());
}
    void setPenColor(const QColor &color) {
    penColor = color;
}
    void setPenThickness(qreal thickness) {
    // Set thickness for the current tool
    switch (currentTool) {
        case ToolType::Pen:
            penToolThickness = thickness;
            break;
        case ToolType::Marker:
            markerToolThickness = thickness;
            break;
        case ToolType::Eraser:
            eraserToolThickness = thickness;
            break;
    }

    // Update the current thickness for efficient drawing
    penThickness = thickness;
}
    void adjustAllToolThicknesses(qreal zoomRatio) {
    // Adjust thickness for all tools to maintain visual consistency
    penToolThickness *= zoomRatio;
    markerToolThickness *= zoomRatio;
    eraserToolThickness *= zoomRatio;

    // Update the current thickness based on the current tool
    switch (currentTool) {
        case ToolType::Pen:
            penThickness = penToolThickness;
            break;
        case ToolType::Marker:
            penThickness = markerToolThickness;
            break;
        case ToolType::Eraser:
            penThickness = eraserToolThickness;
            break;
    }
}
    void setTool(ToolType tool) {
    currentTool = tool;

    // Switch to the thickness for the new tool
    switch (currentTool) {
        case ToolType::Pen:
            penThickness = penToolThickness;
            break;
        case ToolType::Marker:
            penThickness = markerToolThickness;
            break;
        case ToolType::Eraser:
            penThickness = eraserToolThickness;
            break;
    }
}
    void setSaveFolder(const QString &folderPath) {
    saveFolder = folderPath;
    clearPdfNoDelete();

    if (!saveFolder.isEmpty()) {
        QDir().mkpath(saveFolder);
        loadNotebookId();  // ✅ Load notebook ID when save folder is set
    }

    QString bgMetaFile = saveFolder + "/.background_config.txt";  // This metadata is for background styles, not to be confused with pdf directories.
    if (QFile::exists(bgMetaFile)) {
        QFile file(bgMetaFile);
        if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            QTextStream in(&file);
            while (!in.atEnd()) {
                QString line = in.readLine().trimmed();
                if (line.startsWith("style=")) {
                    QString val = line.mid(6);
                    if (val == "Grid") backgroundStyle = BackgroundStyle::Grid;
                    else if (val == "Lines") backgroundStyle = BackgroundStyle::Lines;
                    else backgroundStyle = BackgroundStyle::None;
                } else if (line.startsWith("color=")) {
                    backgroundColor = QColor(line.mid(6));
                } else if (line.startsWith("density=")) {
                    backgroundDensity = line.mid(8).toInt();
                }
            }
            file.close();
        }
    }

    // ✅ Check if the folder has a saved PDF path
    QString metadataFile = saveFolder + "/.pdf_path.txt";
    if (!QFile::exists(metadataFile)) {
        return;
    }

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

    QTextStream in(&file);
    QString storedPdfPath = in.readLine().trimmed();
    file.close();

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

    // ✅ Ensure the stored PDF file still exists before loading
    if (!QFile::exists(storedPdfPath)) {
        return;
    }
    loadPdf(storedPdfPath);
}
    void saveToFile(int pageNumber) {
    if (saveFolder.isEmpty()) {
        return;
    }
    QString filePath = saveFolder + QString("/%1_%2.png").arg(notebookId).arg(pageNumber, 5, 10, QChar('0'));

    // ✅ If no file exists and the buffer is empty, do nothing

    if (!edited) {
        return;
    }

    QImage image(buffer.size(), QImage::Format_ARGB32);
    image.fill(Qt::transparent);
    QPainter painter(&image);
    painter.drawPixmap(0, 0, buffer);
    image.save(filePath, "PNG");
    edited = false;

    // Save markdown windows for this page
    if (markdownManager) {
        markdownManager->saveWindowsForPage(pageNumber);
    }

    // Update note cache with the saved buffer
    noteCache.insert(pageNumber, new QPixmap(buffer));
}
    void saveCurrentPage() {
    MainWindow *mainWin = qobject_cast<MainWindow*>(parentWidget());  // ✅ Get main window
    if (!mainWin) return;

    int currentPage = mainWin->getCurrentPageForCanvas(this);  // ✅ Get correct page
    saveToFile(currentPage);
}
    void loadPage(int pageNumber) {
    if (saveFolder.isEmpty()) return;

    // Hide any markdown windows from the previous page BEFORE loading the new page content.
    // This ensures the correct repaint area and stops the transparency timer.
    if (markdownManager) {
        markdownManager->hideAllWindows();
    }

    // Update current note page tracker
    currentCachedNotePage = pageNumber;

    // Check if note page is already cached
    bool loadedFromCache = false;
    if (noteCache.contains(pageNumber)) {
        // Use cached note page immediately
        buffer = *noteCache.object(pageNumber);
        loadedFromCache = true;
    } else {
        // Load note page from disk and cache it
        loadNotePageToCache(pageNumber);

        // Use the newly cached page or initialize buffer if loading failed
        if (noteCache.contains(pageNumber)) {
            buffer = *noteCache.object(pageNumber);
            loadedFromCache = true;
    } else {
        initializeBuffer(); // Clear the canvas if no file exists
        loadedFromCache = false;
    }
    }

    // Reset edited state when loading a new page
    edited = false;

    // Handle background image loading (PDF or custom background)
    if (isPdfLoaded && pdfDocument && pageNumber >= 0 && pageNumber < pdfDocument->numPages()) {
        // Use PDF as background (should already be cached by loadPdfPage)
        if (pdfCache.contains(pageNumber)) {
        backgroundImage = *pdfCache.object(pageNumber);

            // Resize canvas buffer to match PDF page size if needed
        if (backgroundImage.size() != buffer.size()) {
            QPixmap newBuffer(backgroundImage.size());
            newBuffer.fill(Qt::transparent);

            // Copy existing drawings
            QPainter painter(&newBuffer);
            painter.drawPixmap(0, 0, buffer);

            buffer = newBuffer;
            // Don't constrain widget size - let it expand to fill available space
            // The paintEvent will center the PDF content within the widget

                // Update cache with resized buffer
                noteCache.insert(pageNumber, new QPixmap(buffer));
            }
        }
    } else {
        // Handle custom background images
        QString bgFileName = saveFolder + QString("/bg_%1_%2.png").arg(notebookId).arg(pageNumber, 5, 10, QChar('0'));
        QString metadataFile = saveFolder + QString("/.%1_bgsize_%2.txt").arg(notebookId).arg(pageNumber, 5, 10, QChar('0'));

        int bgWidth = 0, bgHeight = 0;
        if (QFile::exists(metadataFile)) {
            QFile file(metadataFile);
            if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
                QTextStream in(&file);
                in >> bgWidth >> bgHeight;
                file.close();
            }
        }

        if (QFile::exists(bgFileName)) {
            QImage bgImage(bgFileName);
            backgroundImage = QPixmap::fromImage(bgImage);

            // Resize canvas **only if background resolution is different**
            if (bgWidth > 0 && bgHeight > 0 && (bgWidth != width() || bgHeight != height())) {
                // Create a new buffer
                QPixmap newBuffer(bgWidth, bgHeight);
                newBuffer.fill(Qt::transparent);

                // Copy existing drawings to the new buffer
                QPainter painter(&newBuffer);
                painter.drawPixmap(0, 0, buffer);

                // Assign the new buffer
                buffer = newBuffer;
                setMaximumSize(bgWidth, bgHeight);

                // Update cache with resized buffer
                noteCache.insert(pageNumber, new QPixmap(buffer));
            }
        } else {
            backgroundImage = QPixmap(); // No background for this page
            // Only apply device pixel ratio fix if buffer was NOT loaded from cache
            // This prevents resizing cached buffers that might already be correctly sized
            if (!loadedFromCache) {
                QScreen *screen = QGuiApplication::primaryScreen();
                qreal dpr = screen ? screen->devicePixelRatio() : 1.0;
                QSize logicalSize = screen ? screen->size() : QSize(1440, 900);
                QSize expectedPixelSize = logicalSize * dpr;

                if (buffer.size() != expectedPixelSize) {
                    // Buffer is wrong size, need to resize it properly
                    QPixmap newBuffer(expectedPixelSize);
                    newBuffer.fill(Qt::transparent);

                    // Copy existing drawings if buffer was smaller
                    if (!buffer.isNull()) {
                        QPainter painter(&newBuffer);
                        painter.drawPixmap(0, 0, buffer);
                    }

                    buffer = newBuffer;
                    setMaximumSize(expectedPixelSize);
                }
            }
        }
    }

    // Cache adjacent note pages after delay for faster navigation
    checkAndCacheAdjacentNotePages(pageNumber);

    update();
    adjustSize();            // Force the widget to update its size
    parentWidget()->update();

    // Load markdown windows AFTER canvas is fully rendered and sized
    // Use a single-shot timer to ensure the canvas is fully updated
    QTimer::singleShot(0, this, [this, pageNumber]() {
        if (markdownManager) {
            markdownManager->loadWindowsForPage(pageNumber);
        }
    });
}
    void deletePage(int pageNumber) {
    if (saveFolder.isEmpty()) {
        return;
    }
    QString fileName = saveFolder + QString("/%1_%2.png").arg(notebookId).arg(pageNumber, 5, 10, QChar('0'));
    QString bgFileName = saveFolder + QString("/bg_%1_%2.png").arg(notebookId).arg(pageNumber, 5, 10, QChar('0'));
    QString metadataFileName = saveFolder + QString("/.%1_bgsize_%2.txt").arg(notebookId).arg(pageNumber, 5, 10, QChar('0'));

    #ifdef Q_OS_WIN
    // Remove hidden attribute before deleting in Windows
    SetFileAttributesW(reinterpret_cast<const wchar_t *>(metadataFileName.utf16()), FILE_ATTRIBUTE_NORMAL);
    #endif

    QFile::remove(fileName);
    QFile::remove(bgFileName);
    QFile::remove(metadataFileName);

    // Remove deleted page from note cache
    noteCache.remove(pageNumber);

    // Delete markdown windows for this page
    if (markdownManager) {
        markdownManager->deleteWindowsForPage(pageNumber);
    }

    if (pdfDocument){
        loadPdfPage(pageNumber);
    }
    else{
        loadPage(pageNumber);
    }

}
    void setBackground(const QString &filePath, int pageNumber) {
    if (saveFolder.isEmpty()) {
        return; // No save folder set
    }

    // Construct full path inside save folder
    QString bgFileName = saveFolder + QString("/bg_%1_%2.png").arg(notebookId).arg(pageNumber, 5, 10, QChar('0'));

    // Copy the file to the save folder
    QFile::copy(filePath, bgFileName);

    // Load the background image
    QImage bgImage(bgFileName);

    if (!bgImage.isNull()) {
        // Save background resolution in metadata file
        QString metadataFile = saveFolder + QString("/.%1_bgsize_%2.txt")
                                            .arg(notebookId)
                                            .arg(pageNumber, 5, 10, QChar('0'));
        QFile file(metadataFile);
        if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
            QTextStream out(&file);
            out << bgImage.width() << " " << bgImage.height();
            file.close();
        }

        #ifdef Q_OS_WIN
        // Set hidden attribute for metadata on Windows
        SetFileAttributesW(reinterpret_cast<const wchar_t *>(metadataFile.utf16()), FILE_ATTRIBUTE_HIDDEN);
        #endif

        // Only resize if the background size is different
        if (bgImage.width() != width() || bgImage.height() != height()) {
            // Create a new buffer with the new size
            QPixmap newBuffer(bgImage.width(), bgImage.height());
            newBuffer.fill(Qt::transparent);

            // Copy existing drawings
            QPainter painter(&newBuffer);
            painter.drawPixmap(0, 0, buffer);

            // Assign new buffer and update canvas size
            buffer = newBuffer;
            setMaximumSize(bgImage.width(), bgImage.height());
        }

        backgroundImage = QPixmap::fromImage(bgImage);

        update();
        adjustSize();
        parentWidget()->update();
    }

    update();  // Refresh canvas
}
    void saveAnnotated(int pageNumber) {
    if (saveFolder.isEmpty()) return;

    QString filePath = saveFolder + QString("/annotated_%1_%2.png").arg(notebookId).arg(pageNumber, 5, 10, QChar('0'));

    // Use the buffer size to ensure correct resolution
    QImage image(buffer.size(), QImage::Format_ARGB32);
    image.fill(Qt::transparent);
    QPainter painter(&image);

    if (!backgroundImage.isNull()) {
        painter.drawPixmap(0, 0, backgroundImage.scaled(buffer.size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
    }

    painter.drawPixmap(0, 0, buffer);
    image.save(filePath, "PNG");
}
    void setZoom(int zoomLevel) {
    int newZoom = qMax(10, qMin(zoomLevel, 400)); // Limit zoom to 10%-400%
    if (zoomFactor != newZoom) {
        zoomFactor = newZoom;
        internalZoomFactor = zoomFactor; // Sync internal zoom
    update();
        emit zoomChanged(zoomFactor);
    }
}
    int getZoom() const {
    return zoomFactor;
}
    void updatePanOffsets(int xOffset, int yOffset) {
    panOffsetX = xOffset;
    panOffsetY = yOffset;
    update();
}
    int getPanOffsetX() const {
    return panOffsetX;
}
    int getPanOffsetY() const {
    return panOffsetY;
}
    void setPanX(int xOffset) {
    if (panOffsetX != value) {
    panOffsetX = value;
    update();
        emit panChanged(panOffsetX, panOffsetY);
    }
}
    void setPanY(int yOffset) {
    if (panOffsetY != value) {
    panOffsetY = value;
    update();
        emit panChanged(panOffsetX, panOffsetY);
    }
}
    void loadPdf(const QString &pdfPath) {
    pdfDocument = Poppler::Document::load(pdfPath);
    if (pdfDocument && !pdfDocument->isLocked()) {
        // Enable anti-aliasing rendering hints for better text quality
        pdfDocument->setRenderHint(Poppler::Document::Antialiasing, true);
        pdfDocument->setRenderHint(Poppler::Document::TextAntialiasing, true);
        pdfDocument->setRenderHint(Poppler::Document::TextHinting, true);
        pdfDocument->setRenderHint(Poppler::Document::TextSlightHinting, true);

        totalPdfPages = pdfDocument->numPages();
        isPdfLoaded = true;
        totalPdfPages = pdfDocument->numPages();
        loadPdfPage(0); // Load first page
        // ✅ Save the PDF path in the metadata file
        if (!saveFolder.isEmpty()) {
            QString metadataFile = saveFolder + "/.pdf_path.txt";
            QFile file(metadataFile);
            if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
                QTextStream out(&file);
                out << pdfPath;  // Store the absolute path of the PDF
                file.close();
            }
        }
        // Emit signal that PDF was loaded
        emit pdfLoaded();
    }
}
    void loadPdfPage(int pageNumber) {
    if (!pdfDocument) return;

    // Update current page tracker
    currentCachedPage = pageNumber;

    // Check if target page is already cached
    if (pdfCache.contains(pageNumber)) {
        // Display the cached page immediately
        backgroundImage = *pdfCache.object(pageNumber);
        loadPage(pageNumber);  // Load annotations
        loadPdfTextBoxes(pageNumber); // Load text boxes for PDF text selection
        update();

        // Check and cache adjacent pages after delay
        checkAndCacheAdjacentPages(pageNumber);
        return;
    }

    // Target page not in cache - render it immediately
    renderPdfPageToCache(pageNumber);

    // Display the newly rendered page
    if (pdfCache.contains(pageNumber)) {
        backgroundImage = *pdfCache.object(pageNumber);
    } else {
        backgroundImage = QPixmap();  // Clear background if rendering failed
    }

    loadPage(pageNumber);  // Load existing canvas annotations
    loadPdfTextBoxes(pageNumber); // Load text boxes for PDF text selection
    update();

    // Cache adjacent pages after delay
    checkAndCacheAdjacentPages(pageNumber);
}
    bool isPdfLoadedFunc() const {
    return isPdfLoaded;
}
    int getTotalPdfPages() const {
    return totalPdfPages;
}
    Poppler::Document* getPdfDocument() const;
    void clearPdf() {
    pdfDocument.reset();
    pdfDocument = nullptr;
    isPdfLoaded = false;
    totalPdfPages = 0;
    pdfCache.clear();

    // Reset cache system state
    currentCachedPage = -1;
    if (pdfCacheTimer && pdfCacheTimer->isActive()) {
        pdfCacheTimer->stop();
    }

    // Cancel and clean up any active PDF watchers
    for (QFutureWatcher<void>* watcher : activePdfWatchers) {
        if (watcher && !watcher->isFinished()) {
            watcher->cancel();
        }
        watcher->deleteLater();
    }
    activePdfWatchers.clear();

    // ✅ Remove the PDF path file when clearing the PDF
    if (!saveFolder.isEmpty()) {
        QString metadataFile = saveFolder + "/.pdf_path.txt";
        QFile::remove(metadataFile);
    }
}
    void clearPdfNoDelete() {
    pdfDocument.reset();
    pdfDocument = nullptr;
    isPdfLoaded = false;
    totalPdfPages = 0;
    pdfCache.clear();

    // Reset cache system state
    currentCachedPage = -1;
    if (pdfCacheTimer && pdfCacheTimer->isActive()) {
        pdfCacheTimer->stop();
    }

    // Cancel and clean up any active PDF watchers
    for (QFutureWatcher<void>* watcher : activePdfWatchers) {
        if (watcher && !watcher->isFinished()) {
            watcher->cancel();
        }
        watcher->deleteLater();
    }
    activePdfWatchers.clear();
}
    QString getSaveFolder() const {
    return saveFolder;
}
    QColor getPenColor() {
    return penColor;
}
    qreal getPenThickness() {
    return penThickness;
}
    ToolType getCurrentTool() {
    return currentTool;
}
    void loadPdfPreviewAsync(int pageNumber) {
    if (!pdfDocument || pageNumber < 0 || pageNumber >= pdfDocument->numPages()) return;

    QFutureWatcher<QPixmap> *watcher = new QFutureWatcher<QPixmap>(this);

    connect(watcher, &QFutureWatcher<QPixmap>::finished, this, [this, watcher]() {
        QPixmap result = watcher->result();
        if (!result.isNull()) {
            backgroundImage = result;
            update();  // trigger repaint
        }
        watcher->deleteLater();  // Clean up
    });

    QFuture<QPixmap> future = QtConcurrent::run([=]() -> QPixmap {
        std::unique_ptr<Poppler::Page> page(pdfDocument->page(pageNumber));
        if (!page) return QPixmap();

        // Render with document-level anti-aliasing settings
        QImage pdfImage = page->renderToImage(96, 96);
        if (pdfImage.isNull()) return QPixmap();

        QImage upscaled = pdfImage.scaled(pdfImage.width() * (pdfRenderDPI / 96),
                                  pdfImage.height() * (pdfRenderDPI / 96),
                                  Qt::KeepAspectRatio,
                                  Qt::SmoothTransformation);

        return QPixmap::fromImage(upscaled);
    });

    watcher->setFuture(future);
}
    void setBackgroundStyle(BackgroundStyle style) {
    backgroundStyle = style;
    update();  // Trigger repaint
}
    void setBackgroundColor(const QColor &color) {
    backgroundColor = color;
    update();
}
    void setBackgroundDensity(int density) {
    backgroundDensity = density;
    update();
}
    void saveBackgroundMetadata() {
    if (saveFolder.isEmpty()) return;

    QString bgMetaFile = saveFolder + "/.background_config.txt";
    QFile file(bgMetaFile);
    if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QTextStream out(&file);
        QString styleStr = "None";
        if (backgroundStyle == BackgroundStyle::Grid) styleStr = "Grid";
        else if (backgroundStyle == BackgroundStyle::Lines) styleStr = "Lines";

        out << "style=" << styleStr << "\n";
        out << "color=" << backgroundColor.name().toUpper() << "\n";
        out << "density=" << backgroundDensity << "\n";
        file.close();
    }
}
    void exportNotebook(const QString &destinationFile) {
    if (destinationFile.isEmpty()) {
        QMessageBox::warning(nullptr, tr("Export Error"), tr("No export file specified."));
        return;
    }

    if (saveFolder.isEmpty()) {
        QMessageBox::warning(nullptr, tr("Export Error"), tr("No notebook loaded (saveFolder is empty)"));
        return;
    }

    QStringList files;

    // Collect all files from saveFolder
    QDir dir(saveFolder);
    QDirIterator it(saveFolder, QDir::Files, QDirIterator::Subdirectories);
    while (it.hasNext()) {
        QString filePath = it.next();
        QString relativePath = dir.relativeFilePath(filePath);
        files << relativePath;
    }

    if (files.isEmpty()) {
        QMessageBox::warning(nullptr, tr("Export Error"), tr("No files found to export."));
        return;
    }

    // Generate temporary file list
    QString tempFileList = saveFolder + "/filelist.txt";
    QFile listFile(tempFileList);
    if (listFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QTextStream out(&listFile);
        for (const QString &file : files) {
            out << file << "\n";
        }
        listFile.close();
    } else {
        QMessageBox::warning(nullptr, tr("Export Error"), tr("Failed to create temporary file list."));
        return;
    }

#ifdef _WIN32
    QString tarExe = QCoreApplication::applicationDirPath() + "/bsdtar.exe";
#else
    QString tarExe = "tar";
#endif

    QStringList args;
    args << "-cf" << QDir::toNativeSeparators(destinationFile);

    for (const QString &file : files) {
        args << QDir::toNativeSeparators(file);
    }

    QProcess process;
    process.setWorkingDirectory(saveFolder);

    process.start(tarExe, args);
    if (!process.waitForFinished()) {
        QMessageBox::warning(nullptr, tr("Export Error"), tr("Tar process failed to finish."));
        QFile::remove(tempFileList);
        return;
    }

    QFile::remove(tempFileList);

    if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
        QMessageBox::warning(nullptr, tr("Export Error"), tr("Tar process failed."));
        return;
    }

    QMessageBox::information(nullptr, tr("Export"), tr("Notebook exported successfully."));
}
    void importNotebook(const QString &packageFile) {

    // Ask user for destination working folder
    QString destFolder = QFileDialog::getExistingDirectory(nullptr, tr("Select Destination Folder for Imported Notebook"));

    if (destFolder.isEmpty()) {
        QMessageBox::warning(nullptr, tr("Import Canceled"), tr("No destination folder selected."));
        return;
    }

    // Check if destination folder is empty (optional, good practice)
    QDir destDir(destFolder);
    if (!destDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot).isEmpty()) {
        QMessageBox::StandardButton reply = QMessageBox::question(nullptr, tr("Destination Not Empty"),
            tr("The selected folder is not empty. Files may be overwritten. Continue?"),
            QMessageBox::Yes | QMessageBox::No);
        if (reply != QMessageBox::Yes) {
            return;
        }
    }

#ifdef _WIN32
    QString tarExe = QCoreApplication::applicationDirPath() + "/bsdtar.exe";
#else
    QString tarExe = "tar";
#endif

    // Extract package
    QStringList args;
    args << "-xf" << packageFile;

    QProcess process;
    process.setWorkingDirectory(destFolder);
    process.start(tarExe, args);
    process.waitForFinished();

    // Switch notebook folder
    setSaveFolder(destFolder);
    loadPage(0);

    QMessageBox::information(nullptr, tr("Import Complete"), tr("Notebook imported successfully."));
}
    void importNotebookTo(const QString &packageFile, const QString &destFolder) {

    #ifdef _WIN32
        QString tarExe = QCoreApplication::applicationDirPath() + "/bsdtar.exe";
    #else
        QString tarExe = "tar";
    #endif

        QStringList args;
        args << "-xf" << packageFile;

        QProcess process;
        process.setWorkingDirectory(destFolder);
        process.start(tarExe, args);
        process.waitForFinished();

        setSaveFolder(destFolder);
        loadNotebookId();
        loadPage(0);

        QMessageBox::information(nullptr, tr("Import"), tr("Notebook imported successfully."));
    }
    void deleteRopeSelection() {
    if (!selectionBuffer.isNull() && !selectionRect.isEmpty()) {
        // If the selection area hasn't been cleared from the buffer yet, clear it now for deletion
        if (!selectionAreaCleared && !selectionMaskPath.isEmpty()) {
            QPainter painter(&buffer);
            painter.setCompositionMode(QPainter::CompositionMode_Clear);
            painter.fillPath(selectionMaskPath, Qt::transparent);
            painter.end();
        }

        // Clear the selection state
        selectionBuffer = QPixmap();
        selectionRect = QRect();
        exactSelectionRectF = QRectF();
        movingSelection = false;
        selectingWithRope = false;
        selectionJustCopied = false;
        selectionAreaCleared = false;
        selectionMaskPath = QPainterPath();
        selectionBufferRect = QRectF();

        // Mark as edited since we deleted content
        if (!edited) {
            edited = true;
            // Invalidate cache for current page since it's been modified
            invalidateCurrentPageCache();
        }

        // Update the entire canvas to remove selection visuals
        update();
    }
}
    void cancelRopeSelection() {
    if (!selectionBuffer.isNull() && !selectionRect.isEmpty()) {
        // Paste the selection back to its current location (where user moved it)
        QPainter painter(&buffer);
        // Explicitly set composition mode to draw on top of existing content
        painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
        // Use the current selection position
        QPointF currentTopLeft = exactSelectionRectF.isEmpty() ? selectionRect.topLeft() : exactSelectionRectF.topLeft();
        QPointF bufferDest = mapLogicalWidgetToPhysicalBuffer(currentTopLeft);
        painter.drawPixmap(bufferDest.toPoint(), selectionBuffer);
        painter.end();

        // Store selection buffer size for update calculation before clearing it
        QSize selectionSize = selectionBuffer.size();
        QRect updateRect = QRect(currentTopLeft.toPoint(), selectionSize);

        // Clear the selection state
        selectionBuffer = QPixmap();
        selectionRect = QRect();
        exactSelectionRectF = QRectF();
        movingSelection = false;
        selectingWithRope = false;
        selectionJustCopied = false;
        selectionAreaCleared = false;
        selectionMaskPath = QPainterPath();
        selectionBufferRect = QRectF();

        // Update the restored area
        update(updateRect.adjusted(-5, -5, 5, 5));
    }
}
    void copyRopeSelection() {
    if (!selectionBuffer.isNull() && !selectionRect.isEmpty()) {
        // Get current selection position
        QPointF currentTopLeft = exactSelectionRectF.isEmpty() ? selectionRect.topLeft() : exactSelectionRectF.topLeft();

        // Calculate new position (right next to the original), but ensure it stays within canvas bounds
        QPointF newTopLeft = currentTopLeft + QPointF(selectionRect.width() + 5, 0); // 5 pixels gap

        // Check if the new position would extend beyond buffer boundaries and adjust if needed
        QPointF currentBufferDest = mapLogicalWidgetToPhysicalBuffer(currentTopLeft);
        QPointF newBufferDest = mapLogicalWidgetToPhysicalBuffer(newTopLeft);

        // Ensure the copy stays within buffer bounds
        if (newBufferDest.x() + selectionBuffer.width() > buffer.width()) {
            // If it would extend beyond right edge, place it to the left of the original
            newTopLeft = currentTopLeft - QPointF(selectionRect.width() + 5, 0);
            newBufferDest = mapLogicalWidgetToPhysicalBuffer(newTopLeft);

            // If it still doesn't fit on the left, place it below the original
            if (newBufferDest.x() < 0) {
                newTopLeft = currentTopLeft + QPointF(0, selectionRect.height() + 5);
                newBufferDest = mapLogicalWidgetToPhysicalBuffer(newTopLeft);

                // If it doesn't fit below either, place it above
                if (newBufferDest.y() + selectionBuffer.height() > buffer.height()) {
                    newTopLeft = currentTopLeft - QPointF(0, selectionRect.height() + 5);
                    newBufferDest = mapLogicalWidgetToPhysicalBuffer(newTopLeft);

                    // If none of the positions work, just offset slightly and let it extend
                    if (newBufferDest.y() < 0) {
                        newTopLeft = currentTopLeft + QPointF(10, 10);
                        newBufferDest = mapLogicalWidgetToPhysicalBuffer(newTopLeft);
                    }
                }
            }
        }
        if (newBufferDest.y() + selectionBuffer.height() > buffer.height()) {
            // If it would extend beyond bottom edge, place it above the original
            newTopLeft = currentTopLeft - QPointF(0, selectionRect.height() + 5);
            newBufferDest = mapLogicalWidgetToPhysicalBuffer(newTopLeft);
        }

        // First, paste the selection back to its current location to restore the original
        QPainter painter(&buffer);
        // Explicitly set composition mode to draw on top of existing content
        painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
        painter.drawPixmap(currentBufferDest.toPoint(), selectionBuffer);

        // Then, paste the copy to the new location
        // We need to handle the case where the copy extends beyond buffer boundaries
        QRect targetRect(newBufferDest.toPoint(), selectionBuffer.size());
        QRect bufferBounds(0, 0, buffer.width(), buffer.height());
        QRect clippedRect = targetRect.intersected(bufferBounds);

        if (!clippedRect.isEmpty()) {
            // Calculate which part of the selectionBuffer to draw
            QRect sourceRect = QRect(
                clippedRect.x() - targetRect.x(),
                clippedRect.y() - targetRect.y(),
                clippedRect.width(),
                clippedRect.height()
            );

            painter.drawPixmap(clippedRect, selectionBuffer, sourceRect);
        }

        // For the selection buffer, we keep the FULL content, even if parts extend beyond canvas
        // This way when the user drags it back, the full content is preserved
        QPixmap newSelectionBuffer = selectionBuffer; // Keep the full original content

        // DON'T clear the copied area immediately - leave both original and copy on the canvas
        // The copy will only be cleared when the user actually starts moving the selection
        // This way, if the user cancels without moving, both original and copy remain permanently
        painter.end();

        // Update the selection buffer and position
        selectionBuffer = newSelectionBuffer;
        selectionRect = QRect(newTopLeft.toPoint(), selectionRect.size());
        exactSelectionRectF = QRectF(newTopLeft, selectionRect.size());
        selectionJustCopied = true; // Mark that this selection was just copied

        // Mark as edited since we added content
        if (!edited) {
            edited = true;
            // Invalidate cache for current page since it's been modified
            invalidateCurrentPageCache();
        }

        // Update the entire affected area (original + copy + gap)
        QRect updateArea = QRect(currentTopLeft.toPoint(), selectionRect.size())
                          .united(selectionRect)
                          .adjusted(-10, -10, 10, 10);
        update(updateArea);
    }
}
    void setMarkdownSelectionMode(bool enabled) {
    markdownSelectionMode = enabled;

    if (markdownManager) {
        markdownManager->setSelectionMode(enabled);
    }

    if (!enabled) {
        markdownSelecting = false;
    }

    // Update cursor
    setCursor(enabled ? Qt::CrossCursor : Qt::ArrowCursor);

    // Notify signal
    emit markdownSelectionModeChanged(enabled);
}
    bool isMarkdownSelectionMode() const {
    return markdownSelectionMode;
}
    void clearPdfTextSelection() {
    // Clear selection state
    selectedTextBoxes.clear();
    pdfTextSelecting = false;

    // Cancel any pending throttled updates
    if (pdfTextSelectionTimer && pdfTextSelectionTimer->isActive()) {
        pdfTextSelectionTimer->stop();
    }
    hasPendingSelection = false;

    // Refresh display
    update();
}
    QString getSelectedPdfText() const {
    if (selectedTextBoxes.isEmpty()) {
        return QString();
    }

    // Pre-allocate string with estimated size for efficiency
    QString selectedText;
    selectedText.reserve(selectedTextBoxes.size() * 20); // Estimate ~20 chars per text box

    // Build text with space separators
    for (const Poppler::TextBox* textBox : selectedTextBoxes) {
        if (textBox && !textBox->text().isEmpty()) {
            if (!selectedText.isEmpty()) {
                selectedText += " ";
            }
            selectedText += textBox->text();
        }
    }

    return selectedText;
}
    QPointF mapWidgetToCanvas(const QPointF &widgetPoint) const {
    QPointF topLeft = mapWidgetToCanvas(widgetRect.topLeft());
    QPointF bottomRight = mapWidgetToCanvas(widgetRect.bottomRight());

    return QRect(topLeft.toPoint(), bottomRight.toPoint());
}
    QPointF mapCanvasToWidget(const QPointF &canvasPoint) const {
    QPointF topLeft = mapCanvasToWidget(canvasRect.topLeft());
    QPointF bottomRight = mapCanvasToWidget(canvasRect.bottomRight());

    return QRect(topLeft.toPoint(), bottomRight.toPoint());
}
    QRect mapWidgetToCanvas(const QRect &widgetRect) const {
    QPointF topLeft = mapWidgetToCanvas(widgetRect.topLeft());
    QPointF bottomRight = mapWidgetToCanvas(widgetRect.bottomRight());

    return QRect(topLeft.toPoint(), bottomRight.toPoint());
}
    QRect mapCanvasToWidget(const QRect &canvasRect) const {
    QPointF topLeft = mapCanvasToWidget(canvasRect.topLeft());
    QPointF bottomRight = mapCanvasToWidget(canvasRect.bottomRight());

    return QRect(topLeft.toPoint(), bottomRight.toPoint());
}
  protected:
    void paintEvent(QPaintEvent *event) override {
    QPainter painter(this);

    // Save the painter state before transformations
    painter.save();

    // Calculate the scaled canvas size
    qreal scaledCanvasWidth = buffer.width() * (internalZoomFactor / 100.0);
    qreal scaledCanvasHeight = buffer.height() * (internalZoomFactor / 100.0);

    // Calculate centering offsets
    qreal centerOffsetX = 0;
    qreal centerOffsetY = 0;

    // Center horizontally if canvas is smaller than widget
    if (scaledCanvasWidth < width()) {
        centerOffsetX = (width() - scaledCanvasWidth) / 2.0;
    }

    // Center vertically if canvas is smaller than widget
    if (scaledCanvasHeight < height()) {
        centerOffsetY = (height() - scaledCanvasHeight) / 2.0;
    }

    // Apply centering offset first
    painter.translate(centerOffsetX, centerOffsetY);

    // Use internal zoom factor for smoother animation
    painter.scale(internalZoomFactor / 100.0, internalZoomFactor / 100.0);

    // Pan offset needs to be reversed because painter works in transformed coordinates
    painter.translate(-panOffsetX, -panOffsetY);

    // Set clipping rectangle to canvas bounds to prevent painting outside
    painter.setClipRect(0, 0, buffer.width(), buffer.height());

    // 🟨 Notebook-style background rendering
    if (backgroundImage.isNull()) {
        painter.save();

        // Always apply background color regardless of style
        painter.fillRect(QRectF(0, 0, buffer.width(), buffer.height()), backgroundColor);

        // Only draw grid/lines if not "None" style
        if (backgroundStyle != BackgroundStyle::None) {
        QPen linePen(QColor(100, 100, 100, 100));  // Subtle gray lines
        linePen.setWidthF(1.0);
        painter.setPen(linePen);

        qreal scaledDensity = backgroundDensity;

        if (devicePixelRatioF() > 1.0)
            scaledDensity *= devicePixelRatioF();  // Optional DPI handling

        if (backgroundStyle == BackgroundStyle::Lines || backgroundStyle == BackgroundStyle::Grid) {
            for (int y = 0; y < buffer.height(); y += scaledDensity)
                painter.drawLine(0, y, buffer.width(), y);
        }
        if (backgroundStyle == BackgroundStyle::Grid) {
            for (int x = 0; x < buffer.width(); x += scaledDensity)
            painter.drawLine(x, 0, x, buffer.height());
            }
        }

        painter.restore();
    }

    // ✅ Draw loaded image or PDF background if available
    if (!backgroundImage.isNull()) {
        painter.drawPixmap(0, 0, backgroundImage);
    }

    // ✅ Draw user's strokes from the buffer (transparent overlay)
    painter.drawPixmap(0, 0, buffer);

    // Draw straight line preview if in straight line mode and drawing
    // Skip preview for eraser tool
    if (straightLineMode && drawing && currentTool != ToolType::Eraser) {
        // Save the current state before applying the eraser mode
        painter.save();

        // Store current pressure - ensure minimum is 0.5 for consistent preview
        qreal pressure = qMax(0.5, painter.device()->devicePixelRatioF() > 1.0 ? 0.8 : 1.0);

        // Set up the pen based on tool type
        if (currentTool == ToolType::Marker) {
            qreal thickness = penThickness * 8.0;
            QColor markerColor = penColor;
            // Increase alpha for better visibility in straight line mode
            // Using a higher value (80) instead of the regular 4 to compensate for single-pass drawing
            markerColor.setAlpha(80);
            QPen pen(markerColor, thickness, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
            painter.setPen(pen);
        } else { // Default Pen
            // Match the exact same thickness calculation as in drawStroke
            qreal scaledThickness = penThickness * pressure;
            QPen pen(penColor, scaledThickness, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
            painter.setPen(pen);
        }

        // Use the same coordinate transformation logic as in drawStroke
        // to ensure the preview line appears at the exact same position
        QPointF bufferStart, bufferEnd;

        // Convert screen coordinates to buffer coordinates using the same calculations as drawStroke
        qreal scaledCanvasWidth = buffer.width() * (zoomFactor / 100.0);
        qreal scaledCanvasHeight = buffer.height() * (zoomFactor / 100.0);
        qreal centerOffsetX = (scaledCanvasWidth < width()) ? (width() - scaledCanvasWidth) / 2.0 : 0;
        qreal centerOffsetY = (scaledCanvasHeight < height()) ? (height() - scaledCanvasHeight) / 2.0 : 0;

        QPointF adjustedStart = straightLineStartPoint - QPointF(centerOffsetX, centerOffsetY);
        QPointF adjustedEnd = lastPoint - QPointF(centerOffsetX, centerOffsetY);

        bufferStart = (adjustedStart / (zoomFactor / 100.0)) + QPointF(panOffsetX, panOffsetY);
        bufferEnd = (adjustedEnd / (zoomFactor / 100.0)) + QPointF(panOffsetX, panOffsetY);

        // Draw the preview line using the same coordinates that will be used for the final line
        painter.drawLine(bufferStart, bufferEnd);

        // Restore the original painter state
        painter.restore();
    }

    // Draw selection rectangle if in rope tool mode and selecting, moving, or has a selection
    if (ropeToolMode && (selectingWithRope || movingSelection || (!selectionBuffer.isNull() && !selectionRect.isEmpty()))) {
        painter.save(); // Save painter state for overlays
        painter.resetTransform(); // Reset transform to draw directly in logical widget coordinates

        if (selectingWithRope && !lassoPathPoints.isEmpty()) {
            QPen selectionPen(Qt::DashLine);
            selectionPen.setColor(Qt::blue);
            selectionPen.setWidthF(1.5); // Width in logical pixels
            painter.setPen(selectionPen);
            painter.drawPolygon(lassoPathPoints); // lassoPathPoints are logical widget coordinates
        } else if (!selectionBuffer.isNull() && !selectionRect.isEmpty()) {
            // selectionRect is in logical widget coordinates.
            // selectionBuffer is in buffer coordinates, we need to handle scaling correctly
            QPixmap scaledBuffer = selectionBuffer;

            // Calculate the current zoom factor
            qreal currentZoom = internalZoomFactor / 100.0;

            // Scale the selection buffer to match the current zoom level
            if (currentZoom != 1.0) {
                QSize scaledSize = QSize(
                    qRound(scaledBuffer.width() * currentZoom),
                    qRound(scaledBuffer.height() * currentZoom)
                );
                scaledBuffer = scaledBuffer.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
            }

            // Draw it at the logical position
            // Use exactSelectionRectF for smoother movement if available
            QPointF topLeft = exactSelectionRectF.isEmpty() ? selectionRect.topLeft() : exactSelectionRectF.topLeft();
            painter.drawPixmap(topLeft, scaledBuffer);

            QPen selectionBorderPen(Qt::DashLine);
            selectionBorderPen.setColor(Qt::darkCyan);
            selectionBorderPen.setWidthF(1.5); // Width in logical pixels
            painter.setPen(selectionBorderPen);

            // Use exactSelectionRectF for drawing the selection border if available
            if (!exactSelectionRectF.isEmpty()) {
                painter.drawRect(exactSelectionRectF);
            } else {
                painter.drawRect(selectionRect);
            }
        }
        painter.restore(); // Restore painter state to what it was for drawing the main buffer
    }

    // Restore the painter state
    painter.restore();

    // Fill the area outside the canvas with the widget's background color
    QRect widgetRect = rect();
    QRectF canvasRect(
        centerOffsetX - panOffsetX * (internalZoomFactor / 100.0),
        centerOffsetY - panOffsetY * (internalZoomFactor / 100.0),
        buffer.width() * (internalZoomFactor / 100.0),
        buffer.height() * (internalZoomFactor / 100.0)
    );

    // Create regions for areas outside the canvas
    QRegion outsideRegion(widgetRect);
    outsideRegion -= QRegion(canvasRect.toRect());

    // Fill the outside region with the background color
    painter.setClipRegion(outsideRegion);
    painter.fillRect(widgetRect, palette().window().color());

    // Reset clipping for overlay elements that should appear on top
    painter.setClipping(false);

    // Draw markdown selection overlay
    if (markdownSelectionMode && markdownSelecting) {
        painter.save();
        QPen selectionPen(Qt::DashLine);
        selectionPen.setColor(Qt::green);
        selectionPen.setWidthF(2.0);
        painter.setPen(selectionPen);

        QBrush selectionBrush(QColor(0, 255, 0, 30)); // Semi-transparent green
        painter.setBrush(selectionBrush);

        QRect selectionRect = QRect(markdownSelectionStart, markdownSelectionEnd).normalized();
        painter.drawRect(selectionRect);
        painter.restore();
    }

    // Draw PDF text selection overlay on top of everything
    if (pdfTextSelectionEnabled && isPdfLoaded) {
        painter.save(); // Save painter state for PDF text overlay
        painter.resetTransform(); // Reset transform to draw directly in logical widget coordinates

        // Draw selection rectangle if actively selecting
        if (pdfTextSelecting) {
            QPen selectionPen(Qt::DashLine);
            selectionPen.setColor(QColor(0, 120, 215)); // Blue selection rectangle
            selectionPen.setWidthF(2.0); // Make it more visible
            painter.setPen(selectionPen);

            QBrush selectionBrush(QColor(0, 120, 215, 30)); // Semi-transparent blue fill
            painter.setBrush(selectionBrush);

            QRectF selectionRect(pdfSelectionStart, pdfSelectionEnd);
            selectionRect = selectionRect.normalized();
            painter.drawRect(selectionRect);
        }

        // Draw highlights for selected text boxes
        if (!selectedTextBoxes.isEmpty()) {
            QColor highlightColor = QColor(255, 255, 0, 100); // Semi-transparent yellow
            painter.setBrush(highlightColor);
            painter.setPen(Qt::NoPen);

            // Draw highlight rectangles for selected text boxes
            for (const Poppler::TextBox* textBox : selectedTextBoxes) {
                if (textBox) {
                    // Convert PDF coordinates to widget coordinates
                    QRectF pdfRect = textBox->boundingBox();
                    QPointF topLeft = mapPdfToWidgetCoordinates(pdfRect.topLeft());
                    QPointF bottomRight = mapPdfToWidgetCoordinates(pdfRect.bottomRight());
                    QRectF widgetRect(topLeft, bottomRight);
                    widgetRect = widgetRect.normalized();

                    painter.drawRect(widgetRect);
                }
            }
        }

        painter.restore(); // Restore painter state
    }
}
    void tabletEvent(QTabletEvent *event) override {
    // ✅ PRIORITY: Handle PDF text selection with stylus when enabled
    // This redirects stylus input to text selection instead of drawing
    if (pdfTextSelectionEnabled && isPdfLoaded) {
        if (event->type() == QEvent::TabletPress) {
            pdfTextSelecting = true;
            pdfSelectionStart = event->position();
            pdfSelectionEnd = pdfSelectionStart;

            // Clear any existing selected text boxes without resetting pdfTextSelecting
            selectedTextBoxes.clear();

            setCursor(Qt::IBeamCursor); // Ensure cursor is correct
            update(); // Refresh display
            event->accept();
            return;
        } else if (event->type() == QEvent::TabletMove && pdfTextSelecting) {
            pdfSelectionEnd = event->position();

            // Store pending selection for throttled processing (60 FPS throttling)
            pendingSelectionStart = pdfSelectionStart;
            pendingSelectionEnd = pdfSelectionEnd;
            hasPendingSelection = true;

            // Start timer if not already running (throttled to 60 FPS)
            if (!pdfTextSelectionTimer->isActive()) {
                pdfTextSelectionTimer->start();
            }

            // NOTE: No direct update() call here - let the timer handle updates at 60 FPS
            event->accept();
            return;
        } else if (event->type() == QEvent::TabletRelease && pdfTextSelecting) {
            pdfSelectionEnd = event->position();

            // Process any pending selection immediately on release
            if (pdfTextSelectionTimer && pdfTextSelectionTimer->isActive()) {
                pdfTextSelectionTimer->stop();
                if (hasPendingSelection) {
                    updatePdfTextSelection(pendingSelectionStart, pendingSelectionEnd);
                    hasPendingSelection = false;
                }
            } else {
                // Update selection with final position
                updatePdfTextSelection(pdfSelectionStart, pdfSelectionEnd);
            }

            pdfTextSelecting = false;

            // Check for link clicks if no text was selected
            QString selectedText = getSelectedPdfText();
            if (selectedText.isEmpty()) {
                handlePdfLinkClick(event->position());
            } else {
                // Show context menu for text selection
                QPoint globalPos = mapToGlobal(event->position().toPoint());
                showPdfTextSelectionMenu(globalPos);
            }

            event->accept();
            return;
        }
    }

    // ✅ NORMAL STYLUS BEHAVIOR: Only reached when PDF text selection is OFF
    // Hardware eraser detection
    static bool hardwareEraserActive = false;
    bool wasUsingHardwareEraser = false;

    // Track hardware eraser state
    if (event->pointerType() == QPointingDevice::PointerType::Eraser) {
        // Hardware eraser is being used
        wasUsingHardwareEraser = true;

        if (event->type() == QEvent::TabletPress) {
            // Start of eraser stroke - save current tool and switch to eraser
            hardwareEraserActive = true;
            previousTool = currentTool;
            currentTool = ToolType::Eraser;
        }
    }

    // Maintain hardware eraser state across move events
    if (hardwareEraserActive && event->type() != QEvent::TabletRelease) {
        wasUsingHardwareEraser = true;
    }

    // Determine if we're in eraser mode (either hardware eraser or tool set to eraser)
    bool isErasing = (currentTool == ToolType::Eraser);

    if (event->type() == QEvent::TabletPress) {
        drawing = true;
        lastPoint = event->posF(); // Logical widget coordinates
        if (straightLineMode) {
            straightLineStartPoint = lastPoint;
        }
        if (ropeToolMode) {
            if (!selectionBuffer.isNull() && selectionRect.contains(lastPoint.toPoint())) {
                // Start moving an existing selection (or continue if already moving)
                movingSelection = true;
                selectingWithRope = false;
                lastMovePoint = lastPoint;
                // Initialize the exact floating-point rect if it's empty
                if (exactSelectionRectF.isEmpty()) {
                    exactSelectionRectF = QRectF(selectionRect);
                }

                // If this selection was just copied, clear the area now that user is moving it
                if (selectionJustCopied) {
                    QPainter painter(&buffer);
                    painter.setCompositionMode(QPainter::CompositionMode_Clear);
                    QPointF bufferDest = mapLogicalWidgetToPhysicalBuffer(selectionRect.topLeft());
                    QRect clearRect(bufferDest.toPoint(), selectionBuffer.size());
                    // Only clear the part that's within buffer bounds
                    QRect bufferBounds(0, 0, buffer.width(), buffer.height());
                    QRect clippedClearRect = clearRect.intersected(bufferBounds);
                    if (!clippedClearRect.isEmpty()) {
                        painter.fillRect(clippedClearRect, Qt::transparent);
                    }
                    painter.end();
                    selectionJustCopied = false; // Clear the flag
                }

                // If the selection area hasn't been cleared from the buffer yet, clear it now
                if (!selectionAreaCleared && !selectionMaskPath.isEmpty()) {
                    QPainter painter(&buffer);
                    painter.setCompositionMode(QPainter::CompositionMode_Clear);
                    painter.fillPath(selectionMaskPath, Qt::transparent);
                    painter.end();
                    selectionAreaCleared = true;
                }
                // selectionBuffer already has the content.
                // The original area in 'buffer' was already cleared when selection was made.
            } else {
                // Start a new selection or cancel existing one
                if (!selectionBuffer.isNull()) { // If there's an active selection, a tap outside cancels it
                    selectionBuffer = QPixmap();
                    selectionRect = QRect();
                    lassoPathPoints.clear();
                    movingSelection = false;
                    selectingWithRope = false;
                    selectionJustCopied = false;
                    selectionAreaCleared = false;
                    selectionMaskPath = QPainterPath();
                    selectionBufferRect = QRectF();
                    update(); // Full update to remove old selection visuals
                    drawing = false; // Consumed this press for cancel
                    return;
                }
                selectingWithRope = true;
                movingSelection = false;
                selectionJustCopied = false;
                selectionAreaCleared = false;
                selectionMaskPath = QPainterPath();
                selectionBufferRect = QRectF();
                lassoPathPoints.clear();
                lassoPathPoints << lastPoint; // Start the lasso path
                selectionRect = QRect();
                selectionBuffer = QPixmap();
            }
        }
    } else if (event->type() == QEvent::TabletMove && drawing) {
        if (ropeToolMode) {
            if (selectingWithRope) {
                QRectF oldPreviewBoundingRect = lassoPathPoints.boundingRect();
                lassoPathPoints << event->posF();
                lastPoint = event->posF();
                QRectF newPreviewBoundingRect = lassoPathPoints.boundingRect();
                // Update the area of the selection rectangle preview (logical widget coordinates)
                update(oldPreviewBoundingRect.united(newPreviewBoundingRect).toRect().adjusted(-5,-5,5,5));
            } else if (movingSelection) {
                QPointF delta = event->posF() - lastMovePoint; // Delta in logical widget coordinates
                QRect oldWidgetSelectionRect = selectionRect;

                // Update the exact floating-point rectangle first
                exactSelectionRectF.translate(delta);

                // Convert back to integer rect, but only when the position actually changes
                QRect newRect = exactSelectionRectF.toRect();
                if (newRect != selectionRect) {
                    selectionRect = newRect;
                    // Update the combined area of the old and new selection positions (logical widget coordinates)
                    update(oldWidgetSelectionRect.united(selectionRect).adjusted(-2,-2,2,2));
                } else {
                    // Even if the integer position didn't change, we still need to update
                    // to make movement feel smoother, especially with slow movements
                    update(selectionRect.adjusted(-2,-2,2,2));
                }

                lastMovePoint = event->posF();
                if (!edited){
                    edited = true;
                    // Invalidate cache for current page since it's been modified
                    invalidateCurrentPageCache();
                }
            }
        } else if (straightLineMode && !isErasing) {
            // For straight line mode with non-eraser tools, just update the last position
            // and trigger a repaint of only the affected area for preview
            static QElapsedTimer updateTimer;
            static bool timerInitialized = false;

            if (!timerInitialized) {
                updateTimer.start();
                timerInitialized = true;
            }

            // Throttle updates based on time for high CPU usage tools
            bool shouldUpdate = true;

            // Apply throttling for marker which can be CPU intensive
            if (currentTool == ToolType::Marker) {
                shouldUpdate = updateTimer.elapsed() > 16; // Only update every 16ms (approx 60fps)
            }

            if (shouldUpdate) {
                QPointF oldLastPoint = lastPoint;
                lastPoint = event->posF();

                // Calculate affected rectangle that needs updating
                QRectF updateRect = calculatePreviewRect(straightLineStartPoint, oldLastPoint, lastPoint);
                update(updateRect.toRect());

                // Reset timer
                updateTimer.restart();
            } else {
                // Just update the last point without redrawing
                lastPoint = event->posF();
            }
        } else if (straightLineMode && isErasing) {
            // For eraser in straight line mode, continuously erase from start to current point
            // This gives immediate visual feedback and smoother erasing experience

            // Store current point
            QPointF currentPoint = event->posF();

            // Clear previous stroke by redrawing with transparency
            QRectF updateRect = QRectF(straightLineStartPoint, lastPoint).normalized().adjusted(-20, -20, 20, 20);
            update(updateRect.toRect());

            // Erase from start point to current position
            eraseStroke(straightLineStartPoint, currentPoint, event->pressure());

            // Update last point
            lastPoint = currentPoint;

            // Only track benchmarking when enabled
            if (benchmarking) {
                processedTimestamps.push_back(benchmarkTimer.elapsed());
            }
        } else {
            // Normal drawing mode OR eraser regardless of straight line mode
            if (isErasing) {
            eraseStroke(lastPoint, event->posF(), event->pressure());
        } else {
            drawStroke(lastPoint, event->posF(), event->pressure());
        }
        lastPoint = event->posF();

            // Only track benchmarking when enabled
            if (benchmarking) {
        processedTimestamps.push_back(benchmarkTimer.elapsed());
            }
        }
    } else if (event->type() == QEvent::TabletRelease) {
        if (straightLineMode && !isErasing) {
            // Draw the final line on release with the current pressure
            qreal pressure = event->pressure();

            // Always use at least a minimum pressure
            pressure = qMax(pressure, 0.5);

            drawStroke(straightLineStartPoint, event->posF(), pressure);

            // Only track benchmarking when enabled
            if (benchmarking) {
                processedTimestamps.push_back(benchmarkTimer.elapsed());
            }

            // Force repaint to clear the preview line
            update();
            if (!edited){
                edited = true;
                // Invalidate cache for current page since it's been modified
                invalidateCurrentPageCache();
            }
        } else if (straightLineMode && isErasing) {
            // For erasing in straight line mode, most of the work is done during movement
            // Just ensure one final erasing pass from start to end point
            qreal pressure = qMax(event->pressure(), 0.5);

            // Final pass to ensure the entire line is erased
            eraseStroke(straightLineStartPoint, event->posF(), pressure);

            // Force update to clear any remaining artifacts
            update();
            if (!edited){
                edited = true;
                // Invalidate cache for current page since it's been modified
                invalidateCurrentPageCache();
            }
        }

        drawing = false;

        // Reset tool state if we were using the hardware eraser
        if (wasUsingHardwareEraser) {
            currentTool = previousTool;
            hardwareEraserActive = false;  // Reset hardware eraser tracking
        }

        if (ropeToolMode) {
            if (selectingWithRope) {
                if (lassoPathPoints.size() > 2) { // Need at least 3 points for a polygon
                    lassoPathPoints << lassoPathPoints.first(); // Close the polygon

                    if (!lassoPathPoints.boundingRect().isEmpty()) {
                        // 1. Create a QPolygonF in buffer coordinates using proper transformation
                        QPolygonF bufferLassoPath;
                        for (const QPointF& p_widget_logical : qAsConst(lassoPathPoints)) {
                            bufferLassoPath << mapLogicalWidgetToPhysicalBuffer(p_widget_logical);
                        }

                        // 2. Get the bounding box of this path on the buffer
                        QRectF bufferPathBoundingRect = bufferLassoPath.boundingRect();

                        // 3. Copy that part of the main buffer
                        QPixmap originalPiece = buffer.copy(bufferPathBoundingRect.toRect());

                        // 4. Create the selectionBuffer (same size as originalPiece) and fill transparent
                        selectionBuffer = QPixmap(originalPiece.size());
                        selectionBuffer.fill(Qt::transparent);

                        // 5. Create a mask from the lasso path
                        QPainterPath maskPath;
                        // The lasso path for the mask needs to be relative to originalPiece.topLeft()
                        maskPath.addPolygon(bufferLassoPath.translated(-bufferPathBoundingRect.topLeft()));

                        // 6. Paint the originalPiece onto selectionBuffer, using the mask
                        QPainter selectionPainter(&selectionBuffer);
                        selectionPainter.setClipPath(maskPath);
                        selectionPainter.drawPixmap(0,0, originalPiece);
                        selectionPainter.end();

                        // 7. DON'T clear the selected area from the main buffer yet
                        // The area will only be cleared when the user actually starts moving the selection
                        // This way, if the user cancels without moving, the original content remains
                        selectionAreaCleared = false;

                        // Store the mask path and buffer rect for later clearing when movement starts
                        selectionMaskPath = maskPath.translated(bufferPathBoundingRect.topLeft());
                        selectionBufferRect = bufferPathBoundingRect;

                        // 8. Calculate the correct selectionRect in logical widget coordinates
                        QRectF logicalSelectionRect = mapRectBufferToWidgetLogical(bufferPathBoundingRect);
                        selectionRect = logicalSelectionRect.toRect();
                        exactSelectionRectF = logicalSelectionRect;

                        // Update the area of the selection on screen
                        update(logicalSelectionRect.adjusted(-2,-2,2,2).toRect());

                        // Emit signal for context menu at the center of the selection with a delay
                        // This allows the user to immediately start moving the selection if desired
                        QPoint menuPosition = selectionRect.center();
                        QTimer::singleShot(500, this, [this, menuPosition]() {
                            // Only show menu if selection still exists and hasn't been moved
                            if (!selectionBuffer.isNull() && !selectionRect.isEmpty() && !movingSelection) {
                                emit ropeSelectionCompleted(menuPosition);
                            }
                        });
                    }
                }
                lassoPathPoints.clear(); // Ready for next selection, or move
                selectingWithRope = false;
                // Now, if the user presses inside selectionRect, movingSelection will become true.
            } else if (movingSelection) {
                if (!selectionBuffer.isNull() && !selectionRect.isEmpty()) {
                    QPainter painter(&buffer);
                    // Explicitly set composition mode to draw on top of existing content
                    painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
                    // Use exact floating-point position if available for more precise placement
                    QPointF topLeft = exactSelectionRectF.isEmpty() ? selectionRect.topLeft() : exactSelectionRectF.topLeft();
                    // Use proper coordinate transformation to get buffer coordinates
                    QPointF bufferDest = mapLogicalWidgetToPhysicalBuffer(topLeft);
                    painter.drawPixmap(bufferDest.toPoint(), selectionBuffer);
                    painter.end();

                    // Update the pasted area
                    QRectF bufferPasteRect(bufferDest, selectionBuffer.size());
                    update(mapRectBufferToWidgetLogical(bufferPasteRect).adjusted(-2,-2,2,2));

                    // Clear selection after pasting, making it permanent
                    selectionBuffer = QPixmap();
                    selectionRect = QRect();
                    exactSelectionRectF = QRectF();
                    movingSelection = false;
                    selectionJustCopied = false;
                    selectionAreaCleared = false;
                    selectionMaskPath = QPainterPath();
                    selectionBufferRect = QRectF();
                }
            }
        }

    }
    event->accept();
}
    void mousePressEvent(QMouseEvent *event) override {
    // Handle markdown selection when enabled
    if (markdownSelectionMode && event->button() == Qt::LeftButton) {
        markdownSelecting = true;
        markdownSelectionStart = event->pos();
        markdownSelectionEnd = markdownSelectionStart;
        event->accept();
        return;
    }

    // Handle PDF text selection when enabled (mouse/touch fallback - stylus handled in tabletEvent)
    if (pdfTextSelectionEnabled && isPdfLoaded) {
        if (event->button() == Qt::LeftButton) {
            pdfTextSelecting = true;
            pdfSelectionStart = event->position();
            pdfSelectionEnd = pdfSelectionStart;

            // Clear any existing selected text boxes without resetting pdfTextSelecting
            selectedTextBoxes.clear();

            setCursor(Qt::IBeamCursor); // Ensure cursor is correct
            update(); // Refresh display
            event->accept();
            return;
        }
    }

    // Ignore mouse/touch input on canvas for drawing (drawing only works with tablet/stylus)
    event->ignore();
}
    void mouseMoveEvent(QMouseEvent *event) override {
    // Handle markdown selection when enabled
    if (markdownSelectionMode && markdownSelecting) {
        markdownSelectionEnd = event->pos();
        update(); // Refresh to show selection rectangle
        event->accept();
        return;
    }

    // Handle PDF text selection when enabled (mouse/touch fallback - stylus handled in tabletEvent)
    if (pdfTextSelectionEnabled && isPdfLoaded && pdfTextSelecting) {
        pdfSelectionEnd = event->position();

        // Store pending selection for throttled processing
        pendingSelectionStart = pdfSelectionStart;
        pendingSelectionEnd = pdfSelectionEnd;
        hasPendingSelection = true;

        // Start timer if not already running (throttled to 60 FPS)
        if (!pdfTextSelectionTimer->isActive()) {
            pdfTextSelectionTimer->start();
        }

        event->accept();
        return;
    }

    // Update cursor based on mode when not actively selecting
    if (pdfTextSelectionEnabled && isPdfLoaded && !pdfTextSelecting) {
        setCursor(Qt::IBeamCursor);
    }

    event->ignore();
}
    void mouseReleaseEvent(QMouseEvent *event) override {
    // Handle markdown selection when enabled
    if (markdownSelectionMode && markdownSelecting && event->button() == Qt::LeftButton) {
        markdownSelecting = false;

        // Create markdown window if selection is valid
        QRect selectionRect = QRect(markdownSelectionStart, markdownSelectionEnd).normalized();
        if (selectionRect.width() > 50 && selectionRect.height() > 50 && markdownManager) {
            markdownManager->createMarkdownWindow(selectionRect);
        }

        // Exit selection mode
        setMarkdownSelectionMode(false);

        // Force screen update to clear the green selection overlay
        update();

        event->accept();
        return;
    }

    // Handle PDF text selection when enabled (mouse/touch fallback - stylus handled in tabletEvent)
    if (pdfTextSelectionEnabled && isPdfLoaded && pdfTextSelecting) {
        if (event->button() == Qt::LeftButton) {
            pdfSelectionEnd = event->position();

            // Process any pending selection immediately on release
            if (pdfTextSelectionTimer && pdfTextSelectionTimer->isActive()) {
                pdfTextSelectionTimer->stop();
                if (hasPendingSelection) {
                    updatePdfTextSelection(pendingSelectionStart, pendingSelectionEnd);
                    hasPendingSelection = false;
                }
            } else {
                // Update selection with final position
                updatePdfTextSelection(pdfSelectionStart, pdfSelectionEnd);
            }

            pdfTextSelecting = false;

            // Check for link clicks if no text was selected
            QString selectedText = getSelectedPdfText();
            if (selectedText.isEmpty()) {
                handlePdfLinkClick(event->position());
            } else {
                // Show context menu for text selection
                QPoint globalPos = mapToGlobal(event->position().toPoint());
                showPdfTextSelectionMenu(globalPos);
            }

            event->accept();
            return;
        }
    }

    event->ignore();
}
    void resizeEvent(QResizeEvent *event) override {
    // Don't resize the buffer when the widget resizes
    // The buffer size should be determined by the PDF/document content, not the widget size
    // The paintEvent will handle centering the buffer content within the widget

    QWidget::resizeEvent(event);
}
    bool event(QEvent *event) override {
    if (!touchGesturesEnabled) {
        return QWidget::event(event);
    }

    if (event->type() == QEvent::TouchBegin ||
        event->type() == QEvent::TouchUpdate ||
        event->type() == QEvent::TouchEnd) {

        QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event);
        const QList<QTouchEvent::TouchPoint> touchPoints = touchEvent->points();

        activeTouchPoints = touchPoints.count();

        if (activeTouchPoints == 1) {
            // Single finger pan
            const QTouchEvent::TouchPoint &touchPoint = touchPoints.first();

            if (event->type() == QEvent::TouchBegin) {
                isPanning = true;
                lastTouchPos = touchPoint.position();
            } else if (event->type() == QEvent::TouchUpdate && isPanning) {
                QPointF delta = touchPoint.position() - lastTouchPos;

                // Make panning more responsive by using floating-point calculations
                qreal scaledDeltaX = delta.x() / (internalZoomFactor / 100.0);
                qreal scaledDeltaY = delta.y() / (internalZoomFactor / 100.0);

                // Calculate new pan positions with sub-pixel precision
                qreal newPanX = panOffsetX - scaledDeltaX;
                qreal newPanY = panOffsetY - scaledDeltaY;

                // Clamp pan values when canvas is smaller than viewport
                qreal scaledCanvasWidth = buffer.width() * (internalZoomFactor / 100.0);
                qreal scaledCanvasHeight = buffer.height() * (internalZoomFactor / 100.0);

                // If canvas is smaller than widget, lock pan to 0 (centered)
                if (scaledCanvasWidth < width()) {
                    newPanX = 0;
                }
                if (scaledCanvasHeight < height()) {
                    newPanY = 0;
                }

                // Emit signal with integer values for compatibility
                emit panChanged(qRound(newPanX), qRound(newPanY));

                lastTouchPos = touchPoint.position();
            }
        } else if (activeTouchPoints == 2) {
            // Two finger pinch zoom
            isPanning = false;

            const QTouchEvent::TouchPoint &touch1 = touchPoints[0];
            const QTouchEvent::TouchPoint &touch2 = touchPoints[1];

            // Calculate distance between touch points with higher precision
            qreal currentDist = QLineF(touch1.position(), touch2.position()).length();
            qreal startDist = QLineF(touch1.pressPosition(), touch2.pressPosition()).length();

            if (event->type() == QEvent::TouchBegin) {
                lastPinchScale = 1.0;
                // Store the starting internal zoom
                internalZoomFactor = zoomFactor;
            } else if (event->type() == QEvent::TouchUpdate && startDist > 0) {
                qreal scale = currentDist / startDist;

                // Use exponential scaling for more natural feel
                qreal scaleChange = scale / lastPinchScale;

                // Apply scale with higher sensitivity
                internalZoomFactor *= scaleChange;
                internalZoomFactor = qBound(10.0, internalZoomFactor, 400.0);

                // Calculate zoom center (midpoint between two fingers)
                QPointF center = (touch1.position() + touch2.position()) / 2.0;

                // Account for centering offset
                qreal scaledCanvasWidth = buffer.width() * (zoomFactor / 100.0);
                qreal scaledCanvasHeight = buffer.height() * (zoomFactor / 100.0);
                qreal centerOffsetX = (scaledCanvasWidth < width()) ? (width() - scaledCanvasWidth) / 2.0 : 0;
                qreal centerOffsetY = (scaledCanvasHeight < height()) ? (height() - scaledCanvasHeight) / 2.0 : 0;

                // Adjust center point for centering offset
                QPointF adjustedCenter = center - QPointF(centerOffsetX, centerOffsetY);

                // Always update zoom for smooth animation
                int newZoom = qRound(internalZoomFactor);

                // Calculate pan adjustment to keep the zoom centered at pinch point
                QPointF bufferCenter = adjustedCenter / (zoomFactor / 100.0) + QPointF(panOffsetX, panOffsetY);

                // Update zoom factor before emitting
                qreal oldZoomFactor = zoomFactor;
                zoomFactor = newZoom;

                // Emit zoom change even for small changes
                emit zoomChanged(newZoom);

                // Adjust pan to keep center point fixed with sub-pixel precision
                qreal newPanX = bufferCenter.x() - adjustedCenter.x() / (internalZoomFactor / 100.0);
                qreal newPanY = bufferCenter.y() - adjustedCenter.y() / (internalZoomFactor / 100.0);

                // After zoom, check if we need to center
                qreal newScaledCanvasWidth = buffer.width() * (internalZoomFactor / 100.0);
                qreal newScaledCanvasHeight = buffer.height() * (internalZoomFactor / 100.0);

                if (newScaledCanvasWidth < width()) {
                    newPanX = 0;
                }
                if (newScaledCanvasHeight < height()) {
                    newPanY = 0;
                }

                emit panChanged(qRound(newPanX), qRound(newPanY));

                lastPinchScale = scale;

                // Request update only for visible area
                update();
            }
        } else {
            // More than 2 fingers - ignore
            isPanning = false;
        }

        if (event->type() == QEvent::TouchEnd) {
            isPanning = false;
            lastPinchScale = 1.0;
            activeTouchPoints = 0;
            // Sync internal zoom with actual zoom
            internalZoomFactor = zoomFactor;

            // Emit signal that touch gesture has ended
            emit touchGestureEnded();
        }

        event->accept();
        return true;
    }

    return QWidget::event(event);
}
  private:
    QPointF mapLogicalWidgetToPhysicalBuffer(const QPointF& logicalWidgetPoint) {
    // Use the same coordinate transformation logic as drawStroke for consistency
    qreal scaledCanvasWidth = buffer.width() * (zoomFactor / 100.0);
    qreal scaledCanvasHeight = buffer.height() * (zoomFactor / 100.0);
    qreal centerOffsetX = (scaledCanvasWidth < width()) ? (width() - scaledCanvasWidth) / 2.0 : 0;
    qreal centerOffsetY = (scaledCanvasHeight < height()) ? (height() - scaledCanvasHeight) / 2.0 : 0;

    // Convert widget position to buffer position, accounting for centering
    QPointF adjustedPoint = logicalWidgetPoint - QPointF(centerOffsetX, centerOffsetY);
    QPointF bufferPoint = (adjustedPoint / (zoomFactor / 100.0)) + QPointF(panOffsetX, panOffsetY);

    return bufferPoint;
}
    QRect mapRectBufferToWidgetLogical(const QRectF& physicalBufferRect) {
    // Use the same coordinate transformation logic as drawStroke for consistency
    qreal scaledCanvasWidth = buffer.width() * (zoomFactor / 100.0);
    qreal scaledCanvasHeight = buffer.height() * (zoomFactor / 100.0);
    qreal centerOffsetX = (scaledCanvasWidth < width()) ? (width() - scaledCanvasWidth) / 2.0 : 0;
    qreal centerOffsetY = (scaledCanvasHeight < height()) ? (height() - scaledCanvasHeight) / 2.0 : 0;

    // Convert buffer coordinates back to widget coordinates
    QRectF widgetRect = QRectF(
        (physicalBufferRect.topLeft() - QPointF(panOffsetX, panOffsetY)) * (zoomFactor / 100.0) + QPointF(centerOffsetX, centerOffsetY),
        physicalBufferRect.size() * (zoomFactor / 100.0)
    );

    return widgetRect.toRect();
}
    QPixmap buffer;
    QImage background;
    QPointF lastPoint;
    QPointF straightLineStartPoint;
    bool drawing;
    QColor penColor;
    qreal penThickness;
    ToolType currentTool;
    ToolType previousTool;
    qreal penToolThickness = 5.0;
    qreal markerToolThickness = 5.0;
    qreal eraserToolThickness = 5.0;
    QString saveFolder;
    QPixmap backgroundImage;
    bool straightLineMode = false;
    bool ropeToolMode = false;
    QPixmap selectionBuffer;
    QRect selectionRect;
    QRectF exactSelectionRectF;
    QPolygonF lassoPathPoints;
    bool selectingWithRope = false;
    bool movingSelection = false;
    bool selectionJustCopied = false;
    bool selectionAreaCleared = false;
    QPainterPath selectionMaskPath;
    QRectF selectionBufferRect;
    QPointF lastMovePoint;
    int zoomFactor;
    int panOffsetX;
    int panOffsetY;
    qreal internalZoomFactor = 100.0;
    void initializeBuffer() {
    QScreen *screen = QGuiApplication::primaryScreen();
    qreal dpr = screen ? screen->devicePixelRatio() : 1.0;

    // Get logical screen size
    QSize logicalSize = screen ? screen->size() : QSize(1440, 900);
    QSize pixelSize = logicalSize * dpr;

    buffer = QPixmap(pixelSize);
    buffer.fill(Qt::transparent);

    setMaximumSize(pixelSize); // 🔥 KEY LINE to make full canvas drawable
}
    void drawStroke(const QPointF &start, const QPointF &end, qreal pressure) {
    if (buffer.isNull()) {
        initializeBuffer();
    }

    if (!edited){
        edited = true;
        // Invalidate cache for current page since it's been modified
        invalidateCurrentPageCache();
    }

    QPainter painter(&buffer);
    painter.setRenderHint(QPainter::Antialiasing);

    qreal thickness = penThickness;

    qreal updatePadding = (currentTool == ToolType::Marker) ? thickness * 4.0 : 10;

    if (currentTool == ToolType::Marker) {
        thickness *= 8.0;
        QColor markerColor = penColor;
        // Adjust alpha based on whether we're in straight line mode
        if (straightLineMode) {
            // For straight line mode, use higher alpha to make it more visible
            markerColor.setAlpha(40);
        } else {
            // For regular drawing, use lower alpha for the usual marker effect
            markerColor.setAlpha(4);
        }
        QPen pen(markerColor, thickness, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
        painter.setPen(pen);
    } else { // Default Pen
        qreal scaledThickness = thickness * pressure;  // **Linear pressure scaling**
        QPen pen(penColor, scaledThickness, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
        painter.setPen(pen);
    }

    // Calculate centering offsets
    qreal scaledCanvasWidth = buffer.width() * (zoomFactor / 100.0);
    qreal scaledCanvasHeight = buffer.height() * (zoomFactor / 100.0);
    qreal centerOffsetX = (scaledCanvasWidth < width()) ? (width() - scaledCanvasWidth) / 2.0 : 0;
    qreal centerOffsetY = (scaledCanvasHeight < height()) ? (height() - scaledCanvasHeight) / 2.0 : 0;

    // Convert screen position to buffer position, accounting for centering
    QPointF adjustedStart = start - QPointF(centerOffsetX, centerOffsetY);
    QPointF adjustedEnd = end - QPointF(centerOffsetX, centerOffsetY);

    QPointF bufferStart = (adjustedStart / (zoomFactor / 100.0)) + QPointF(panOffsetX, panOffsetY);
    QPointF bufferEnd = (adjustedEnd / (zoomFactor / 100.0)) + QPointF(panOffsetX, panOffsetY);

    painter.drawLine(bufferStart, bufferEnd);

    QRectF updateRect = QRectF(bufferStart, bufferEnd)
                        .normalized()
                        .adjusted(-updatePadding, -updatePadding, updatePadding, updatePadding);

    QRect scaledUpdateRect = QRect(
        ((updateRect.topLeft() - QPointF(panOffsetX, panOffsetY)) * (zoomFactor / 100.0) + QPointF(centerOffsetX, centerOffsetY)).toPoint(),
        ((updateRect.bottomRight() - QPointF(panOffsetX, panOffsetY)) * (zoomFactor / 100.0) + QPointF(centerOffsetX, centerOffsetY)).toPoint()
    );
    update(scaledUpdateRect);
}
    void eraseStroke(const QPointF &start, const QPointF &end, qreal pressure) {
    if (buffer.isNull()) {
        initializeBuffer();
    }

    if (!edited){
        edited = true;
        // Invalidate cache for current page since it's been modified
        invalidateCurrentPageCache();
    }

    QPainter painter(&buffer);
    painter.setCompositionMode(QPainter::CompositionMode_Clear);

    qreal eraserThickness = penThickness * 6.0;
    QPen eraserPen(Qt::transparent, eraserThickness, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
    painter.setPen(eraserPen);

    // Calculate centering offsets
    qreal scaledCanvasWidth = buffer.width() * (zoomFactor / 100.0);
    qreal scaledCanvasHeight = buffer.height() * (zoomFactor / 100.0);
    qreal centerOffsetX = (scaledCanvasWidth < width()) ? (width() - scaledCanvasWidth) / 2.0 : 0;
    qreal centerOffsetY = (scaledCanvasHeight < height()) ? (height() - scaledCanvasHeight) / 2.0 : 0;

    // Convert screen position to buffer position, accounting for centering
    QPointF adjustedStart = start - QPointF(centerOffsetX, centerOffsetY);
    QPointF adjustedEnd = end - QPointF(centerOffsetX, centerOffsetY);

    QPointF bufferStart = (adjustedStart / (zoomFactor / 100.0)) + QPointF(panOffsetX, panOffsetY);
    QPointF bufferEnd = (adjustedEnd / (zoomFactor / 100.0)) + QPointF(panOffsetX, panOffsetY);

    painter.drawLine(bufferStart, bufferEnd);

    qreal updatePadding = eraserThickness / 2.0 + 5.0; // Half the eraser thickness plus some extra padding
    QRectF updateRect = QRectF(bufferStart, bufferEnd)
                        .normalized()
                        .adjusted(-updatePadding, -updatePadding, updatePadding, updatePadding);

    QRect scaledUpdateRect = QRect(
        ((updateRect.topLeft() - QPointF(panOffsetX, panOffsetY)) * (zoomFactor / 100.0) + QPointF(centerOffsetX, centerOffsetY)).toPoint(),
        ((updateRect.bottomRight() - QPointF(panOffsetX, panOffsetY)) * (zoomFactor / 100.0) + QPointF(centerOffsetX, centerOffsetY)).toPoint()
    );
    update(scaledUpdateRect);
}
    QRectF calculatePreviewRect(const QPointF &start, const QPointF &oldEnd, const QPointF &newEnd) {
    // Calculate centering offsets - use the same calculation as in paintEvent
    qreal scaledCanvasWidth = buffer.width() * (zoomFactor / 100.0);
    qreal scaledCanvasHeight = buffer.height() * (zoomFactor / 100.0);
    qreal centerOffsetX = (scaledCanvasWidth < width()) ? (width() - scaledCanvasWidth) / 2.0 : 0;
    qreal centerOffsetY = (scaledCanvasHeight < height()) ? (height() - scaledCanvasHeight) / 2.0 : 0;

    // Calculate the buffer coordinates for our points (same as in drawStroke)
    QPointF adjustedStart = start - QPointF(centerOffsetX, centerOffsetY);
    QPointF adjustedOldEnd = oldEnd - QPointF(centerOffsetX, centerOffsetY);
    QPointF adjustedNewEnd = newEnd - QPointF(centerOffsetX, centerOffsetY);

    QPointF bufferStart = (adjustedStart / (zoomFactor / 100.0)) + QPointF(panOffsetX, panOffsetY);
    QPointF bufferOldEnd = (adjustedOldEnd / (zoomFactor / 100.0)) + QPointF(panOffsetX, panOffsetY);
    QPointF bufferNewEnd = (adjustedNewEnd / (zoomFactor / 100.0)) + QPointF(panOffsetX, panOffsetY);

    // Now convert from buffer coordinates to screen coordinates
    QPointF screenStart = (bufferStart - QPointF(panOffsetX, panOffsetY)) * (zoomFactor / 100.0) + QPointF(centerOffsetX, centerOffsetY);
    QPointF screenOldEnd = (bufferOldEnd - QPointF(panOffsetX, panOffsetY)) * (zoomFactor / 100.0) + QPointF(centerOffsetX, centerOffsetY);
    QPointF screenNewEnd = (bufferNewEnd - QPointF(panOffsetX, panOffsetY)) * (zoomFactor / 100.0) + QPointF(centerOffsetX, centerOffsetY);

    // Create rectangles for both the old and new lines
    QRectF oldLineRect = QRectF(screenStart, screenOldEnd).normalized();
    QRectF newLineRect = QRectF(screenStart, screenNewEnd).normalized();

    // Calculate padding based on pen thickness and device pixel ratio
    qreal dpr = devicePixelRatioF();
    qreal padding;

    if (currentTool == ToolType::Eraser) {
        padding = penThickness * 6.0 * dpr;
    } else if (currentTool == ToolType::Marker) {
        padding = penThickness * 8.0 * dpr;
    } else {
        padding = penThickness * dpr;
    }

    // Ensure minimum padding
    padding = qMax(padding, 15.0);

    // Combine rectangles with appropriate padding
    QRectF combinedRect = oldLineRect.united(newLineRect);
    return combinedRect.adjusted(-padding, -padding, padding, padding);
}
    QCache<int, QPixmap> pdfCache;
    std::unique_ptr<Poppler::Document> pdfDocument;
    int currentPdfPage;
    bool isPdfLoaded = false;
    int totalPdfPages = 0;
    int lastActivePage = 0;
    int lastZoomLevel = 100;
    int lastPanX = 0;
    int lastPanY = 0;
    bool edited = false;
    bool benchmarking;
    std::deque<qint64> processedTimestamps;
    QElapsedTimer benchmarkTimer;
    QString notebookId;
    void loadNotebookId() {
    QString idFile = saveFolder + "/.notebook_id.txt";
    if (QFile::exists(idFile)) {
        QFile file(idFile);
        if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            QTextStream in(&file);
            notebookId = in.readLine().trimmed();
        }
    } else {
        // No ID file → create new random ID
        notebookId = QUuid::createUuid().toString(QUuid::WithoutBraces).replace("-", "");
        saveNotebookId();
    }
}
    void saveNotebookId() {
    QString idFile = saveFolder + "/.notebook_id.txt";
    QFile file(idFile);
    if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QTextStream out(&file);
        out << notebookId;
    }
}
    int pdfRenderDPI = 192;
    bool touchGesturesEnabled = false;
    QPointF lastTouchPos;
    qreal lastPinchScale = 1.0;
    bool isPanning = false;
    int activeTouchPoints = 0;
    BackgroundStyle backgroundStyle = BackgroundStyle::None;
    QColor backgroundColor = Qt::white;
    int backgroundDensity = 40;
    bool pdfTextSelectionEnabled = false;
    bool pdfTextSelecting = false;
    QPointF pdfSelectionStart;
    QPointF pdfSelectionEnd;
    QList<Poppler::TextBox*> currentPdfTextBoxes;
    QList<Poppler::TextBox*> selectedTextBoxes;
    std::unique_ptr<Poppler::Page> currentPdfPageForText;
    QTimer* pdfTextSelectionTimer = nullptr;
    QPointF pendingSelectionStart;
    QPointF pendingSelectionEnd;
    bool hasPendingSelection = false;
    QTimer* pdfCacheTimer = nullptr;
    int currentCachedPage = -1;
    int pendingCacheTargetPage = -1;
    QList<QFutureWatcher<void>*> activePdfWatchers;
    QCache<int, QPixmap> noteCache;
    QTimer* noteCacheTimer = nullptr;
    int currentCachedNotePage = -1;
    int pendingNoteCacheTargetPage = -1;
    QList<QFutureWatcher<void>*> activeNoteWatchers;
    MarkdownWindowManager* markdownManager = nullptr;
    bool markdownSelectionMode = false;
    QPoint markdownSelectionStart;
    QPoint markdownSelectionEnd;
    bool markdownSelecting = false;
    void loadPdfTextBoxes(int pageNumber) {
    // Clear existing text boxes
    qDeleteAll(currentPdfTextBoxes);
    currentPdfTextBoxes.clear();

    if (!pdfDocument || pageNumber < 0 || pageNumber >= pdfDocument->numPages()) {
        return;
    }

    // Get the page for text operations
    currentPdfPageForText = std::unique_ptr<Poppler::Page>(pdfDocument->page(pageNumber));
    if (!currentPdfPageForText) {
        return;
    }

    // Load text boxes for the page
    auto textBoxVector = currentPdfPageForText->textList();
    currentPdfTextBoxes.clear();
    for (auto& textBox : textBoxVector) {
        currentPdfTextBoxes.append(textBox.release()); // Transfer ownership to QList
    }
}
    QPointF mapWidgetToPdfCoordinates(const QPointF &widgetPoint) {
    if (!currentPdfPageForText || backgroundImage.isNull()) {
        return QPointF();
    }

    // Use the same zoom factor as in paintEvent (internalZoomFactor for smooth animations)
    qreal zoom = internalZoomFactor / 100.0;

    // Calculate the scaled canvas size (same as in paintEvent)
    qreal scaledCanvasWidth = buffer.width() * zoom;
    qreal scaledCanvasHeight = buffer.height() * zoom;

    // Calculate centering offsets (same as in paintEvent)
    qreal centerOffsetX = 0;
    qreal centerOffsetY = 0;

    // Center horizontally if canvas is smaller than widget
    if (scaledCanvasWidth < width()) {
        centerOffsetX = (width() - scaledCanvasWidth) / 2.0;
    }

    // Center vertically if canvas is smaller than widget
    if (scaledCanvasHeight < height()) {
        centerOffsetY = (height() - scaledCanvasHeight) / 2.0;
    }

    // Reverse the transformations applied in paintEvent
    QPointF adjustedPoint = widgetPoint;
    adjustedPoint -= QPointF(centerOffsetX, centerOffsetY);
    adjustedPoint /= zoom;
    adjustedPoint += QPointF(panOffsetX, panOffsetY);

    // Convert to PDF coordinates
    // Since Poppler renders the PDF in Qt coordinate system (top-left origin),
    // we don't need to flip the Y coordinate
    QSizeF pdfPageSize = currentPdfPageForText->pageSizeF();
    QSizeF imageSize = backgroundImage.size();

    // Scale from image coordinates to PDF coordinates
    qreal scaleX = pdfPageSize.width() / imageSize.width();
    qreal scaleY = pdfPageSize.height() / imageSize.height();

    QPointF pdfPoint;
    pdfPoint.setX(adjustedPoint.x() * scaleX);
    pdfPoint.setY(adjustedPoint.y() * scaleY); // No Y-axis flipping needed

    return pdfPoint;
}
    QPointF mapPdfToWidgetCoordinates(const QPointF &pdfPoint) {
    if (!currentPdfPageForText || backgroundImage.isNull()) {
        return QPointF();
    }

    // Convert from PDF coordinates to image coordinates
    QSizeF pdfPageSize = currentPdfPageForText->pageSizeF();
    QSizeF imageSize = backgroundImage.size();

    // Scale from PDF coordinates to image coordinates
    qreal scaleX = imageSize.width() / pdfPageSize.width();
    qreal scaleY = imageSize.height() / pdfPageSize.height();

    QPointF imagePoint;
    imagePoint.setX(pdfPoint.x() * scaleX);
    imagePoint.setY(pdfPoint.y() * scaleY); // No Y-axis flipping needed

    // Use the same zoom factor as in paintEvent (internalZoomFactor for smooth animations)
    qreal zoom = internalZoomFactor / 100.0;

    // Apply the same transformations as in paintEvent
    QPointF widgetPoint = imagePoint;
    widgetPoint -= QPointF(panOffsetX, panOffsetY);
    widgetPoint *= zoom;

    // Calculate the scaled canvas size (same as in paintEvent)
    qreal scaledCanvasWidth = buffer.width() * zoom;
    qreal scaledCanvasHeight = buffer.height() * zoom;

    // Calculate centering offsets (same as in paintEvent)
    qreal centerOffsetX = 0;
    qreal centerOffsetY = 0;

    // Center horizontally if canvas is smaller than widget
    if (scaledCanvasWidth < width()) {
        centerOffsetX = (width() - scaledCanvasWidth) / 2.0;
    }

    // Center vertically if canvas is smaller than widget
    if (scaledCanvasHeight < height()) {
        centerOffsetY = (height() - scaledCanvasHeight) / 2.0;
    }

    widgetPoint += QPointF(centerOffsetX, centerOffsetY);

    return widgetPoint;
}
    void updatePdfTextSelection(const QPointF &start, const QPointF &end) {
    // Early return if PDF is not loaded or no text boxes available
    if (!isPdfLoaded || currentPdfTextBoxes.isEmpty()) {
        return;
    }

    // Clear previous selection efficiently
    selectedTextBoxes.clear();

    // Create normalized selection rectangle in widget coordinates
    QRectF widgetSelectionRect(start, end);
    widgetSelectionRect = widgetSelectionRect.normalized();

    // Convert to PDF coordinate space
    QPointF pdfTopLeft = mapWidgetToPdfCoordinates(widgetSelectionRect.topLeft());
    QPointF pdfBottomRight = mapWidgetToPdfCoordinates(widgetSelectionRect.bottomRight());
    QRectF pdfSelectionRect(pdfTopLeft, pdfBottomRight);
    pdfSelectionRect = pdfSelectionRect.normalized();

    // Reserve space for efficiency if we expect many selections
    selectedTextBoxes.reserve(qMin(currentPdfTextBoxes.size(), 50));

    // Find intersecting text boxes with optimized loop
    for (const Poppler::TextBox* textBox : currentPdfTextBoxes) {
        if (textBox && textBox->boundingBox().intersects(pdfSelectionRect)) {
            selectedTextBoxes.append(const_cast<Poppler::TextBox*>(textBox));
        }
    }

    // Only emit signal and update if we have selected text
    if (!selectedTextBoxes.isEmpty()) {
        QString selectedText = getSelectedPdfText();
        if (!selectedText.isEmpty()) {
            emit pdfTextSelected(selectedText);
        }
    }

    // Trigger repaint to show selection
    update();
}
    void handlePdfLinkClick(const QPointF &clickPoint) {
    if (!isPdfLoaded || !currentPdfPageForText) {
        return;
    }

    // Convert widget coordinates to PDF coordinates
    QPointF pdfPoint = mapWidgetToPdfCoordinates(position);

    // Get PDF page size for reference
    QSizeF pdfPageSize = currentPdfPageForText->pageSizeF();

    // Convert to normalized coordinates (0.0 to 1.0) to match Poppler's link coordinate system
    QPointF normalizedPoint(pdfPoint.x() / pdfPageSize.width(), pdfPoint.y() / pdfPageSize.height());

    // Get links for the current page
    auto links = currentPdfPageForText->links();

    for (const auto& link : links) {
        QRectF linkArea = link->linkArea();

        // Normalize the rectangle to handle negative width/height
        QRectF normalizedLinkArea = linkArea.normalized();

        // Check if the normalized rectangle contains the normalized point
        if (normalizedLinkArea.contains(normalizedPoint)) {
            // Handle different types of links
            if (link->linkType() == Poppler::Link::Goto) {
                Poppler::LinkGoto* gotoLink = static_cast<Poppler::LinkGoto*>(link.get());
                if (gotoLink && gotoLink->destination().pageNumber() >= 0) {
                    int targetPage = gotoLink->destination().pageNumber() - 1; // Convert to 0-based
                    emit pdfLinkClicked(targetPage);
                    return;
                }
            }
            // Add other link types as needed (URI, etc.)
        }
    }
}
    void showPdfTextSelectionMenu(const QPoint &position) {
    QString selectedText = getSelectedPdfText();
    if (selectedText.isEmpty()) {
        return; // No text selected, don't show menu
    }

    // Create context menu
    QMenu *contextMenu = new QMenu(this);
    contextMenu->setAttribute(Qt::WA_DeleteOnClose);

    // Add Copy action
    QAction *copyAction = contextMenu->addAction(tr("Copy"));
    copyAction->setIcon(QIcon(":/resources/icons/copy.png")); // You may need to add this icon
    connect(copyAction, &QAction::triggered, this, [selectedText]() {
        QClipboard *clipboard = QGuiApplication::clipboard();
        clipboard->setText(selectedText);
    });

    // Add separator
    contextMenu->addSeparator();

    // Add Cancel action
    QAction *cancelAction = contextMenu->addAction(tr("Cancel"));
    cancelAction->setIcon(QIcon(":/resources/icons/cross.png"));
    connect(cancelAction, &QAction::triggered, this, [this]() {
        clearPdfTextSelection();
    });

    // Show the menu at the specified position
    contextMenu->popup(position);
}
    QList<Poppler::TextBox*> getTextBoxesInSelection(const QPointF &start, const QPointF &end) {
    QList<Poppler::TextBox*> selectedBoxes;

    if (!currentPdfPageForText) {
        // qDebug() << "PDF text selection: No current page for text";
        return selectedBoxes;
    }

    // Convert widget coordinates to PDF coordinates
    QPointF pdfStart = mapWidgetToPdfCoordinates(start);
    QPointF pdfEnd = mapWidgetToPdfCoordinates(end);

    // qDebug() << "PDF text selection: Widget coords" << start << "to" << end;
    // qDebug() << "PDF text selection: PDF coords" << pdfStart << "to" << pdfEnd;

    // Create selection rectangle in PDF coordinates
    QRectF selectionRect(pdfStart, pdfEnd);
    selectionRect = selectionRect.normalized();

    // qDebug() << "PDF text selection: Selection rect in PDF coords:" << selectionRect;

    // Find text boxes that intersect with the selection
    int intersectionCount = 0;
    for (Poppler::TextBox* textBox : currentPdfTextBoxes) {
        if (textBox) {
            QRectF textBoxRect = textBox->boundingBox();
            bool intersects = textBoxRect.intersects(selectionRect);

            if (intersects) {
                selectedBoxes.append(textBox);
                intersectionCount++;
                // qDebug() << "PDF text selection: Text box intersects:" << textBox->text()
                //          << "at" << textBoxRect;
            }
        }
    }

    // qDebug() << "PDF text selection: Found" << intersectionCount << "intersecting text boxes";

    return selectedBoxes;
}
    void renderPdfPageToCache(int pageNumber) {
    if (!pdfDocument || !isValidPageNumber(pageNumber)) {
        return;
    }

    // Check if already cached
    if (pdfCache.contains(pageNumber)) {
        return;
    }

    // Ensure the cache holds only 10 pages max
    if (pdfCache.count() >= 10) {
        auto oldestKey = pdfCache.keys().first();
        pdfCache.remove(oldestKey);
    }

    // Render the page and store it in the cache
    std::unique_ptr<Poppler::Page> page(pdfDocument->page(pageNumber));
    if (page) {
        // Render with document-level anti-aliasing settings
        QImage pdfImage = page->renderToImage(pdfRenderDPI, pdfRenderDPI);
        if (!pdfImage.isNull()) {
            QPixmap cachedPixmap = QPixmap::fromImage(pdfImage);
            pdfCache.insert(pageNumber, new QPixmap(cachedPixmap));
        }
    }
}
    void checkAndCacheAdjacentPages(int targetPage) {
    if (!pdfDocument || !isValidPageNumber(targetPage)) {
        return;
    }

    // Calculate adjacent pages
    int prevPage = targetPage - 1;
    int nextPage = targetPage + 1;

    // Check what needs to be cached
    bool needPrevPage = isValidPageNumber(prevPage) && !pdfCache.contains(prevPage);
    bool needCurrentPage = !pdfCache.contains(targetPage);
    bool needNextPage = isValidPageNumber(nextPage) && !pdfCache.contains(nextPage);

    // If all pages are cached, nothing to do
    if (!needPrevPage && !needCurrentPage && !needNextPage) {
        return;
    }

    // Stop any existing timer
    if (pdfCacheTimer && pdfCacheTimer->isActive()) {
        pdfCacheTimer->stop();
    }

    // Create timer if it doesn't exist
    if (!pdfCacheTimer) {
        pdfCacheTimer = new QTimer(this);
        pdfCacheTimer->setSingleShot(true);
        connect(pdfCacheTimer, &QTimer::timeout, this, &InkCanvas::cacheAdjacentPages);
    }

    // Store the target page for validation when timer fires
    pendingCacheTargetPage = targetPage;

    // Start 1-second delay timer
    pdfCacheTimer->start(1000);
}
    bool isValidPageNumber(int pageNumber) const {
    return (pageNumber >= 0 && pageNumber < totalPdfPages);
}
    void loadNotePageToCache(int pageNumber) {
    // Check if already cached
    if (noteCache.contains(pageNumber)) {
        return;
    }

    QString filePath = getNotePageFilePath(pageNumber);
    if (filePath.isEmpty()) {
        return;
    }

    // Ensure the cache doesn't exceed its limit
    if (noteCache.count() >= 15) {
        // QCache will automatically remove least recently used items
        // but we can be explicit about it
        auto keys = noteCache.keys();
        if (!keys.isEmpty()) {
            noteCache.remove(keys.first());
        }
    }

    // Load note page from disk if it exists
    if (QFile::exists(filePath)) {
        QPixmap notePixmap;
        if (notePixmap.load(filePath)) {
            noteCache.insert(pageNumber, new QPixmap(notePixmap));
        }
    }
    // If file doesn't exist, we don't cache anything - loadPage will handle initialization
}
    void checkAndCacheAdjacentNotePages(int targetPage) {
    if (saveFolder.isEmpty()) {
        return;
    }

    // Calculate adjacent pages
    int prevPage = targetPage - 1;
    int nextPage = targetPage + 1;

    // Check what needs to be cached (we don't have a max page limit for notes)
    bool needPrevPage = (prevPage >= 0) && !noteCache.contains(prevPage);
    bool needCurrentPage = !noteCache.contains(targetPage);
    bool needNextPage = !noteCache.contains(nextPage); // No upper limit check for notes

    // If all nearby pages are cached, nothing to do
    if (!needPrevPage && !needCurrentPage && !needNextPage) {
        return;
    }

    // Stop any existing timer
    if (noteCacheTimer && noteCacheTimer->isActive()) {
        noteCacheTimer->stop();
    }

    // Create timer if it doesn't exist
    if (!noteCacheTimer) {
        noteCacheTimer = new QTimer(this);
        noteCacheTimer->setSingleShot(true);
        connect(noteCacheTimer, &QTimer::timeout, this, &InkCanvas::cacheAdjacentNotePages);
    }

    // Store the target page for validation when timer fires
    pendingNoteCacheTargetPage = targetPage;

    // Start 1-second delay timer
    noteCacheTimer->start(1000);
}
    QString getNotePageFilePath(int pageNumber) const {
    if (saveFolder.isEmpty() || notebookId.isEmpty()) {
        return QString();
    }
    return saveFolder + QString("/%1_%2.png").arg(notebookId).arg(pageNumber, 5, 10, QChar('0'));
}
    void invalidateCurrentPageCache() {
    if (currentCachedNotePage >= 0) {
        noteCache.remove(currentCachedNotePage);
    }
}
  private:
    void processPendingTextSelection() {
    if (!hasPendingSelection) {
        return;
    }

    // Process the pending selection update
    updatePdfTextSelection(pendingSelectionStart, pendingSelectionEnd);
    hasPendingSelection = false;
}
    void cacheAdjacentPages() {
    if (!pdfDocument || currentCachedPage < 0) {
        return;
    }

    // Check if the user has moved to a different page since the timer was started
    // If so, skip caching adjacent pages as they're no longer relevant
    if (pendingCacheTargetPage != currentCachedPage) {
        return; // User switched pages, don't cache adjacent pages for old page
    }

    int targetPage = currentCachedPage;
    int prevPage = targetPage - 1;
    int nextPage = targetPage + 1;

    // Create list of pages to cache asynchronously
    QList<int> pagesToCache;

    // Add previous page if needed
    if (isValidPageNumber(prevPage) && !pdfCache.contains(prevPage)) {
        pagesToCache.append(prevPage);
    }

    // Add next page if needed
    if (isValidPageNumber(nextPage) && !pdfCache.contains(nextPage)) {
        pagesToCache.append(nextPage);
    }

    // Cache pages asynchronously
    for (int pageNum : pagesToCache) {
        QFutureWatcher<void> *watcher = new QFutureWatcher<void>(this);

        // Track the watcher for cleanup
        activePdfWatchers.append(watcher);

        connect(watcher, &QFutureWatcher<void>::finished, this, [this, watcher]() {
            // Remove from active list and delete
            activePdfWatchers.removeOne(watcher);
            watcher->deleteLater();
        });

        // Capture pageNum by value to avoid issues with lambda capture
        QFuture<void> future = QtConcurrent::run([this, pageNum]() {
            renderPdfPageToCache(pageNum);
        });

        watcher->setFuture(future);
    }
}
    void cacheAdjacentNotePages() {
    if (saveFolder.isEmpty() || currentCachedNotePage < 0) {
        return;
    }

    // Check if the user has moved to a different page since the timer was started
    // If so, skip caching adjacent pages as they're no longer relevant
    if (pendingNoteCacheTargetPage != currentCachedNotePage) {
        return; // User switched pages, don't cache adjacent pages for old page
    }

    int targetPage = currentCachedNotePage;
    int prevPage = targetPage - 1;
    int nextPage = targetPage + 1;

    // Create list of note pages to cache asynchronously
    QList<int> notePagesToCache;

    // Add previous page if needed (check for >= 0 since notes can start from page 0)
    if (prevPage >= 0 && !noteCache.contains(prevPage)) {
        notePagesToCache.append(prevPage);
    }

    // Add next page if needed (no upper limit check for notes)
    if (!noteCache.contains(nextPage)) {
        notePagesToCache.append(nextPage);
    }

    // Cache note pages asynchronously
    for (int pageNum : notePagesToCache) {
        QFutureWatcher<void> *watcher = new QFutureWatcher<void>(this);

        // Track the watcher for cleanup
        activeNoteWatchers.append(watcher);

        connect(watcher, &QFutureWatcher<void>::finished, this, [this, watcher]() {
            // Remove from active list and delete
            activeNoteWatchers.removeOne(watcher);
            watcher->deleteLater();
        });

        // Capture pageNum by value to avoid issues with lambda capture
        QFuture<void> future = QtConcurrent::run([this, pageNum]() {
            loadNotePageToCache(pageNum);
        });

        watcher->setFuture(future);
    }
}
};

class MarkdownWindow {
    Q_OBJECT

public:
    explicit MarkdownWindowManager(InkCanvas *canvas, QObject *parent = nullptr);
    ~MarkdownWindowManager() {
    clearAllWindows();
}
    MarkdownWindow* createMarkdownWindow(const QRect &rect);
    void removeMarkdownWindow(MarkdownWindow *window) {
    if (!window) return;

    // Remove from current windows
    currentWindows.removeAll(window);

    // Remove from all page windows
    for (auto it = pageWindows.begin(); it != pageWindows.end(); ++it) {
        it.value().removeAll(window);
    }

    emit windowRemoved(window);

    // Delete the window
    window->deleteLater();
}
    void clearAllWindows() {
    // Stop transparency timer
    transparencyTimer->stop();
    currentlyFocusedWindow = nullptr;
    windowsAreTransparent = false;

    // Clear current windows
    for (MarkdownWindow *window : currentWindows) {
        window->deleteLater();
    }
    currentWindows.clear();

    // Clear page windows
    for (auto it = pageWindows.begin(); it != pageWindows.end(); ++it) {
        for (MarkdownWindow *window : it.value()) {
            window->deleteLater();
        }
    }
    pageWindows.clear();
}
    void saveWindowsForPage(int pageNumber) {
    if (!canvas || currentWindows.isEmpty()) return;

    // Update page windows map
    pageWindows[pageNumber] = currentWindows;

    // Save to file
    saveWindowData(pageNumber, currentWindows);
}
    void loadWindowsForPage(int pageNumber) {
    if (!canvas) return;

    // qDebug() << "MarkdownWindowManager::loadWindowsForPage(" << pageNumber << ")";

    // This method is now only responsible for loading and showing the
    // windows for the specified page. Hiding of old windows is handled before this.

    QList<MarkdownWindow*> newPageWindows;

    // Check if we have windows for this page in memory
    if (pageWindows.contains(pageNumber)) {
        // qDebug() << "Found windows in memory for page" << pageNumber;
        newPageWindows = pageWindows[pageNumber];
    } else {
        // qDebug() << "Loading windows from file for page" << pageNumber;
        // Load from file
        newPageWindows = loadWindowData(pageNumber);

        // Update page windows map
        if (!newPageWindows.isEmpty()) {
            pageWindows[pageNumber] = newPageWindows;
        }
    }

    // qDebug() << "Loaded" << newPageWindows.size() << "windows for page" << pageNumber;

    // Update the current window list and show the windows
    currentWindows = newPageWindows;
    for (MarkdownWindow *window : currentWindows) {
        // Validate that the window is within canvas bounds
        if (!window->isValidForCanvas()) {
            // qDebug() << "Warning: Markdown window at" << window->getCanvasRect()
            //          << "is outside canvas bounds" << canvas->getCanvasRect();
            // Optionally adjust the window position to fit within bounds
            QRect canvasBounds = canvas->getCanvasRect();
            QRect windowRect = window->getCanvasRect();

            // Clamp the window position to canvas bounds
            int newX = qMax(0, qMin(windowRect.x(), canvasBounds.width() - windowRect.width()));
            int newY = qMax(0, qMin(windowRect.y(), canvasBounds.height() - windowRect.height()));

            if (newX != windowRect.x() || newY != windowRect.y()) {
                QRect adjustedRect(newX, newY, windowRect.width(), windowRect.height());
                window->setCanvasRect(adjustedRect);
                // qDebug() << "Adjusted window position to" << adjustedRect;
            }
        }

        // Ensure canvas connections are set up for loaded windows
        window->ensureCanvasConnections();

        window->show();
        window->updateScreenPosition();
        // Make sure window is not transparent when loaded
        window->setTransparent(false);
    }

    // Start transparency timer if there are windows but none are focused
    if (!currentWindows.isEmpty()) {
        // qDebug() << "Starting transparency timer for" << currentWindows.size() << "windows";
        resetTransparencyTimer();
    } else {
        // qDebug() << "No windows to show, not starting timer";
    }
}
    void deleteWindowsForPage(int pageNumber) {
    // Remove windows from memory
    if (pageWindows.contains(pageNumber)) {
        QList<MarkdownWindow*> windows = pageWindows[pageNumber];
        for (MarkdownWindow *window : windows) {
            window->deleteLater();
        }
        pageWindows.remove(pageNumber);
    }

    // Delete file
    QString filePath = getWindowDataFilePath(pageNumber);
    if (QFile::exists(filePath)) {
        QFile::remove(filePath);
    }

    // Clear current windows if they belong to this page
    if (pageWindows.value(pageNumber) == currentWindows) {
        currentWindows.clear();
    }
}
    void setSelectionMode(bool enabled) {
    selectionMode = enabled;

    // Change cursor for canvas
    if (canvas) {
        canvas->setCursor(enabled ? Qt::CrossCursor : Qt::ArrowCursor);
    }
}
    QList<MarkdownWindow*> getCurrentPageWindows() const {
    return currentWindows;
}
    void resetTransparencyTimer() {
    // qDebug() << "MarkdownWindowManager::resetTransparencyTimer() called";
    transparencyTimer->stop();

    // DON'T make all windows opaque here - only stop the timer
    // The focused window should be made opaque by the caller if needed
    windowsAreTransparent = false; // Reset the state

    // Start the timer again
    transparencyTimer->start();
    // qDebug() << "Transparency timer started for 10 seconds";
}
    void setWindowsTransparent(bool transparent) {
    if (windowsAreTransparent == transparent) return;

    // qDebug() << "MarkdownWindowManager::setWindowsTransparent(" << transparent << ")";
    // qDebug() << "Current windows count:" << currentWindows.size();
    // qDebug() << "Currently focused window:" << currentlyFocusedWindow;

    windowsAreTransparent = transparent;

    // Apply transparency logic:
    // - If transparent=true: make all windows except focused one transparent
    // - If transparent=false: make all windows opaque
    for (MarkdownWindow *window : currentWindows) {
        if (transparent) {
            // When making transparent, only make non-focused windows transparent
            if (window != currentlyFocusedWindow) {
                // qDebug() << "Setting unfocused window" << window << "transparent: true";
                window->setTransparent(true);
            } else {
                // qDebug() << "Keeping focused window" << window << "opaque";
                window->setTransparent(false);
            }
        } else {
            // When making opaque, make all windows opaque
            // qDebug() << "Setting window" << window << "transparent: false";
            window->setTransparent(false);
        }
    }
}
    void hideAllWindows() {
    // Stop transparency timer when hiding windows
    transparencyTimer->stop();
    currentlyFocusedWindow = nullptr;
    windowsAreTransparent = false;

    // Hide all current windows
    for (MarkdownWindow *window : currentWindows) {
        window->hide();
        window->setTransparent(false); // Reset transparency state
    }
}
    void windowCreated(MarkdownWindow *window);
    void windowRemoved(MarkdownWindow *window);
  private:
    void onWindowDeleteRequested(MarkdownWindow *window) {
    removeMarkdownWindow(window);

    // Mark canvas as edited since we deleted a markdown window
    if (canvas) {
        canvas->setEdited(true);
    }

    // Save current state
    if (canvas) {
        MainWindow *mainWindow = qobject_cast<MainWindow*>(canvas->parent());
        if (mainWindow) {
            int currentPage = mainWindow->getCurrentPageForCanvas(canvas);
            saveWindowsForPage(currentPage);
        }
    }
}
    void onWindowFocusChanged(MarkdownWindow *window, bool focused) {
    // qDebug() << "MarkdownWindowManager::onWindowFocusChanged(" << window << ", " << focused << ")";

    if (focused) {
        // A window gained focus - make it opaque immediately and reset timer
        // qDebug() << "Window gained focus, setting as currently focused";
        currentlyFocusedWindow = window;

        // Make the focused window opaque immediately
        window->setTransparent(false);

        // Reset timer to start counting down for making other windows transparent
        resetTransparencyTimer();
    } else {
        // A window lost focus
        // qDebug() << "Window lost focus";
        if (currentlyFocusedWindow == window) {
            // qDebug() << "Clearing currently focused window";
            currentlyFocusedWindow = nullptr;
        }

        // Check if any window still has focus
        bool anyWindowFocused = false;
        for (MarkdownWindow *w : currentWindows) {
            if (w->isEditorFocused()) {
                // qDebug() << "Found another focused window:" << w;
                anyWindowFocused = true;
                currentlyFocusedWindow = w;
                // Make the newly focused window opaque
                w->setTransparent(false);
                break;
            }
        }

        if (!anyWindowFocused) {
            // qDebug() << "No window has focus, starting transparency timer";
            // No window has focus, start transparency timer
            resetTransparencyTimer();
        } else {
            // qDebug() << "Another window still has focus, resetting timer";
            // Another window has focus, reset timer
            resetTransparencyTimer();
        }
    }
}
    void onWindowContentChanged(MarkdownWindow *window) {
    // qDebug() << "MarkdownWindowManager::onWindowContentChanged(" << window << ")";

    // Content changed, make this window the focused one and reset transparency timer
    currentlyFocusedWindow = window;
    window->setTransparent(false); // Make the active window opaque

    // Reset transparency timer
    resetTransparencyTimer();
}
    void onTransparencyTimerTimeout() {
    // qDebug() << "MarkdownWindowManager::onTransparencyTimerTimeout() - Timer expired!";
    // Make all windows except the focused one semi-transparent
    setWindowsTransparent(true);
}
  private:
    InkCanvas *canvas;
    bool selectionMode = false;
    QMap<int, QList<MarkdownWindow*>> pageWindows;
    QList<MarkdownWindow*> currentWindows;
    QTimer *transparencyTimer;
    MarkdownWindow *currentlyFocusedWindow = nullptr;
    bool windowsAreTransparent = false;
    QString getWindowDataFilePath(int pageNumber) const {
    QString saveFolder = getSaveFolder();
    QString notebookId = getNotebookId();

    if (saveFolder.isEmpty() || notebookId.isEmpty()) {
        return QString();
    }

    return saveFolder + QString("/.%1_markdown_%2.json").arg(notebookId).arg(pageNumber, 5, 10, QChar('0'));
}
    void saveWindowData(int pageNumber, const QList<MarkdownWindow*> &windows) {
    QString filePath = getWindowDataFilePath(pageNumber);
    if (filePath.isEmpty()) return;

    QJsonArray windowsArray;
    for (MarkdownWindow *window : windows) {
        QVariantMap windowData = window->serialize();
        windowsArray.append(QJsonObject::fromVariantMap(windowData));
    }

    QJsonDocument doc(windowsArray);

    QFile file(filePath);
    if (file.open(QIODevice::WriteOnly)) {
        file.write(doc.toJson());
        file.close();

        #ifdef Q_OS_WIN
        // Set hidden attribute on Windows
        SetFileAttributesW(reinterpret_cast<const wchar_t *>(filePath.utf16()), FILE_ATTRIBUTE_HIDDEN);
        #endif
    }
}
    QList<MarkdownWindow*> loadWindowData(int pageNumber) {
    QList<MarkdownWindow*> windows;

    QString filePath = getWindowDataFilePath(pageNumber);
    if (filePath.isEmpty() || !QFile::exists(filePath)) {
        return windows;
    }

    QFile file(filePath);
    if (!file.open(QIODevice::ReadOnly)) {
        return windows;
    }

    QByteArray data = file.readAll();
    file.close();

    QJsonParseError error;
    QJsonDocument doc = QJsonDocument::fromJson(data, &error);
    if (error.error != QJsonParseError::NoError) {
        qWarning() << "Failed to parse markdown window data:" << error.errorString();
        return windows;
    }

    QJsonArray windowsArray = doc.array();
    for (const QJsonValue &value : windowsArray) {
        QJsonObject windowObj = value.toObject();
        QVariantMap windowData = windowObj.toVariantMap();

        // Create window with default rect (will be updated by deserialize)
        MarkdownWindow *window = new MarkdownWindow(QRect(0, 0, 300, 200), canvas);
        window->deserialize(windowData);

        // Connect signals
        connectWindowSignals(window);

        windows.append(window);
        window->show();
    }

    return windows;
}
    QString getSaveFolder() const {
    return canvas ? canvas->getSaveFolder() : QString();
}
    QString getNotebookId() const {
    if (!canvas) return QString();

    QString saveFolder = canvas->getSaveFolder();
    if (saveFolder.isEmpty()) return QString();

    QString idFile = saveFolder + "/.notebook_id.txt";
    if (QFile::exists(idFile)) {
        QFile file(idFile);
        if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            QTextStream in(&file);
            QString notebookId = in.readLine().trimmed();
            file.close();
            return notebookId;
        }
    }

    return "notebook"; // Default fallback
}
    QRect convertScreenToCanvasRect(const QRect &screenRect) const {
    if (!canvas) {
        // qDebug() << "MarkdownWindowManager::convertScreenToCanvasRect() - No canvas, returning screen rect:" << screenRect;
        return screenRect;
    }

    // Use the new coordinate conversion methods from InkCanvas
    QRect canvasRect = canvas->mapWidgetToCanvas(screenRect);
    // qDebug() << "MarkdownWindowManager::convertScreenToCanvasRect() - Screen rect:" << screenRect << "-> Canvas rect:" << canvasRect;
    // qDebug() << "  Canvas size:" << canvas->getCanvasSize() << "Zoom:" << canvas->getZoomFactor() << "Pan:" << canvas->getPanOffset();
    return canvasRect;
}
    void connectWindowSignals(MarkdownWindow *window) {
    // qDebug() << "MarkdownWindowManager::connectWindowSignals() - Connecting signals for window" << window;

    // Connect existing signals
    connect(window, &MarkdownWindow::deleteRequested, this, &MarkdownWindowManager::onWindowDeleteRequested);
    connect(window, &MarkdownWindow::contentChanged, this, [this, window]() {
        onWindowContentChanged(window);

        // Mark canvas as edited since markdown content changed
        if (canvas) {
            canvas->setEdited(true);

            // Get current page and save windows
            MainWindow *mainWindow = qobject_cast<MainWindow*>(canvas->parent());
            if (mainWindow) {
                int currentPage = mainWindow->getCurrentPageForCanvas(canvas);
                saveWindowsForPage(currentPage);
            }
        }
    });
    connect(window, &MarkdownWindow::windowMoved, this, [this, window](MarkdownWindow*) {
        // qDebug() << "Window moved:" << window;

        // Window was moved, make it the focused one and reset transparency timer
        currentlyFocusedWindow = window;
        window->setTransparent(false); // Make the active window opaque
        resetTransparencyTimer();

        // Mark canvas as edited since window was moved
        if (canvas) {
            canvas->setEdited(true);
        }
    });
    connect(window, &MarkdownWindow::windowResized, this, [this, window](MarkdownWindow*) {
        // qDebug() << "Window resized:" << window;

        // Window was resized, make it the focused one and reset transparency timer
        currentlyFocusedWindow = window;
        window->setTransparent(false); // Make the active window opaque
        resetTransparencyTimer();

        // Mark canvas as edited since window was resized
        if (canvas) {
            canvas->setEdited(true);
        }
    });

    // Connect new transparency-related signals
    connect(window, &MarkdownWindow::focusChanged, this, &MarkdownWindowManager::onWindowFocusChanged);
    connect(window, &MarkdownWindow::editorFocusChanged, this, &MarkdownWindowManager::onWindowFocusChanged);
    connect(window, &MarkdownWindow::windowInteracted, this, [this, window](MarkdownWindow*) {
        // qDebug() << "Window interacted:" << window;

        // Window was clicked/interacted with, make it the focused one and reset transparency timer
        currentlyFocusedWindow = window;
        window->setTransparent(false); // Make the active window opaque
        resetTransparencyTimer();
    });

    // qDebug() << "All signals connected for window" << window;
}
  public:
    void updateAllWindowPositions() {
    // Update positions of all current windows
    for (MarkdownWindow *window : currentWindows) {
        if (window) {
            window->updateScreenPosition();
        }
    }
}
};

class PdfOpenDialog {
    Q_OBJECT

public:
    enum Result {
        Cancel,
        CreateNewFolder,
        UseExistingFolder
    };
    explicit PdfOpenDialog(const QString &pdfPath, QWidget *parent = nullptr) {
    setWindowTitle(tr("Open PDF with SpeedyNote"));
    setWindowIcon(QIcon(":/resources/icons/mainicon.png"));
    setModal(true);

    // Remove setFixedSize and use proper size management instead
    // Calculate size based on DPI
    QScreen *screen = QGuiApplication::primaryScreen();
    qreal dpr = screen ? screen->devicePixelRatio() : 1.0;

    // Set minimum and maximum sizes instead of fixed size
    int baseWidth = 500;
    int baseHeight = 200;

    // Scale sizes appropriately for DPI
    int scaledWidth = static_cast<int>(baseWidth * qMax(1.0, dpr * 0.8));
    int scaledHeight = static_cast<int>(baseHeight * qMax(1.0, dpr * 0.8));

    setMinimumSize(scaledWidth, scaledHeight);
    setMaximumSize(scaledWidth + 100, scaledHeight + 50); // Allow some flexibility

    // Set size policy to prevent unwanted resizing
    setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);

    setupUI();

    // Ensure the dialog is properly sized after UI setup
    adjustSize();

    // Center the dialog on parent or screen
    if (parent) {
        move(parent->geometry().center() - rect().center());
    } else {
        move(screen->geometry().center() - rect().center());
    }
}
    static bool hasValidNotebookFolder(const QString &pdfPath, QString &folderPath) {
    QFileInfo fileInfo(pdfPath);
    QString suggestedFolderName = fileInfo.baseName(); // Filename without extension
    QString pdfDir = fileInfo.absolutePath(); // Directory containing the PDF
    QString potentialFolderPath = pdfDir + "/" + suggestedFolderName;

    if (isValidNotebookFolder(potentialFolderPath, pdfPath)) {
        folderPath = potentialFolderPath;
        return true;
    }

    return false;
}
  protected:
    void resizeEvent(QResizeEvent *event) override {
    // Handle resize events smoothly to prevent jitter during window dragging
    if (event->size() != event->oldSize()) {
        // Only process if size actually changed
        QDialog::resizeEvent(event);

        // Ensure the dialog stays within reasonable bounds
        QSize newSize = event->size();
        QSize minSize = minimumSize();
        QSize maxSize = maximumSize();

        // Clamp the size to prevent unwanted resizing
        newSize = newSize.expandedTo(minSize).boundedTo(maxSize);

        if (newSize != event->size()) {
            // If size needs to be adjusted, do it without triggering another resize event
            resize(newSize);
        }
    } else {
        // If size hasn't changed, just call parent implementation
        QDialog::resizeEvent(event);
    }
}
  private:
    void onCreateNewFolder() {
    QFileInfo fileInfo(pdfPath);
    QString suggestedFolderName = fileInfo.baseName();

    // Get the directory where the PDF is located
    QString pdfDir = fileInfo.absolutePath();
    QString newFolderPath = pdfDir + "/" + suggestedFolderName;

    // Check if folder already exists
    if (QDir(newFolderPath).exists()) {
        QMessageBox::StandardButton reply = QMessageBox::question(
            this,
            tr("Folder Exists"),
            tr("A folder named \"%1\" already exists in the same directory as the PDF.\n\nDo you want to use this existing folder?").arg(suggestedFolderName),
            QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel
        );

        if (reply == QMessageBox::Yes) {
            selectedFolder = newFolderPath;
            result = UseExistingFolder;
            accept();
            return;
        } else if (reply == QMessageBox::Cancel) {
            return; // Stay in dialog
        }
        // If No, continue to create with different name

        // Try to create with incremented name
        int counter = 1;
        do {
            newFolderPath = pdfDir + "/" + suggestedFolderName + QString("_%1").arg(counter);
            counter++;
        } while (QDir(newFolderPath).exists() && counter < 100);

        if (counter >= 100) {
            QMessageBox::warning(this, tr("Error"), tr("Could not create a unique folder name."));
            return;
        }
    }

    // Create the new folder
    QDir dir;
    if (dir.mkpath(newFolderPath)) {
        selectedFolder = newFolderPath;
        result = CreateNewFolder;
        accept();
    } else {
        QMessageBox::warning(this, tr("Error"), tr("Failed to create folder: %1").arg(newFolderPath));
    }
}
    void onUseExistingFolder() {
    QString folder = QFileDialog::getExistingDirectory(
        this,
        tr("Select Existing Notebook Folder"),
        QFileInfo(pdfPath).absolutePath()
    );

    if (!folder.isEmpty()) {
        selectedFolder = folder;
        result = UseExistingFolder;
        accept();
    }
}
    void onCancel() {
    result = Cancel;
    reject();
}
  private:
    void setupUI() {
    QVBoxLayout *mainLayout = new QVBoxLayout(this);
    mainLayout->setSpacing(15);
    mainLayout->setContentsMargins(20, 20, 20, 20);

    // Set layout size constraint to prevent unwanted resizing
    mainLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);

    // Icon and title
    QHBoxLayout *headerLayout = new QHBoxLayout();
    headerLayout->setSpacing(10);

    QLabel *iconLabel = new QLabel();
    QPixmap icon = QIcon(":/resources/icons/mainicon.png").pixmap(48, 48);
    iconLabel->setPixmap(icon);
    iconLabel->setFixedSize(48, 48);
    iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);

    QLabel *titleLabel = new QLabel(tr("Open PDF with SpeedyNote"));
    titleLabel->setStyleSheet("font-size: 16px; font-weight: bold;");
    titleLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);

    headerLayout->addWidget(iconLabel);
    headerLayout->addWidget(titleLabel);
    headerLayout->addStretch();

    mainLayout->addLayout(headerLayout);

    // PDF file info
    QFileInfo fileInfo(pdfPath);
    QString fileName = fileInfo.fileName();
    QString folderName = fileInfo.baseName(); // Filename without extension

    QLabel *messageLabel = new QLabel(tr("PDF File: %1\n\nHow would you like to open this PDF?").arg(fileName));
    messageLabel->setWordWrap(true);
    messageLabel->setStyleSheet("font-size: 12px; color: #666;");
    messageLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);

    mainLayout->addWidget(messageLabel);

    // Buttons
    QVBoxLayout *buttonLayout = new QVBoxLayout();
    buttonLayout->setSpacing(10);

    // Create new folder button
    QPushButton *createFolderBtn = new QPushButton(tr("Create New Notebook Folder (\"%1\")").arg(folderName));
    createFolderBtn->setIcon(QApplication::style()->standardIcon(QStyle::SP_DirIcon));
    createFolderBtn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    createFolderBtn->setMinimumHeight(40);
    createFolderBtn->setStyleSheet(R"(
        QPushButton {
            text-align: left;
            padding: 10px;
            border: 1px solid palette(mid);
            border-radius: 5px;
            background: palette(button);
        }
        QPushButton:hover {
            background: palette(light);
            border-color: palette(dark);
        }
        QPushButton:pressed {
            background: palette(midlight);
        }
    )");
    connect(createFolderBtn, &QPushButton::clicked, this, &PdfOpenDialog::onCreateNewFolder);

    // Use existing folder button
    QPushButton *existingFolderBtn = new QPushButton(tr("Use Existing Notebook Folder..."));
    existingFolderBtn->setIcon(QApplication::style()->standardIcon(QStyle::SP_DirOpenIcon));
    existingFolderBtn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    existingFolderBtn->setMinimumHeight(40);
    existingFolderBtn->setStyleSheet(R"(
        QPushButton {
            text-align: left;
            padding: 10px;
            border: 1px solid palette(mid);
            border-radius: 5px;
            background: palette(button);
        }
        QPushButton:hover {
            background: palette(light);
            border-color: palette(dark);
        }
        QPushButton:pressed {
            background: palette(midlight);
        }
    )");
    connect(existingFolderBtn, &QPushButton::clicked, this, &PdfOpenDialog::onUseExistingFolder);

    buttonLayout->addWidget(createFolderBtn);
    buttonLayout->addWidget(existingFolderBtn);

    mainLayout->addLayout(buttonLayout);

    // Cancel button
    QHBoxLayout *cancelLayout = new QHBoxLayout();
    cancelLayout->addStretch();

    QPushButton *cancelBtn = new QPushButton(tr("Cancel"));
    cancelBtn->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogCancelButton));
    cancelBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
    cancelBtn->setMinimumSize(80, 30);
    cancelBtn->setStyleSheet(R"(
        QPushButton {
            padding: 8px 20px;
            border: 1px solid palette(mid);
            border-radius: 3px;
            background: palette(button);
        }
        QPushButton:hover {
            background: palette(light);
        }
        QPushButton:pressed {
            background: palette(midlight);
        }
    )");
    connect(cancelBtn, &QPushButton::clicked, this, &PdfOpenDialog::onCancel);

    cancelLayout->addWidget(cancelBtn);
    mainLayout->addLayout(cancelLayout);
}
    static bool isValidNotebookFolder(const QString &folderPath, const QString &pdfPath) {
    QDir folder(folderPath);
    if (!folder.exists()) {
        return false;
    }

    // Priority 1: Check for .pdf_path.txt file with matching PDF path
    QString pdfPathFile = folderPath + "/.pdf_path.txt";
    if (QFile::exists(pdfPathFile)) {
        QFile file(pdfPathFile);
        if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            QTextStream in(&file);
            QString storedPdfPath = in.readLine().trimmed();
            file.close();

            if (!storedPdfPath.isEmpty()) {
                // Compare absolute paths to handle relative path differences
                QFileInfo currentPdf(pdfPath);
                QFileInfo storedPdf(storedPdfPath);

                // Check if the stored PDF file still exists and matches
                if (storedPdf.exists() &&
                    currentPdf.absoluteFilePath() == storedPdf.absoluteFilePath()) {
                    return true;
                }
            }
        }
    }

    // Priority 2: Check for .notebook_id.txt file (SpeedyNote notebook folder)
    QString notebookIdFile = folderPath + "/.notebook_id.txt";
    if (QFile::exists(notebookIdFile)) {
        // Additional check: ensure this folder has some notebook content
        QStringList contentFiles = folder.entryList(
            QStringList() << "*.png" << ".pdf_path.txt" << ".bookmarks.txt" << ".background_config.txt",
            QDir::Files
        );

        // If it has notebook-related files, it's likely a valid SpeedyNote folder
        // but not necessarily for this specific PDF
        if (!contentFiles.isEmpty()) {
            // This is a SpeedyNote notebook, but for a different PDF
            // Return false so the user gets the dialog to choose what to do
            return false;
        }
    }

    return false;
}
    Result result;
    QString selectedFolder;
    QString pdfPath;
};

#ifndef LINENUMBERAREA_H
#define LINENUMBERAREA_H

#include <QDebug>
#include <QPainter>
#include <QScrollBar>
#include <QWidget>

#include "qmarkdowntextedit.h"

class LineNumArea final : public QWidget {
    Q_OBJECT

   public:
    explicit LineNumArea(QMarkdownTextEdit *parent)
        : QWidget(parent), textEdit(parent) {
        Q_ASSERT(parent);

        _currentLineColor = QColor(QStringLiteral("#eef067"));
        _otherLinesColor = QColor(QStringLiteral("#a6a6a6"));
        setHidden(true);

        // We always use fixed font to avoid "width" issues
        setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
    }

    void setCurrentLineColor(QColor color) { _currentLineColor = color; }

    void setOtherLineColor(QColor color) {
        _otherLinesColor = std::move(color);
    }

    int lineNumAreaWidth() const {
        if (!enabled) {
            return 0;
        }

        int digits = 2;
        int max = std::max(1, textEdit->blockCount());
        while (max >= 10) {
            max /= 10;
            ++digits;
        }

#if QT_VERSION >= 0x050B00
        int space =
            13 + textEdit->fontMetrics().horizontalAdvance(u'9') * digits;
#else
        int space =
            13 + textEdit->fontMetrics().width(QLatin1Char('9')) * digits;
#endif

        return space;
    }

    bool isLineNumAreaEnabled() const { return enabled; }

    void setLineNumAreaEnabled(bool e) {
        enabled = e;
        setHidden(!e);
    }

    QSize sizeHint() const override { return {lineNumAreaWidth(), 0}; }

   protected:
    void paintEvent(QPaintEvent *event) override {
        QPainter painter(this);

        painter.fillRect(event->rect(),
                         palette().color(QPalette::Active, QPalette::Window));

        auto block = textEdit->firstVisibleBlock();
        int blockNumber = block.blockNumber();
        qreal top = textEdit->blockBoundingGeometry(block)
                        .translated(textEdit->contentOffset())
                        .top();
        // Maybe the top is not 0?
        top += textEdit->viewportMargins().top();
        qreal bottom = top;

        const QPen currentLine = _currentLineColor;
        const QPen otherLines = _otherLinesColor;
        painter.setFont(font());

        while (block.isValid() && top <= event->rect().bottom()) {
            top = bottom;
            bottom = top + textEdit->blockBoundingRect(block).height();
            if (block.isVisible() && bottom >= event->rect().top()) {
                QString number = QString::number(blockNumber + 1);

                auto isCurrentLine =
                    textEdit->textCursor().blockNumber() == blockNumber;
                painter.setPen(isCurrentLine ? currentLine : otherLines);

                painter.drawText(-5, top, sizeHint().width(),
                                 textEdit->fontMetrics().height(),
                                 Qt::AlignRight, number);
            }

            block = block.next();
            ++blockNumber;
        }
    }

   private:
    bool enabled = false;
    QMarkdownTextEdit *textEdit;
    QColor _currentLineColor;
    QColor _otherLinesColor;
};

#endif    // LINENUMBERAREA_H

class ControlPanelDialog {
    Q_OBJECT

public:
    explicit ControlPanelDialog(MainWindow *mainWindow, InkCanvas *targetCanvas, QWidget *parent = nullptr);
  private:
    void applyChanges() {
    if (!canvas) return;

    BackgroundStyle style = static_cast<BackgroundStyle>(
        styleCombo->currentData().toInt()
    );

    canvas->setBackgroundStyle(style);
    canvas->setBackgroundColor(selectedColor);
    canvas->setBackgroundDensity(densitySpin->value());
    canvas->update();
    canvas->saveBackgroundMetadata();

    // ✅ Save these settings as defaults for new tabs
    if (mainWindowRef) {
        mainWindowRef->saveDefaultBackgroundSettings(style, selectedColor, densitySpin->value());
    }

    // ✅ Apply button mappings back to MainWindow with internal keys
    if (mainWindowRef) {
        for (const QString &buttonKey : holdMappingCombos.keys()) {
            QString displayString = holdMappingCombos[buttonKey]->currentText();
            QString internalKey = ButtonMappingHelper::displayToInternalKey(displayString, true);  // true = isDialMode
            mainWindowRef->setHoldMapping(buttonKey, internalKey);
        }
        for (const QString &buttonKey : pressMappingCombos.keys()) {
            QString displayString = pressMappingCombos[buttonKey]->currentText();
            QString internalKey = ButtonMappingHelper::displayToInternalKey(displayString, false);  // false = isAction
            mainWindowRef->setPressMapping(buttonKey, internalKey);
        }

        // ✅ Save to persistent settings
        mainWindowRef->saveButtonMappings();

        // ✅ Apply theme settings
        mainWindowRef->setUseCustomAccentColor(useCustomAccentCheckbox->isChecked());
        if (selectedAccentColor.isValid()) {
            mainWindowRef->setCustomAccentColor(selectedAccentColor);
        }

        // ✅ Apply color palette setting
        mainWindowRef->setUseBrighterPalette(useBrighterPaletteCheckbox->isChecked());
    }
}
    void chooseColor() {
    QColor chosen = QColorDialog::getColor(selectedColor, this, tr("Select Background Color"));
    if (chosen.isValid()) {
        selectedColor = chosen;
        colorButton->setStyleSheet(QString("background-color: %1").arg(selectedColor.name()));
    }
}
    void addKeyboardMapping() {
    // Step 1: Capture key sequence
    KeyCaptureDialog captureDialog(this);
    if (captureDialog.exec() != QDialog::Accepted) {
        return;
    }

    QString keySequence = captureDialog.getCapturedKeySequence();
    if (keySequence.isEmpty()) {
        return;
    }

    // Check if key sequence already exists
    if (mainWindowRef && mainWindowRef->getKeyboardMappings().contains(keySequence)) {
        QMessageBox::warning(this, tr("Key Already Mapped"),
            tr("The key sequence '%1' is already mapped. Please choose a different key combination.").arg(keySequence));
        return;
    }

    // Step 2: Choose action
    QStringList actions = ButtonMappingHelper::getTranslatedActions();
    bool ok;
    QString selectedAction = QInputDialog::getItem(this, tr("Select Action"),
        tr("Choose the action to perform when '%1' is pressed:").arg(keySequence),
        actions, 0, false, &ok);

    if (!ok || selectedAction.isEmpty()) {
        return;
    }

    // Convert display name to internal key
    QString internalKey = ButtonMappingHelper::displayToInternalKey(selectedAction, false);

    // Add the mapping
    if (mainWindowRef) {
        mainWindowRef->addKeyboardMapping(keySequence, internalKey);

        // Update table
        int row = keyboardTable->rowCount();
        keyboardTable->insertRow(row);
        keyboardTable->setItem(row, 0, new QTableWidgetItem(keySequence));
        keyboardTable->setItem(row, 1, new QTableWidgetItem(selectedAction));
    }
}
    void removeKeyboardMapping() {
    int currentRow = keyboardTable->currentRow();
    if (currentRow < 0) {
        QMessageBox::information(this, tr("No Selection"), tr("Please select a mapping to remove."));
        return;
    }

    QTableWidgetItem *keyItem = keyboardTable->item(currentRow, 0);
    if (!keyItem) return;

    QString keySequence = keyItem->text();

    // Confirm removal
    int ret = QMessageBox::question(this, tr("Remove Mapping"),
        tr("Are you sure you want to remove the keyboard shortcut '%1'?").arg(keySequence),
        QMessageBox::Yes | QMessageBox::No, QMessageBox::No);

    if (ret == QMessageBox::Yes) {
        // Remove from MainWindow
        if (mainWindowRef) {
            mainWindowRef->removeKeyboardMapping(keySequence);
        }

        // Remove from table
        keyboardTable->removeRow(currentRow);
    }
}
    void openControllerMapping() {
    if (!mainWindowRef) {
        QMessageBox::warning(this, tr("Error"), tr("MainWindow reference not available."));
        return;
    }

    SDLControllerManager *controllerManager = mainWindowRef->getControllerManager();
    if (!controllerManager) {
        QMessageBox::warning(this, tr("Controller Not Available"),
            tr("Controller manager is not available. Please ensure a controller is connected and restart the application."));
        return;
    }

    if (!controllerManager->getJoystick()) {
        QMessageBox::warning(this, tr("No Controller Detected"),
            tr("No controller is currently connected. Please connect your controller and restart the application."));
        return;
    }

    ControllerMappingDialog dialog(controllerManager, this);
    dialog.exec();
}
    void reconnectController() {
    if (!mainWindowRef) {
        QMessageBox::warning(this, tr("Error"), tr("MainWindow reference not available."));
        return;
    }

    SDLControllerManager *controllerManager = mainWindowRef->getControllerManager();
    if (!controllerManager) {
        QMessageBox::warning(this, tr("Controller Not Available"),
            tr("Controller manager is not available."));
        return;
    }

    // Show reconnecting message
    controllerStatusLabel->setText(tr("🔄 Reconnecting..."));
    controllerStatusLabel->setStyleSheet("color: orange;");

    // Force the UI to update immediately
    QApplication::processEvents();

    // Attempt to reconnect using thread-safe method
    QMetaObject::invokeMethod(controllerManager, "reconnect", Qt::BlockingQueuedConnection);

    // Update status after reconnection attempt
    updateControllerStatus();

    // Show result message
    if (controllerManager->getJoystick()) {
        // Reconnect the controller signals in MainWindow
        mainWindowRef->reconnectControllerSignals();

        QMessageBox::information(this, tr("Reconnection Successful"),
            tr("Controller has been successfully reconnected!"));
    } else {
        QMessageBox::warning(this, tr("Reconnection Failed"),
            tr("Failed to reconnect controller. Please ensure your controller is powered on and in pairing mode, then try again."));
    }
}
  private:
    InkCanvas *canvas;
    QTabWidget *tabWidget;
    QWidget *backgroundTab;
    QComboBox *styleCombo;
    QPushButton *colorButton;
    QSpinBox *densitySpin;
    QPushButton *applyButton;
    QPushButton *okButton;
    QPushButton *cancelButton;
    QColor selectedColor;
    void createBackgroundTab() {
    backgroundTab = new QWidget(this);

    QLabel *styleLabel = new QLabel(tr("Background Style:"));
    styleCombo = new QComboBox();
    styleCombo->addItem(tr("None"), static_cast<int>(BackgroundStyle::None));
    styleCombo->addItem(tr("Grid"), static_cast<int>(BackgroundStyle::Grid));
    styleCombo->addItem(tr("Lines"), static_cast<int>(BackgroundStyle::Lines));

    QLabel *colorLabel = new QLabel(tr("Background Color:"));
    colorButton = new QPushButton();
    connect(colorButton, &QPushButton::clicked, this, &ControlPanelDialog::chooseColor);

    QLabel *densityLabel = new QLabel(tr("Density:"));
    densitySpin = new QSpinBox();
    densitySpin->setRange(10, 200);
    densitySpin->setSuffix(" px");
    densitySpin->setSingleStep(5);

    QGridLayout *layout = new QGridLayout(backgroundTab);
    layout->addWidget(styleLabel, 0, 0);
    layout->addWidget(styleCombo, 0, 1);
    layout->addWidget(colorLabel, 1, 0);
    layout->addWidget(colorButton, 1, 1);
    layout->addWidget(densityLabel, 2, 0);
    layout->addWidget(densitySpin, 2, 1);
    // layout->setColumnStretch(1, 1); // Stretch the second column
    layout->setRowStretch(3, 1); // Stretch the last row
}
    void loadFromCanvas() {
    styleCombo->setCurrentIndex(static_cast<int>(canvas->getBackgroundStyle()));
    densitySpin->setValue(canvas->getBackgroundDensity());
    selectedColor = canvas->getBackgroundColor();

    colorButton->setStyleSheet(QString("background-color: %1").arg(selectedColor.name()));

    if (mainWindowRef) {
        for (const QString &buttonKey : holdMappingCombos.keys()) {
            QString internalKey = mainWindowRef->getHoldMapping(buttonKey);
            QString displayString = ButtonMappingHelper::internalKeyToDisplay(internalKey, true);  // true = isDialMode
            int index = holdMappingCombos[buttonKey]->findText(displayString);
            if (index >= 0) holdMappingCombos[buttonKey]->setCurrentIndex(index);
        }

        for (const QString &buttonKey : pressMappingCombos.keys()) {
            QString internalKey = mainWindowRef->getPressMapping(buttonKey);
            QString displayString = ButtonMappingHelper::internalKeyToDisplay(internalKey, false);  // false = isAction
            int index = pressMappingCombos[buttonKey]->findText(displayString);
            if (index >= 0) pressMappingCombos[buttonKey]->setCurrentIndex(index);
        }

        // Load theme settings
        useCustomAccentCheckbox->setChecked(mainWindowRef->isUsingCustomAccentColor());

        // Get the stored custom accent color
        selectedAccentColor = mainWindowRef->getCustomAccentColor();

        accentColorButton->setStyleSheet(QString("background-color: %1").arg(selectedAccentColor.name()));
        accentColorButton->setEnabled(useCustomAccentCheckbox->isChecked());

        // Load color palette setting
        useBrighterPaletteCheckbox->setChecked(mainWindowRef->isUsingBrighterPalette());
    }
}
    MainWindow *mainWindowRef;
    InkCanvas *canvasRef;
    QWidget *performanceTab;
    QWidget *toolbarTab;
    void createToolbarTab() {
    toolbarTab = new QWidget(this);
    QVBoxLayout *toolbarLayout = new QVBoxLayout(toolbarTab);

    // ✅ Checkbox to show/hide benchmark controls
    QCheckBox *benchmarkVisibilityCheckbox = new QCheckBox(tr("Show Benchmark Controls"), toolbarTab);
    benchmarkVisibilityCheckbox->setChecked(mainWindowRef->areBenchmarkControlsVisible());
    toolbarLayout->addWidget(benchmarkVisibilityCheckbox);
    QLabel *benchmarkNote = new QLabel(tr("This will show/hide the benchmark controls on the toolbar. Press the clock button to start/stop the benchmark."));
    benchmarkNote->setWordWrap(true);
    benchmarkNote->setStyleSheet("color: gray; font-size: 10px;");
    toolbarLayout->addWidget(benchmarkNote);

    // ✅ Checkbox to show/hide zoom buttons
    QCheckBox *zoomButtonsVisibilityCheckbox = new QCheckBox(tr("Show Zoom Buttons"), toolbarTab);
    zoomButtonsVisibilityCheckbox->setChecked(mainWindowRef->areZoomButtonsVisible());
    toolbarLayout->addWidget(zoomButtonsVisibilityCheckbox);
    QLabel *zoomButtonsNote = new QLabel(tr("This will show/hide the 0.5x, 1x, and 2x zoom buttons on the toolbar"));
    zoomButtonsNote->setWordWrap(true);
    zoomButtonsNote->setStyleSheet("color: gray; font-size: 10px;");
    toolbarLayout->addWidget(zoomButtonsNote);

    QCheckBox *scrollOnTopCheckBox = new QCheckBox(tr("Scroll on Top after Page Switching"), toolbarTab);
    scrollOnTopCheckBox->setChecked(mainWindowRef->isScrollOnTopEnabled());
    toolbarLayout->addWidget(scrollOnTopCheckBox);
    QLabel *scrollNote = new QLabel(tr("Enabling this will make the page scroll to the top after switching to a new page."));
    scrollNote->setWordWrap(true);
    scrollNote->setStyleSheet("color: gray; font-size: 10px;");
    toolbarLayout->addWidget(scrollNote);

    toolbarLayout->addStretch();
    toolbarTab->setLayout(toolbarLayout);
    tabWidget->addTab(toolbarTab, tr("Features"));

    // Connect the checkbox
    connect(benchmarkVisibilityCheckbox, &QCheckBox::toggled, mainWindowRef, &MainWindow::setBenchmarkControlsVisible);
    connect(zoomButtonsVisibilityCheckbox, &QCheckBox::toggled, mainWindowRef, &MainWindow::setZoomButtonsVisible);
    connect(scrollOnTopCheckBox, &QCheckBox::toggled, mainWindowRef, &MainWindow::setScrollOnTopEnabled);
}
    void createPerformanceTab() {
    performanceTab = new QWidget();
    QVBoxLayout *layout = new QVBoxLayout(performanceTab);

    QCheckBox *previewToggle = new QCheckBox(tr("Enable Low-Resolution PDF Previews"));
    previewToggle->setChecked(mainWindowRef->isLowResPreviewEnabled());

    connect(previewToggle, &QCheckBox::toggled, mainWindowRef, &MainWindow::setLowResPreviewEnabled);

    QLabel *note = new QLabel(tr("Disabling this may improve dial smoothness on low-end devices."));
    note->setWordWrap(true);
    note->setStyleSheet("color: gray; font-size: 10px;");

    QLabel *dpiLabel = new QLabel(tr("PDF Rendering DPI:"));
    QComboBox *dpiSelector = new QComboBox();
    dpiSelector->addItems({"96", "192", "288", "384", "480"});
    dpiSelector->setCurrentText(QString::number(mainWindowRef->getPdfDPI()));

    connect(dpiSelector, &QComboBox::currentTextChanged, this, [=](const QString &value) {
        mainWindowRef->setPdfDPI(value.toInt());
    });

    QLabel *notePDF = new QLabel(tr("Adjust how the PDF is rendered. Higher DPI means better quality but slower performance. DO NOT CHANGE THIS OPTION WHEN MULTIPLE TABS ARE OPEN. THIS MAY LEAD TO UNDEFINED BEHAVIOR!"));
    notePDF->setWordWrap(true);
    notePDF->setStyleSheet("color: gray; font-size: 10px;");

    layout->addWidget(previewToggle);
    layout->addWidget(note);
    layout->addWidget(dpiLabel);
    layout->addWidget(dpiSelector);
    layout->addWidget(notePDF);

    layout->addStretch();

    // return performanceTab;
}
    QWidget *controllerMappingTab;
    QPushButton *reconnectButton;
    QLabel *controllerStatusLabel;
    QMap<QString, QComboBox*> holdMappingCombos;
    QMap<QString, QComboBox*> pressMappingCombos;
    void createButtonMappingTab() {
    QWidget *buttonTab = new QWidget(this);
    QVBoxLayout *layout = new QVBoxLayout(buttonTab);

    QStringList buttonKeys = ButtonMappingHelper::getInternalButtonKeys();
    QStringList buttonDisplayNames = ButtonMappingHelper::getTranslatedButtons();
    QStringList dialModes = ButtonMappingHelper::getTranslatedDialModes();
    QStringList actions = ButtonMappingHelper::getTranslatedActions();

    for (int i = 0; i < buttonKeys.size(); ++i) {
        const QString &buttonKey = buttonKeys[i];
        const QString &buttonDisplayName = buttonDisplayNames[i];

        QHBoxLayout *h = new QHBoxLayout();
        h->addWidget(new QLabel(buttonDisplayName));  // Use translated button name

        QComboBox *holdCombo = new QComboBox();
        holdCombo->addItems(dialModes);  // Add translated dial mode names
        holdMappingCombos[buttonKey] = holdCombo;
        h->addWidget(new QLabel(tr("Hold:")));
        h->addWidget(holdCombo);

        QComboBox *pressCombo = new QComboBox();
        pressCombo->addItems(actions);  // Add translated action names
        pressMappingCombos[buttonKey] = pressCombo;
        h->addWidget(new QLabel(tr("Press:")));
        h->addWidget(pressCombo);

        layout->addLayout(h);
    }

    layout->addStretch();
    buttonTab->setLayout(layout);
    tabWidget->addTab(buttonTab, tr("Button Mapping"));
}
    void createControllerMappingTab() {
    controllerMappingTab = new QWidget(this);
    QVBoxLayout *layout = new QVBoxLayout(controllerMappingTab);

    // Instructions
    QLabel *instructionLabel = new QLabel(tr("Configure physical controller button mappings for your Joy-Con or other controller:"), controllerMappingTab);
    instructionLabel->setWordWrap(true);
    layout->addWidget(instructionLabel);

    QLabel *noteLabel = new QLabel(tr("Note: This maps your physical controller buttons to the logical Joy-Con functions used by the application. "
                                     "After setting up the physical mapping, you can configure what actions each logical button performs in the 'Button Mapping' tab."), controllerMappingTab);
    noteLabel->setWordWrap(true);
    noteLabel->setStyleSheet("color: gray; font-size: 10px; margin-bottom: 10px;");
    layout->addWidget(noteLabel);

    // Button to open controller mapping dialog
    QPushButton *openMappingButton = new QPushButton(tr("Configure Controller Mapping"), controllerMappingTab);
    openMappingButton->setMinimumHeight(40);
    connect(openMappingButton, &QPushButton::clicked, this, &ControlPanelDialog::openControllerMapping);
    layout->addWidget(openMappingButton);

    // Button to reconnect controller
    reconnectButton = new QPushButton(tr("Reconnect Controller"), controllerMappingTab);
    reconnectButton->setMinimumHeight(40);
    reconnectButton->setStyleSheet("QPushButton { background-color: #4CAF50; color: white; font-weight: bold; }");
    connect(reconnectButton, &QPushButton::clicked, this, &ControlPanelDialog::reconnectController);
    layout->addWidget(reconnectButton);

    // Status information
    QLabel *statusLabel = new QLabel(tr("Current controller status:"), controllerMappingTab);
    statusLabel->setStyleSheet("font-weight: bold; margin-top: 20px;");
    layout->addWidget(statusLabel);

    // Dynamic status label
    controllerStatusLabel = new QLabel(controllerMappingTab);
    updateControllerStatus();
    layout->addWidget(controllerStatusLabel);

    layout->addStretch();

    tabWidget->addTab(controllerMappingTab, tr("Controller Mapping"));
}
    void createKeyboardMappingTab() {
    keyboardTab = new QWidget(this);
    QVBoxLayout *layout = new QVBoxLayout(keyboardTab);

    // Instructions
    QLabel *instructionLabel = new QLabel(tr("Configure custom keyboard shortcuts for application actions:"), keyboardTab);
    instructionLabel->setWordWrap(true);
    layout->addWidget(instructionLabel);

    // Table to show current mappings
    keyboardTable = new QTableWidget(0, 2, keyboardTab);
    keyboardTable->setHorizontalHeaderLabels({tr("Key Sequence"), tr("Action")});
    keyboardTable->horizontalHeader()->setStretchLastSection(true);
    keyboardTable->setSelectionBehavior(QAbstractItemView::SelectRows);
    keyboardTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
    layout->addWidget(keyboardTable);

    // Buttons
    QHBoxLayout *buttonLayout = new QHBoxLayout();
    addKeyboardMappingButton = new QPushButton(tr("Add Mapping"), keyboardTab);
    removeKeyboardMappingButton = new QPushButton(tr("Remove Mapping"), keyboardTab);

    buttonLayout->addWidget(addKeyboardMappingButton);
    buttonLayout->addWidget(removeKeyboardMappingButton);
    buttonLayout->addStretch();

    layout->addLayout(buttonLayout);

    // Connections
    connect(addKeyboardMappingButton, &QPushButton::clicked, this, &ControlPanelDialog::addKeyboardMapping);
    connect(removeKeyboardMappingButton, &QPushButton::clicked, this, &ControlPanelDialog::removeKeyboardMapping);

    // Load current mappings
    if (mainWindowRef) {
        QMap<QString, QString> mappings = mainWindowRef->getKeyboardMappings();
        keyboardTable->setRowCount(mappings.size());
        int row = 0;
        for (auto it = mappings.begin(); it != mappings.end(); ++it) {
            keyboardTable->setItem(row, 0, new QTableWidgetItem(it.key()));
            QString displayAction = ButtonMappingHelper::internalKeyToDisplay(it.value(), false);
            keyboardTable->setItem(row, 1, new QTableWidgetItem(displayAction));
            row++;
        }
    }

    tabWidget->addTab(keyboardTab, tr("Keyboard Shortcuts"));
}
    QWidget *keyboardTab;
    QTableWidget *keyboardTable;
    QPushButton *addKeyboardMappingButton;
    QPushButton *removeKeyboardMappingButton;
    QWidget *themeTab;
    QCheckBox *useCustomAccentCheckbox;
    QPushButton *accentColorButton;
    QColor selectedAccentColor;
    QCheckBox *useBrighterPaletteCheckbox;
    void createThemeTab() {
    themeTab = new QWidget(this);
    QVBoxLayout *layout = new QVBoxLayout(themeTab);

    // Custom accent color
    useCustomAccentCheckbox = new QCheckBox(tr("Use Custom Accent Color"), themeTab);
    layout->addWidget(useCustomAccentCheckbox);

    QLabel *accentColorLabel = new QLabel(tr("Accent Color:"), themeTab);
    accentColorButton = new QPushButton(themeTab);
    accentColorButton->setFixedSize(100, 30);
    connect(accentColorButton, &QPushButton::clicked, this, &ControlPanelDialog::chooseAccentColor);

    QHBoxLayout *accentColorLayout = new QHBoxLayout();
    accentColorLayout->addWidget(accentColorLabel);
    accentColorLayout->addWidget(accentColorButton);
    accentColorLayout->addStretch();
    layout->addLayout(accentColorLayout);

    QLabel *accentColorNote = new QLabel(tr("When enabled, use a custom accent color instead of the system accent color for the toolbar, dial, and tab selection."));
    accentColorNote->setWordWrap(true);
    accentColorNote->setStyleSheet("color: gray; font-size: 10px;");
    layout->addWidget(accentColorNote);

    // Enable/disable accent color button based on checkbox
    connect(useCustomAccentCheckbox, &QCheckBox::toggled, accentColorButton, &QPushButton::setEnabled);
    connect(useCustomAccentCheckbox, &QCheckBox::toggled, accentColorLabel, &QLabel::setEnabled);

    // Color palette preference
    useBrighterPaletteCheckbox = new QCheckBox(tr("Use Brighter Color Palette"), themeTab);
    layout->addWidget(useBrighterPaletteCheckbox);

    QLabel *paletteNote = new QLabel(tr("When enabled, use brighter colors (good for dark PDF backgrounds). When disabled, use darker colors (good for light PDF backgrounds). This setting is independent of the UI theme."));
    paletteNote->setWordWrap(true);
    paletteNote->setStyleSheet("color: gray; font-size: 10px;");
    layout->addWidget(paletteNote);

    layout->addStretch();

    tabWidget->addTab(themeTab, tr("Theme"));
}
    void chooseAccentColor() {
    QColor chosen = QColorDialog::getColor(selectedAccentColor, this, tr("Select Accent Color"));
    if (chosen.isValid()) {
        selectedAccentColor = chosen;
        accentColorButton->setStyleSheet(QString("background-color: %1").arg(selectedAccentColor.name()));
    }
}
    void updateControllerStatus() {
    if (!mainWindowRef || !controllerStatusLabel) return;

    SDLControllerManager *controllerManager = mainWindowRef->getControllerManager();
    if (!controllerManager) {
        controllerStatusLabel->setText(tr("✗ Controller manager not available"));
        controllerStatusLabel->setStyleSheet("color: red;");
        return;
    }

    if (controllerManager->getJoystick()) {
        controllerStatusLabel->setText(tr("✓ Controller connected"));
        controllerStatusLabel->setStyleSheet("color: green; font-weight: bold;");
    } else {
        controllerStatusLabel->setText(tr("✗ No controller detected"));
        controllerStatusLabel->setStyleSheet("color: red; font-weight: bold;");
    }
}
};

class InkCanvas {
    Q_OBJECT

public:
    explicit RecentNotebooksManager(QObject *parent = nullptr);
    void addRecentNotebook(const QString& folderPath, InkCanvas* canvasForPreview = nullptr) {
    if (folderPath.isEmpty()) return;

    // Remove if already exists to move it to the front
    recentNotebookPaths.removeAll(folderPath);
    recentNotebookPaths.prepend(folderPath);

    // Trim the list if it exceeds the maximum size
    while (recentNotebookPaths.size() > MAX_RECENT_NOTEBOOKS) {
        recentNotebookPaths.removeLast();
    }

    generateAndSaveCoverPreview(folderPath, canvasForPreview);
    saveRecentNotebooks();
}
    QStringList getRecentNotebooks() const {
    return recentNotebookPaths;
}
    QString getCoverImagePathForNotebook(const QString& folderPath) const {
    QString coverDir = getCoverImageDir();
    QString coverFileName = sanitizeFolderName(folderPath) + "_cover.png";
    QString coverFilePath = coverDir + coverFileName;
    if (QFile::exists(coverFilePath)) {
        return coverFilePath;
    }
    return ""; // Return empty if no cover exists
}
    QString getNotebookDisplayName(const QString& folderPath) const {
    // Check for PDF metadata first
    QString metadataFile = folderPath + "/.pdf_path.txt";
    if (QFile::exists(metadataFile)) {
        QFile file(metadataFile);
        if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            QTextStream in(&file);
            QString pdfPath = in.readLine().trimmed();
            file.close();
            if (!pdfPath.isEmpty()) {
                return QFileInfo(pdfPath).fileName();
            }
        }
    }
    // Fallback to folder name
    return QFileInfo(folderPath).fileName();
}
    void generateAndSaveCoverPreview(const QString& folderPath, InkCanvas* optionalCanvas = nullptr) {
    QString coverDir = getCoverImageDir();
    QString coverFileName = sanitizeFolderName(folderPath) + "_cover.png";
    QString coverFilePath = coverDir + coverFileName;

    QImage coverImage(400, 300, QImage::Format_ARGB32_Premultiplied);
    coverImage.fill(Qt::white); // Default background
    QPainter painter(&coverImage);
    painter.setRenderHint(QPainter::SmoothPixmapTransform);

    if (optionalCanvas && optionalCanvas->width() > 0 && optionalCanvas->height() > 0) {
        int logicalCanvasWidth = optionalCanvas->width();
        int logicalCanvasHeight = optionalCanvas->height();

        double targetAspectRatio = 4.0 / 3.0;
        double canvasAspectRatio = (double)logicalCanvasWidth / logicalCanvasHeight;

        int grabWidth, grabHeight;
        int xOffset = 0, yOffset = 0;

        if (canvasAspectRatio > targetAspectRatio) { // Canvas is wider than target 4:3
            grabHeight = logicalCanvasHeight;
            grabWidth = qRound(logicalCanvasHeight * targetAspectRatio);
            xOffset = (logicalCanvasWidth - grabWidth) / 2;
        } else { // Canvas is taller than or equal to target 4:3
            grabWidth = logicalCanvasWidth;
            grabHeight = qRound(logicalCanvasWidth / targetAspectRatio);
            yOffset = (logicalCanvasHeight - grabHeight) / 2;
        }

        if (grabWidth > 0 && grabHeight > 0) {
            QRect grabRect(xOffset, yOffset, grabWidth, grabHeight);
            QPixmap capturedView = optionalCanvas->grab(grabRect);
            painter.drawPixmap(coverImage.rect(), capturedView, capturedView.rect());
        } else {
            // Fallback if calculated grab dimensions are invalid, draw placeholder or fill
            // This case should ideally not be hit if canvas width/height > 0
            painter.fillRect(coverImage.rect(), Qt::lightGray);
            painter.setPen(Qt::black);
            painter.drawText(coverImage.rect(), Qt::AlignCenter, tr("Preview Error"));
        }
    } else {
        // Fallback: check for a saved "annotated_..._00000.png" or the first page
        // This part remains the same as it deals with pre-rendered images.
        QString notebookIdStr;
        InkCanvas tempCanvas(nullptr); // Temporary canvas to access potential notebookId property logic if it were there
                                     // However, notebookId is specific to an instance with a saveFolder.
                                     // For a generic fallback, we might need a different way or assume a common naming if notebookId is unknown.
                                     // For now, let's assume a simple naming or improve this fallback if needed.

        // Attempt to get notebookId if folderPath is a valid notebook with an ID file
        QFile idFile(folderPath + "/.notebook_id.txt");
        if (idFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
            QTextStream in(&idFile);
            notebookIdStr = in.readLine().trimmed();
            idFile.close();
        }

        QString firstPagePath, firstAnnotatedPagePath;
        if (!notebookIdStr.isEmpty()) {
            firstPagePath = folderPath + QString("/%1_00000.png").arg(notebookIdStr);
            firstAnnotatedPagePath = folderPath + QString("/annotated_%1_00000.png").arg(notebookIdStr);
        } else {
            // Fallback if no ID, try a generic name (less ideal)
            // This situation should be rare if notebooks are managed properly
            // For robustness, one might iterate files or use a known default page name
            // For this example, we'll stick to a pattern that might not always work without an ID
            // Consider that `InkCanvas(nullptr).property("notebookId")` was problematic as `notebookId` is instance specific.
        }

        QImage pageImage;
        if (!firstAnnotatedPagePath.isEmpty() && QFile::exists(firstAnnotatedPagePath)) {
            pageImage.load(firstAnnotatedPagePath);
        } else if (!firstPagePath.isEmpty() && QFile::exists(firstPagePath)) {
            pageImage.load(firstPagePath);
        }

        if (!pageImage.isNull()) {
            painter.drawImage(coverImage.rect(), pageImage, pageImage.rect());
        } else {
            // If no image found, draw a placeholder
            painter.fillRect(coverImage.rect(), Qt::darkGray);
            painter.setPen(Qt::white);
            painter.drawText(coverImage.rect(), Qt::AlignCenter, tr("No Page 0 Preview"));
        }
    }
    coverImage.save(coverFilePath, "PNG");
}
  private:
    void loadRecentNotebooks() {
    recentNotebookPaths = settings.value("recentNotebooks").toStringList();
}
    void saveRecentNotebooks() {
    settings.setValue("recentNotebooks", recentNotebookPaths);
}
    QString getCoverImageDir() const {
    return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/RecentCovers/";
}
    QString sanitizeFolderName(const QString& folderPath) const {
    return QFileInfo(folderPath).baseName().replace(QRegularExpression("[^a-zA-Z0-9_]"), "_");
}
    QStringList recentNotebookPaths;
    const int MAX_RECENT_NOTEBOOKS = 16;
    QSettings settings;
};

class SDLControllerManager {
    Q_OBJECT

public:
    explicit ControllerMappingDialog(SDLControllerManager *controllerManager, QWidget *parent = nullptr);
  private:
    void startButtonMapping(const QString &logicalButton) {
    if (!controller) return;

    // Disable all mapping buttons during mapping
    for (auto button : mappingButtons) {
        button->setEnabled(false);
    }

    // Update the current button being mapped
    currentMappingButton = logicalButton;
    mappingButtons[logicalButton]->setText(tr("Press button..."));

    // Start button detection mode
    controller->startButtonDetection();

    // Start timeout timer
    mappingTimeoutTimer->start();

    // Show status message
    QApplication::setOverrideCursor(Qt::WaitCursor);
}
    void onRawButtonPressed(int sdlButton, const QString &buttonName) {
    if (currentMappingButton.isEmpty()) return;

    // Stop detection and timeout
    controller->stopButtonDetection();
    mappingTimeoutTimer->stop();
    QApplication::restoreOverrideCursor();

    // Check if this button is already mapped to another function
    QMap<QString, int> currentMappings = controller->getAllPhysicalMappings();
    QString conflictingButton;
    for (auto it = currentMappings.begin(); it != currentMappings.end(); ++it) {
        if (it.value() == sdlButton && it.key() != currentMappingButton) {
            conflictingButton = it.key();
            break;
        }
    }

    if (!conflictingButton.isEmpty()) {
        int ret = QMessageBox::question(this, tr("Button Conflict"),
            tr("The button '%1' is already mapped to '%2'.\n\nDo you want to reassign it to '%3'?")
            .arg(buttonName).arg(conflictingButton).arg(currentMappingButton),
            QMessageBox::Yes | QMessageBox::No);

        if (ret != QMessageBox::Yes) {
            // Reset UI and return
            mappingButtons[currentMappingButton]->setText(tr("Map"));
            for (auto button : mappingButtons) {
                button->setEnabled(true);
            }
            currentMappingButton.clear();
            return;
        }

        // Clear the conflicting mapping
        controller->setPhysicalButtonMapping(conflictingButton, -1);
        currentMappingLabels[conflictingButton]->setText("Not mapped");
        currentMappingLabels[conflictingButton]->setStyleSheet("color: gray;");
    }

    // Set the new mapping
    controller->setPhysicalButtonMapping(currentMappingButton, sdlButton);

    // Get appropriate text color for current theme
    QString textColor = isDarkMode() ? "white" : "black";

    // Update UI
    currentMappingLabels[currentMappingButton]->setText(buttonName);
    currentMappingLabels[currentMappingButton]->setStyleSheet(QString("color: %1; font-weight: bold;").arg(textColor));
    mappingButtons[currentMappingButton]->setText(tr("Map"));

    // Re-enable all buttons
    for (auto button : mappingButtons) {
        button->setEnabled(true);
    }

    currentMappingButton.clear();

    QMessageBox::information(this, tr("Mapping Complete"),
        tr("Button '%1' has been successfully mapped!").arg(buttonName));
}
    void resetToDefaults() {
    int ret = QMessageBox::question(this, tr("Reset to Defaults"),
        tr("Are you sure you want to reset all button mappings to their default values?\n\nThis will overwrite your current configuration."),
        QMessageBox::Yes | QMessageBox::No);

    if (ret == QMessageBox::Yes) {
        // Get default mappings and apply them
        QMap<QString, int> defaults = controller->getDefaultMappings();
        for (auto it = defaults.begin(); it != defaults.end(); ++it) {
            controller->setPhysicalButtonMapping(it.key(), it.value());
        }

        // Update display
        updateMappingDisplay();

        QMessageBox::information(this, tr("Reset Complete"),
            tr("All button mappings have been reset to their default values."));
    }
}
    void applyMappings() {
    // Mappings are already applied in real-time, just close the dialog
    accept();
}
  private:
    SDLControllerManager *controller;
    QGridLayout *mappingLayout;
    QMap<QString, QLabel*> buttonLabels;
    QMap<QString, QLabel*> currentMappingLabels;
    QMap<QString, QPushButton*> mappingButtons;
    QPushButton *resetButton;
    QPushButton *applyButton;
    QPushButton *cancelButton;
    QString currentMappingButton;
    QTimer *mappingTimeoutTimer;
    void setupUI() {
    QVBoxLayout *mainLayout = new QVBoxLayout(this);

    // Instructions
    QLabel *instructionLabel = new QLabel(tr("Map your physical controller buttons to Joy-Con functions.\n"
                                           "Click 'Map' next to each function, then press the corresponding button on your controller."), this);
    instructionLabel->setWordWrap(true);
    instructionLabel->setStyleSheet("font-weight: bold; margin-bottom: 10px;");
    mainLayout->addWidget(instructionLabel);

    // Mapping grid
    QWidget *mappingWidget = new QWidget(this);
    mappingLayout = new QGridLayout(mappingWidget);

    // Headers
    mappingLayout->addWidget(new QLabel(tr("Joy-Con Function"), this), 0, 0);
    mappingLayout->addWidget(new QLabel(tr("Description"), this), 0, 1);
    mappingLayout->addWidget(new QLabel(tr("Current Mapping"), this), 0, 2);
    mappingLayout->addWidget(new QLabel(tr("Action"), this), 0, 3);

    // Get logical button descriptions
    QMap<QString, QString> descriptions = getLogicalButtonDescriptions();

    // Create mapping rows for each logical button
    QStringList logicalButtons = {"LEFTSHOULDER", "RIGHTSHOULDER", "PADDLE2", "PADDLE4",
                                 "Y", "A", "B", "X", "LEFTSTICK", "START", "GUIDE"};

    int row = 1;
    for (const QString &logicalButton : logicalButtons) {
        // Function name
        QLabel *functionLabel = new QLabel(logicalButton, this);
        functionLabel->setStyleSheet("font-weight: bold;");
        buttonLabels[logicalButton] = functionLabel;
        mappingLayout->addWidget(functionLabel, row, 0);

        // Description
        QLabel *descLabel = new QLabel(descriptions.value(logicalButton, "Unknown"), this);
        descLabel->setWordWrap(true);
        mappingLayout->addWidget(descLabel, row, 1);

        // Current mapping display
        QLabel *currentLabel = new QLabel("Not mapped", this);
        currentLabel->setStyleSheet("color: gray;");
        currentMappingLabels[logicalButton] = currentLabel;
        mappingLayout->addWidget(currentLabel, row, 2);

        // Map button
        QPushButton *mapButton = new QPushButton(tr("Map"), this);
        mappingButtons[logicalButton] = mapButton;
        connect(mapButton, &QPushButton::clicked, this, [this, logicalButton]() {
            startButtonMapping(logicalButton);
        });
        mappingLayout->addWidget(mapButton, row, 3);

        row++;
    }

    mainLayout->addWidget(mappingWidget);

    // Buttons
    QHBoxLayout *buttonLayout = new QHBoxLayout();

    resetButton = new QPushButton(tr("Reset to Defaults"), this);
    connect(resetButton, &QPushButton::clicked, this, &ControllerMappingDialog::resetToDefaults);
    buttonLayout->addWidget(resetButton);

    buttonLayout->addStretch();

    applyButton = new QPushButton(tr("Apply"), this);
    connect(applyButton, &QPushButton::clicked, this, &ControllerMappingDialog::applyMappings);
    buttonLayout->addWidget(applyButton);

    cancelButton = new QPushButton(tr("Cancel"), this);
    connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
    buttonLayout->addWidget(cancelButton);

    mainLayout->addLayout(buttonLayout);
}
    QMap<QString, QString> getLogicalButtonDescriptions() const {
    QMap<QString, QString> descriptions;
    descriptions["LEFTSHOULDER"] = tr("L Button (Left Shoulder)");
    descriptions["RIGHTSHOULDER"] = tr("ZL Button (Left Trigger)");
    descriptions["PADDLE2"] = tr("SL Button (Side Left)");
    descriptions["PADDLE4"] = tr("SR Button (Side Right)");
    descriptions["Y"] = tr("Up Arrow (D-Pad Up)");
    descriptions["A"] = tr("Down Arrow (D-Pad Down)");
    descriptions["B"] = tr("Left Arrow (D-Pad Left)");
    descriptions["X"] = tr("Right Arrow (D-Pad Right)");
    descriptions["LEFTSTICK"] = tr("Analog Stick Press");
    descriptions["START"] = tr("Minus Button (-)");
    descriptions["GUIDE"] = tr("Screenshot Button");
    return descriptions;
}
    void loadCurrentMappings() {
    QMap<QString, int> currentMappings = controller->getAllPhysicalMappings();

    // Get appropriate text color for current theme
    QString textColor = isDarkMode() ? "white" : "black";

    for (auto it = currentMappings.begin(); it != currentMappings.end(); ++it) {
        const QString &logicalButton = it.key();
        int physicalButton = it.value();

        if (currentMappingLabels.contains(logicalButton)) {
            QString physicalName = controller->getPhysicalButtonName(physicalButton);
            currentMappingLabels[logicalButton]->setText(physicalName);
            currentMappingLabels[logicalButton]->setStyleSheet(QString("color: %1; font-weight: bold;").arg(textColor));
        }
    }
}
    void updateMappingDisplay() {
    loadCurrentMappings();
}
    bool isDarkMode() const {
    // Same logic as MainWindow::isDarkMode()
    QColor bg = palette().color(QPalette::Window);
    return bg.lightness() < 128;  // Lightness scale: 0 (black) - 255 (white)
}
};

class QPlainTextEditSearchWidget {
    Q_OBJECT

   public:
    enum SearchMode { PlainTextMode, WholeWordsMode, RegularExpressionMode };
    explicit QPlainTextEditSearchWidget(QPlainTextEdit *parent = nullptr) {
    ui->setupUi(this);
    _textEdit = parent;
    _darkMode = false;
    hide();
    ui->searchCountLabel->setStyleSheet(QStringLiteral("* {color: grey}"));
    // hiding will leave a open space in the horizontal layout
    ui->searchCountLabel->setEnabled(false);
    _currentSearchResult = 0;
    _searchResultCount = 0;

    connect(ui->closeButton, &QPushButton::clicked, this,
            &QPlainTextEditSearchWidget::deactivate);
    connect(ui->searchLineEdit, &QLineEdit::textChanged, this,
            &QPlainTextEditSearchWidget::searchLineEditTextChanged);
    connect(ui->searchDownButton, &QPushButton::clicked, this,
            &QPlainTextEditSearchWidget::doSearchDown);
    connect(ui->searchUpButton, &QPushButton::clicked, this,
            &QPlainTextEditSearchWidget::doSearchUp);
    connect(ui->replaceToggleButton, &QPushButton::toggled, this,
            &QPlainTextEditSearchWidget::setReplaceMode);
    connect(ui->replaceButton, &QPushButton::clicked, this,
            &QPlainTextEditSearchWidget::doReplace);
    connect(ui->replaceAllButton, &QPushButton::clicked, this,
            &QPlainTextEditSearchWidget::doReplaceAll);

    connect(&_debounceTimer, &QTimer::timeout, this,
            &QPlainTextEditSearchWidget::performSearch);

    installEventFilter(this);
    ui->searchLineEdit->installEventFilter(this);
    ui->replaceLineEdit->installEventFilter(this);

#ifdef Q_OS_MAC
    // set the spacing to 8 for OS X
    layout()->setSpacing(8);
    ui->buttonFrame->layout()->setSpacing(9);

    // set the margin to 0 for the top buttons for OS X
    QString buttonStyle = QStringLiteral("QPushButton {margin: 0}");
    ui->closeButton->setStyleSheet(buttonStyle);
    ui->searchDownButton->setStyleSheet(buttonStyle);
    ui->searchUpButton->setStyleSheet(buttonStyle);
    ui->replaceToggleButton->setStyleSheet(buttonStyle);
    ui->matchCaseSensitiveButton->setStyleSheet(buttonStyle);
#endif
}
    bool doSearch(bool searchDown = true, bool allowRestartAtTop = true,
                  bool updateUI = true) {
    if (_debounceTimer.isActive()) {
        stopDebounce();
    }

    const QString text = ui->searchLineEdit->text();

    if (text.isEmpty()) {
        if (updateUI) {
            ui->searchLineEdit->setStyleSheet(QLatin1String(""));
        }

        return false;
    }

    const int searchMode = ui->modeComboBox->currentIndex();
    const bool caseSensitive = ui->matchCaseSensitiveButton->isChecked();

    QFlags<QTextDocument::FindFlag> options =
        searchDown ? QTextDocument::FindFlag(0) : QTextDocument::FindBackward;
    if (searchMode == WholeWordsMode) {
        options |= QTextDocument::FindWholeWords;
    }

    if (caseSensitive) {
        options |= QTextDocument::FindCaseSensitively;
    }

    // block signal to reduce too many signals being fired and too many updates
    _textEdit->blockSignals(true);

    bool found =
        searchMode == RegularExpressionMode
            ?
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
            _textEdit->find(
                QRegularExpression(
                    text, caseSensitive
                              ? QRegularExpression::NoPatternOption
                              : QRegularExpression::CaseInsensitiveOption),
                options)
            :
#else
            _textEdit->find(QRegExp(text, caseSensitive ? Qt::CaseSensitive
                                                        : Qt::CaseInsensitive),
                            options)
            :
#endif
            _textEdit->find(text, options);

    _textEdit->blockSignals(false);

    if (found) {
        const int result =
            searchDown ? ++_currentSearchResult : --_currentSearchResult;
        _currentSearchResult = std::min(result, _searchResultCount);

        updateSearchCountLabelText();
    }

    // start at the top (or bottom) if not found
    if (!found && allowRestartAtTop) {
        _textEdit->moveCursor(searchDown ? QTextCursor::Start
                                         : QTextCursor::End);
        found =
            searchMode == RegularExpressionMode
                ?
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
                _textEdit->find(
                    QRegularExpression(
                        text, caseSensitive
                                  ? QRegularExpression::NoPatternOption
                                  : QRegularExpression::CaseInsensitiveOption),
                    options)
                :
#else
                _textEdit->find(
                    QRegExp(text, caseSensitive ? Qt::CaseSensitive
                                                : Qt::CaseInsensitive),
                    options)
                :
#endif
                _textEdit->find(text, options);

        if (found && updateUI) {
            _currentSearchResult = searchDown ? 1 : _searchResultCount;
            updateSearchCountLabelText();
        }
    }

    if (updateUI) {
        const QRect rect = _textEdit->cursorRect();
        QMargins margins = _textEdit->layout()->contentsMargins();
        const int searchWidgetHotArea = _textEdit->height() - this->height();
        const int marginBottom =
            (rect.y() > searchWidgetHotArea) ? (this->height() + 10) : 0;

        // move the search box a bit up if we would block the search result
        if (margins.bottom() != marginBottom) {
            margins.setBottom(marginBottom);
            _textEdit->layout()->setContentsMargins(margins);
        }

        // add a background color according if we found the text or not
        const QString bgColorCode = _darkMode
                                        ? (found ? QStringLiteral("#135a13")
                                                 : QStringLiteral("#8d2b36"))
                                    : found ? QStringLiteral("#D5FAE2")
                                            : QStringLiteral("#FAE9EB");
        const QString fgColorCode =
            _darkMode ? QStringLiteral("#cccccc") : QStringLiteral("#404040");

        ui->searchLineEdit->setStyleSheet(
            QStringLiteral("* { background: ") + bgColorCode +
            QStringLiteral("; color: ") + fgColorCode + QStringLiteral("; }"));

        // restore the search extra selections after the find command
        this->setSearchExtraSelections();
    }

    return found;
}
    void setDarkMode(bool enabled) {
    _darkMode = enabled;
}
    ~QPlainTextEditSearchWidget() { delete ui; }
    void setSearchText(const QString &searchText) {
    ui->searchLineEdit->setText(searchText);
}
    void setSearchMode(SearchMode searchMode) {
    ui->modeComboBox->setCurrentIndex(searchMode);
}
    void setDebounceDelay(uint debounceDelay) {
    _debounceTimer.setInterval(static_cast<int>(debounceDelay));
}
    void activate(bool focus) {
    setReplaceMode(ui->modeComboBox->currentIndex() !=
                   SearchMode::PlainTextMode);
    show();

    // preset the selected text as search text if there is any and there is no
    // other search text
    const QString selectedText = _textEdit->textCursor().selectedText();
    if (!selectedText.isEmpty() && ui->searchLineEdit->text().isEmpty()) {
        ui->searchLineEdit->setText(selectedText);
    }

    if (focus) {
        ui->searchLineEdit->setFocus();
    }

    ui->searchLineEdit->selectAll();
    updateSearchExtraSelections();
    doSearchDown();
}
    void clearSearchExtraSelections() {
    _searchExtraSelections.clear();
    setSearchExtraSelections();
}
    void updateSearchExtraSelections() {
    _searchExtraSelections.clear();
    const auto textCursor = _textEdit->textCursor();
    _textEdit->moveCursor(QTextCursor::Start);
    const QColor color = selectionColor;
    QTextCharFormat extraFmt;
    extraFmt.setBackground(color);
    int findCounter = 0;
    const int searchMode = ui->modeComboBox->currentIndex();

    while (doSearch(true, false, false)) {
        findCounter++;

        // prevent infinite loops from regular expression searches like "$", "^"
        // or "\b"
        if (searchMode == RegularExpressionMode && findCounter >= 10000) {
            break;
        }

        QTextEdit::ExtraSelection extra = QTextEdit::ExtraSelection();
        extra.format = extraFmt;

        extra.cursor = _textEdit->textCursor();
        _searchExtraSelections.append(extra);
    }

    _textEdit->setTextCursor(textCursor);
    this->setSearchExtraSelections();
}
  private:
    Ui::QPlainTextEditSearchWidget *ui;
    int _searchResultCount;
    int _currentSearchResult;
    QList<QTextEdit::ExtraSelection> _searchExtraSelections;
    QColor selectionColor;
    QTimer _debounceTimer;
    QString _searchTerm;
    void setSearchExtraSelections() const {
    this->_textEdit->setExtraSelections(this->_searchExtraSelections);
}
    void stopDebounce() {
    _debounceTimer.stop();
    ui->searchDownButton->setEnabled(true);
    ui->searchUpButton->setEnabled(true);
}
  protected:
    QPlainTextEdit *_textEdit;
    bool _darkMode;
    bool eventFilter(QObject *obj, QEvent *event) override {
    if (event->type() == QEvent::KeyPress) {
        auto *keyEvent = static_cast<QKeyEvent *>(event);

        if (keyEvent->key() == Qt::Key_Escape) {
            deactivate();
            return true;
        } else if ((!_debounceTimer.isActive() &&
                    keyEvent->modifiers().testFlag(Qt::ShiftModifier) &&
                    (keyEvent->key() == Qt::Key_Return)) ||
                   (keyEvent->key() == Qt::Key_Up)) {
            doSearchUp();
            return true;
        } else if (!_debounceTimer.isActive() &&
                   ((keyEvent->key() == Qt::Key_Return) ||
                    (keyEvent->key() == Qt::Key_Down))) {
            doSearchDown();
            return true;
        } else if (!_debounceTimer.isActive() &&
                   keyEvent->key() == Qt::Key_F3) {
            doSearch(!keyEvent->modifiers().testFlag(Qt::ShiftModifier));
            return true;
        }

        //        if ((obj == ui->replaceLineEdit) && (keyEvent->key() ==
        //        Qt::Key_Tab)
        //                && ui->replaceToggleButton->isChecked()) {
        //            ui->replaceLineEdit->setFocus();
        //        }

        return false;
    }

    return QWidget::eventFilter(obj, event);
}
  public:
    void activate() {
    setReplaceMode(ui->modeComboBox->currentIndex() !=
                   SearchMode::PlainTextMode);
    show();

    // preset the selected text as search text if there is any and there is no
    // other search text
    const QString selectedText = _textEdit->textCursor().selectedText();
    if (!selectedText.isEmpty() && ui->searchLineEdit->text().isEmpty()) {
        ui->searchLineEdit->setText(selectedText);
    }

    if (focus) {
        ui->searchLineEdit->setFocus();
    }

    ui->searchLineEdit->selectAll();
    updateSearchExtraSelections();
    doSearchDown();
}
    void deactivate() {
    stopDebounce();

    hide();

    // Clear the search extra selections when closing the search bar
    clearSearchExtraSelections();

    _textEdit->setFocus();
}
    void doSearchDown() { doSearch(true); }
    void doSearchUp() { doSearch(false); }
    void setReplaceMode(bool enabled) {
    ui->replaceToggleButton->setChecked(enabled);
    ui->replaceLabel->setVisible(enabled);
    ui->replaceLineEdit->setVisible(enabled);
    ui->modeLabel->setVisible(enabled);
    ui->buttonFrame->setVisible(enabled);
    ui->matchCaseSensitiveButton->setVisible(enabled);
}
    void activateReplace() {
    // replacing is prohibited if the text edit is readonly
    if (_textEdit->isReadOnly()) {
        return;
    }

    ui->searchLineEdit->setText(_textEdit->textCursor().selectedText());
    ui->searchLineEdit->selectAll();
    activate();
    setReplaceMode(true);
}
    bool doReplace(bool forAll = false) {
    if (_textEdit->isReadOnly()) {
        return false;
    }

    QTextCursor cursor = _textEdit->textCursor();

    if (!forAll && cursor.selectedText().isEmpty()) {
        return false;
    }

    const int searchMode = ui->modeComboBox->currentIndex();
    if (searchMode == RegularExpressionMode) {
        QString text = cursor.selectedText();
        text.replace(QRegularExpression(ui->searchLineEdit->text()),
                     ui->replaceLineEdit->text());
        cursor.insertText(text);
    } else {
        cursor.insertText(ui->replaceLineEdit->text());
    }

    if (!forAll) {
        const int position = cursor.position();

        if (!doSearch(true)) {
            // restore the last cursor position if text wasn't found any more
            cursor.setPosition(position);
            _textEdit->setTextCursor(cursor);
        }
    }

    return true;
}
    void doReplaceAll() {
    if (_textEdit->isReadOnly()) {
        return;
    }

    // start at the top
    _textEdit->moveCursor(QTextCursor::Start);

    // replace until everything to the bottom is replaced
    while (doSearch(true, false) && doReplace(true)) {
    }
}
    void reset() {
    ui->searchLineEdit->clear();
    setSearchMode(SearchMode::PlainTextMode);
    setReplaceMode(false);
    ui->searchCountLabel->setEnabled(false);
}
    void doSearchCount() {
    // Note that we are moving the anchor, so the search will start from the top
    // again! Alternative: Restore cursor position afterward, but then we will
    // not know
    //              at what _currentSearchResult we currently are
    _textEdit->moveCursor(QTextCursor::Start, QTextCursor::MoveAnchor);

    bool found;
    _searchResultCount = 0;
    _currentSearchResult = 0;
    const int searchMode = ui->modeComboBox->currentIndex();

    do {
        found = doSearch(true, false, false);
        if (found) {
            _searchResultCount++;
        }

        // prevent infinite loops from regular expression searches like "$", "^"
        // or "\b"
        if (searchMode == RegularExpressionMode &&
            _searchResultCount >= 10000) {
            break;
        }
    } while (found);

    updateSearchCountLabelText();
}
  protected:
    void searchLineEditTextChanged(const QString &arg1) {
    _searchTerm = arg1;

    if (_debounceTimer.interval() != 0 && !_searchTerm.isEmpty()) {
        _debounceTimer.start();
        ui->searchDownButton->setEnabled(false);
        ui->searchUpButton->setEnabled(false);
    } else {
        performSearch();
    }
}
    void performSearch() {
    doSearchCount();
    updateSearchExtraSelections();
    doSearchDown();
}
    void updateSearchCountLabelText() {
    ui->searchCountLabel->setEnabled(true);
    ui->searchCountLabel->setText(QStringLiteral("%1/%2").arg(
        _currentSearchResult == 0 ? QChar('-')
                                  : QString::number(_currentSearchResult),
        _searchResultCount == 0 ? QChar('-')
                                : QString::number(_searchResultCount)));
}
    void setSearchSelectionColor(const QColor &color) {
    selectionColor = color;
}
  private:
    void on_modeComboBox_currentIndexChanged(int index) {
    Q_UNUSED(index)
    doSearchCount();
    doSearchDown();
}
    void on_matchCaseSensitiveButton_toggled(bool checked) {
    Q_UNUSED(checked)
    doSearchCount();
    doSearchDown();
}
};

class QMarkdownTextEdit {
    Q_OBJECT

public:
    explicit MarkdownWindow(const QRect &rect, QWidget *parent = nullptr);
    ~MarkdownWindow() = default;
    QString getMarkdownContent() const {
    return markdownEditor ? markdownEditor->toPlainText() : QString();
}
    void setMarkdownContent(const QString &content) {
    if (markdownEditor) {
        markdownEditor->setPlainText(content);
    }
}
    QRect getWindowRect() const {
    return geometry();
}
    void setWindowRect(const QRect &rect) {
    setGeometry(rect);
}
    QRect getCanvasRect() const {
    return canvasRect;
}
    void setCanvasRect(const QRect &canvasRect) {
    canvasRect = rect;
    updateScreenPosition();
}
    void updateScreenPosition() {
    // Prevent recursive updates
    if (isUpdatingPosition) return;
    isUpdatingPosition = true;

    // Debug: Log when this is called due to external changes (not during mouse movement)
    if (!dragging && !resizing) {
        // qDebug() << "MarkdownWindow::updateScreenPosition() called for window" << this << "Canvas rect:" << canvasRect;
    }

    // Get the canvas parent to access coordinate conversion methods
    if (QWidget *canvas = parentWidget()) {
        // Try to cast to InkCanvas to use the new coordinate methods
        if (InkCanvas *inkCanvas = qobject_cast<InkCanvas*>(canvas)) {
            // Use the new coordinate conversion methods
            QRect screenRect = inkCanvas->mapCanvasToWidget(canvasRect);
            // qDebug() << "MarkdownWindow::updateScreenPosition() - Canvas rect:" << canvasRect << "-> Screen rect:" << screenRect;
            // qDebug() << "  Canvas size:" << inkCanvas->getCanvasSize() << "Zoom:" << inkCanvas->getZoomFactor() << "Pan:" << inkCanvas->getPanOffset();
            setGeometry(screenRect);
        } else {
            // Fallback: use canvas coordinates directly
            // qDebug() << "MarkdownWindow::updateScreenPosition() - No InkCanvas parent, using canvas rect directly:" << canvasRect;
            setGeometry(canvasRect);
        }
    } else {
        // No parent, use canvas coordinates directly
        // qDebug() << "MarkdownWindow::updateScreenPosition() - No parent, using canvas rect directly:" << canvasRect;
        setGeometry(canvasRect);
    }

    isUpdatingPosition = false;
}
    QVariantMap serialize() const {
    QVariantMap data;
    data["canvas_x"] = canvasRect.x();
    data["canvas_y"] = canvasRect.y();
    data["canvas_width"] = canvasRect.width();
    data["canvas_height"] = canvasRect.height();
    data["content"] = getMarkdownContent();

    // Add canvas size information for debugging and consistency checks
    if (InkCanvas *inkCanvas = qobject_cast<InkCanvas*>(parentWidget())) {
        QSize canvasSize = inkCanvas->getCanvasSize();
        data["canvas_buffer_width"] = canvasSize.width();
        data["canvas_buffer_height"] = canvasSize.height();
        data["zoom_factor"] = inkCanvas->getZoomFactor();
        QPointF panOffset = inkCanvas->getPanOffset();
        data["pan_x"] = panOffset.x();
        data["pan_y"] = panOffset.y();
    }

    return data;
}
    void deserialize(const QVariantMap &data) {
    int x = data.value("canvas_x", 0).toInt();
    int y = data.value("canvas_y", 0).toInt();
    int width = data.value("canvas_width", 300).toInt();
    int height = data.value("canvas_height", 200).toInt();
    QString content = data.value("content", "# New Markdown Window").toString();

    QRect loadedRect = QRect(x, y, width, height);
    // qDebug() << "MarkdownWindow::deserialize() - Loaded canvas rect:" << loadedRect;
    // qDebug() << "  Original canvas size:" << data.value("canvas_buffer_width", -1).toInt() << "x" << data.value("canvas_buffer_height", -1).toInt();

    // Apply bounds checking to loaded coordinates
    if (InkCanvas *inkCanvas = qobject_cast<InkCanvas*>(parentWidget())) {
        QRect canvasBounds = inkCanvas->getCanvasRect();

        // Ensure window stays within canvas bounds
        int maxX = canvasBounds.width() - loadedRect.width();
        int maxY = canvasBounds.height() - loadedRect.height();

        loadedRect.setX(qMax(0, qMin(loadedRect.x(), maxX)));
        loadedRect.setY(qMax(0, qMin(loadedRect.y(), maxY)));

        if (loadedRect != QRect(x, y, width, height)) {
            // qDebug() << "  Bounds-corrected canvas rect:" << loadedRect << "Canvas bounds:" << canvasBounds;
        }
    }

    canvasRect = loadedRect;
    setMarkdownContent(content);

    // Ensure canvas connections are set up
    ensureCanvasConnections();

    updateScreenPosition();
}
    void focusEditor() {
    if (markdownEditor) {
        markdownEditor->setFocus();
    }
}
    bool isEditorFocused() const {
    return markdownEditor && markdownEditor->hasFocus();
}
    void setTransparent(bool transparent) {
    if (isTransparentState == transparent) return;

    // qDebug() << "MarkdownWindow::setTransparent(" << transparent << ") for window" << this;
    isTransparentState = transparent;

    // Apply transparency effect by changing the background style
    if (transparent) {
        // qDebug() << "Setting window background to semi-transparent";
        applyTransparentStyle();
    } else {
        // qDebug() << "Setting window background to opaque";
        applyStyle(); // Restore normal style
    }
}
    bool isTransparent() const {
    return isTransparentState;
}
    bool isValidForCanvas() const {
    if (InkCanvas *inkCanvas = qobject_cast<InkCanvas*>(parentWidget())) {
        QRect canvasBounds = inkCanvas->getCanvasRect();

        // Check if the window is at least partially within the canvas bounds
        return canvasBounds.intersects(canvasRect);
    }

    // If no canvas parent, assume valid
    return true;
}
    QString getCoordinateInfo() const {
    QString info;
    info += QString("Canvas Coordinates: (%1, %2) %3x%4\n")
            .arg(canvasRect.x()).arg(canvasRect.y())
            .arg(canvasRect.width()).arg(canvasRect.height());

    QRect screenRect = geometry();
    info += QString("Screen Coordinates: (%1, %2) %3x%4\n")
            .arg(screenRect.x()).arg(screenRect.y())
            .arg(screenRect.width()).arg(screenRect.height());

    if (InkCanvas *inkCanvas = qobject_cast<InkCanvas*>(parentWidget())) {
        QSize canvasSize = inkCanvas->getCanvasSize();
        info += QString("Canvas Size: %1x%2\n")
                .arg(canvasSize.width()).arg(canvasSize.height());

        info += QString("Zoom Factor: %1\n").arg(inkCanvas->getZoomFactor());

        QPointF panOffset = inkCanvas->getPanOffset();
        info += QString("Pan Offset: (%1, %2)\n")
                .arg(panOffset.x()).arg(panOffset.y());

        info += QString("Valid for Canvas: %1\n").arg(isValidForCanvas() ? "Yes" : "No");
    } else {
        info += "No InkCanvas parent found\n";
    }

    return info;
}
    void ensureCanvasConnections() {
    // Connect to canvas pan/zoom changes to update position
    if (InkCanvas *inkCanvas = qobject_cast<InkCanvas*>(parentWidget())) {
        // Disconnect any existing connections to avoid duplicates
        disconnect(inkCanvas, &InkCanvas::panChanged, this, &MarkdownWindow::updateScreenPosition);
        disconnect(inkCanvas, &InkCanvas::zoomChanged, this, &MarkdownWindow::updateScreenPosition);

        // Reconnect
        connect(inkCanvas, &InkCanvas::panChanged, this, &MarkdownWindow::updateScreenPosition);
        connect(inkCanvas, &InkCanvas::zoomChanged, this, &MarkdownWindow::updateScreenPosition);

        // Install event filter to handle canvas resize events (for layout changes)
        inkCanvas->installEventFilter(this);

        // qDebug() << "MarkdownWindow::ensureCanvasConnections() - Connected to canvas signals for window" << this;
    } else {
        // qDebug() << "MarkdownWindow::ensureCanvasConnections() - No InkCanvas parent found for window" << this;
    }
}
    void deleteRequested(MarkdownWindow *window);
    void contentChanged();
    void windowMoved(MarkdownWindow *window);
    void windowResized(MarkdownWindow *window);
    void focusChanged(MarkdownWindow *window, bool focused);
    void editorFocusChanged(MarkdownWindow *window, bool focused);
    void windowInteracted(MarkdownWindow *window);
  protected:
    void mousePressEvent(QMouseEvent *event) override {
    if (event->button() == Qt::LeftButton) {
        // Emit signal to indicate window interaction
        emit windowInteracted(this);

        ResizeHandle handle = getResizeHandle(event->pos());

        if (handle != None) {
            // Start resizing
            resizing = true;
            isUserInteracting = true;
            currentResizeHandle = handle;
            resizeStartPosition = event->globalPosition().toPoint();
            resizeStartRect = geometry();
        } else if (event->pos().y() < 24) { // Header area
            // Start dragging
            dragging = true;
            isUserInteracting = true;
            dragStartPosition = event->globalPosition().toPoint();
            windowStartPosition = pos();
        }
    }

    QWidget::mousePressEvent(event);
}
    void mouseMoveEvent(QMouseEvent *event) override {
    if (resizing) {
        QPoint delta = event->globalPosition().toPoint() - resizeStartPosition;
        QRect newRect = resizeStartRect;

        // Apply resize based on the stored handle
        switch (currentResizeHandle) {
            case TopLeft:
                newRect.setTopLeft(newRect.topLeft() + delta);
                break;
            case TopRight:
                newRect.setTopRight(newRect.topRight() + delta);
                break;
            case BottomLeft:
                newRect.setBottomLeft(newRect.bottomLeft() + delta);
                break;
            case BottomRight:
                newRect.setBottomRight(newRect.bottomRight() + delta);
                break;
            case Top:
                newRect.setTop(newRect.top() + delta.y());
                break;
            case Bottom:
                newRect.setBottom(newRect.bottom() + delta.y());
                break;
            case Left:
                newRect.setLeft(newRect.left() + delta.x());
                break;
            case Right:
                newRect.setRight(newRect.right() + delta.x());
                break;
            default:
                break;
        }

        // Enforce minimum size
        newRect.setSize(newRect.size().expandedTo(QSize(200, 150)));

        // Keep resized window within canvas bounds
        if (InkCanvas *inkCanvas = qobject_cast<InkCanvas*>(parentWidget())) {
            // Convert to canvas coordinates to check bounds
            QRect tempCanvasRect = inkCanvas->mapWidgetToCanvas(newRect);
            QRect canvasBounds = inkCanvas->getCanvasRect();

            // Apply canvas bounds constraints
            int maxCanvasX = canvasBounds.width() - tempCanvasRect.width();
            int maxCanvasY = canvasBounds.height() - tempCanvasRect.height();

            tempCanvasRect.setX(qMax(0, qMin(tempCanvasRect.x(), maxCanvasX)));
            tempCanvasRect.setY(qMax(0, qMin(tempCanvasRect.y(), maxCanvasY)));

            // Also ensure the window doesn't get resized beyond canvas bounds
            if (tempCanvasRect.right() > canvasBounds.width()) {
                tempCanvasRect.setWidth(canvasBounds.width() - tempCanvasRect.x());
            }
            if (tempCanvasRect.bottom() > canvasBounds.height()) {
                tempCanvasRect.setHeight(canvasBounds.height() - tempCanvasRect.y());
            }

            // Convert back to screen coordinates for the constrained geometry
            newRect = inkCanvas->mapCanvasToWidget(tempCanvasRect);
        }

        // Update the widget geometry directly during resizing
        setGeometry(newRect);

        // Convert screen coordinates back to canvas coordinates
        convertScreenToCanvasRect(newRect);
        emit windowResized(this);
    } else if (dragging) {
        QPoint delta = event->globalPosition().toPoint() - dragStartPosition;
        QPoint newPos = windowStartPosition + delta;

        // Keep window within canvas bounds (not just parent widget bounds)
        if (InkCanvas *inkCanvas = qobject_cast<InkCanvas*>(parentWidget())) {
            // Convert current position to canvas coordinates to check bounds
            QRect tempScreenRect(newPos, size());
            QRect tempCanvasRect = inkCanvas->mapWidgetToCanvas(tempScreenRect);
            QRect canvasBounds = inkCanvas->getCanvasRect();

            // Apply canvas bounds constraints
            int maxCanvasX = canvasBounds.width() - tempCanvasRect.width();
            int maxCanvasY = canvasBounds.height() - tempCanvasRect.height();

            tempCanvasRect.setX(qMax(0, qMin(tempCanvasRect.x(), maxCanvasX)));
            tempCanvasRect.setY(qMax(0, qMin(tempCanvasRect.y(), maxCanvasY)));

            // Convert back to screen coordinates for the constrained position
            QRect constrainedScreenRect = inkCanvas->mapCanvasToWidget(tempCanvasRect);
            newPos = constrainedScreenRect.topLeft();

            // Debug: Log when position is constrained
            if (tempCanvasRect != inkCanvas->mapWidgetToCanvas(QRect(windowStartPosition + delta, size()))) {
                // qDebug() << "MarkdownWindow: Constraining position to canvas bounds. Canvas rect:" << tempCanvasRect;
            }
        } else {
            // Fallback: Keep window within parent bounds
            if (parentWidget()) {
                QRect parentRect = parentWidget()->rect();
                newPos.setX(qMax(0, qMin(newPos.x(), parentRect.width() - width())));
                newPos.setY(qMax(0, qMin(newPos.y(), parentRect.height() - height())));
            }
        }

        // Update the widget position directly during dragging
        move(newPos);

        // Convert screen position back to canvas coordinates
        QRect newScreenRect(newPos, size());
        convertScreenToCanvasRect(newScreenRect);
        emit windowMoved(this);
    } else {
        // Update cursor for resize handles
        updateCursor(event->pos());
    }

    QWidget::mouseMoveEvent(event);
}
    void mouseReleaseEvent(QMouseEvent *event) override {
    if (event->button() == Qt::LeftButton) {
        resizing = false;
        dragging = false;
        isUserInteracting = false;
        currentResizeHandle = None;
    }

    QWidget::mouseReleaseEvent(event);
}
    void resizeEvent(QResizeEvent *event) override {
    QWidget::resizeEvent(event);

    // Emit windowResized if this is a user-initiated resize or if we're not updating position due to canvas changes
    if (isUserInteracting || !isUpdatingPosition) {
        emit windowResized(this);
    }
}
    void paintEvent(QPaintEvent *event) override {
    QWidget::paintEvent(event);

    // Draw resize handles
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    QColor handleColor = hasFocus() ? QColor(74, 144, 226) : QColor(180, 180, 180);
    painter.setPen(QPen(handleColor, 2));
    painter.setBrush(QBrush(handleColor));

    // Draw corner handles
    int handleSize = 6;
    painter.drawEllipse(0, 0, handleSize, handleSize); // Top-left
    painter.drawEllipse(width() - handleSize, 0, handleSize, handleSize); // Top-right
    painter.drawEllipse(0, height() - handleSize, handleSize, handleSize); // Bottom-left
    painter.drawEllipse(width() - handleSize, height() - handleSize, handleSize, handleSize); // Bottom-right
}
    void focusInEvent(QFocusEvent *event) override {
    // qDebug() << "MarkdownWindow::focusInEvent() - Window" << this << "gained focus";
    QWidget::focusInEvent(event);
    emit focusChanged(this, true);
}
    void focusOutEvent(QFocusEvent *event) override {
    // qDebug() << "MarkdownWindow::focusOutEvent() - Window" << this << "lost focus";
    QWidget::focusOutEvent(event);
    emit focusChanged(this, false);
}
    bool eventFilter(QObject *obj, QEvent *event) override {
    if (obj == markdownEditor) {
        if (event->type() == QEvent::FocusIn) {
            // qDebug() << "MarkdownWindow::eventFilter() - Editor gained focus in window" << this;
            emit editorFocusChanged(this, true);
        } else if (event->type() == QEvent::FocusOut) {
            // qDebug() << "MarkdownWindow::eventFilter() - Editor lost focus in window" << this;
            emit editorFocusChanged(this, false);
        }
    } else if (obj == parentWidget() && event->type() == QEvent::Resize) {
        // Canvas widget was resized (likely due to layout changes like showing/hiding sidebars)
        // Update our screen position to maintain canvas position
        QTimer::singleShot(0, this, [this]() {
            updateScreenPosition();
        });
    }
    return QWidget::eventFilter(obj, event);
}
  private:
    void onDeleteClicked() {
    emit deleteRequested(this);
}
    void onMarkdownTextChanged() {
    emit contentChanged();
}
  private:
    enum ResizeHandle {
        None,
        TopLeft,
        TopRight,
        BottomLeft,
        BottomRight,
        Top,
        Bottom,
        Left,
        Right
    };
    QMarkdownTextEdit *markdownEditor;
    QPushButton *deleteButton;
    QLabel *titleLabel;
    QVBoxLayout *mainLayout;
    QHBoxLayout *headerLayout;
    bool dragging = false;
    QPoint dragStartPosition;
    QPoint windowStartPosition;
    bool resizing = false;
    QPoint resizeStartPosition;
    QRect resizeStartRect;
    ResizeHandle currentResizeHandle = None;
    QRect canvasRect;
    bool isTransparentState = false;
    bool isUpdatingPosition = false;
    bool isUserInteracting = false;
    ResizeHandle getResizeHandle(const QPoint &pos) const {
    const int handleSize = 8;
    const QRect rect = this->rect();

    // Check corner handles first
    if (QRect(0, 0, handleSize, handleSize).contains(pos))
        return TopLeft;
    if (QRect(rect.width() - handleSize, 0, handleSize, handleSize).contains(pos))
        return TopRight;
    if (QRect(0, rect.height() - handleSize, handleSize, handleSize).contains(pos))
        return BottomLeft;
    if (QRect(rect.width() - handleSize, rect.height() - handleSize, handleSize, handleSize).contains(pos))
        return BottomRight;

    // Check edge handles
    if (QRect(0, 0, rect.width(), handleSize).contains(pos))
        return Top;
    if (QRect(0, rect.height() - handleSize, rect.width(), handleSize).contains(pos))
        return Bottom;
    if (QRect(0, 0, handleSize, rect.height()).contains(pos))
        return Left;
    if (QRect(rect.width() - handleSize, 0, handleSize, rect.height()).contains(pos))
        return Right;

    return None;
}
    void updateCursor(const QPoint &pos) {
    ResizeHandle handle = getResizeHandle(pos);

    switch (handle) {
        case TopLeft:
        case BottomRight:
            setCursor(Qt::SizeFDiagCursor);
            break;
        case TopRight:
        case BottomLeft:
            setCursor(Qt::SizeBDiagCursor);
            break;
        case Top:
        case Bottom:
            setCursor(Qt::SizeVerCursor);
            break;
        case Left:
        case Right:
            setCursor(Qt::SizeHorCursor);
            break;
        default:
            if (pos.y() < 24) { // Header area
                setCursor(Qt::SizeAllCursor);
            } else {
                setCursor(Qt::ArrowCursor);
            }
            break;
    }
}
    void setupUI() {
    mainLayout = new QVBoxLayout(this);
    mainLayout->setContentsMargins(2, 2, 2, 2);
    mainLayout->setSpacing(0);

    // Header with title and delete button
    headerLayout = new QHBoxLayout();
    headerLayout->setContentsMargins(4, 2, 4, 2);
    headerLayout->setSpacing(4);

    titleLabel = new QLabel("Markdown", this);
    titleLabel->setStyleSheet("font-weight: bold; color: #333;");

    deleteButton = new QPushButton("×", this);
    deleteButton->setFixedSize(16, 16);
    deleteButton->setStyleSheet(R"(
        QPushButton {
            background-color: #ff4444;
            color: white;
            border: none;
            border-radius: 8px;
            font-weight: bold;
            font-size: 10px;
        }
        QPushButton:hover {
            background-color: #ff6666;
        }
        QPushButton:pressed {
            background-color: #cc2222;
        }
    )");

    connect(deleteButton, &QPushButton::clicked, this, &MarkdownWindow::onDeleteClicked);

    headerLayout->addWidget(titleLabel);
    headerLayout->addStretch();
    headerLayout->addWidget(deleteButton);

    // Markdown editor
    markdownEditor = new QMarkdownTextEdit(this);
    markdownEditor->setPlainText(""); // Start with empty content

    connect(markdownEditor, &QMarkdownTextEdit::textChanged, this, &MarkdownWindow::onMarkdownTextChanged);

    // Connect editor focus events using event filter
    markdownEditor->installEventFilter(this);

    mainLayout->addLayout(headerLayout);
    mainLayout->addWidget(markdownEditor);
}
    void applyStyle() {
    // Detect dark mode using the same method as MainWindow
    bool isDarkMode = palette().color(QPalette::Window).lightness() < 128;

    QString backgroundColor = isDarkMode ? "#2b2b2b" : "white";
    QString borderColor = isDarkMode ? "#555555" : "#cccccc";
    QString headerBackgroundColor = isDarkMode ? "#3c3c3c" : "#f0f0f0";
    QString focusBorderColor = isDarkMode ? "#6ca9dc" : "#4a90e2";

    setStyleSheet(QString(R"(
        MarkdownWindow {
            background-color: %1;
            border: 2px solid %2;
            border-radius: 4px;
        }
        MarkdownWindow:focus {
            border-color: %3;
        }
    )").arg(backgroundColor, borderColor, focusBorderColor));

    // Style the header
    if (headerLayout && headerLayout->parentWidget()) {
        headerLayout->parentWidget()->setStyleSheet(QString(R"(
            background-color: %1;
            border-bottom: 1px solid %2;
        )").arg(headerBackgroundColor, borderColor));
    }

    // Reset the markdown editor style to default (opaque)
    if (markdownEditor) {
        markdownEditor->setStyleSheet(""); // Clear any custom styles
    }
}
    void applyTransparentStyle() {
    // Detect dark mode using the same method as MainWindow
    bool isDarkMode = palette().color(QPalette::Window).lightness() < 128;

    QString backgroundColor = isDarkMode ? "rgba(43, 43, 43, 0.3)" : "rgba(255, 255, 255, 0.3)"; // 30% opacity
    QString borderColor = isDarkMode ? "#555555" : "#cccccc";
    QString headerBackgroundColor = isDarkMode ? "rgba(60, 60, 60, 0.3)" : "rgba(240, 240, 240, 0.3)"; // 30% opacity
    QString focusBorderColor = isDarkMode ? "#6ca9dc" : "#4a90e2";

    setStyleSheet(QString(R"(
        MarkdownWindow {
            background-color: %1;
            border: 2px solid %2;
            border-radius: 4px;
        }
        MarkdownWindow:focus {
            border-color: %3;
        }
    )").arg(backgroundColor, borderColor, focusBorderColor));

    // Style the header with transparency
    if (headerLayout && headerLayout->parentWidget()) {
        headerLayout->parentWidget()->setStyleSheet(QString(R"(
            background-color: %1;
            border-bottom: 1px solid %2;
        )").arg(headerBackgroundColor, borderColor));
    }

    // Make the markdown editor background semi-transparent too
    if (markdownEditor) {
        QString editorBg = isDarkMode ? "rgba(43, 43, 43, 0.3)" : "rgba(255, 255, 255, 0.3)";
        markdownEditor->setStyleSheet(QString(R"(
            QMarkdownTextEdit {
                background-color: %1;
                border: none;
            }
        )").arg(editorBg));
    }
}
    void convertScreenToCanvasRect(const QRect &screenRect) {
    // Get the canvas parent to access coordinate conversion methods
    if (QWidget *canvas = parentWidget()) {
        // Try to cast to InkCanvas to use the new coordinate methods
        if (InkCanvas *inkCanvas = qobject_cast<InkCanvas*>(canvas)) {
            // Use the new coordinate conversion methods
            QRect oldCanvasRect = canvasRect;
            QRect newCanvasRect = inkCanvas->mapWidgetToCanvas(screenRect);

            // Store the converted canvas coordinates without bounds checking
            // Bounds checking should only happen during user interaction (dragging/resizing)
            canvasRect = newCanvasRect;
            // Only log if there was a significant change (and not during mouse movement)
            if ((canvasRect.topLeft() - oldCanvasRect.topLeft()).manhattanLength() > 10 && !dragging && !resizing) {
                // qDebug() << "MarkdownWindow::convertScreenToCanvasRect() - Screen rect:" << screenRect << "-> Canvas rect:" << canvasRect;
                // qDebug() << "  Old canvas rect:" << oldCanvasRect;
            }
        } else {
            // Fallback: use screen coordinates directly
            // qDebug() << "MarkdownWindow::convertScreenToCanvasRect() - No InkCanvas parent, using screen rect directly:" << screenRect;
            canvasRect = screenRect;
        }
    } else {
        // No parent, use screen coordinates directly
        // qDebug() << "MarkdownWindow::convertScreenToCanvasRect() - No parent, using screen rect directly:" << screenRect;
        canvasRect = screenRect;
    }
}
};

class QPushButton {
    Q_OBJECT

public:
    explicit RecentNotebooksDialog(MainWindow *mainWindow, RecentNotebooksManager *manager, QWidget *parent = nullptr);
  private:
    void onNotebookClicked() {
    QPushButton *button = qobject_cast<QPushButton*>(sender());
    if (button) {
        QString notebookPath = button->property("notebookPath").toString();
        if (!notebookPath.isEmpty() && mainWindowRef) {
            // This logic is similar to MainWindow::selectFolder but directly sets the folder.
            InkCanvas *canvas = mainWindowRef->currentCanvas(); // Get current canvas from MainWindow
            if (canvas) {
                if (canvas->isEdited()){
                     mainWindowRef->saveCurrentPage(); // Use MainWindow's save method
                }
                canvas->setSaveFolder(notebookPath);
                mainWindowRef->switchPageWithDirection(1, 1); // Use MainWindow's direction-aware switchPage method
                mainWindowRef->pageInput->setValue(1); // Update pageInput in MainWindow
                mainWindowRef->updateTabLabel(); // Update tab label in MainWindow

                // Update recent notebooks list and refresh cover page
                if (notebookManager) {
                    // Generate and save fresh cover preview
                    notebookManager->generateAndSaveCoverPreview(notebookPath, canvas);
                    // Add/update in recent list (this moves it to the top)
                    notebookManager->addRecentNotebook(notebookPath, canvas);
                }
            }
            accept(); // Close the dialog
        }
    }
}
  private:
    void setupUi() {
    QVBoxLayout *mainLayout = new QVBoxLayout(this);
    QScrollArea *scrollArea = new QScrollArea(this);
    scrollArea->setWidgetResizable(true);
    QWidget *gridContainer = new QWidget();
    gridLayout = new QGridLayout(gridContainer);
    gridLayout->setSpacing(15);

    scrollArea->setWidget(gridContainer);
    mainLayout->addWidget(scrollArea);
    setLayout(mainLayout);
}
    void populateGrid() {
    QStringList recentPaths = notebookManager->getRecentNotebooks();
    int row = 0;
    int col = 0;
    const int numCols = 4;

    for (const QString &path : recentPaths) {
        if (path.isEmpty()) continue;

        QPushButton *button = new QPushButton(this);
        button->setFixedSize(180, 180); // Larger buttons
        button->setProperty("notebookPath", path); // Store path for slot
        connect(button, &QPushButton::clicked, this, &RecentNotebooksDialog::onNotebookClicked);

        QVBoxLayout *buttonLayout = new QVBoxLayout(button);
        buttonLayout->setContentsMargins(5,5,5,5);

        QLabel *coverLabel = new QLabel(this);
        coverLabel->setFixedSize(170, 127); // 4:3 aspect ratio for cover
        coverLabel->setAlignment(Qt::AlignCenter);
        QString coverPath = notebookManager->getCoverImagePathForNotebook(path);
        if (!coverPath.isEmpty()) {
            QPixmap coverPixmap(coverPath);
            coverLabel->setPixmap(coverPixmap.scaled(coverLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
        } else {
            coverLabel->setText(tr("No Preview"));
        }
        coverLabel->setStyleSheet("border: 1px solid gray;");

        QLabel *nameLabel = new QLabel(notebookManager->getNotebookDisplayName(path), this);
        nameLabel->setAlignment(Qt::AlignCenter);
        nameLabel->setWordWrap(true);
        nameLabel->setFixedHeight(40); // Fixed height for name label

        buttonLayout->addWidget(coverLabel);
        buttonLayout->addWidget(nameLabel);
        button->setLayout(buttonLayout);

        gridLayout->addWidget(button, row, col);

        col++;
        if (col >= numCols) {
            col = 0;
            row++;
        }
    }
}
    RecentNotebooksManager *notebookManager;
    QGridLayout *gridLayout;
    MainWindow *mainWindowRef;
};

class InternalDialMode {
    Q_DECLARE_TR_FUNCTIONS(ButtonMappingHelper)
  public:
    static QString dialModeToInternalKey(InternalDialMode mode) {
    switch (mode) {
        case InternalDialMode::None: return "none";
        case InternalDialMode::PageSwitching: return "page_switching";
        case InternalDialMode::ZoomControl: return "zoom_control";
        case InternalDialMode::ThicknessControl: return "thickness_control";

        case InternalDialMode::ToolSwitching: return "tool_switching";
        case InternalDialMode::PresetSelection: return "preset_selection";
        case InternalDialMode::PanAndPageScroll: return "pan_and_page_scroll";
    }
    return "none";
}
    static QString actionToInternalKey(InternalControllerAction action) {
    switch (action) {
        case InternalControllerAction::None: return "none";
        case InternalControllerAction::ToggleFullscreen: return "toggle_fullscreen";
        case InternalControllerAction::ToggleDial: return "toggle_dial";
        case InternalControllerAction::Zoom50: return "zoom_50";
        case InternalControllerAction::ZoomOut: return "zoom_out";
        case InternalControllerAction::Zoom200: return "zoom_200";
        case InternalControllerAction::AddPreset: return "add_preset";
        case InternalControllerAction::DeletePage: return "delete_page";
        case InternalControllerAction::FastForward: return "fast_forward";
        case InternalControllerAction::OpenControlPanel: return "open_control_panel";
        case InternalControllerAction::RedColor: return "red_color";
        case InternalControllerAction::BlueColor: return "blue_color";
        case InternalControllerAction::YellowColor: return "yellow_color";
        case InternalControllerAction::GreenColor: return "green_color";
        case InternalControllerAction::BlackColor: return "black_color";
        case InternalControllerAction::WhiteColor: return "white_color";
        case InternalControllerAction::CustomColor: return "custom_color";
        case InternalControllerAction::ToggleSidebar: return "toggle_sidebar";
        case InternalControllerAction::Save: return "save";
        case InternalControllerAction::StraightLineTool: return "straight_line_tool";
        case InternalControllerAction::RopeTool: return "rope_tool";
        case InternalControllerAction::SetPenTool: return "set_pen_tool";
        case InternalControllerAction::SetMarkerTool: return "set_marker_tool";
        case InternalControllerAction::SetEraserTool: return "set_eraser_tool";
        case InternalControllerAction::TogglePdfTextSelection: return "toggle_pdf_text_selection";
        case InternalControllerAction::ToggleOutline: return "toggle_outline";
        case InternalControllerAction::ToggleBookmarks: return "toggle_bookmarks";
        case InternalControllerAction::AddBookmark: return "add_bookmark";
        case InternalControllerAction::ToggleTouchGestures: return "toggle_touch_gestures";
    }
    return "none";
}
    static InternalDialMode internalKeyToDialMode(const QString &key) {
    if (key == "none") return InternalDialMode::None;
    if (key == "page_switching") return InternalDialMode::PageSwitching;
    if (key == "zoom_control") return InternalDialMode::ZoomControl;
    if (key == "thickness_control") return InternalDialMode::ThicknessControl;

    if (key == "tool_switching") return InternalDialMode::ToolSwitching;
    if (key == "preset_selection") return InternalDialMode::PresetSelection;
    if (key == "pan_and_page_scroll") return InternalDialMode::PanAndPageScroll;
    return InternalDialMode::None;
}
    static InternalControllerAction internalKeyToAction(const QString &key) {
    if (key == "none") return InternalControllerAction::None;
    if (key == "toggle_fullscreen") return InternalControllerAction::ToggleFullscreen;
    if (key == "toggle_dial") return InternalControllerAction::ToggleDial;
    if (key == "zoom_50") return InternalControllerAction::Zoom50;
    if (key == "zoom_out") return InternalControllerAction::ZoomOut;
    if (key == "zoom_200") return InternalControllerAction::Zoom200;
    if (key == "add_preset") return InternalControllerAction::AddPreset;
    if (key == "delete_page") return InternalControllerAction::DeletePage;
    if (key == "fast_forward") return InternalControllerAction::FastForward;
    if (key == "open_control_panel") return InternalControllerAction::OpenControlPanel;
    if (key == "red_color") return InternalControllerAction::RedColor;
    if (key == "blue_color") return InternalControllerAction::BlueColor;
    if (key == "yellow_color") return InternalControllerAction::YellowColor;
    if (key == "green_color") return InternalControllerAction::GreenColor;
    if (key == "black_color") return InternalControllerAction::BlackColor;
    if (key == "white_color") return InternalControllerAction::WhiteColor;
    if (key == "custom_color") return InternalControllerAction::CustomColor;
    if (key == "toggle_sidebar") return InternalControllerAction::ToggleSidebar;
    if (key == "save") return InternalControllerAction::Save;
    if (key == "straight_line_tool") return InternalControllerAction::StraightLineTool;
    if (key == "rope_tool") return InternalControllerAction::RopeTool;
    if (key == "set_pen_tool") return InternalControllerAction::SetPenTool;
    if (key == "set_marker_tool") return InternalControllerAction::SetMarkerTool;
    if (key == "set_eraser_tool") return InternalControllerAction::SetEraserTool;
    if (key == "toggle_pdf_text_selection") return InternalControllerAction::TogglePdfTextSelection;
    if (key == "toggle_outline") return InternalControllerAction::ToggleOutline;
    if (key == "toggle_bookmarks") return InternalControllerAction::ToggleBookmarks;
    if (key == "add_bookmark") return InternalControllerAction::AddBookmark;
    if (key == "toggle_touch_gestures") return InternalControllerAction::ToggleTouchGestures;
    return InternalControllerAction::None;
}
    static QStringList getTranslatedDialModes() {
    return {
        tr("None"),
        tr("Page Switching"),
        tr("Zoom Control"),
        tr("Thickness Control"),

        tr("Tool Switching"),
        tr("Preset Selection"),
        tr("Pan and Page Scroll")
    };
}
    static QStringList getTranslatedActions() {
    return {
        tr("None"),
        tr("Toggle Fullscreen"),
        tr("Toggle Dial"),
        tr("Zoom 50%"),
        tr("Zoom Out"),
        tr("Zoom 200%"),
        tr("Add Preset"),
        tr("Delete Page"),
        tr("Fast Forward"),
        tr("Open Control Panel"),
        tr("Red"),
        tr("Blue"),
        tr("Yellow"),
        tr("Green"),
        tr("Black"),
        tr("White"),
        tr("Custom Color"),
        tr("Toggle Sidebar"),
        tr("Save"),
        tr("Straight Line Tool"),
        tr("Rope Tool"),
        tr("Set Pen Tool"),
        tr("Set Marker Tool"),
        tr("Set Eraser Tool"),
        tr("Toggle PDF Text Selection"),
        tr("Toggle PDF Outline"),
        tr("Toggle Bookmarks"),
        tr("Add/Remove Bookmark"),
        tr("Toggle Touch Gestures")
    };
}
    static QStringList getTranslatedButtons() {
    return {
        tr("Left Shoulder"),
        tr("Right Shoulder"),
        tr("Paddle 2"),
        tr("Paddle 4"),
        tr("Y Button"),
        tr("A Button"),
        tr("B Button"),
        tr("X Button"),
        tr("Left Stick"),
        tr("Start Button"),
        tr("Guide Button")
    };
}
    static QStringList getInternalDialModeKeys() {
    return {
        "none",
        "page_switching",
        "zoom_control",
        "thickness_control",

        "tool_switching",
        "preset_selection",
        "pan_and_page_scroll"
    };
}
    static QStringList getInternalActionKeys() {
    return {
        "none",
        "toggle_fullscreen",
        "toggle_dial",
        "zoom_50",
        "zoom_out",
        "zoom_200",
        "add_preset",
        "delete_page",
        "fast_forward",
        "open_control_panel",
        "red_color",
        "blue_color",
        "yellow_color",
        "green_color",
        "black_color",
        "white_color",
        "custom_color",
        "toggle_sidebar",
        "save",
        "straight_line_tool",
        "rope_tool",
        "set_pen_tool",
        "set_marker_tool",
        "set_eraser_tool",
        "toggle_pdf_text_selection",
        "toggle_outline",
        "toggle_bookmarks",
        "add_bookmark",
        "toggle_touch_gestures"
    };
}
    static QStringList getInternalButtonKeys() {
    return {
        "LEFTSHOULDER",
        "RIGHTSHOULDER",
        "PADDLE2",
        "PADDLE4",
        "Y",
        "A",
        "B",
        "X",
        "LEFTSTICK",
        "START",
        "GUIDE"
    };
}
    static QString displayToInternalKey(const QString &displayString, bool isDialMode) {
    if (isDialMode) {
        QStringList translated = getTranslatedDialModes();
        QStringList internal = getInternalDialModeKeys();
        int index = translated.indexOf(displayString);
        return (index >= 0) ? internal[index] : "none";
    } else {
        QStringList translated = getTranslatedActions();
        QStringList internal = getInternalActionKeys();
        int index = translated.indexOf(displayString);
        return (index >= 0) ? internal[index] : "none";
    }
}
    static QString internalKeyToDisplay(const QString &internalKey, bool isDialMode) {
    if (isDialMode) {
        QStringList translated = getTranslatedDialModes();
        QStringList internal = getInternalDialModeKeys();
        int index = internal.indexOf(internalKey);
        return (index >= 0) ? translated[index] : tr("None");
    } else {
        QStringList translated = getTranslatedActions();
        QStringList internal = getInternalActionKeys();
        int index = internal.indexOf(internalKey);
        return (index >= 0) ? translated[index] : tr("None");
    }
}
};

class KeyCaptureDialog {
    Q_OBJECT

public:
    explicit KeyCaptureDialog(QWidget *parent = nullptr);
  protected:
    void keyPressEvent(QKeyEvent *event) override {
    // Build key sequence string
    QStringList modifiers;

    if (event->modifiers() & Qt::ControlModifier) modifiers << "Ctrl";
    if (event->modifiers() & Qt::ShiftModifier) modifiers << "Shift";
    if (event->modifiers() & Qt::AltModifier) modifiers << "Alt";
    if (event->modifiers() & Qt::MetaModifier) modifiers << "Meta";

    // Don't capture modifier keys alone
    if (event->key() == Qt::Key_Control || event->key() == Qt::Key_Shift ||
        event->key() == Qt::Key_Alt || event->key() == Qt::Key_Meta) {
        return;
    }

    // Don't capture Escape (let it close the dialog)
    if (event->key() == Qt::Key_Escape) {
        QDialog::keyPressEvent(event);
        return;
    }

    QString keyString = QKeySequence(event->key()).toString();

    if (!modifiers.isEmpty()) {
        capturedSequence = modifiers.join("+") + "+" + keyString;
    } else {
        capturedSequence = keyString;
    }

    updateDisplay();
    event->accept();
}
  private:
    void clearSequence() {
    capturedSequence.clear();
    updateDisplay();
    setFocus();  // Return focus to dialog for key capture
}
  private:
    QLabel *instructionLabel;
    QLabel *capturedLabel;
    QPushButton *clearButton;
    QPushButton *okButton;
    QPushButton *cancelButton;
    QString capturedSequence;
    void updateDisplay() {
    if (capturedSequence.isEmpty()) {
        capturedLabel->setText(tr("(No key captured yet)"));
        okButton->setEnabled(false);
    } else {
        capturedLabel->setText(capturedSequence);
        okButton->setEnabled(true);
    }
}
};

class MainWindow {
    Q_OBJECT

   public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow() { delete ui; }
  private:
    Ui::MainWindow *ui;
};

#ifndef TOOLTYPE_H
#define TOOLTYPE_H

// ✅ Now it can be used anywhere
enum class ToolType {
    Pen,
    Marker,
    Eraser
};

#endif // TOOLTYPE_H

This code segment functions to: Initialize application subsystems for cross-platform compatibility and input device management, configure internationalization settings based on system locale, process command-line arguments to determine file type handling, and launch the graphical interface with appropriate resource loading based on input parameters.

```cpp
[TASK_BEGIN]
int main(int argc, char *argv[]) {
#ifdef _WIN32
    SetConsoleOutputCP(65001);  // Set console output to UTF-8
#endif

    QApplication app(argc, argv);

    // Setup localization
    QTranslator translator;
    QString lang = QLocale::system().name();
    if (translator.load("languages/lang_" + lang)) {
        app.installTranslator(&translator);
    }

    MainWindow window;
    window.show();

    return app.exec();
}
[TASK_END]
```

int main(int argc, char *argv[]) {
#ifdef _WIN32
    SetConsoleOutputCP(65001);
#endif
    QApplication app(argc, argv);
    QTranslator translator;
    QString lang = QLocale::system().name();
    if (translator.load("languages/lang_" + lang)) {
        app.installTranslator(&translator);
    }
    MainWindow window;
    window.show();
    return app.exec();
}