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) {
            InkCanvas *canvas = mainWindowRef->currentCanvas();
            if (canvas) {
                if (canvas->isEdited()){
                     mainWindowRef->saveCurrentPage();
                }
                canvas->setSaveFolder(notebookPath);
                mainWindowRef->switchPageWithDirection(1, 1);
                mainWindowRef->pageInput->setValue(1);
                mainWindowRef->updateTabLabel();
                if (notebookManager) {
                    notebookManager->generateAndSaveCoverPreview(notebookPath, canvas);
                    notebookManager->addRecentNotebook(notebookPath, canvas);
                }
            }
            accept();
        }
    }
}
  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);
        button->setProperty("notebookPath", path);
        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);
        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);
        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 QTreeWidgetItem {
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    virtual ~MainWindow() {

    saveButtonMappings();  // ✅ Save on exit, as backup
    delete canvas;
}
    int getCurrentPageForCanvas(InkCanvas *canvas) {
    return pageMap.contains(canvas) ? pageMap[canvas] : 0;
}
    bool lowResPreviewEnabled = true;
    void setLowResPreviewEnabled(bool enabled) {
    lowResPreviewEnabled = enabled;

    QSettings settings("SpeedyNote", "App");
    settings.setValue("lowResPreviewEnabled", enabled);
}
    bool isLowResPreviewEnabled() const {
    return lowResPreviewEnabled;
}
    bool areBenchmarkControlsVisible() const {
    return benchmarkButton->isVisible() && benchmarkLabel->isVisible();
}
    void setBenchmarkControlsVisible(bool visible) {
    benchmarkButton->setVisible(visible);
    benchmarkLabel->setVisible(visible);
}
    bool zoomButtonsVisible = true;
    bool areZoomButtonsVisible() const {
    return zoomButtonsVisible;
}
    void setZoomButtonsVisible(bool visible) {
    zoom50Button->setVisible(visible);
    dezoomButton->setVisible(visible);
    zoom200Button->setVisible(visible);

    QSettings settings("SpeedyNote", "App");
    settings.setValue("zoomButtonsVisible", visible);

    // Update zoomButtonsVisible flag and trigger layout update
    zoomButtonsVisible = visible;

    // Trigger layout update to adjust responsive thresholds
    if (layoutUpdateTimer) {
        layoutUpdateTimer->stop();
        layoutUpdateTimer->start(50); // Quick update for settings change
    } else {
        updateToolbarLayout(); // Direct update if no timer exists yet
    }
}
    bool scrollOnTopEnabled = false;
    bool isScrollOnTopEnabled() const {
    return scrollOnTopEnabled;
}
    void setScrollOnTopEnabled(bool enabled) {
    scrollOnTopEnabled = enabled;

    QSettings settings("SpeedyNote", "App");
    settings.setValue("scrollOnTopEnabled", enabled);
}
    bool touchGesturesEnabled = true;
    bool areTouchGesturesEnabled() const {
    return touchGesturesEnabled;
}
    void setTouchGesturesEnabled(bool enabled) {
    touchGesturesEnabled = enabled;

    // Apply to all canvases
    for (int i = 0; i < canvasStack->count(); ++i) {
        InkCanvas *canvas = qobject_cast<InkCanvas*>(canvasStack->widget(i));
        if (canvas) {
            canvas->setTouchGesturesEnabled(enabled);
        }
    }

    QSettings settings("SpeedyNote", "App");
    settings.setValue("touchGesturesEnabled", enabled);
}
    QColor customAccentColor;
    bool useCustomAccentColor = false;
    QColor getAccentColor() const {
    if (useCustomAccentColor && customAccentColor.isValid()) {
        return customAccentColor;
    }

    // Return system accent color
    QPalette palette = QGuiApplication::palette();
    return palette.highlight().color();
}
    void setCustomAccentColor(const QColor &color) {
    if (customAccentColor != color) {
        customAccentColor = color;
        saveThemeSettings();
        // Always update theme if custom accent color is enabled
        if (useCustomAccentColor) {
            updateTheme();
        }
    }
}
    void setUseCustomAccentColor(bool use) {
    if (useCustomAccentColor != use) {
        useCustomAccentColor = use;
        updateTheme();
        saveThemeSettings();
    }
}
    void setUseBrighterPalette(bool use) {
    if (useBrighterPalette != use) {
        useBrighterPalette = use;

        // Update all colors - call updateColorPalette which handles null checks
        updateColorPalette();

        // Save preference
        QSettings settings("SpeedyNote", "App");
        settings.setValue("useBrighterPalette", useBrighterPalette);
    }
}
    QColor getDefaultPenColor() {
    return isDarkMode() ? Qt::white : Qt::black;
}
    SDLControllerManager *controllerManager = nullptr;
    QThread *controllerThread = nullptr;
    QString getHoldMapping(const QString &buttonName) {
    return buttonHoldMapping.value(buttonName, "None");
}
    QString getPressMapping(const QString &buttonName) {
    return buttonPressMapping.value(buttonName, "None");
}
    void saveButtonMappings() {
    QSettings settings("SpeedyNote", "App");

    settings.beginGroup("ButtonHoldMappings");
    for (auto it = buttonHoldMapping.begin(); it != buttonHoldMapping.end(); ++it) {
        settings.setValue(it.key(), it.value());
    }
    settings.endGroup();

    settings.beginGroup("ButtonPressMappings");
    for (auto it = buttonPressMapping.begin(); it != buttonPressMapping.end(); ++it) {
        settings.setValue(it.key(), it.value());
    }
    settings.endGroup();
}
    void loadButtonMappings() {
    QSettings settings("SpeedyNote", "App");

    // First, check if we need to migrate old settings
    migrateOldButtonMappings();

    settings.beginGroup("ButtonHoldMappings");
    QStringList holdKeys = settings.allKeys();
    for (const QString &key : holdKeys) {
        buttonHoldMapping[key] = settings.value(key, "none").toString();
    }
    settings.endGroup();

    settings.beginGroup("ButtonPressMappings");
    QStringList pressKeys = settings.allKeys();
    for (const QString &key : pressKeys) {
        QString value = settings.value(key, "none").toString();
        buttonPressMapping[key] = value;

        // ✅ Convert internal key to action enum
        buttonPressActionMapping[key] = stringToAction(value);
    }
    settings.endGroup();
}
    void setHoldMapping(const QString &buttonName, const QString &dialMode) {
    buttonHoldMapping[buttonName] = dialMode;
}
    void setPressMapping(const QString &buttonName, const QString &action) {
    buttonPressMapping[buttonName] = action;
    buttonPressActionMapping[buttonName] = stringToAction(action);  // ✅ THIS LINE WAS MISSING
}
    DialMode dialModeFromString(const QString &mode) {
    // Convert internal key to our existing DialMode enum
    InternalDialMode internalMode = ButtonMappingHelper::internalKeyToDialMode(mode);

    switch (internalMode) {
        case InternalDialMode::None: return PageSwitching; // Default fallback
        case InternalDialMode::PageSwitching: return PageSwitching;
        case InternalDialMode::ZoomControl: return ZoomControl;
        case InternalDialMode::ThicknessControl: return ThicknessControl;

        case InternalDialMode::ToolSwitching: return ToolSwitching;
        case InternalDialMode::PresetSelection: return PresetSelection;
        case InternalDialMode::PanAndPageScroll: return PanAndPageScroll;
    }
    return PanAndPageScroll;  // Default fallback
}
    void importNotebookFromFile(const QString &packageFile) {

    QString destDir = QFileDialog::getExistingDirectory(this, tr("Select Working Directory for Notebook"));

    if (destDir.isEmpty()) {
        QMessageBox::warning(this, tr("Import Cancelled"), tr("No directory selected. Notebook will not be opened."));
        return;
    }

    InkCanvas *canvas = currentCanvas();
    if (!canvas) return;

    canvas->importNotebookTo(packageFile, destDir);

    // Change saveFolder in InkCanvas
    canvas->setSaveFolder(destDir);
    canvas->loadPage(0);
    updateZoom(); // ✅ Update zoom and pan range after importing notebook
}
    void openPdfFile(const QString &pdfPath) {
    // Check if the PDF file exists
    if (!QFile::exists(pdfPath)) {
        QMessageBox::warning(this, tr("File Not Found"), tr("The PDF file could not be found:\n%1").arg(pdfPath));
        return;
    }

    // First, check if there's already a valid notebook folder for this PDF
    QString existingFolderPath;
    if (PdfOpenDialog::hasValidNotebookFolder(pdfPath, existingFolderPath)) {
        // Found a valid notebook folder, open it directly without showing dialog
        InkCanvas *canvas = currentCanvas();
        if (!canvas) return;

        // Save current work if edited
        if (canvas->isEdited()) {
            saveCurrentPage();
        }

        // Set the existing folder as save folder
        canvas->setSaveFolder(existingFolderPath);

        // Load the PDF
        canvas->loadPdf(pdfPath);

        // Update tab label and recent notebooks
        updateTabLabel();
        if (recentNotebooksManager) {
            recentNotebooksManager->addRecentNotebook(existingFolderPath, canvas);
        }

        // Switch to page 1 and update UI
        switchPageWithDirection(1, 1);
        pageInput->setValue(1);
        updateZoom();
        updatePanRange();

        return; // Exit early, no need to show dialog
    }

    // No valid notebook folder found, show the dialog with options
    PdfOpenDialog dialog(pdfPath, this);
    dialog.exec();

    PdfOpenDialog::Result result = dialog.getResult();
    QString selectedFolder = dialog.getSelectedFolder();

    if (result == PdfOpenDialog::Cancel) {
        return; // User cancelled, do nothing
    }

    InkCanvas *canvas = currentCanvas();
    if (!canvas) return;

    // Save current work if edited
    if (canvas->isEdited()) {
        saveCurrentPage();
    }

    if (result == PdfOpenDialog::CreateNewFolder) {
        // Set the new folder as save folder
        canvas->setSaveFolder(selectedFolder);

        // Load the PDF
        canvas->loadPdf(pdfPath);

        // Update tab label and recent notebooks
        updateTabLabel();
        if (recentNotebooksManager) {
            recentNotebooksManager->addRecentNotebook(selectedFolder, canvas);
        }

        // Switch to page 1 and update UI
        switchPageWithDirection(1, 1);
        pageInput->setValue(1);
        updateZoom();
        updatePanRange();

    } else if (result == PdfOpenDialog::UseExistingFolder) {
        // Check if the existing folder is linked to the same PDF
        QString metadataFile = selectedFolder + "/.pdf_path.txt";
        bool isLinkedToSamePdf = false;

        if (QFile::exists(metadataFile)) {
            QFile file(metadataFile);
            if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
                QTextStream in(&file);
                QString existingPdfPath = in.readLine().trimmed();
                file.close();

                // Compare absolute paths
                QFileInfo existingInfo(existingPdfPath);
                QFileInfo newInfo(pdfPath);
                isLinkedToSamePdf = (existingInfo.absoluteFilePath() == newInfo.absoluteFilePath());
            }
        }

        if (!isLinkedToSamePdf && QFile::exists(metadataFile)) {
            // Folder is linked to a different PDF, ask user what to do
            QMessageBox::StandardButton reply = QMessageBox::question(
                this,
                tr("Different PDF Linked"),
                tr("This notebook folder is already linked to a different PDF file.\n\nDo you want to replace the link with the new PDF?"),
                QMessageBox::Yes | QMessageBox::No
            );

            if (reply == QMessageBox::No) {
                return; // User chose not to replace
            }
        }

        // Set the existing folder as save folder
        canvas->setSaveFolder(selectedFolder);

        // Load the PDF
        canvas->loadPdf(pdfPath);

        // Update tab label and recent notebooks
        updateTabLabel();
        if (recentNotebooksManager) {
            recentNotebooksManager->addRecentNotebook(selectedFolder, canvas);
        }

        // Switch to page 1 and update UI
        switchPageWithDirection(1, 1);
        pageInput->setValue(1);
        updateZoom();
        updatePanRange();
    }
}
    void setPdfDPI(int dpi) {
    if (dpi != pdfRenderDPI) {
        pdfRenderDPI = dpi;
        savePdfDPI(dpi);

        // Apply immediately to current canvas if needed
        if (currentCanvas()) {
            currentCanvas()->setPDFRenderDPI(dpi);
            currentCanvas()->clearPdfCache();
            currentCanvas()->loadPdfPage(getCurrentPageForCanvas(currentCanvas()));  // Optional: add this if needed
            updateZoom();
            updatePanRange();
        }
    }
}
    void savePdfDPI(int dpi) {
    QSettings settings("SpeedyNote", "App");
    settings.setValue("pdfRenderDPI", dpi);
}
    void saveDefaultBackgroundSettings(BackgroundStyle style, QColor color, int density) {
    QSettings settings("SpeedyNote", "App");
    settings.setValue("defaultBackgroundStyle", static_cast<int>(style));
    settings.setValue("defaultBackgroundColor", color.name());
    settings.setValue("defaultBackgroundDensity", density);
}
    void loadDefaultBackgroundSettings(BackgroundStyle &style, QColor &color, int &density) {
    QSettings settings("SpeedyNote", "App");
    style = static_cast<BackgroundStyle>(settings.value("defaultBackgroundStyle", static_cast<int>(BackgroundStyle::Grid)).toInt());
    color = QColor(settings.value("defaultBackgroundColor", "#FFFFFF").toString());
    density = settings.value("defaultBackgroundDensity", 30).toInt();

    // Ensure valid values
    if (!color.isValid()) color = Qt::white;
    if (density < 10) density = 10;
    if (density > 200) density = 200;
}
    void saveThemeSettings() {
    QSettings settings("SpeedyNote", "App");
    settings.setValue("useCustomAccentColor", useCustomAccentColor);
    if (customAccentColor.isValid()) {
        settings.setValue("customAccentColor", customAccentColor.name());
    }
    settings.setValue("useBrighterPalette", useBrighterPalette);
}
    void loadThemeSettings() {
    QSettings settings("SpeedyNote", "App");
    useCustomAccentColor = settings.value("useCustomAccentColor", false).toBool();
    QString colorName = settings.value("customAccentColor", "#0078D4").toString();
    customAccentColor = QColor(colorName);
    useBrighterPalette = settings.value("useBrighterPalette", false).toBool();

    // Ensure valid values
    if (!customAccentColor.isValid()) {
        customAccentColor = QColor("#0078D4"); // Default blue
    }

    // Apply theme immediately after loading
    updateTheme();
}
    void updateTheme() {
    // Update control bar background color
    QColor accentColor = getAccentColor();
    if (controlBar) {
        controlBar->setStyleSheet(QString(R"(
        QWidget#controlBar {
            background-color: %1;
            }
        )").arg(accentColor.name()));
    }

    // Update dial background color
    if (pageDial) {
        pageDial->setStyleSheet(QString(R"(
        QDial {
            background-color: %1;
            }
        )").arg(accentColor.name()));
    }

    // Update add tab button styling
    if (addTabButton) {
        bool darkMode = isDarkMode();
        QString buttonBgColor = darkMode ? "rgba(80, 80, 80, 255)" : "rgba(220, 220, 220, 255)";
        QString buttonHoverColor = darkMode ? "rgba(90, 90, 90, 255)" : "rgba(200, 200, 200, 255)";
        QString buttonPressColor = darkMode ? "rgba(70, 70, 70, 255)" : "rgba(180, 180, 180, 255)";
        QString borderColor = darkMode ? "rgba(100, 100, 100, 255)" : "rgba(180, 180, 180, 255)";

        addTabButton->setStyleSheet(QString(R"(
            QPushButton {
                background-color: %1;
                border: 1px solid %2;
                border-radius: 12px;
                margin: 2px;
            }
            QPushButton:hover {
                background-color: %3;
            }
            QPushButton:pressed {
                background-color: %4;
            }
        )").arg(buttonBgColor).arg(borderColor).arg(buttonHoverColor).arg(buttonPressColor));
    }

    // Update PDF outline sidebar styling
    if (outlineSidebar && outlineTree) {
        bool darkMode = isDarkMode();
        QString bgColor = darkMode ? "rgba(45, 45, 45, 255)" : "rgba(250, 250, 250, 255)";
        QString borderColor = darkMode ? "rgba(80, 80, 80, 255)" : "rgba(200, 200, 200, 255)";
        QString textColor = darkMode ? "#E0E0E0" : "#333";
        QString hoverColor = darkMode ? "rgba(60, 60, 60, 255)" : "rgba(240, 240, 240, 255)";
        QString selectedColor = QString("rgba(%1, %2, %3, 100)").arg(accentColor.red()).arg(accentColor.green()).arg(accentColor.blue());

        outlineSidebar->setStyleSheet(QString(R"(
            QWidget {
                background-color: %1;
                border-right: 1px solid %2;
            }
            QLabel {
                color: %3;
                background: transparent;
            }
        )").arg(bgColor).arg(borderColor).arg(textColor));

        outlineTree->setStyleSheet(QString(R"(
            QTreeWidget {
                background-color: %1;
                border: none;
                color: %2;
                outline: none;
            }
            QTreeWidget::item {
                padding: 4px;
                border: none;
            }
            QTreeWidget::item:hover {
                background-color: %3;
            }
            QTreeWidget::item:selected {
                background-color: %4;
                color: %2;
            }
            QTreeWidget::branch {
                background: transparent;
            }
            QTreeWidget::branch:has-children:!has-siblings:closed,
            QTreeWidget::branch:closed:has-children:has-siblings {
                border-image: none;
                image: url(:/resources/icons/down_arrow.png);
            }
            QTreeWidget::branch:open:has-children:!has-siblings,
            QTreeWidget::branch:open:has-children:has-siblings {
                border-image: none;
                image: url(:/resources/icons/up_arrow.png);
            }
            QScrollBar:vertical {
                background: rgba(200, 200, 200, 80);
                border: none;
                margin: 0px;
                width: 16px !important;
                max-width: 16px !important;
            }
            QScrollBar:vertical:hover {
                background: rgba(200, 200, 200, 120);
            }
            QScrollBar::handle:vertical {
                background: rgba(100, 100, 100, 150);
                border-radius: 2px;
                min-height: 120px;
            }
            QScrollBar::handle:vertical:hover {
                background: rgba(80, 80, 80, 210);
            }
            QScrollBar::add-line:vertical,
            QScrollBar::sub-line:vertical {
                width: 0px;
                height: 0px;
                background: none;
                border: none;
            }
            QScrollBar::add-page:vertical,
            QScrollBar::sub-page:vertical {
                background: transparent;
            }
        )").arg(bgColor).arg(textColor).arg(hoverColor).arg(selectedColor));

        // Apply same styling to bookmarks tree
        bookmarksTree->setStyleSheet(QString(R"(
            QTreeWidget {
                background-color: %1;
                border: none;
                color: %2;
                outline: none;
            }
            QTreeWidget::item {
                padding: 2px;
                border: none;
                min-height: 26px;
            }
            QTreeWidget::item:hover {
                background-color: %3;
            }
            QTreeWidget::item:selected {
                background-color: %4;
                color: %2;
            }
            QScrollBar:vertical {
                background: rgba(200, 200, 200, 80);
                border: none;
                margin: 0px;
                width: 16px !important;
                max-width: 16px !important;
            }
            QScrollBar:vertical:hover {
                background: rgba(200, 200, 200, 120);
            }
            QScrollBar::handle:vertical {
                background: rgba(100, 100, 100, 150);
                border-radius: 2px;
                min-height: 120px;
            }
            QScrollBar::handle:vertical:hover {
                background: rgba(80, 80, 80, 210);
            }
            QScrollBar::add-line:vertical,
            QScrollBar::sub-line:vertical {
                width: 0px;
                height: 0px;
                background: none;
                border: none;
            }
            QScrollBar::add-page:vertical,
            QScrollBar::sub-page:vertical {
                background: transparent;
            }
        )").arg(bgColor).arg(textColor).arg(hoverColor).arg(selectedColor));
    }

    // Update horizontal tab bar styling with accent color
    if (tabList) {
        bool darkMode = isDarkMode();
        QString bgColor = darkMode ? "rgba(60, 60, 60, 255)" : "rgba(240, 240, 240, 255)";
        QString itemBgColor = darkMode ? "rgba(80, 80, 80, 255)" : "rgba(220, 220, 220, 255)";
        QString selectedBgColor = darkMode ? "rgba(45, 45, 45, 255)" : "white";
        QString borderColor = darkMode ? "rgba(100, 100, 100, 255)" : "rgba(180, 180, 180, 255)";
        QString hoverBgColor = darkMode ? "rgba(90, 90, 90, 255)" : "rgba(230, 230, 230, 255)";

        tabList->setStyleSheet(QString(R"(
        QListWidget {
            background-color: %1;
            border: none;
            border-bottom: 2px solid %2;
            outline: none;
        }
        QListWidget::item {
            background-color: %3;
            border: 1px solid %4;
            border-bottom: none;
            margin-right: 1px;
            margin-top: 2px;
            padding: 0px;
            min-width: 80px;
            max-width: 120px;
        }
        QListWidget::item:selected {
            background-color: %5;
            border: 1px solid %4;
            border-bottom: 2px solid %2;
            margin-top: 1px;
        }
        QListWidget::item:hover:!selected {
            background-color: %6;
        }
        QScrollBar:horizontal {
            background: %1;
            height: 8px;
            border: none;
            margin: 0px;
            border-top: 1px solid %4;
        }
        QScrollBar::handle:horizontal {
            background: rgba(150, 150, 150, 120);
            border-radius: 4px;
            min-width: 20px;
            margin: 1px;
        }
        QScrollBar::handle:horizontal:hover {
            background: rgba(120, 120, 120, 200);
        }
        QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {
            width: 0px;
            height: 0px;
            background: none;
            border: none;
        }
        QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {
            background: transparent;
        }
        )").arg(bgColor)
           .arg(accentColor.name())
           .arg(itemBgColor)
           .arg(borderColor)
           .arg(selectedBgColor)
           .arg(hoverBgColor));
    }

    // Force icon reload for all buttons that use themed icons
    if (loadPdfButton) loadPdfButton->setIcon(loadThemedIcon("pdf"));
    if (clearPdfButton) clearPdfButton->setIcon(loadThemedIcon("pdfdelete"));
    if (pdfTextSelectButton) pdfTextSelectButton->setIcon(loadThemedIcon("ibeam"));
    if (exportNotebookButton) exportNotebookButton->setIcon(loadThemedIcon("export"));
    if (importNotebookButton) importNotebookButton->setIcon(loadThemedIcon("import"));
    if (benchmarkButton) benchmarkButton->setIcon(loadThemedIcon("benchmark"));
    if (toggleTabBarButton) toggleTabBarButton->setIcon(loadThemedIcon("tabs"));
    if (toggleOutlineButton) toggleOutlineButton->setIcon(loadThemedIcon("outline"));
    if (toggleBookmarksButton) toggleBookmarksButton->setIcon(loadThemedIcon("bookmark"));
    if (toggleBookmarkButton) toggleBookmarkButton->setIcon(loadThemedIcon("star"));
    if (selectFolderButton) selectFolderButton->setIcon(loadThemedIcon("folder"));
    if (saveButton) saveButton->setIcon(loadThemedIcon("save"));
    if (saveAnnotatedButton) saveAnnotatedButton->setIcon(loadThemedIcon("saveannotated"));
    if (fullscreenButton) fullscreenButton->setIcon(loadThemedIcon("fullscreen"));
    if (backgroundButton) backgroundButton->setIcon(loadThemedIcon("background"));
    if (straightLineToggleButton) straightLineToggleButton->setIcon(loadThemedIcon("straightLine"));
    if (ropeToolButton) ropeToolButton->setIcon(loadThemedIcon("rope"));
    if (markdownButton) markdownButton->setIcon(loadThemedIcon("markdown"));
    if (deletePageButton) deletePageButton->setIcon(loadThemedIcon("trash"));
    if (zoomButton) zoomButton->setIcon(loadThemedIcon("zoom"));
    if (dialToggleButton) dialToggleButton->setIcon(loadThemedIcon("dial"));
    if (fastForwardButton) fastForwardButton->setIcon(loadThemedIcon("fastforward"));
    if (jumpToPageButton) jumpToPageButton->setIcon(loadThemedIcon("bookpage"));
    if (thicknessButton) thicknessButton->setIcon(loadThemedIcon("thickness"));
    if (btnPageSwitch) btnPageSwitch->setIcon(loadThemedIcon("bookpage"));
    if (btnZoom) btnZoom->setIcon(loadThemedIcon("zoom"));
    if (btnThickness) btnThickness->setIcon(loadThemedIcon("thickness"));
    if (btnTool) btnTool->setIcon(loadThemedIcon("pen"));
    if (btnPresets) btnPresets->setIcon(loadThemedIcon("preset"));
    if (btnPannScroll) btnPannScroll->setIcon(loadThemedIcon("scroll"));
    if (addPresetButton) addPresetButton->setIcon(loadThemedIcon("savepreset"));
    if (openControlPanelButton) openControlPanelButton->setIcon(loadThemedIcon("settings"));
    if (openRecentNotebooksButton) openRecentNotebooksButton->setIcon(loadThemedIcon("recent"));
    if (penToolButton) penToolButton->setIcon(loadThemedIcon("pen"));
    if (markerToolButton) markerToolButton->setIcon(loadThemedIcon("marker"));
    if (eraserToolButton) eraserToolButton->setIcon(loadThemedIcon("eraser"));

    // Update button styles with new theme
    bool darkMode = isDarkMode();
    QString newButtonStyle = createButtonStyle(darkMode);

    // Update all buttons that use the buttonStyle
    if (loadPdfButton) loadPdfButton->setStyleSheet(newButtonStyle);
    if (clearPdfButton) clearPdfButton->setStyleSheet(newButtonStyle);
    if (pdfTextSelectButton) pdfTextSelectButton->setStyleSheet(newButtonStyle);
    if (exportNotebookButton) exportNotebookButton->setStyleSheet(newButtonStyle);
    if (importNotebookButton) importNotebookButton->setStyleSheet(newButtonStyle);
    if (benchmarkButton) benchmarkButton->setStyleSheet(newButtonStyle);
    if (toggleTabBarButton) toggleTabBarButton->setStyleSheet(newButtonStyle);
    if (toggleOutlineButton) toggleOutlineButton->setStyleSheet(newButtonStyle);
    if (toggleBookmarksButton) toggleBookmarksButton->setStyleSheet(newButtonStyle);
    if (toggleBookmarkButton) toggleBookmarkButton->setStyleSheet(newButtonStyle);
    if (selectFolderButton) selectFolderButton->setStyleSheet(newButtonStyle);
    if (saveButton) saveButton->setStyleSheet(newButtonStyle);
    if (saveAnnotatedButton) saveAnnotatedButton->setStyleSheet(newButtonStyle);
    if (fullscreenButton) fullscreenButton->setStyleSheet(newButtonStyle);
    if (redButton) redButton->setStyleSheet(newButtonStyle);
    if (blueButton) blueButton->setStyleSheet(newButtonStyle);
    if (yellowButton) yellowButton->setStyleSheet(newButtonStyle);
    if (greenButton) greenButton->setStyleSheet(newButtonStyle);
    if (blackButton) blackButton->setStyleSheet(newButtonStyle);
    if (whiteButton) whiteButton->setStyleSheet(newButtonStyle);
    if (thicknessButton) thicknessButton->setStyleSheet(newButtonStyle);
    if (penToolButton) penToolButton->setStyleSheet(newButtonStyle);
    if (markerToolButton) markerToolButton->setStyleSheet(newButtonStyle);
    if (eraserToolButton) eraserToolButton->setStyleSheet(newButtonStyle);
    if (backgroundButton) backgroundButton->setStyleSheet(newButtonStyle);
    if (straightLineToggleButton) straightLineToggleButton->setStyleSheet(newButtonStyle);
    if (ropeToolButton) ropeToolButton->setStyleSheet(newButtonStyle);
    if (markdownButton) markdownButton->setStyleSheet(newButtonStyle);
    if (deletePageButton) deletePageButton->setStyleSheet(newButtonStyle);
    if (zoomButton) zoomButton->setStyleSheet(newButtonStyle);
    if (dialToggleButton) dialToggleButton->setStyleSheet(newButtonStyle);
    if (fastForwardButton) fastForwardButton->setStyleSheet(newButtonStyle);
    if (jumpToPageButton) jumpToPageButton->setStyleSheet(newButtonStyle);
    if (btnPageSwitch) btnPageSwitch->setStyleSheet(newButtonStyle);
    if (btnZoom) btnZoom->setStyleSheet(newButtonStyle);
    if (btnThickness) btnThickness->setStyleSheet(newButtonStyle);
    if (btnTool) btnTool->setStyleSheet(newButtonStyle);
    if (btnPresets) btnPresets->setStyleSheet(newButtonStyle);
    if (btnPannScroll) btnPannScroll->setStyleSheet(newButtonStyle);
    if (addPresetButton) addPresetButton->setStyleSheet(newButtonStyle);
    if (openControlPanelButton) openControlPanelButton->setStyleSheet(newButtonStyle);
    if (openRecentNotebooksButton) openRecentNotebooksButton->setStyleSheet(newButtonStyle);
    if (zoom50Button) zoom50Button->setStyleSheet(newButtonStyle);
    if (dezoomButton) dezoomButton->setStyleSheet(newButtonStyle);
    if (zoom200Button) zoom200Button->setStyleSheet(newButtonStyle);
    if (prevPageButton) prevPageButton->setStyleSheet(newButtonStyle);
    if (nextPageButton) nextPageButton->setStyleSheet(newButtonStyle);

    // Update color buttons with palette-based icons
    if (redButton) {
        QString redIconPath = useBrighterPalette ? ":/resources/icons/pen_light_red.png" : ":/resources/icons/pen_dark_red.png";
        redButton->setIcon(QIcon(redIconPath));
    }
    if (blueButton) {
        QString blueIconPath = useBrighterPalette ? ":/resources/icons/pen_light_blue.png" : ":/resources/icons/pen_dark_blue.png";
        blueButton->setIcon(QIcon(blueIconPath));
    }
    if (yellowButton) {
        QString yellowIconPath = useBrighterPalette ? ":/resources/icons/pen_light_yellow.png" : ":/resources/icons/pen_dark_yellow.png";
        yellowButton->setIcon(QIcon(yellowIconPath));
    }
    if (greenButton) {
        QString greenIconPath = useBrighterPalette ? ":/resources/icons/pen_light_green.png" : ":/resources/icons/pen_dark_green.png";
        greenButton->setIcon(QIcon(greenIconPath));
    }
    if (blackButton) {
        QString blackIconPath = darkMode ? ":/resources/icons/pen_light_black.png" : ":/resources/icons/pen_dark_black.png";
        blackButton->setIcon(QIcon(blackIconPath));
    }
    if (whiteButton) {
        QString whiteIconPath = darkMode ? ":/resources/icons/pen_light_white.png" : ":/resources/icons/pen_dark_white.png";
        whiteButton->setIcon(QIcon(whiteIconPath));
    }

    // Update tab close button icons and label styling
    if (tabList) {
        bool darkMode = isDarkMode();
        QString labelColor = darkMode ? "#E0E0E0" : "#333";

        for (int i = 0; i < tabList->count(); ++i) {
            QListWidgetItem *item = tabList->item(i);
            if (item) {
                QWidget *tabWidget = tabList->itemWidget(item);
                if (tabWidget) {
                    QPushButton *closeButton = tabWidget->findChild<QPushButton*>();
                    if (closeButton) {
                        closeButton->setIcon(loadThemedIcon("cross"));
                    }

                    QLabel *tabLabel = tabWidget->findChild<QLabel*>("tabLabel");
                    if (tabLabel) {
                        tabLabel->setStyleSheet(QString("color: %1; font-weight: 500; padding: 2px; text-align: left;").arg(labelColor));
                    }
                }
            }
        }
    }

    // Update dial display
    updateDialDisplay();
}
    void migrateOldButtonMappings() {
    QSettings settings("SpeedyNote", "App");

    // Check if migration is needed by looking for old format strings
    settings.beginGroup("ButtonHoldMappings");
    QStringList holdKeys = settings.allKeys();
    bool needsMigration = false;

    for (const QString &key : holdKeys) {
        QString value = settings.value(key).toString();
        // If we find old English strings, we need to migrate
        if (value == "PageSwitching" || value == "ZoomControl" || value == "ThicknessControl" ||
            value == "ToolSwitching" || value == "PresetSelection" ||
            value == "PanAndPageScroll") {
            needsMigration = true;
            break;
        }
    }
    settings.endGroup();

    if (!needsMigration) {
        settings.beginGroup("ButtonPressMappings");
        QStringList pressKeys = settings.allKeys();
        for (const QString &key : pressKeys) {
            QString value = settings.value(key).toString();
            // Check for old English action strings
            if (value == "Toggle Fullscreen" || value == "Toggle Dial" || value == "Zoom 50%" ||
                value == "Add Preset" || value == "Delete Page" || value == "Fast Forward" ||
                value == "Open Control Panel" || value == "Custom Color") {
                needsMigration = true;
                break;
            }
        }
        settings.endGroup();
    }

    if (!needsMigration) return;

    // Perform migration
    // qDebug() << "Migrating old button mappings to new format...";

    // Migrate hold mappings
    settings.beginGroup("ButtonHoldMappings");
    holdKeys = settings.allKeys();
    for (const QString &key : holdKeys) {
        QString oldValue = settings.value(key).toString();
        QString newValue = migrateOldDialModeString(oldValue);
        if (newValue != oldValue) {
            settings.setValue(key, newValue);
        }
    }
    settings.endGroup();

    // Migrate press mappings
    settings.beginGroup("ButtonPressMappings");
    QStringList pressKeys = settings.allKeys();
    for (const QString &key : pressKeys) {
        QString oldValue = settings.value(key).toString();
        QString newValue = migrateOldActionString(oldValue);
        if (newValue != oldValue) {
            settings.setValue(key, newValue);
        }
    }
    settings.endGroup();

   // qDebug() << "Button mapping migration completed.";
}
    QString migrateOldDialModeString(const QString &oldString) {
    // Convert old English strings to new internal keys
    if (oldString == "None") return "none";
    if (oldString == "PageSwitching") return "page_switching";
    if (oldString == "ZoomControl") return "zoom_control";
    if (oldString == "ThicknessControl") return "thickness_control";

    if (oldString == "ToolSwitching") return "tool_switching";
    if (oldString == "PresetSelection") return "preset_selection";
    if (oldString == "PanAndPageScroll") return "pan_and_page_scroll";
    return oldString; // Return as-is if not found (might already be new format)
}
    QString migrateOldActionString(const QString &oldString) {
    // Convert old English strings to new internal keys
    if (oldString == "None") return "none";
    if (oldString == "Toggle Fullscreen") return "toggle_fullscreen";
    if (oldString == "Toggle Dial") return "toggle_dial";
    if (oldString == "Zoom 50%") return "zoom_50";
    if (oldString == "Zoom Out") return "zoom_out";
    if (oldString == "Zoom 200%") return "zoom_200";
    if (oldString == "Add Preset") return "add_preset";
    if (oldString == "Delete Page") return "delete_page";
    if (oldString == "Fast Forward") return "fast_forward";
    if (oldString == "Open Control Panel") return "open_control_panel";
    if (oldString == "Red") return "red_color";
    if (oldString == "Blue") return "blue_color";
    if (oldString == "Yellow") return "yellow_color";
    if (oldString == "Green") return "green_color";
    if (oldString == "Black") return "black_color";
    if (oldString == "White") return "white_color";
    if (oldString == "Custom Color") return "custom_color";
    if (oldString == "Toggle Sidebar") return "toggle_sidebar";
    if (oldString == "Save") return "save";
    if (oldString == "Straight Line Tool") return "straight_line_tool";
    if (oldString == "Rope Tool") return "rope_tool";
    if (oldString == "Set Pen Tool") return "set_pen_tool";
    if (oldString == "Set Marker Tool") return "set_marker_tool";
    if (oldString == "Set Eraser Tool") return "set_eraser_tool";
    if (oldString == "Toggle PDF Text Selection") return "toggle_pdf_text_selection";
    return oldString; // Return as-is if not found (might already be new format)
}
    InkCanvas* currentCanvas();
    void saveCurrentPage() {
    currentCanvas()->saveToFile(getCurrentPageForCanvas(currentCanvas()));
}
    void saveCurrentPageConcurrent() {
    InkCanvas *canvas = currentCanvas();
    if (!canvas || !canvas->isEdited()) return;

    int pageNumber = getCurrentPageForCanvas(canvas);
    QString saveFolder = canvas->getSaveFolder();

    if (saveFolder.isEmpty()) return;

    // Create a copy of the buffer for concurrent saving
    QPixmap bufferCopy = canvas->getBuffer();

    // Save markdown windows for this page (this must be done on the main thread)
    if (canvas->getMarkdownManager()) {
        canvas->getMarkdownManager()->saveWindowsForPage(pageNumber);
    }

    // Run the save operation concurrently
    QFuture<void> future = QtConcurrent::run([saveFolder, pageNumber, bufferCopy]() {
        // Get notebook ID from the save folder (similar to how InkCanvas does it)
        QString notebookId = "notebook"; // Default fallback
        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();
                file.close();
            }
        }

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

        QImage image(bufferCopy.size(), QImage::Format_ARGB32);
        image.fill(Qt::transparent);
        QPainter painter(&image);
        painter.drawPixmap(0, 0, bufferCopy);
        image.save(filePath, "PNG");
    });

    // Mark as not edited since we're saving
    canvas->setEdited(false);
}
    void switchPage(int pageNumber) {
    InkCanvas *canvas = currentCanvas();
    if (!canvas) return;

    if (currentCanvas()->isEdited()){
        saveCurrentPageConcurrent(); // Use concurrent saving for smoother page flipping
    }

    int oldPage = getCurrentPageForCanvas(currentCanvas()) + 1; // Convert to 1-based for comparison
    int newPage = pageNumber - 1;
    pageMap[canvas] = newPage;  // ✅ Save the page for this tab

    if (canvas->isPdfLoadedFunc() && pageNumber - 1 < canvas->getTotalPdfPages()) {
        canvas->loadPdfPage(newPage);
    } else {
        canvas->loadPage(newPage);
    }

    canvas->setLastActivePage(newPage);
    updateZoom();
    // It seems panXSlider and panYSlider can be null here during startup.
    if(panXSlider && panYSlider){
    canvas->setLastPanX(panXSlider->maximum());
    canvas->setLastPanY(panYSlider->maximum());

        // ✅ Enhanced scroll-on-top functionality
        if (scrollOnTopEnabled && panYSlider->maximum() > 0) {
            if (pageNumber > oldPage) {
                // Forward page switching → scroll to top
                panYSlider->setValue(0);
            } else if (pageNumber < oldPage) {
                // Backward page switching → scroll to bottom
                panYSlider->setValue(panYSlider->maximum());
            }
        }
    }
    updateDialDisplay();
    updateBookmarkButtonState(); // Update bookmark button state when switching pages
}
    void switchPageWithDirection(int pageNumber, int direction) {
    InkCanvas *canvas = currentCanvas();
    if (!canvas) return;

    if (currentCanvas()->isEdited()){
        saveCurrentPageConcurrent(); // Use concurrent saving for smoother page flipping
    }

    int newPage = pageNumber - 1;
    pageMap[canvas] = newPage;  // ✅ Save the page for this tab

    if (canvas->isPdfLoadedFunc() && pageNumber - 1 < canvas->getTotalPdfPages()) {
        canvas->loadPdfPage(newPage);
    } else {
        canvas->loadPage(newPage);
    }

    canvas->setLastActivePage(newPage);
    updateZoom();
    // It seems panXSlider and panYSlider can be null here during startup.
    if(panXSlider && panYSlider){
        canvas->setLastPanX(panXSlider->maximum());
        canvas->setLastPanY(panYSlider->maximum());

        // ✅ Enhanced scroll-on-top functionality with explicit direction
        if (scrollOnTopEnabled && panYSlider->maximum() > 0) {
            if (direction > 0) {
                // Forward page switching → scroll to top
                panYSlider->setValue(0);
            } else if (direction < 0) {
                // Backward page switching → scroll to bottom
                panYSlider->setValue(panYSlider->maximum());
            }
        }
    }
    updateDialDisplay();
    updateBookmarkButtonState(); // Update bookmark button state when switching pages
}
    void updateTabLabel() {
    int index = tabList->currentRow();
    if (index < 0) return;

    InkCanvas *canvas = currentCanvas();
    if (!canvas) return;

    QString folderPath = canvas->getSaveFolder(); // ✅ Get save folder
    if (folderPath.isEmpty()) return;

    QString tabName;

    // ✅ Check if there is an assigned PDF
    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();

            // ✅ Extract just the PDF filename (not full path)
            QFileInfo pdfInfo(pdfPath);
            if (pdfInfo.exists()) {
                tabName = elideTabText(pdfInfo.fileName(), 90); // e.g., "mydocument.pdf" (elided)
            }
        }
    }

    // ✅ If no PDF, use the folder name
    if (tabName.isEmpty()) {
        QFileInfo folderInfo(folderPath);
        tabName = elideTabText(folderInfo.fileName(), 90); // e.g., "MyNotebook" (elided)
    }

    QListWidgetItem *tabItem = tabList->item(index);
    if (tabItem) {
        QWidget *tabWidget = tabList->itemWidget(tabItem); // Get the tab's custom widget
        if (tabWidget) {
            QLabel *tabLabel = tabWidget->findChild<QLabel *>(); // Get the QLabel inside
            if (tabLabel) {
                tabLabel->setText(tabName); // ✅ Update tab label
                tabLabel->setWordWrap(false); // No wrapping for horizontal tabs
            }
        }
    }
}
    QSpinBox *pageInput;
    QPushButton *prevPageButton;
    QPushButton *nextPageButton;
    void addKeyboardMapping(const QString &keySequence, const QString &action) {
    // List of IME-related shortcuts that should not be intercepted
    QStringList imeShortcuts = {
        "Ctrl+Space",      // Primary IME toggle
        "Ctrl+Shift",      // Language switching
        "Ctrl+Alt",        // IME functions
        "Shift+Alt",       // Alternative language switching
        "Alt+Shift"        // Alternative language switching (reversed)
    };

    // Don't allow mapping of IME-related shortcuts
    if (imeShortcuts.contains(keySequence)) {
        qWarning() << "Cannot map IME-related shortcut:" << keySequence;
        return;
    }

    keyboardMappings[keySequence] = action;
    keyboardActionMapping[keySequence] = stringToAction(action);
    saveKeyboardMappings();
}
    void removeKeyboardMapping(const QString &keySequence) {
    keyboardMappings.remove(keySequence);
    keyboardActionMapping.remove(keySequence);
    saveKeyboardMappings();
}
    QMap<QString, QString> getKeyboardMappings() const {
    return keyboardMappings;
}
    void reconnectControllerSignals() {
    if (!controllerManager || !pageDial) {
        return;
    }

    // Reset internal dial state
    tracking = false;
    accumulatedRotation = 0;
    grossTotalClicks = 0;
    tempClicks = 0;
    lastAngle = 0;
    startAngle = 0;
    pendingPageFlip = 0;
    accumulatedRotationAfterLimit = 0;

    // Disconnect all existing connections to avoid duplicates
    disconnect(controllerManager, nullptr, this, nullptr);
    disconnect(controllerManager, nullptr, pageDial, nullptr);

    // Reconnect all controller signals
    connect(controllerManager, &SDLControllerManager::buttonHeld, this, &MainWindow::handleButtonHeld);
    connect(controllerManager, &SDLControllerManager::buttonReleased, this, &MainWindow::handleButtonReleased);
    connect(controllerManager, &SDLControllerManager::leftStickAngleChanged, pageDial, &QDial::setValue);
    connect(controllerManager, &SDLControllerManager::leftStickReleased, pageDial, &QDial::sliderReleased);
    connect(controllerManager, &SDLControllerManager::buttonSinglePress, this, &MainWindow::handleControllerButton);

    // Re-establish dial mode connections by changing to current mode
    DialMode currentMode = currentDialMode;
    changeDialMode(currentMode);

    // Update dial display to reflect current state
    updateDialDisplay();

    // qDebug() << "Controller signals reconnected successfully";
}
    void updateDialButtonState() {
    // Check if dial is visible
    bool isDialVisible = dialContainer && dialContainer->isVisible();

    if (dialToggleButton) {
        dialToggleButton->setProperty("selected", isDialVisible);

        // Force style update
        dialToggleButton->style()->unpolish(dialToggleButton);
        dialToggleButton->style()->polish(dialToggleButton);
    }
}
    void updateFastForwardButtonState() {
    if (fastForwardButton) {
        fastForwardButton->setProperty("selected", fastForwardMode);

        // Force style update
        fastForwardButton->style()->unpolish(fastForwardButton);
        fastForwardButton->style()->polish(fastForwardButton);
    }
}
    void updateToolButtonStates() {
    if (!currentCanvas()) return;

    // Reset all tool buttons
    penToolButton->setProperty("selected", false);
    markerToolButton->setProperty("selected", false);
    eraserToolButton->setProperty("selected", false);

    // Set the selected property for the current tool
    ToolType currentTool = currentCanvas()->getCurrentTool();
    switch (currentTool) {
        case ToolType::Pen:
            penToolButton->setProperty("selected", true);
            break;
        case ToolType::Marker:
            markerToolButton->setProperty("selected", true);
            break;
        case ToolType::Eraser:
            eraserToolButton->setProperty("selected", true);
            break;
    }

    // Force style update
    penToolButton->style()->unpolish(penToolButton);
    penToolButton->style()->polish(penToolButton);
    markerToolButton->style()->unpolish(markerToolButton);
    markerToolButton->style()->polish(markerToolButton);
    eraserToolButton->style()->unpolish(eraserToolButton);
    eraserToolButton->style()->polish(eraserToolButton);
}
    void handleColorButtonClick() {
    if (!currentCanvas()) return;

    ToolType currentTool = currentCanvas()->getCurrentTool();

    // If in eraser mode, switch back to pen mode
    if (currentTool == ToolType::Eraser) {
        currentCanvas()->setTool(ToolType::Pen);
        updateToolButtonStates();
        updateThicknessSliderForCurrentTool();
    }

    // If rope tool is enabled, turn it off
    if (currentCanvas()->isRopeToolMode()) {
        currentCanvas()->setRopeToolMode(false);
        updateRopeToolButtonState();
    }

    // For marker and straight line mode, leave them as they are
    // No special handling needed - they can work with color changes
}
    void updateThicknessSliderForCurrentTool() {
    if (!currentCanvas() || !thicknessSlider) return;

    // Block signals to prevent recursive calls
    thicknessSlider->blockSignals(true);

    // Update slider to reflect current tool's thickness
    qreal currentThickness = currentCanvas()->getPenThickness();

    // Convert thickness back to slider value (reverse of updateThickness calculation)
    qreal visualThickness = currentThickness * (currentCanvas()->getZoom() / 100.0);
    int sliderValue = qBound(1, static_cast<int>(qRound(visualThickness)), 50);

    thicknessSlider->setValue(sliderValue);
    thicknessSlider->blockSignals(false);
}
    void updatePdfTextSelectButtonState() {
    // Check if PDF text selection is enabled
    bool isEnabled = currentCanvas() && currentCanvas()->isPdfTextSelectionEnabled();

    if (pdfTextSelectButton) {
        pdfTextSelectButton->setProperty("selected", isEnabled);

        // Force style update (uses the same buttonStyle as other toggle buttons)
        pdfTextSelectButton->style()->unpolish(pdfTextSelectButton);
        pdfTextSelectButton->style()->polish(pdfTextSelectButton);
    }
}
  private:
    void toggleBenchmark() {
    benchmarking = !benchmarking;
    if (benchmarking) {
        currentCanvas()->startBenchmark();
        benchmarkTimer->start(1000); // Update every second
    } else {
        currentCanvas()->stopBenchmark();
        benchmarkTimer->stop();
        benchmarkLabel->setText(tr("PR:N/A"));
    }
}
    void updateBenchmarkDisplay() {
    int sampleRate = currentCanvas()->getProcessedRate();
    benchmarkLabel->setText(QString(tr("PR:%1 Hz")).arg(sampleRate));
}
    void applyCustomColor() {
    QString colorCode = customColorInput->text();
    if (!colorCode.startsWith("#")) {
        colorCode.prepend("#");
    }
    currentCanvas()->setPenColor(QColor(colorCode));
    updateDialDisplay();
}
    void updateThickness(int value) {
    // Calculate thickness based on the slider value at 100% zoom
    // The slider value represents the desired visual thickness
    qreal visualThickness = value; // Scale slider value to reasonable thickness

    // Apply zoom scaling to maintain visual consistency
    qreal actualThickness = visualThickness * (100.0 / currentCanvas()->getZoom());

    currentCanvas()->setPenThickness(actualThickness);
}
    void adjustThicknessForZoom(int oldZoom, int newZoom) {
    // Adjust all tool thicknesses to maintain visual consistency when zoom changes
    if (oldZoom == newZoom || oldZoom <= 0 || newZoom <= 0) return;

    InkCanvas* canvas = currentCanvas();
    if (!canvas) return;

    qreal zoomRatio = qreal(oldZoom) / qreal(newZoom);
    ToolType currentTool = canvas->getCurrentTool();

    // Adjust thickness for all tools, not just the current one
    canvas->adjustAllToolThicknesses(zoomRatio);

    // Update the thickness slider to reflect the current tool's new thickness
    updateThicknessSliderForCurrentTool();

    // ✅ FIXED: Update dial display to show new thickness immediately during zoom changes
    updateDialDisplay();
}
    void changeTool(int index) {
    if (index == 0) {
        currentCanvas()->setTool(ToolType::Pen);
    } else if (index == 1) {
        currentCanvas()->setTool(ToolType::Marker);
    } else if (index == 2) {
        currentCanvas()->setTool(ToolType::Eraser);
    }
    updateToolButtonStates();
    updateThicknessSliderForCurrentTool();
    updateDialDisplay();
}
    void selectFolder() {
    QString folder = QFileDialog::getExistingDirectory(this, tr("Select Save Folder"));
    if (!folder.isEmpty()) {
        InkCanvas *canvas = currentCanvas();
        if (canvas) {
            if (canvas->isEdited()){
                saveCurrentPage();
            }
            canvas->setSaveFolder(folder);
        switchPageWithDirection(1, 1); // Going to page 1 is forward direction
        pageInput->setValue(1);
        updateTabLabel();
            recentNotebooksManager->addRecentNotebook(folder, canvas); // Track when folder is selected
        }
    }
}
    void saveCanvas() {
    currentCanvas()->saveToFile(getCurrentPageForCanvas(currentCanvas()));
}
    void saveAnnotated() {
    currentCanvas()->saveAnnotated(getCurrentPageForCanvas(currentCanvas()));
}
    void deleteCurrentPage() {
    currentCanvas()->deletePage(getCurrentPageForCanvas(currentCanvas()));
}
    void loadPdf() {
    InkCanvas *canvas = currentCanvas();
    if (!canvas) return;

    QString saveFolder = canvas->getSaveFolder();
    QString tempDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/temp_session";

    // Check if no save folder is set or if it's the temporary directory
    if (saveFolder.isEmpty() || saveFolder == tempDir) {
        QMessageBox::warning(this, tr("Cannot Load PDF"),
            tr("Please select a permanent save folder before loading a PDF.\n\nClick the folder icon to choose a location for your notebook."));
        return;
    }

    QString filePath = QFileDialog::getOpenFileName(this, tr("Select PDF"), "", "PDF Files (*.pdf)");
    if (!filePath.isEmpty()) {
        currentCanvas()->loadPdf(filePath);
        updateTabLabel(); // ✅ Update the tab name after assigning a PDF
        updateZoom(); // ✅ Update zoom and pan range after PDF is loaded

        // Refresh PDF outline if sidebar is visible
        if (outlineSidebarVisible) {
            loadPdfOutline();
        }
    }
}
    void clearPdf() {
    InkCanvas *canvas = currentCanvas();
    if (!canvas) return;

    canvas->clearPdf();
    updateZoom(); // ✅ Update zoom and pan range after PDF is cleared
}
    void updateZoom() {
    InkCanvas *canvas = currentCanvas();
    if (canvas) {
        canvas->setZoom(zoomSlider->value());
        canvas->setLastZoomLevel(zoomSlider->value());  // ✅ Store zoom level per tab
        updatePanRange();
        // updateThickness(thicknessSlider->value()); // ✅ REMOVED: This was resetting thickness on page switch
        // updateDialDisplay();
    }
}
    void onZoomSliderChanged(int value) {
    // This handles manual zoom slider changes and preserves thickness
    int oldZoom = currentCanvas() ? currentCanvas()->getZoom() : 100;
    int newZoom = value;

    updateZoom();
    adjustThicknessForZoom(oldZoom, newZoom); // Maintain visual thickness consistency
}
    void applyZoom() {
    bool ok;
    int zoomValue = zoomInput->text().toInt(&ok);
    if (ok && zoomValue > 0) {
        currentCanvas()->setZoom(zoomValue);
        updatePanRange(); // Update slider range after zoom change
    }
}
    void updatePanRange() {
    int zoom = currentCanvas()->getZoom();

    QSize canvasSize = currentCanvas()->getCanvasSize();
    QSize viewportSize = QGuiApplication::primaryScreen()->size() * QGuiApplication::primaryScreen()->devicePixelRatio();
    qreal dpr = initialDpr;

    // Get the actual widget size instead of screen size for more accurate calculation
    QSize actualViewportSize = size();

    // Adjust viewport size for toolbar and tab bar layout
    QSize effectiveViewportSize = actualViewportSize;
    int toolbarHeight = isToolbarTwoRows ? 80 : 50; // Toolbar height
    int tabBarHeight = (tabBarContainer && tabBarContainer->isVisible()) ? 38 : 0; // Tab bar height
    effectiveViewportSize.setHeight(actualViewportSize.height() - toolbarHeight - tabBarHeight);

    // Calculate scaled canvas size using proper DPR scaling
    int scaledCanvasWidth = canvasSize.width() * zoom / 100;
    int scaledCanvasHeight = canvasSize.height() * zoom / 100;

    // Calculate max pan values - if canvas is smaller than viewport, pan should be 0
    int maxPanX = qMax(0, scaledCanvasWidth - effectiveViewportSize.width());
    int maxPanY = qMax(0, scaledCanvasHeight - effectiveViewportSize.height());

    // Scale the pan range properly
    int maxPanX_scaled = maxPanX * 100 / zoom;
    int maxPanY_scaled = maxPanY * 100 / zoom;

    // Set range to 0 when canvas is smaller than viewport (centered)
    if (scaledCanvasWidth <= effectiveViewportSize.width()) {
        panXSlider->setRange(0, 0);
        panXSlider->setValue(0);
        // No need for horizontal scrollbar
        panXSlider->setVisible(false);
    } else {
    panXSlider->setRange(0, maxPanX_scaled);
        // Show scrollbar only if mouse is near and timeout hasn't occurred
        if (scrollbarsVisible && !scrollbarHideTimer->isActive()) {
            scrollbarHideTimer->start();
        }
    }

    if (scaledCanvasHeight <= effectiveViewportSize.height()) {
        panYSlider->setRange(0, 0);
        panYSlider->setValue(0);
        // No need for vertical scrollbar
        panYSlider->setVisible(false);
    } else {
        panYSlider->setRange(0, maxPanY_scaled);
        // Show scrollbar only if mouse is near and timeout hasn't occurred
        if (scrollbarsVisible && !scrollbarHideTimer->isActive()) {
            scrollbarHideTimer->start();
        }
    }
}
    void updatePanX(int value) {
    InkCanvas *canvas = currentCanvas();
    if (canvas) {
        canvas->setPanX(value);
        canvas->setLastPanX(value);  // ✅ Store panX per tab

        // Show horizontal scrollbar temporarily
        if (panXSlider->maximum() > 0) {
            panXSlider->setVisible(true);
            scrollbarsVisible = true;

            // Make sure scrollbar position matches the canvas position
            if (panXSlider->value() != value) {
                panXSlider->blockSignals(true);
                panXSlider->setValue(value);
                panXSlider->blockSignals(false);
            }

            if (scrollbarHideTimer->isActive()) {
                scrollbarHideTimer->stop();
            }
            scrollbarHideTimer->start();
        }
    }
}
    void updatePanY(int value) {
    InkCanvas *canvas = currentCanvas();
    if (canvas) {
        canvas->setPanY(value);
        canvas->setLastPanY(value);  // ✅ Store panY per tab

        // Show vertical scrollbar temporarily
        if (panYSlider->maximum() > 0) {
            panYSlider->setVisible(true);
            scrollbarsVisible = true;

            // Make sure scrollbar position matches the canvas position
            if (panYSlider->value() != value) {
                panYSlider->blockSignals(true);
                panYSlider->setValue(value);
                panYSlider->blockSignals(false);
            }

            if (scrollbarHideTimer->isActive()) {
                scrollbarHideTimer->stop();
            }
            scrollbarHideTimer->start();
        }
    }
}
    void selectBackground() {
    QString filePath = QFileDialog::getOpenFileName(this, tr("Select Background Image"), "", "Images (*.png *.jpg *.jpeg)");
    if (!filePath.isEmpty()) {
        currentCanvas()->setBackground(filePath, getCurrentPageForCanvas(currentCanvas()));
        updateZoom(); // ✅ Update zoom and pan range after background is set
    }
}
    void forceUIRefresh() {
    setWindowState(Qt::WindowNoState);  // Restore first
    setWindowState(Qt::WindowMaximized);  // Maximize again
}
    void switchTab(int index) {
    if (!canvasStack || !tabList || !pageInput || !zoomSlider || !panXSlider || !panYSlider) {
        // qDebug() << "Error: switchTab() called before UI was fully initialized!";
        return;
    }

    if (index >= 0 && index < canvasStack->count()) {
        canvasStack->setCurrentIndex(index);

        // Ensure the tab list selection is properly set and styled
        if (tabList->currentRow() != index) {
            tabList->setCurrentRow(index);
        }

        InkCanvas *canvas = currentCanvas();
        if (canvas) {
            int savedPage = canvas->getLastActivePage();

            // ✅ Only call blockSignals if pageInput is valid
            if (pageInput) {
                pageInput->blockSignals(true);
                pageInput->setValue(savedPage + 1);
                pageInput->blockSignals(false);
            }

            // ✅ Ensure zoomSlider exists before calling methods
            if (zoomSlider) {
                zoomSlider->blockSignals(true);
                zoomSlider->setValue(canvas->getLastZoomLevel());
                zoomSlider->blockSignals(false);
                canvas->setZoom(canvas->getLastZoomLevel());
            }

            // ✅ Ensure pan sliders exist before modifying values
            if (panXSlider && panYSlider) {
                panXSlider->blockSignals(true);
                panYSlider->blockSignals(true);
                panXSlider->setValue(canvas->getLastPanX());
                panYSlider->setValue(canvas->getLastPanY());
                panXSlider->blockSignals(false);
                panYSlider->blockSignals(false);
                updatePanRange();
            }
            updateDialDisplay();
            updateColorButtonStates();  // Update button states when switching tabs
            updateStraightLineButtonState();  // Update straight line button state when switching tabs
            updateRopeToolButtonState(); // Update rope tool button state when switching tabs
            updateMarkdownButtonState(); // Update markdown button state when switching tabs
            updatePdfTextSelectButtonState(); // Update PDF text selection button state when switching tabs
            updateBookmarkButtonState(); // Update bookmark button state when switching tabs
            updateDialButtonState();     // Update dial button state when switching tabs
            updateFastForwardButtonState(); // Update fast forward button state when switching tabs
            updateToolButtonStates();   // Update tool button states when switching tabs
            updateThicknessSliderForCurrentTool(); // Update thickness slider for current tool when switching tabs

            // Refresh PDF outline if sidebar is visible
            if (outlineSidebarVisible) {
                loadPdfOutline();
            }
        }
    }
}
    void addNewTab() {
    if (!tabList || !canvasStack) return;  // Ensure tabList and canvasStack exist

    int newTabIndex = tabList->count();  // New tab index
    QWidget *tabWidget = new QWidget();  // Custom tab container
    tabWidget->setObjectName("tabWidget"); // Name the widget for easy retrieval later
    QHBoxLayout *tabLayout = new QHBoxLayout(tabWidget);
    tabLayout->setContentsMargins(5, 2, 5, 2);

    // ✅ Create the label (Tab Name) - optimized for horizontal layout
    QLabel *tabLabel = new QLabel(QString("Tab %1").arg(newTabIndex + 1), tabWidget);
    tabLabel->setObjectName("tabLabel"); // ✅ Name the label for easy retrieval later
    tabLabel->setWordWrap(false); // ✅ No wrapping for horizontal tabs
    tabLabel->setFixedWidth(115); // ✅ Narrower width for compact layout
    tabLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
    tabLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); // Left-align to show filename start
    tabLabel->setTextFormat(Qt::PlainText); // Ensure plain text for proper eliding
    // Tab label styling will be updated by theme

    // ✅ Create the close button (❌) - styled for browser-like tabs
    QPushButton *closeButton = new QPushButton(tabWidget);
    closeButton->setFixedSize(14, 14); // Smaller to fit narrower tabs
    closeButton->setIcon(loadThemedIcon("cross")); // Set themed icon
    closeButton->setStyleSheet(R"(
        QPushButton {
            border: none;
            background: transparent;
            border-radius: 6px;
            padding: 1px;
        }
        QPushButton:hover {
            background: rgba(255, 100, 100, 150);
            border-radius: 6px;
        }
        QPushButton:pressed {
            background: rgba(255, 50, 50, 200);
            border-radius: 6px;
        }
    )"); // Themed styling with hover and press effects

    // ✅ Create new InkCanvas instance EARLIER so it can be captured by the lambda
    InkCanvas *newCanvas = new InkCanvas(this);

    // ✅ Handle tab closing when the button is clicked
    connect(closeButton, &QPushButton::clicked, this, [=]() { // newCanvas is now captured

        // Prevent closing if it's the last remaining tab
        if (tabList->count() <= 1) {
            // Optional: show a message or do nothing silently
            QMessageBox::information(this, tr("Notice"), tr("At least one tab must remain open."));

            return;
        }

        // Find the index of the tab associated with this button's parent (tabWidget)
        int indexToRemove = -1;
        // newCanvas is captured by the lambda, representing the canvas of the tab being closed.
        // tabWidget is also captured.
        for (int i = 0; i < tabList->count(); ++i) {
            if (tabList->itemWidget(tabList->item(i)) == tabWidget) {
                indexToRemove = i;
                break;
            }
        }

        if (indexToRemove == -1) {
            qWarning() << "Could not find tab to remove based on tabWidget.";
            // Fallback or error handling if needed, though this shouldn't happen if tabWidget is valid.
            // As a fallback, try to find the index based on newCanvas if lists are in sync.
            for (int i = 0; i < canvasStack->count(); ++i) {
                if (canvasStack->widget(i) == newCanvas) {
                    indexToRemove = i;
                    break;
                }
            }
            if (indexToRemove == -1) {
                 qWarning() << "Could not find tab to remove based on newCanvas either.";
                 return; // Critical error, cannot proceed.
            }
        }

        // At this point, newCanvas is the InkCanvas instance for the tab being closed.
        // And indexToRemove is its index in tabList and canvasStack.

        // 1. Ensure the notebook has a unique save folder if it's temporary/edited
        ensureTabHasUniqueSaveFolder(newCanvas); // Pass the specific canvas

        // 2. Get the final save folder path
        QString folderPath = newCanvas->getSaveFolder();
        QString tempDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/temp_session";

        // 3. Update cover preview and recent list if it's a permanent notebook
        if (!folderPath.isEmpty() && folderPath != tempDir && recentNotebooksManager) {
            recentNotebooksManager->generateAndSaveCoverPreview(folderPath, newCanvas);
            // Add/update in recent list. This also moves it to the top.
            recentNotebooksManager->addRecentNotebook(folderPath, newCanvas);
        }

        // 4. Update the tab's label directly as folderPath might have changed
        QLabel *label = tabWidget->findChild<QLabel*>("tabLabel");
        if (label) {
            QString tabNameText;
            if (!folderPath.isEmpty() && folderPath != tempDir) { // Only for permanent notebooks
                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 (QFile::exists(pdfPath)) { // Check if PDF file actually exists
                            tabNameText = elideTabText(QFileInfo(pdfPath).fileName(), 90); // Elide to fit tab width
                        }
                    }
                }
                // Fallback to folder name if no PDF or PDF path invalid
                if (tabNameText.isEmpty()) {
                    tabNameText = elideTabText(QFileInfo(folderPath).fileName(), 90); // Elide to fit tab width
                }
            }
            // Only update the label if a new valid name was determined.
            // If it's still a temp folder, the original "Tab X" label remains appropriate.
            if (!tabNameText.isEmpty()) {
                label->setText(tabNameText);
            }
        }

        // 5. Remove the tab
        removeTabAt(indexToRemove);
    });

    // ✅ Add widgets to the tab layout
    tabLayout->addWidget(tabLabel);
    tabLayout->addWidget(closeButton);
    tabLayout->setStretch(0, 1);
    tabLayout->setStretch(1, 0);

    // ✅ Create the tab item and set widget (horizontal layout)
    QListWidgetItem *tabItem = new QListWidgetItem();
    tabItem->setSizeHint(QSize(135, 22)); // ✅ Narrower and thinner for compact layout
    tabList->addItem(tabItem);
    tabList->setItemWidget(tabItem, tabWidget);  // Attach tab layout

    canvasStack->addWidget(newCanvas);

    // ✅ Connect touch gesture signals
    connect(newCanvas, &InkCanvas::zoomChanged, this, &MainWindow::handleTouchZoomChange);
    connect(newCanvas, &InkCanvas::panChanged, this, &MainWindow::handleTouchPanChange);
    connect(newCanvas, &InkCanvas::touchGestureEnded, this, &MainWindow::handleTouchGestureEnd);
    connect(newCanvas, &InkCanvas::ropeSelectionCompleted, this, &MainWindow::showRopeSelectionMenu);
    connect(newCanvas, &InkCanvas::pdfLinkClicked, this, [this](int targetPage) {
        // Navigate to the target page when a PDF link is clicked
        if (targetPage >= 0 && targetPage < 9999) {
            switchPageWithDirection(targetPage + 1, (targetPage + 1 > getCurrentPageForCanvas(currentCanvas()) + 1) ? 1 : -1);
            pageInput->setValue(targetPage + 1);
        }
    });
    connect(newCanvas, &InkCanvas::pdfLoaded, this, [this]() {
        // Refresh PDF outline if sidebar is visible
        if (outlineSidebarVisible) {
            loadPdfOutline();
        }
    });
    connect(newCanvas, &InkCanvas::markdownSelectionModeChanged, this, &MainWindow::updateMarkdownButtonState);

    // Install event filter to detect mouse movement for scrollbar visibility
    newCanvas->setMouseTracking(true);
    newCanvas->installEventFilter(this);

    // Disable tablet tracking for canvases for now to prevent crashes
    // TODO: Find a safer way to implement hover tooltips without tablet tracking
    // QTimer::singleShot(50, this, [newCanvas]() {
    //     try {
    //         if (newCanvas && newCanvas->window() && newCanvas->window()->windowHandle()) {
    //             newCanvas->setAttribute(Qt::WA_TabletTracking, true);
    //         }
    //     } catch (...) {
    //         // Silently ignore tablet tracking errors
    //     }
    // });

    // ✅ Apply touch gesture setting
    newCanvas->setTouchGesturesEnabled(touchGesturesEnabled);

    pageMap[newCanvas] = 0;

    // ✅ Select the new tab
    tabList->setCurrentItem(tabItem);
    canvasStack->setCurrentWidget(newCanvas);

    zoomSlider->setValue(100 / initialDpr); // Set initial zoom level based on DPR
    updateDialDisplay();
    updateStraightLineButtonState();  // Initialize straight line button state for the new tab
    updateRopeToolButtonState(); // Initialize rope tool button state for the new tab
    updatePdfTextSelectButtonState(); // Initialize PDF text selection button state for the new tab
    updateBookmarkButtonState(); // Initialize bookmark button state for the new tab
    updateMarkdownButtonState(); // Initialize markdown button state for the new tab
    updateDialButtonState();     // Initialize dial button state for the new tab
    updateFastForwardButtonState(); // Initialize fast forward button state for the new tab
    updateToolButtonStates();   // Initialize tool button states for the new tab

    QString tempDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/temp_session";
    newCanvas->setSaveFolder(tempDir);

    // Load persistent background settings
    BackgroundStyle defaultStyle;
    QColor defaultColor;
    int defaultDensity;
    loadDefaultBackgroundSettings(defaultStyle, defaultColor, defaultDensity);

    newCanvas->setBackgroundStyle(defaultStyle);
    newCanvas->setBackgroundColor(defaultColor);
    newCanvas->setBackgroundDensity(defaultDensity);
    newCanvas->setPDFRenderDPI(getPdfDPI());

    // Update color button states for the new tab
    updateColorButtonStates();
}
    void removeTabAt(int index) {
    if (!tabList || !canvasStack) return; // Ensure UI elements exist
    if (index < 0 || index >= canvasStack->count()) return;

    // ✅ Remove tab entry
    QListWidgetItem *item = tabList->takeItem(index);
    delete item;

    // ✅ Remove and delete the canvas safely
    QWidget *canvasWidget = canvasStack->widget(index); // Get widget before removal
    // ensureTabHasUniqueSaveFolder(currentCanvas()); // Moved to the close button lambda

    if (canvasWidget) {
        canvasStack->removeWidget(canvasWidget); // Remove from stack
        // InkCanvas *canvasInstance = qobject_cast<InkCanvas*>(canvasWidget);
        // if (canvasInstance) {
        //     QString folderPath = canvasInstance->getSaveFolder();
        //     QString tempDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/temp_session";
        //     if (!folderPath.isEmpty() && folderPath != tempDir && recentNotebooksManager) {
        //         recentNotebooksManager->addRecentNotebook(folderPath, canvasInstance); // Moved to close button lambda
        //     }
        // }
        delete canvasWidget; // Now delete the widget (and its InkCanvas)
    }

    // ✅ Select the previous tab (or first tab if none left)
    if (tabList->count() > 0) {
        int newIndex = qMax(0, index - 1);
        tabList->setCurrentRow(newIndex);
        canvasStack->setCurrentWidget(canvasStack->widget(newIndex));
    }

    // QWidget *canvasWidget = canvasStack->widget(index); // Redeclaration - remove this block
    // InkCanvas *canvasInstance = qobject_cast<InkCanvas*>(canvasWidget);
    //
    // if (canvasInstance) {
    //     QString folderPath = canvasInstance->getSaveFolder();
    //     if (!folderPath.isEmpty() && folderPath != QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/temp_session") {
    //         // recentNotebooksManager->addRecentNotebook(folderPath, canvasInstance); // Moved to close button lambda
    //     }
    // }
}
    void toggleZoomSlider() {
    if (zoomFrame->isVisible()) {
        zoomFrame->hide();
        return;
    }

    // ✅ Set as a standalone pop-up window so it can receive events
    zoomFrame->setWindowFlags(Qt::Popup);

    // ✅ Position it right below the button
    QPoint buttonPos = zoomButton->mapToGlobal(QPoint(0, zoomButton->height()));
    zoomFrame->move(buttonPos.x(), buttonPos.y() + 5);
    zoomFrame->show();
}
    void toggleThicknessSlider() {
    if (thicknessFrame->isVisible()) {
        thicknessFrame->hide();
        return;
    }

    // ✅ Set as a standalone pop-up window so it can receive events
    thicknessFrame->setWindowFlags(Qt::Popup);

    // ✅ Position it right below the button
    QPoint buttonPos = thicknessButton->mapToGlobal(QPoint(0, thicknessButton->height()));
    thicknessFrame->move(buttonPos.x(), buttonPos.y() + 5);

    thicknessFrame->show();
}
    void toggleFullscreen() {
    if (isFullScreen()) {
        showNormal();  // Exit fullscreen mode
    } else {
        showFullScreen();  // Enter fullscreen mode
    }
}
    void showJumpToPageDialog() {
    bool ok;
    int currentPage = getCurrentPageForCanvas(currentCanvas()) + 1;  // ✅ Convert zero-based to one-based
    int newPage = QInputDialog::getInt(this, "Jump to Page", "Enter Page Number:",
                                       currentPage, 1, 9999, 1, &ok);
    if (ok) {
        // ✅ Use direction-aware page switching for jump-to-page
        int direction = (newPage > currentPage) ? 1 : (newPage < currentPage) ? -1 : 0;
        if (direction != 0) {
            switchPageWithDirection(newPage, direction);
        } else {
            switchPage(newPage); // Same page, no direction needed
        }
        pageInput->setValue(newPage);
    }
}
    void goToPreviousPage() {
    int currentPage = getCurrentPageForCanvas(currentCanvas()) + 1;  // Convert to 1-based
    if (currentPage > 1) {
        int newPage = currentPage - 1;
        switchPageWithDirection(newPage, -1); // -1 indicates backward
        pageInput->setValue(newPage);
    }
}
    void goToNextPage() {
    int currentPage = getCurrentPageForCanvas(currentCanvas()) + 1;  // Convert to 1-based
    int newPage = currentPage + 1;
    switchPageWithDirection(newPage, 1); // 1 indicates forward
    pageInput->setValue(newPage);
}
    void onPageInputChanged(int newPage) {
    int currentPage = getCurrentPageForCanvas(currentCanvas()) + 1;  // Convert to 1-based

    // ✅ Use direction-aware page switching for spinbox
    int direction = (newPage > currentPage) ? 1 : (newPage < currentPage) ? -1 : 0;
    if (direction != 0) {
        switchPageWithDirection(newPage, direction);
    } else {
        switchPage(newPage); // Same page, no direction needed
    }
}
    void toggleDial() {
    if (!dialContainer) {
        // ✅ Create floating container for the dial
        dialContainer = new QWidget(this);
        dialContainer->setObjectName("dialContainer");
        dialContainer->setFixedSize(140, 140);
        dialContainer->setAttribute(Qt::WA_TranslucentBackground);
        dialContainer->setAttribute(Qt::WA_NoSystemBackground);
        dialContainer->setAttribute(Qt::WA_OpaquePaintEvent);
        dialContainer->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
        dialContainer->setStyleSheet("background: transparent; border-radius: 100px;");  // ✅ More transparent

        // ✅ Create dial
        pageDial = new QDial(dialContainer);
        pageDial->setFixedSize(140, 140);
        pageDial->setMinimum(0);
        pageDial->setMaximum(360);
        pageDial->setWrapping(true);  // ✅ Allow full-circle rotation

        // Apply theme color immediately when dial is created
        QColor accentColor = getAccentColor();
        pageDial->setStyleSheet(QString(R"(
        QDial {
            background-color: %1;
            }
        )").arg(accentColor.name()));

        /*

        modeDial = new QDial(dialContainer);
        modeDial->setFixedSize(150, 150);
        modeDial->setMinimum(0);
        modeDial->setMaximum(300);  // 6 modes, 60° each
        modeDial->setSingleStep(60);
        modeDial->setWrapping(true);
        modeDial->setStyleSheet("background:rgb(0, 76, 147);");
        modeDial->move(25, 25);

        */

        dialColorPreview = new QFrame(dialContainer);
        dialColorPreview->setFixedSize(30, 30);
        dialColorPreview->setStyleSheet("border-radius: 15px; border: 1px solid black;");
        dialColorPreview->move(55, 35); // Center of dial

        dialIconView = new QLabel(dialContainer);
        dialIconView->setFixedSize(30, 30);
        dialIconView->setStyleSheet("border-radius: 1px; border: 1px solid black;");
        dialIconView->move(55, 35); // Center of dial

        // ✅ Position dial near top-right corner initially
        positionDialContainer();

        dialDisplay = new QLabel(dialContainer);
        dialDisplay->setAlignment(Qt::AlignCenter);
        dialDisplay->setFixedSize(80, 80);
        dialDisplay->move(30, 30);  // Center position inside the dial

        int fontId = QFontDatabase::addApplicationFont(":/resources/fonts/Jersey20-Regular.ttf");
        // int chnFontId = QFontDatabase::addApplicationFont(":/resources/fonts/NotoSansSC-Medium.ttf");
        QStringList fontFamilies = QFontDatabase::applicationFontFamilies(fontId);

        if (!fontFamilies.isEmpty()) {
            QFont pixelFont(fontFamilies.at(0), 11);
            dialDisplay->setFont(pixelFont);
        }

        dialDisplay->setStyleSheet("background-color: black; color: white; font-size: 14px; border-radius: 4px;");

        dialHiddenButton = new QPushButton(dialContainer);
        dialHiddenButton->setFixedSize(80, 80);
        dialHiddenButton->move(30, 30); // Same position as dialDisplay
        dialHiddenButton->setStyleSheet("background: transparent; border: none;"); // ✅ Fully invisible
        dialHiddenButton->setFocusPolicy(Qt::NoFocus); // ✅ Prevents accidental focus issues
        dialHiddenButton->setEnabled(false);  // ✅ Disabled by default

        // ✅ Connection will be set in changeDialMode() based on current mode

        dialColorPreview->raise();  // ✅ Ensure it's on top
        dialIconView->raise();
        // ✅ Connect dial input and release
        // connect(pageDial, &QDial::valueChanged, this, &MainWindow::handleDialInput);
        // connect(pageDial, &QDial::sliderReleased, this, &MainWindow::onDialReleased);

        // connect(modeDial, &QDial::valueChanged, this, &MainWindow::handleModeSelection);
        changeDialMode(currentDialMode);  // ✅ Set initial mode

        // ✅ Enable drag detection
        dialContainer->installEventFilter(this);
    }

    // ✅ Ensure that `dialContainer` is always initialized before setting visibility
    if (dialContainer) {
        dialContainer->setVisible(!dialContainer->isVisible());
    }

    initializeDialSound();  // ✅ Ensure sound is loaded

    // Inside toggleDial():

    if (!dialDisplay) {
        dialDisplay = new QLabel(dialContainer);
    }
    updateDialDisplay(); // ✅ Ensure it's updated before showing

    if (controllerManager) {
        connect(controllerManager, &SDLControllerManager::buttonHeld, this, &MainWindow::handleButtonHeld);
        connect(controllerManager, &SDLControllerManager::buttonReleased, this, &MainWindow::handleButtonReleased);
        connect(controllerManager, &SDLControllerManager::leftStickAngleChanged, pageDial, &QDial::setValue);
        connect(controllerManager, &SDLControllerManager::leftStickReleased, pageDial, &QDial::sliderReleased);
        connect(controllerManager, &SDLControllerManager::buttonSinglePress, this, &MainWindow::handleControllerButton);
    }

    loadButtonMappings();  // ✅ Load button mappings for the controller

    // Update button state to reflect dial visibility
    updateDialButtonState();
}
    void positionDialContainer() {
    if (!dialContainer) return;

    // Get window dimensions
    int windowWidth = width();
    int windowHeight = height();

    // Get dial dimensions
    int dialWidth = dialContainer->width();   // 140px
    int dialHeight = dialContainer->height(); // 140px

    // Calculate toolbar height based on current layout
    int toolbarHeight = isToolbarTwoRows ? 80 : 50; // Approximate heights

    // Add tab bar height if visible
    int tabBarHeight = (tabBarContainer && tabBarContainer->isVisible()) ? 38 : 0;

    // Define margins from edges
    int rightMargin = 20;  // Distance from right edge
    int topMargin = 20;    // Distance from top edge (below toolbar and tabs)

    // Calculate ideal position (top-right corner with margins)
    int idealX = windowWidth - dialWidth - rightMargin;
    int idealY = toolbarHeight + tabBarHeight + topMargin;

    // Ensure dial stays within window bounds with minimum margins
    int minMargin = 10;
    int maxX = windowWidth - dialWidth - minMargin;
    int maxY = windowHeight - dialHeight - minMargin;

    // Clamp position to stay within bounds
    int finalX = qBound(minMargin, idealX, maxX);
    int finalY = qBound(toolbarHeight + tabBarHeight + minMargin, idealY, maxY);

    // Move the dial to the calculated position
    dialContainer->move(finalX, finalY);
}
    void handleDialInput(int angle) {
    if (!tracking) {
        startAngle = angle;  // ✅ Set initial position
        accumulatedRotation = 0;  // ✅ Reset tracking
        tracking = true;
        lastAngle = angle;
        return;
    }

    int delta = angle - lastAngle;

    // ✅ Handle 360-degree wrapping
    if (delta > 180) delta -= 360;  // Example: 350° → 10° should be -20° instead of +340°
    if (delta < -180) delta += 360; // Example: 10° → 350° should be +20° instead of -340°

    accumulatedRotation += delta;  // ✅ Accumulate movement

    // ✅ Detect crossing a 45-degree boundary
    int currentClicks = accumulatedRotation / 45; // Total number of "clicks" crossed
    int previousClicks = (accumulatedRotation - delta) / 45; // Previous click count

    if (currentClicks != previousClicks) {  // ✅ Play sound if a new boundary is crossed

        if (dialClickSound) {
            dialClickSound->play();

            // ✅ Vibrate controller
            SDL_Joystick *joystick = controllerManager->getJoystick();
            if (joystick) {
                // Note: SDL_JoystickRumble requires SDL 2.0.9+
                // For older versions, this will be a no-op
                #if SDL_VERSION_ATLEAST(2, 0, 9)
                SDL_JoystickRumble(joystick, 0xA000, 0xF000, 10);  // Vibrate shortly
                #endif
            }

            grossTotalClicks += 1;
            tempClicks = currentClicks;
            updateDialDisplay();

            if (isLowResPreviewEnabled()) {
                int previewPage = qBound(1, getCurrentPageForCanvas(currentCanvas()) + currentClicks, 99999);
                currentCanvas()->loadPdfPreviewAsync(previewPage);
            }
        }
    }

    lastAngle = angle;  // ✅ Store last position
}
    void onDialReleased() {
    if (!tracking) return;  // ✅ Ignore if no tracking

    int pagesToAdvance = fastForwardMode ? 8 : 1;
    int totalClicks = accumulatedRotation / 45;  // ✅ Convert degrees to pages

    /*
    int leftOver = accumulatedRotation % 45;  // ✅ Track remaining rotation
    if (leftOver > 22 && totalClicks >= 0) {
        totalClicks += 1;  // ✅ Round up if more than halfway
    }
    else if (leftOver <= -22 && totalClicks >= 0) {
        totalClicks -= 1;  // ✅ Round down if more than halfway
    }
    */

    if (totalClicks != 0 || grossTotalClicks != 0) {  // ✅ Only switch pages if movement happened
        // Use concurrent autosave for smoother dial operation
        if (currentCanvas()->isEdited()) {
            saveCurrentPageConcurrent();
        }

        int currentPage = getCurrentPageForCanvas(currentCanvas()) + 1;
        int newPage = qBound(1, currentPage + totalClicks * pagesToAdvance, 99999);

        // ✅ Use direction-aware page switching for dial
        int direction = (totalClicks * pagesToAdvance > 0) ? 1 : -1;
        switchPageWithDirection(newPage, direction);
        pageInput->setValue(newPage);
        tempClicks = 0;
        updateDialDisplay();
        /*
        if (dialClickSound) {
            dialClickSound->play();
        }
        */
    }

    accumulatedRotation = 0;  // ✅ Reset tracking
    grossTotalClicks = 0;
    tracking = false;
}
    void initializeDialSound() {
    if (!dialClickSound) {
        dialClickSound = new QSoundEffect(this);
        dialClickSound->setSource(QUrl::fromLocalFile(":/resources/sounds/dial_click.wav")); // ✅ Path to the sound file
        dialClickSound->setVolume(0.8);  // ✅ Set volume (0.0 - 1.0)
    }
}
    void changeDialMode(DialMode mode) {

    if (!dialContainer) return;  // ✅ Ensure dial container exists
    currentDialMode = mode; // ✅ Set new mode
    updateDialDisplay();

    // ✅ Enable dialHiddenButton for PanAndPageScroll and ZoomControl modes
    dialHiddenButton->setEnabled(currentDialMode == PanAndPageScroll || currentDialMode == ZoomControl);

    // ✅ Disconnect previous slots
    disconnect(pageDial, &QDial::valueChanged, nullptr, nullptr);
    disconnect(pageDial, &QDial::sliderReleased, nullptr, nullptr);

    // ✅ Disconnect dialHiddenButton to reconnect with appropriate function
    disconnect(dialHiddenButton, &QPushButton::clicked, nullptr, nullptr);

    // ✅ Connect dialHiddenButton to appropriate function based on mode
    if (currentDialMode == PanAndPageScroll) {
        connect(dialHiddenButton, &QPushButton::clicked, this, &MainWindow::toggleControlBar);
    } else if (currentDialMode == ZoomControl) {
        connect(dialHiddenButton, &QPushButton::clicked, this, &MainWindow::cycleZoomLevels);
    }

    dialColorPreview->hide();
    dialDisplay->setStyleSheet("background-color: black; color: white; font-size: 14px; border-radius: 40px;");

    // ✅ Connect the correct function set for the current mode
    switch (currentDialMode) {
        case PageSwitching:
            connect(pageDial, &QDial::valueChanged, this, &MainWindow::handleDialInput);
            connect(pageDial, &QDial::sliderReleased, this, &MainWindow::onDialReleased);
            break;
        case ZoomControl:
            connect(pageDial, &QDial::valueChanged, this, &MainWindow::handleDialZoom);
            connect(pageDial, &QDial::sliderReleased, this, &MainWindow::onZoomReleased);
            break;
        case ThicknessControl:
            connect(pageDial, &QDial::valueChanged, this, &MainWindow::handleDialThickness);
            connect(pageDial, &QDial::sliderReleased, this, &MainWindow::onThicknessReleased);
            break;

        case ToolSwitching:
            connect(pageDial, &QDial::valueChanged, this, &MainWindow::handleToolSelection);
            connect(pageDial, &QDial::sliderReleased, this, &MainWindow::onToolReleased);
            break;
        case PresetSelection:
            connect(pageDial, &QDial::valueChanged, this, &MainWindow::handlePresetSelection);
            connect(pageDial, &QDial::sliderReleased, this, &MainWindow::onPresetReleased);
            break;
        case PanAndPageScroll:
            connect(pageDial, &QDial::valueChanged, this, &MainWindow::handleDialPanScroll);
            connect(pageDial, &QDial::sliderReleased, this, &MainWindow::onPanScrollReleased);
            break;

    }
}
    void handleDialZoom(int angle) {
    if (!tracking) {
        startAngle = angle;
        accumulatedRotation = 0;
        tracking = true;
        lastAngle = angle;
        return;
    }

    int delta = angle - lastAngle;

    // ✅ Handle 360-degree wrapping
    if (delta > 180) delta -= 360;
    if (delta < -180) delta += 360;

    accumulatedRotation += delta;

    if (abs(delta) < 4) {
        return;
    }

    // ✅ Apply zoom dynamically (instead of waiting for release)
    int oldZoom = zoomSlider->value();
    int newZoom = qBound(10, oldZoom + (delta / 4), 400);
    zoomSlider->setValue(newZoom);
    updateZoom();  // ✅ Ensure zoom updates immediately
    updateDialDisplay();

    lastAngle = angle;
}
    void handleDialThickness(int angle) {
    if (!tracking) {
        startAngle = angle;
        tracking = true;
        lastAngle = angle;
        return;
    }

    int delta = angle - lastAngle;
    if (delta > 180) delta -= 360;
    if (delta < -180) delta += 360;

    int thicknessStep = fastForwardMode ? 5 : 1;
    currentCanvas()->setPenThickness(qBound<qreal>(1.0, currentCanvas()->getPenThickness() + (delta / 10.0) * thicknessStep, 50.0));

    updateDialDisplay();
    lastAngle = angle;
}
    void onZoomReleased() {
    accumulatedRotation = 0;
    tracking = false;
}
    void onThicknessReleased() {
    accumulatedRotation = 0;
    tracking = false;
}
    void updateDialDisplay() {
    if (!dialDisplay) return;
    if (!dialColorPreview) return;
    if (!dialIconView) return;
    dialIconView->show();
    qreal dpr = initialDpr;
    QColor currentColor = currentCanvas()->getPenColor();
    switch (currentDialMode) {
        case DialMode::PageSwitching:
            if (fastForwardMode){
                dialDisplay->setText(QString(tr("\n\nPage\n%1").arg(getCurrentPageForCanvas(currentCanvas()) + 1 + tempClicks * 8)));
            }
            else {
                dialDisplay->setText(QString(tr("\n\nPage\n%1").arg(getCurrentPageForCanvas(currentCanvas()) + 1 + tempClicks)));
            }
            dialIconView->setPixmap(QPixmap(":/resources/reversed_icons/bookpage_reversed.png").scaled(30, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation));
            break;
        case DialMode::ThicknessControl:
            {
                QString toolName;
                switch (currentCanvas()->getCurrentTool()) {
                    case ToolType::Pen:
                        toolName = tr("Pen");
                        break;
                    case ToolType::Marker:
                        toolName = tr("Marker");
                        break;
                    case ToolType::Eraser:
                        toolName = tr("Eraser");
                        break;
                }
                dialDisplay->setText(QString(tr("\n\n%1\n%2").arg(toolName).arg(QString::number(currentCanvas()->getPenThickness(), 'f', 1))));
            dialIconView->setPixmap(QPixmap(":/resources/reversed_icons/thickness_reversed.png").scaled(30, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation));
            }
            break;
        case DialMode::ZoomControl:
            dialDisplay->setText(QString(tr("\n\nZoom\n%1%").arg(currentCanvas() ? currentCanvas()->getZoom() * initialDpr : zoomSlider->value() * initialDpr)));
            dialIconView->setPixmap(QPixmap(":/resources/reversed_icons/zoom_reversed.png").scaled(30, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation));
            break;

        case DialMode::ToolSwitching:
            // ✅ Convert ToolType to QString for display
            switch (currentCanvas()->getCurrentTool()) {
                case ToolType::Pen:
                    dialDisplay->setText(tr("\n\n\nPen"));
                    dialIconView->setPixmap(QPixmap(":/resources/reversed_icons/pen_reversed.png").scaled(30, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation));
                    break;
                case ToolType::Marker:
                    dialDisplay->setText(tr("\n\n\nMarker"));
                    dialIconView->setPixmap(QPixmap(":/resources/reversed_icons/marker_reversed.png").scaled(30, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation));
                    break;
                case ToolType::Eraser:
                    dialDisplay->setText(tr("\n\n\nEraser"));
                    dialIconView->setPixmap(QPixmap(":/resources/reversed_icons/eraser_reversed.png").scaled(30, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation));
                    break;
            }
            break;
        case PresetSelection:
            dialColorPreview->show();
            dialIconView->hide();
            dialColorPreview->setStyleSheet(QString("background-color: %1; border-radius: 15px; border: 1px solid black;")
                                            .arg(colorPresets[currentPresetIndex].name()));
            dialDisplay->setText(QString(tr("\n\nPreset %1\n#%2"))
                                            .arg(currentPresetIndex + 1)  // ✅ Display preset index (1-based)
                                            .arg(colorPresets[currentPresetIndex].name().remove("#"))); // ✅ Display hex color
            // dialIconView->setPixmap(QPixmap(":/resources/reversed_icons/preset_reversed.png").scaled(30, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation));
            break;
        case DialMode::PanAndPageScroll:
            dialIconView->setPixmap(QPixmap(":/resources/icons/scroll_reversed.png").scaled(30, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation));
            QString fullscreenStatus = controlBarVisible ? tr("Etr") : tr("Exit");
            dialDisplay->setText(QString(tr("\n\nPage %1\n%2 FulScr")).arg(getCurrentPageForCanvas(currentCanvas()) + 1).arg(fullscreenStatus));
            break;
    }
}
    void handleToolSelection(int angle) {
    static int lastToolIndex = -1;  // ✅ Track last tool index

    // ✅ Snap to closest fixed 120° step
    int snappedAngle = (angle + 60) / 120 * 120;  // Round to nearest 120°
    int toolIndex = snappedAngle / 120;  // Convert to tool index (0, 1, 2)

    if (toolIndex >= 3) toolIndex = 0;  // ✅ Wrap around at 360° → Back to Pen (0)

    if (toolIndex != lastToolIndex) {  // ✅ Only switch if tool actually changes
        toolSelector->setCurrentIndex(toolIndex);  // ✅ Change tool
        lastToolIndex = toolIndex;  // ✅ Update last selected tool

        // ✅ Play click sound when tool changes
        if (dialClickSound) {
            dialClickSound->play();
        }

        SDL_Joystick *joystick = controllerManager->getJoystick();

        if (joystick) {
            #if SDL_VERSION_ATLEAST(2, 0, 9)
            SDL_JoystickRumble(joystick, 0xA000, 0xF000, 20);  // ✅ Vibrate controller
            #endif
        }

        updateToolButtonStates();  // ✅ Update tool button states
        updateDialDisplay();  // ✅ Update dial display]
    }
}
    void onToolReleased() {

}
    void handlePresetSelection(int angle) {
    static int lastAngle = angle;
    int delta = angle - lastAngle;

    // ✅ Handle 360-degree wrapping
    if (delta > 180) delta -= 360;
    if (delta < -180) delta += 360;

    if (abs(delta) >= 60) {  // ✅ Change preset every 60° (6 presets)
        lastAngle = angle;
        currentPresetIndex = (currentPresetIndex + (delta > 0 ? 1 : -1) + colorPresets.size()) % colorPresets.size();

        QColor selectedColor = colorPresets[currentPresetIndex];
        currentCanvas()->setPenColor(selectedColor);
        updateCustomColorButtonStyle(selectedColor);
        updateDialDisplay();
        updateColorButtonStates();  // Update button states when preset is selected

        if (dialClickSound) dialClickSound->play();  // ✅ Provide feedback
        SDL_Joystick *joystick = controllerManager->getJoystick();
            if (joystick) {
                #if SDL_VERSION_ATLEAST(2, 0, 9)
                SDL_JoystickRumble(joystick, 0xA000, 0xF000, 25);  // Vibrate shortly
                #endif
            }
    }
}
    void onPresetReleased() {
    accumulatedRotation = 0;
    tracking = false;
}
    void addColorPreset() {
    QColor currentColor = currentCanvas()->getPenColor();

    // ✅ Prevent duplicates
    if (!colorPresets.contains(currentColor)) {
        if (colorPresets.size() >= 6) {
            colorPresets.dequeue();  // ✅ Remove oldest color
        }
        colorPresets.enqueue(currentColor);
    }
}
    void updateColorPalette() {
    // Clear existing presets
    colorPresets.clear();
    currentPresetIndex = 0;

    // Add default pen color (theme-aware)
    colorPresets.enqueue(getDefaultPenColor());

    // Add palette colors
    colorPresets.enqueue(getPaletteColor("red"));
    colorPresets.enqueue(getPaletteColor("yellow"));
    colorPresets.enqueue(getPaletteColor("blue"));
    colorPresets.enqueue(getPaletteColor("green"));
    colorPresets.enqueue(QColor("#000000")); // Black (always same)
    colorPresets.enqueue(QColor("#FFFFFF")); // White (always same)

    // Only update UI elements if they exist
    if (redButton && blueButton && yellowButton && greenButton) {
        bool darkMode = isDarkMode();

        // Update color button icons based on current palette (not theme)
        QString redIconPath = useBrighterPalette ? ":/resources/icons/pen_light_red.png" : ":/resources/icons/pen_dark_red.png";
        QString blueIconPath = useBrighterPalette ? ":/resources/icons/pen_light_blue.png" : ":/resources/icons/pen_dark_blue.png";
        QString yellowIconPath = useBrighterPalette ? ":/resources/icons/pen_light_yellow.png" : ":/resources/icons/pen_dark_yellow.png";
        QString greenIconPath = useBrighterPalette ? ":/resources/icons/pen_light_green.png" : ":/resources/icons/pen_dark_green.png";

        redButton->setIcon(QIcon(redIconPath));
        blueButton->setIcon(QIcon(blueIconPath));
        yellowButton->setIcon(QIcon(yellowIconPath));
        greenButton->setIcon(QIcon(greenIconPath));

        // Update color button states
        updateColorButtonStates();
    }
}
    QColor getPaletteColor(const QString &colorName) {
    if (useBrighterPalette) {
        // Brighter colors (good for dark backgrounds)
        if (colorName == "red") return QColor("#FF7755");
        if (colorName == "yellow") return QColor("#EECC00");
        if (colorName == "blue") return QColor("#66CCFF");
        if (colorName == "green") return QColor("#55FF77");
    } else {
        // Darker colors (good for light backgrounds)
        if (colorName == "red") return QColor("#AA0000");
        if (colorName == "yellow") return QColor("#997700");
        if (colorName == "blue") return QColor("#0000AA");
        if (colorName == "green") return QColor("#007700");
    }

    // Fallback colors
    if (colorName == "black") return QColor("#000000");
    if (colorName == "white") return QColor("#FFFFFF");

    return QColor("#000000"); // Default fallback
}
    qreal getDevicePixelRatio() {
    QScreen *screen = QGuiApplication::primaryScreen();
    qreal devicePixelRatio = screen ? screen->devicePixelRatio() : 1.0; // Default to 1.0 if null
    return devicePixelRatio;
}
    bool isDarkMode() {
    QColor bg = palette().color(QPalette::Window);
    return bg.lightness() < 128;  // Lightness scale: 0 (black) - 255 (white)
}
    QIcon loadThemedIcon(const QString& baseName) {
    QString path = isDarkMode()
        ? QString(":/resources/icons/%1_reversed.png").arg(baseName)
        : QString(":/resources/icons/%1.png").arg(baseName);
    return QIcon(path);
}
    QString createButtonStyle(bool darkMode) {
    if (darkMode) {
        // Dark mode: Keep current white highlights (good contrast)
        return R"(
            QPushButton {
                background: transparent;
                border: none;
                padding: 6px;
            }
            QPushButton:hover {
                background: rgba(255, 255, 255, 50);
            }
            QPushButton:pressed {
                background: rgba(0, 0, 0, 50);
            }
            QPushButton[selected="true"] {
                background: rgba(255, 255, 255, 100);
                border: 2px solid rgba(255, 255, 255, 150);
                padding: 4px;
                border-radius: 4px;
            }
            QPushButton[selected="true"]:hover {
                background: rgba(255, 255, 255, 120);
            }
            QPushButton[selected="true"]:pressed {
                background: rgba(0, 0, 0, 50);
            }
        )";
    } else {
        // Light mode: Use darker colors for better visibility
        return R"(
            QPushButton {
                background: transparent;
                border: none;
                padding: 6px;
            }
            QPushButton:hover {
                background: rgba(0, 0, 0, 30);
            }
            QPushButton:pressed {
                background: rgba(0, 0, 0, 60);
            }
            QPushButton[selected="true"] {
                background: rgba(0, 0, 0, 80);
                border: 2px solid rgba(0, 0, 0, 120);
                padding: 4px;
                border-radius: 4px;
            }
            QPushButton[selected="true"]:hover {
                background: rgba(0, 0, 0, 100);
            }
            QPushButton[selected="true"]:pressed {
                background: rgba(0, 0, 0, 140);
            }
        )";
    }
}
    void handleDialPanScroll(int angle) {
    if (!tracking) {
        startAngle = angle;
        accumulatedRotation = 0;
        accumulatedRotationAfterLimit = 0;
        tracking = true;
        lastAngle = angle;
        pendingPageFlip = 0;
        return;
    }

    int delta = angle - lastAngle;

    // Handle 360 wrap
    if (delta > 180) delta -= 360;
    if (delta < -180) delta += 360;

    accumulatedRotation += delta;

    // Pan scroll
    int panDelta = delta * 4;  // Adjust scroll sensitivity here
    int currentPan = panYSlider->value();
    int newPan = currentPan + panDelta;

    // Clamp pan slider
    newPan = qBound(panYSlider->minimum(), newPan, panYSlider->maximum());
    panYSlider->setValue(newPan);

    // ✅ NEW → if slider reached top/bottom, accumulate AFTER LIMIT
    if (newPan == panYSlider->maximum()) {
        accumulatedRotationAfterLimit += delta;

        if (accumulatedRotationAfterLimit >= 120) {
            pendingPageFlip = +1;  // Flip next when released
        }
    }
    else if (newPan == panYSlider->minimum()) {
        accumulatedRotationAfterLimit += delta;

        if (accumulatedRotationAfterLimit <= -120) {
            pendingPageFlip = -1;  // Flip previous when released
        }
    }
    else {
        // Reset after limit accumulator when not at limit
        accumulatedRotationAfterLimit = 0;
        pendingPageFlip = 0;
    }

    lastAngle = angle;
}
    void onPanScrollReleased() {
    // ✅ Perform page flip only when dial released and flip is pending
    if (pendingPageFlip != 0) {
        // Use concurrent saving for smooth dial operation
        if (currentCanvas()->isEdited()) {
            saveCurrentPageConcurrent();
        }

        int currentPage = getCurrentPageForCanvas(currentCanvas());
        int newPage = qBound(1, currentPage + pendingPageFlip + 1, 99999);

        // ✅ Use direction-aware page switching for pan-and-scroll dial
        switchPageWithDirection(newPage, pendingPageFlip);
        pageInput->setValue(newPage);
        updateDialDisplay();

        SDL_Joystick *joystick = controllerManager->getJoystick();
        if (joystick) {
            #if SDL_VERSION_ATLEAST(2, 0, 9)
            SDL_JoystickRumble(joystick, 0xA000, 0xF000, 25);  // Vibrate shortly
            #endif
        }
    }

    // Reset states
    pendingPageFlip = 0;
    accumulatedRotation = 0;
    accumulatedRotationAfterLimit = 0;
    tracking = false;
}
    void handleTouchZoomChange(int newZoom) {
    // Update zoom slider without triggering updateZoom again
    zoomSlider->blockSignals(true);
    int oldZoom = zoomSlider->value();  // Get the old zoom value before updating the slider
    zoomSlider->setValue(newZoom);
    zoomSlider->blockSignals(false);

    // Show both scrollbars during gesture
    if (panXSlider->maximum() > 0) {
        panXSlider->setVisible(true);
    }
    if (panYSlider->maximum() > 0) {
        panYSlider->setVisible(true);
    }
    scrollbarsVisible = true;

    // Update canvas zoom directly
    InkCanvas *canvas = currentCanvas();
    if (canvas) {
        // The canvas zoom has already been set by the gesture processing in InkCanvas::event()
        // So we don't need to set it again, just update the last zoom level
        canvas->setLastZoomLevel(newZoom);
        updatePanRange();

        // ✅ FIXED: Add thickness adjustment for pinch-to-zoom gestures to maintain visual consistency
        adjustThicknessForZoom(oldZoom, newZoom);

        updateDialDisplay();
    }
}
    void handleTouchPanChange(int panX, int panY) {
    // Clamp values to valid ranges
    panX = qBound(panXSlider->minimum(), panX, panXSlider->maximum());
    panY = qBound(panYSlider->minimum(), panY, panYSlider->maximum());

    // Show both scrollbars during gesture
    if (panXSlider->maximum() > 0) {
        panXSlider->setVisible(true);
    }
    if (panYSlider->maximum() > 0) {
        panYSlider->setVisible(true);
    }
    scrollbarsVisible = true;

    // Update sliders without triggering their valueChanged signals
    panXSlider->blockSignals(true);
    panYSlider->blockSignals(true);
    panXSlider->setValue(panX);
    panYSlider->setValue(panY);
    panXSlider->blockSignals(false);
    panYSlider->blockSignals(false);

    // Update canvas pan directly
    InkCanvas *canvas = currentCanvas();
    if (canvas) {
        canvas->setPanX(panX);
        canvas->setPanY(panY);
        canvas->setLastPanX(panX);
        canvas->setLastPanY(panY);
    }
}
    void handleTouchGestureEnd() {
    // Hide scrollbars immediately when touch gesture ends
    panXSlider->setVisible(false);
    panYSlider->setVisible(false);
    scrollbarsVisible = false;
}
    void updateColorButtonStates() {
    // Check if there's a current canvas
    if (!currentCanvas()) return;

    // Get current pen color
    QColor currentColor = currentCanvas()->getPenColor();

    // Determine if we're in dark mode to match the correct colors
    bool darkMode = isDarkMode();

    // Reset all color buttons to original style
    redButton->setProperty("selected", false);
    blueButton->setProperty("selected", false);
    yellowButton->setProperty("selected", false);
    greenButton->setProperty("selected", false);
    blackButton->setProperty("selected", false);
    whiteButton->setProperty("selected", false);

    // Set the selected property for the matching color button based on current palette
    QColor redColor = getPaletteColor("red");
    QColor blueColor = getPaletteColor("blue");
    QColor yellowColor = getPaletteColor("yellow");
    QColor greenColor = getPaletteColor("green");

    if (currentColor == redColor) {
        redButton->setProperty("selected", true);
    } else if (currentColor == blueColor) {
        blueButton->setProperty("selected", true);
    } else if (currentColor == yellowColor) {
        yellowButton->setProperty("selected", true);
    } else if (currentColor == greenColor) {
        greenButton->setProperty("selected", true);
    } else if (currentColor == QColor("#000000")) {
        blackButton->setProperty("selected", true);
    } else if (currentColor == QColor("#FFFFFF")) {
        whiteButton->setProperty("selected", true);
    }

    // Force style update
    redButton->style()->unpolish(redButton);
    redButton->style()->polish(redButton);
    blueButton->style()->unpolish(blueButton);
    blueButton->style()->polish(blueButton);
    yellowButton->style()->unpolish(yellowButton);
    yellowButton->style()->polish(yellowButton);
    greenButton->style()->unpolish(greenButton);
    greenButton->style()->polish(greenButton);
    blackButton->style()->unpolish(blackButton);
    blackButton->style()->polish(blackButton);
    whiteButton->style()->unpolish(whiteButton);
    whiteButton->style()->polish(whiteButton);
}
    void selectColorButton(QPushButton* selectedButton) {
    updateColorButtonStates();
}
    void updateStraightLineButtonState() {
    // Check if there's a current canvas
    if (!currentCanvas()) return;

    // Update the button state to match the canvas straight line mode
    bool isEnabled = currentCanvas()->isStraightLineMode();

    // Set visual indicator that the button is active/inactive
    if (straightLineToggleButton) {
        straightLineToggleButton->setProperty("selected", isEnabled);

        // Force style update
        straightLineToggleButton->style()->unpolish(straightLineToggleButton);
        straightLineToggleButton->style()->polish(straightLineToggleButton);
    }
}
    void updateRopeToolButtonState() {
    // Check if there's a current canvas
    if (!currentCanvas()) return;

    // Update the button state to match the canvas rope tool mode
    bool isEnabled = currentCanvas()->isRopeToolMode();

    // Set visual indicator that the button is active/inactive
    if (ropeToolButton) {
        ropeToolButton->setProperty("selected", isEnabled);

        // Force style update
        ropeToolButton->style()->unpolish(ropeToolButton);
        ropeToolButton->style()->polish(ropeToolButton);
    }
}
    void updateMarkdownButtonState() {
    // Check if there's a current canvas
    if (!currentCanvas()) return;

    // Update the button state to match the canvas markdown selection mode
    bool isEnabled = currentCanvas()->isMarkdownSelectionMode();

    // Set visual indicator that the button is active/inactive
    if (markdownButton) {
        markdownButton->setProperty("selected", isEnabled);

        // Force style update
        markdownButton->style()->unpolish(markdownButton);
        markdownButton->style()->polish(markdownButton);
    }
}
    void setPenTool() {
    if (!currentCanvas()) return;
    currentCanvas()->setTool(ToolType::Pen);
    updateToolButtonStates();
    updateThicknessSliderForCurrentTool();
    updateDialDisplay();
}
    void setMarkerTool() {
    if (!currentCanvas()) return;
    currentCanvas()->setTool(ToolType::Marker);
    updateToolButtonStates();
    updateThicknessSliderForCurrentTool();
    updateDialDisplay();
}
    void setEraserTool() {
    if (!currentCanvas()) return;
    currentCanvas()->setTool(ToolType::Eraser);
    updateToolButtonStates();
    updateThicknessSliderForCurrentTool();
    updateDialDisplay();
}
    QColor getContrastingTextColor(const QColor &backgroundColor) {
    // Calculate relative luminance using the formula from WCAG 2.0
    double r = backgroundColor.redF();
    double g = backgroundColor.greenF();
    double b = backgroundColor.blueF();

    // Gamma correction
    r = (r <= 0.03928) ? r/12.92 : pow((r + 0.055)/1.055, 2.4);
    g = (g <= 0.03928) ? g/12.92 : pow((g + 0.055)/1.055, 2.4);
    b = (b <= 0.03928) ? b/12.92 : pow((b + 0.055)/1.055, 2.4);

    // Calculate luminance
    double luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;

    // Use white text for darker backgrounds
    return (luminance < 0.5) ? Qt::white : Qt::black;
}
    void updateCustomColorButtonStyle(const QColor &color) {
    QColor textColor = getContrastingTextColor(color);
    customColorButton->setStyleSheet(QString("background-color: %1; color: %2")
        .arg(color.name())
        .arg(textColor.name()));
    customColorButton->setText(QString("%1").arg(color.name()).toUpper());
}
    void openRecentNotebooksDialog() {
    RecentNotebooksDialog dialog(this, recentNotebooksManager, this);
    dialog.exec();
}
    void showPendingTooltip() {
    // This function is now unused since we disabled tablet tracking
    // Tooltips will work through normal mouse hover events instead
    // Keeping the function for potential future use
}
    void showRopeSelectionMenu(const QPoint &position) {
    // Create context menu for rope tool selection
    QMenu *contextMenu = new QMenu(this);
    contextMenu->setAttribute(Qt::WA_DeleteOnClose);

    // Add Copy action
    QAction *copyAction = contextMenu->addAction(tr("Copy"));
    copyAction->setIcon(loadThemedIcon("copy"));
    connect(copyAction, &QAction::triggered, this, [this]() {
        if (currentCanvas()) {
            currentCanvas()->copyRopeSelection();
        }
    });

    // Add Delete action
    QAction *deleteAction = contextMenu->addAction(tr("Delete"));
    deleteAction->setIcon(loadThemedIcon("trash"));
    connect(deleteAction, &QAction::triggered, this, [this]() {
        if (currentCanvas()) {
            currentCanvas()->deleteRopeSelection();
        }
    });

    // Add Cancel action
    QAction *cancelAction = contextMenu->addAction(tr("Cancel"));
    cancelAction->setIcon(loadThemedIcon("cross"));
    connect(cancelAction, &QAction::triggered, this, [this]() {
        if (currentCanvas()) {
            currentCanvas()->cancelRopeSelection();
        }
    });

    // Convert position from canvas coordinates to global coordinates
    QPoint globalPos = currentCanvas()->mapToGlobal(position);

    // Show the menu at the specified position
    contextMenu->popup(globalPos);
}
    void toggleOutlineSidebar() {
    outlineSidebarVisible = !outlineSidebarVisible;

    // Hide bookmarks sidebar if it's visible when opening outline
    if (outlineSidebarVisible && bookmarksSidebar && bookmarksSidebar->isVisible()) {
        bookmarksSidebar->setVisible(false);
        bookmarksSidebarVisible = false;
        // Update bookmarks button state
        if (toggleBookmarksButton) {
            toggleBookmarksButton->setProperty("selected", false);
            toggleBookmarksButton->style()->unpolish(toggleBookmarksButton);
            toggleBookmarksButton->style()->polish(toggleBookmarksButton);
        }
    }

    outlineSidebar->setVisible(outlineSidebarVisible);

    // Update button toggle state
    if (toggleOutlineButton) {
        toggleOutlineButton->setProperty("selected", outlineSidebarVisible);
        toggleOutlineButton->style()->unpolish(toggleOutlineButton);
        toggleOutlineButton->style()->polish(toggleOutlineButton);
    }

    // Load PDF outline when showing sidebar for the first time
    if (outlineSidebarVisible) {
        loadPdfOutline();
    }
}
    void onOutlineItemClicked(QTreeWidgetItem *item, int column) {
    Q_UNUSED(column);

    if (!item) return;

    // Get the page number stored in the item data
    QVariant pageData = item->data(0, Qt::UserRole);
    if (pageData.isValid()) {
        int pageNumber = pageData.toInt();
        if (pageNumber >= 0) {
            // Switch to the selected page (convert from 0-based to 1-based)
            switchPage(pageNumber + 1);
            pageInput->setValue(pageNumber + 1);
        }
    }
}
    void loadPdfOutline() {
    if (!outlineTree) return;

    outlineTree->clear();

    // Get current PDF document
    Poppler::Document* pdfDoc = getPdfDocument();
    if (!pdfDoc) return;

    // Get the outline from the PDF document
    QVector<Poppler::OutlineItem> outlineItems = pdfDoc->outline();

    if (outlineItems.isEmpty()) {
        // If no outline exists, show page numbers as fallback
        int pageCount = pdfDoc->numPages();
        for (int i = 0; i < pageCount; ++i) {
            QTreeWidgetItem* item = new QTreeWidgetItem(outlineTree);
            item->setText(0, QString(tr("Page %1")).arg(i + 1));
            item->setData(0, Qt::UserRole, i); // Store 0-based page index
        }
    } else {
        // Process the actual PDF outline
        for (const Poppler::OutlineItem& outlineItem : outlineItems) {
            addOutlineItem(outlineItem, nullptr);
        }
    }

    // Expand the first level by default
    outlineTree->expandToDepth(0);
}
    void addOutlineItem(const Poppler::OutlineItem& outlineItem, QTreeWidgetItem* parentItem) {
    if (outlineItem.isNull()) return;

    QTreeWidgetItem* item;
    if (parentItem) {
        item = new QTreeWidgetItem(parentItem);
    } else {
        item = new QTreeWidgetItem(outlineTree);
    }

    // Set the title
    item->setText(0, outlineItem.name());

    // Try to get the page number from the destination
    int pageNumber = -1;
    auto destination = outlineItem.destination();
    if (destination) {
        pageNumber = destination->pageNumber();
    }

    // Store the page number (1-based) in the item data
    if (pageNumber >= 0) {
        item->setData(0, Qt::UserRole, pageNumber + 1); // Convert to 1-based
    }

    // Add child items recursively
    if (outlineItem.hasChildren()) {
        QVector<Poppler::OutlineItem> children = outlineItem.children();
        for (const Poppler::OutlineItem& child : children) {
            addOutlineItem(child, item);
        }
    }
}
    Poppler::Document* getPdfDocument();
    void toggleBookmarksSidebar() {
    if (!bookmarksSidebar) return;

    bool isVisible = bookmarksSidebar->isVisible();

    // Hide outline sidebar if it's visible
    if (!isVisible && outlineSidebar && outlineSidebar->isVisible()) {
        outlineSidebar->setVisible(false);
        outlineSidebarVisible = false;
        // Update outline button state
        if (toggleOutlineButton) {
            toggleOutlineButton->setProperty("selected", false);
            toggleOutlineButton->style()->unpolish(toggleOutlineButton);
            toggleOutlineButton->style()->polish(toggleOutlineButton);
        }
    }

    bookmarksSidebar->setVisible(!isVisible);
    bookmarksSidebarVisible = !isVisible;

    // Update button toggle state
    if (toggleBookmarksButton) {
        toggleBookmarksButton->setProperty("selected", bookmarksSidebarVisible);
        toggleBookmarksButton->style()->unpolish(toggleBookmarksButton);
        toggleBookmarksButton->style()->polish(toggleBookmarksButton);
    }

    if (bookmarksSidebarVisible) {
        loadBookmarks(); // Refresh bookmarks when opening
    }
}
    void onBookmarkItemClicked(QTreeWidgetItem *item, int column) {
    Q_UNUSED(column);
    if (!item) return;

    // Get the page number from the item data
    bool ok;
    int pageNumber = item->data(0, Qt::UserRole).toInt(&ok);
    if (ok && pageNumber > 0) {
        // Navigate to the bookmarked page
        switchPageWithDirection(pageNumber, (pageNumber > getCurrentPageForCanvas(currentCanvas()) + 1) ? 1 : -1);
        pageInput->setValue(pageNumber);
    }
}
    void loadBookmarks() {
    if (!bookmarksTree || !currentCanvas()) return;

    bookmarksTree->clear();

    // Get the current notebook's save folder
    QString saveFolder = currentCanvas()->getSaveFolder();
    if (saveFolder.isEmpty()) return;

    QString bookmarksFile = saveFolder + "/.bookmarks.txt";
    QFile file(bookmarksFile);

    bookmarks.clear();

    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream in(&file);
        while (!in.atEnd()) {
            QString line = in.readLine().trimmed();
            if (line.isEmpty()) continue;

            QStringList parts = line.split('\t', Qt::KeepEmptyParts);
            if (parts.size() >= 2) {
                bool ok;
                int pageNum = parts[0].toInt(&ok);
                if (ok) {
                    QString title = parts[1];
                    bookmarks[pageNum] = title;
                }
            }
        }
        file.close();
    }

    // Populate the tree widget
    for (auto it = bookmarks.begin(); it != bookmarks.end(); ++it) {
        QTreeWidgetItem *item = new QTreeWidgetItem(bookmarksTree);

        // Create a custom widget for each bookmark item
        QWidget *itemWidget = new QWidget();
        QHBoxLayout *itemLayout = new QHBoxLayout(itemWidget);
        itemLayout->setContentsMargins(5, 2, 5, 2);
        itemLayout->setSpacing(5);

        // Page number label (fixed width)
        QLabel *pageLabel = new QLabel(QString(tr("Page %1")).arg(it.key()));
        pageLabel->setFixedWidth(60);
        pageLabel->setStyleSheet("font-weight: bold; color: #666;");
        itemLayout->addWidget(pageLabel);

        // Editable title (supports Windows handwriting if available)
        QLineEdit *titleEdit = new QLineEdit(it.value());
        titleEdit->setPlaceholderText("Enter bookmark title...");
        titleEdit->setProperty("pageNumber", it.key()); // Store page number for saving

        // Enable IME support for multi-language input
        titleEdit->setAttribute(Qt::WA_InputMethodEnabled, true);
        titleEdit->setInputMethodHints(Qt::ImhNone); // Allow all input methods
        titleEdit->installEventFilter(this); // Install event filter for IME handling

        // Connect to save when editing is finished
        connect(titleEdit, &QLineEdit::editingFinished, this, [this, titleEdit]() {
            int pageNum = titleEdit->property("pageNumber").toInt();
            QString newTitle = titleEdit->text().trimmed();

            if (newTitle.isEmpty()) {
                // Remove bookmark if title is empty
                bookmarks.remove(pageNum);
            } else {
                // Update bookmark title
                bookmarks[pageNum] = newTitle;
            }
            saveBookmarks();
            updateBookmarkButtonState(); // Update button state
        });

        itemLayout->addWidget(titleEdit, 1);

        // Store page number in item data for navigation
        item->setData(0, Qt::UserRole, it.key());

        // Set the custom widget
        bookmarksTree->setItemWidget(item, 0, itemWidget);

        // Set item height
        item->setSizeHint(0, QSize(0, 30));
    }

    updateBookmarkButtonState(); // Update button state after loading
}
    void saveBookmarks() {
    if (!currentCanvas()) return;

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

    QString bookmarksFile = saveFolder + "/.bookmarks.txt";
    QFile file(bookmarksFile);

    if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QTextStream out(&file);

        // Sort bookmarks by page number
        QList<int> sortedPages = bookmarks.keys();
        std::sort(sortedPages.begin(), sortedPages.end());

        for (int pageNum : sortedPages) {
            out << pageNum << '\t' << bookmarks[pageNum] << '\n';
        }

        file.close();
    }
}
    void toggleCurrentPageBookmark() {
    if (!currentCanvas()) return;

    int currentPage = getCurrentPageForCanvas(currentCanvas()) + 1; // Convert to 1-based

    if (bookmarks.contains(currentPage)) {
        // Remove bookmark
        bookmarks.remove(currentPage);
    } else {
        // Add bookmark with default title
        QString defaultTitle = QString(tr("Bookmark %1")).arg(currentPage);
        bookmarks[currentPage] = defaultTitle;
    }

    saveBookmarks();
    updateBookmarkButtonState();

    // Refresh bookmarks view if visible
    if (bookmarksSidebarVisible) {
        loadBookmarks();
    }
}
    void updateBookmarkButtonState() {
    if (!toggleBookmarkButton || !currentCanvas()) return;

    int currentPage = getCurrentPageForCanvas(currentCanvas()) + 1; // Convert to 1-based
    bool isBookmarked = bookmarks.contains(currentPage);

    toggleBookmarkButton->setProperty("selected", isBookmarked);

    // Update tooltip
    if (isBookmarked) {
        toggleBookmarkButton->setToolTip(tr("Remove Bookmark"));
    } else {
        toggleBookmarkButton->setToolTip(tr("Add Bookmark"));
    }

    // Force style update
    toggleBookmarkButton->style()->unpolish(toggleBookmarkButton);
    toggleBookmarkButton->style()->polish(toggleBookmarkButton);
}
  private:
    InkCanvas *canvas;
    QPushButton *benchmarkButton;
    QLabel *benchmarkLabel;
    QTimer *benchmarkTimer;
    bool benchmarking;
    QPushButton *exportNotebookButton;
    QPushButton *importNotebookButton;
    QPushButton *redButton;
    QPushButton *blueButton;
    QPushButton *yellowButton;
    QPushButton *greenButton;
    QPushButton *blackButton;
    QPushButton *whiteButton;
    QLineEdit *customColorInput;
    QPushButton *customColorButton;
    QPushButton *thicknessButton;
    QSlider *thicknessSlider;
    QFrame *thicknessFrame;
    QComboBox *toolSelector;
    QPushButton *penToolButton;
    QPushButton *markerToolButton;
    QPushButton *eraserToolButton;
    QPushButton *deletePageButton;
    QPushButton *selectFolderButton;
    QPushButton *saveButton;
    QPushButton *saveAnnotatedButton;
    QPushButton *fullscreenButton;
    QPushButton *openControlPanelButton;
    QPushButton *openRecentNotebooksButton;
    QPushButton *loadPdfButton;
    QPushButton *clearPdfButton;
    QPushButton *pdfTextSelectButton;
    QPushButton *toggleTabBarButton;
    QMap<InkCanvas*, int> pageMap;
    QPushButton *backgroundButton;
    QPushButton *straightLineToggleButton;
    QPushButton *ropeToolButton;
    QPushButton *markdownButton;
    QSlider *zoomSlider;
    QPushButton *zoomButton;
    QFrame *zoomFrame;
    QPushButton *dezoomButton;
    QPushButton *zoom50Button;
    QPushButton *zoom200Button;
    QWidget *zoomContainer;
    QLineEdit *zoomInput;
    QScrollBar *panXSlider;
    QScrollBar *panYSlider;
    QListWidget *tabList;
    QStackedWidget *canvasStack;
    QPushButton *addTabButton;
    QWidget *tabBarContainer;
    QWidget *outlineSidebar;
    QTreeWidget *outlineTree;
    QPushButton *toggleOutlineButton;
    bool outlineSidebarVisible = false;
    QWidget *bookmarksSidebar;
    QTreeWidget *bookmarksTree;
    QPushButton *toggleBookmarksButton;
    QPushButton *toggleBookmarkButton;
    QPushButton *touchGesturesButton;
    bool bookmarksSidebarVisible = false;
    QMap<int, QString> bookmarks;
    QPushButton *jumpToPageButton;
    QWidget *dialContainer = nullptr;
    QDial *pageDial = nullptr;
    QDial *modeDial = nullptr;
    QPushButton *dialToggleButton;
    bool fastForwardMode = false;
    QPushButton *fastForwardButton;
    int lastAngle = 0;
    int startAngle = 0;
    bool tracking = false;
    int accumulatedRotation = 0;
    QSoundEffect *dialClickSound = nullptr;
    int grossTotalClicks = 0;
    DialMode currentDialMode = PanAndPageScroll;
    DialMode temporaryDialMode = None;
    QComboBox *dialModeSelector;
    QPushButton *colorPreview;
    QLabel *dialDisplay = nullptr;
    QFrame *dialColorPreview;
    QLabel *dialIconView;
    QFont pixelFont;
    QPushButton *btnPageSwitch;
    QPushButton *btnZoom;
    QPushButton *btnThickness;
    QPushButton *btnTool;
    QPushButton *btnPresets;
    QPushButton *btnPannScroll;
    int tempClicks = 0;
    QPushButton *dialHiddenButton;
    QQueue<QColor> colorPresets;
    QPushButton *addPresetButton;
    int currentPresetIndex = 0;
    bool useBrighterPalette = false;
    qreal initialDpr = 1.0;
    QWidget *sidebarContainer;
    QWidget *controlBar;
    void setTemporaryDialMode(DialMode mode) {
    if (temporaryDialMode == None) {
        temporaryDialMode = currentDialMode;
    }
    changeDialMode(mode);
}
    void clearTemporaryDialMode() {
    if (temporaryDialMode != None) {
        changeDialMode(temporaryDialMode);
        temporaryDialMode = None;
    }
}
    bool controlBarVisible = true;
    void toggleControlBar() {
    // Proper fullscreen toggle: handle both sidebar and control bar

    if (controlBarVisible) {
        // Going into fullscreen mode

        // First, remember current tab bar state
        sidebarWasVisibleBeforeFullscreen = tabBarContainer->isVisible();

        // Hide tab bar if it's visible
        if (tabBarContainer->isVisible()) {
            tabBarContainer->setVisible(false);
        }

        // Hide control bar
        controlBarVisible = false;
        controlBar->setVisible(false);

        // Hide floating popup widgets when control bar is hidden to prevent stacking
        if (zoomFrame && zoomFrame->isVisible()) zoomFrame->hide();
        if (thicknessFrame && thicknessFrame->isVisible()) thicknessFrame->hide();

        // Hide orphaned widgets that are not added to any layout
        if (colorPreview) colorPreview->hide();
        if (thicknessButton) thicknessButton->hide();
        if (jumpToPageButton) jumpToPageButton->hide();

        if (toolSelector) toolSelector->hide();
        if (zoomButton) zoomButton->hide();
        if (customColorInput) customColorInput->hide();

        // Find and hide local widgets that might be orphaned
        QList<QComboBox*> comboBoxes = findChildren<QComboBox*>();
        for (QComboBox* combo : comboBoxes) {
            if (combo->parent() == this && !combo->isVisible()) {
                // Already hidden, keep it hidden
            } else if (combo->parent() == this) {
                // This might be the orphaned dialModeSelector or similar
                combo->hide();
            }
        }
    } else {
        // Coming out of fullscreen mode

        // Restore control bar
        controlBarVisible = true;
        controlBar->setVisible(true);

        // Restore tab bar to its previous state
        tabBarContainer->setVisible(sidebarWasVisibleBeforeFullscreen);

        // Show orphaned widgets when control bar is visible
        // Note: These are kept hidden normally since they're not in the layout
        // Only show them if they were specifically intended to be visible
    }

    // Update dial display to reflect new status
    updateDialDisplay();

    // Force layout update to recalculate space
    if (auto *canvas = currentCanvas()) {
        QTimer::singleShot(0, this, [this, canvas]() {
            canvas->setMaximumSize(canvas->getCanvasSize());
        });
    }
}
    void cycleZoomLevels() {
    if (!zoomSlider) return;

    int currentZoom = zoomSlider->value();
    int targetZoom;

    // Calculate the scaled zoom levels based on initial DPR
    int zoom50 = 50 / initialDpr;
    int zoom100 = 100 / initialDpr;
    int zoom200 = 200 / initialDpr;

    // Cycle through 0.5x -> 1x -> 2x -> 0.5x...
    if (currentZoom <= zoom50 + 5) { // Close to 0.5x (with small tolerance)
        targetZoom = zoom100; // Go to 1x
    } else if (currentZoom <= zoom100 + 5) { // Close to 1x
        targetZoom = zoom200; // Go to 2x
    } else { // Any other zoom level or close to 2x
        targetZoom = zoom50; // Go to 0.5x
    }

    zoomSlider->setValue(targetZoom);
    updateZoom();
    updateDialDisplay();
}
    bool sidebarWasVisibleBeforeFullscreen = true;
    int accumulatedRotationAfterLimit = 0;
    int pendingPageFlip = 0;
    QMap<QString, QString> buttonHoldMapping;
    QMap<QString, QString> buttonPressMapping;
    QMap<QString, ControllerAction> buttonPressActionMapping;
    QMap<QString, QString> keyboardMappings;
    QMap<QString, ControllerAction> keyboardActionMapping;
    QTimer *tooltipTimer;
    QWidget *lastHoveredWidget;
    QPoint pendingTooltipPos;
    void handleButtonHeld(const QString &buttonName) {
    QString mode = buttonHoldMapping.value(buttonName, "None");
    if (mode != "None") {
        setTemporaryDialMode(dialModeFromString(mode));
        return;
    }
}
    void handleButtonReleased(const QString &buttonName) {
    QString mode = buttonHoldMapping.value(buttonName, "None");
    if (mode != "None") {
        clearTemporaryDialMode();
    }
}
    void handleControllerButton(const QString &buttonName) {  // This is for single press functions
    ControllerAction action = buttonPressActionMapping.value(buttonName, ControllerAction::None);

    switch (action) {
        case ControllerAction::ToggleFullscreen:
            fullscreenButton->click();
            break;
        case ControllerAction::ToggleDial:
            toggleDial();
            break;
        case ControllerAction::Zoom50:
            zoom50Button->click();
            break;
        case ControllerAction::ZoomOut:
            dezoomButton->click();
            break;
        case ControllerAction::Zoom200:
            zoom200Button->click();
            break;
        case ControllerAction::AddPreset:
            addPresetButton->click();
            break;
        case ControllerAction::DeletePage:
            deletePageButton->click();  // assuming you have this
            break;
        case ControllerAction::FastForward:
            fastForwardButton->click();  // assuming you have this
            break;
        case ControllerAction::OpenControlPanel:
            openControlPanelButton->click();
            break;
        case ControllerAction::RedColor:
            redButton->click();
            break;
        case ControllerAction::BlueColor:
            blueButton->click();
            break;
        case ControllerAction::YellowColor:
            yellowButton->click();
            break;
        case ControllerAction::GreenColor:
            greenButton->click();
            break;
        case ControllerAction::BlackColor:
            blackButton->click();
            break;
        case ControllerAction::WhiteColor:
            whiteButton->click();
            break;
        case ControllerAction::CustomColor:
            customColorButton->click();
            break;
        case ControllerAction::ToggleSidebar:
            toggleTabBarButton->click();
            break;
        case ControllerAction::Save:
            saveButton->click();
            break;
        case ControllerAction::StraightLineTool:
            straightLineToggleButton->click();
            break;
        case ControllerAction::RopeTool:
            ropeToolButton->click();
            break;
        case ControllerAction::SetPenTool:
            setPenTool();
            break;
        case ControllerAction::SetMarkerTool:
            setMarkerTool();
            break;
        case ControllerAction::SetEraserTool:
            setEraserTool();
            break;
        case ControllerAction::TogglePdfTextSelection:
            pdfTextSelectButton->click();
            break;
        case ControllerAction::ToggleOutline:
            toggleOutlineButton->click();
            break;
        case ControllerAction::ToggleBookmarks:
            toggleBookmarksButton->click();
            break;
        case ControllerAction::AddBookmark:
            toggleBookmarkButton->click();
            break;
        case ControllerAction::ToggleTouchGestures:
            touchGesturesButton->click();
            break;
        default:
            break;
    }
}
    void handleKeyboardShortcut(const QString &keySequence) {
    ControllerAction action = keyboardActionMapping.value(keySequence, ControllerAction::None);

    // Use the same handler as Joy-Con buttons
    switch (action) {
        case ControllerAction::ToggleFullscreen:
            fullscreenButton->click();
            break;
        case ControllerAction::ToggleDial:
            toggleDial();
            break;
        case ControllerAction::Zoom50:
            zoom50Button->click();
            break;
        case ControllerAction::ZoomOut:
            dezoomButton->click();
            break;
        case ControllerAction::Zoom200:
            zoom200Button->click();
            break;
        case ControllerAction::AddPreset:
            addPresetButton->click();
            break;
        case ControllerAction::DeletePage:
            deletePageButton->click();
            break;
        case ControllerAction::FastForward:
            fastForwardButton->click();
            break;
        case ControllerAction::OpenControlPanel:
            openControlPanelButton->click();
            break;
        case ControllerAction::RedColor:
            redButton->click();
            break;
        case ControllerAction::BlueColor:
            blueButton->click();
            break;
        case ControllerAction::YellowColor:
            yellowButton->click();
            break;
        case ControllerAction::GreenColor:
            greenButton->click();
            break;
        case ControllerAction::BlackColor:
            blackButton->click();
            break;
        case ControllerAction::WhiteColor:
            whiteButton->click();
            break;
        case ControllerAction::CustomColor:
            customColorButton->click();
            break;
        case ControllerAction::ToggleSidebar:
            toggleTabBarButton->click();
            break;
        case ControllerAction::Save:
            saveButton->click();
            break;
        case ControllerAction::StraightLineTool:
            straightLineToggleButton->click();
            break;
        case ControllerAction::RopeTool:
            ropeToolButton->click();
            break;
        case ControllerAction::SetPenTool:
            setPenTool();
            break;
        case ControllerAction::SetMarkerTool:
            setMarkerTool();
            break;
        case ControllerAction::SetEraserTool:
            setEraserTool();
            break;
        case ControllerAction::TogglePdfTextSelection:
            pdfTextSelectButton->click();
            break;
        case ControllerAction::ToggleOutline:
            toggleOutlineButton->click();
            break;
        case ControllerAction::ToggleBookmarks:
            toggleBookmarksButton->click();
            break;
        case ControllerAction::AddBookmark:
            toggleBookmarkButton->click();
            break;
        case ControllerAction::ToggleTouchGestures:
            touchGesturesButton->click();
            break;
        default:
            break;
    }
}
    void saveKeyboardMappings() {
    QSettings settings("SpeedyNote", "App");
    settings.beginGroup("KeyboardMappings");
    for (auto it = keyboardMappings.begin(); it != keyboardMappings.end(); ++it) {
        settings.setValue(it.key(), it.value());
    }
    settings.endGroup();
}
    void loadKeyboardMappings() {
    QSettings settings("SpeedyNote", "App");
    settings.beginGroup("KeyboardMappings");
    QStringList keys = settings.allKeys();

    // List of IME-related shortcuts that should not be intercepted
    QStringList imeShortcuts = {
        "Ctrl+Space",      // Primary IME toggle
        "Ctrl+Shift",      // Language switching
        "Ctrl+Alt",        // IME functions
        "Shift+Alt",       // Alternative language switching
        "Alt+Shift"        // Alternative language switching (reversed)
    };

    for (const QString &key : keys) {
        // Skip IME-related shortcuts
        if (imeShortcuts.contains(key)) {
            // Remove from settings if it exists
            settings.remove(key);
            continue;
        }

        QString value = settings.value(key).toString();
        keyboardMappings[key] = value;
        keyboardActionMapping[key] = stringToAction(value);
    }
    settings.endGroup();

    // Save settings to persist the removal of IME shortcuts
    settings.sync();
}
    void ensureTabHasUniqueSaveFolder(InkCanvas* canvas) {
    if (!canvas) return;

    if (canvasStack->count() == 0) return;

    QString currentFolder = canvas->getSaveFolder();
    QString tempFolder = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/temp_session";

    if (currentFolder.isEmpty() || currentFolder == tempFolder) {

        QDir sourceDir(tempFolder);
        QStringList pageFiles = sourceDir.entryList(QStringList() << "*.png", QDir::Files);

        // No pages to save → skip prompting
        if (pageFiles.isEmpty()) {
            return;
        }

        QMessageBox::warning(this, tr("Unsaved Notebook"),
                             tr("This notebook is still using a temporary session folder.\nPlease select a permanent folder to avoid data loss."));

        QString selectedFolder = QFileDialog::getExistingDirectory(this, tr("Select Save Folder"));
        if (selectedFolder.isEmpty()) return;

        QDir destDir(selectedFolder);
        if (!destDir.exists()) {
            QDir().mkpath(selectedFolder);
        }

        // Copy contents from temp to selected folder
        for (const QString &file : sourceDir.entryList(QDir::Files)) {
            QString srcFilePath = tempFolder + "/" + file;
            QString dstFilePath = selectedFolder + "/" + file;

            // If file already exists at destination, remove it to avoid rename failure
            if (QFile::exists(dstFilePath)) {
                QFile::remove(dstFilePath);
            }

            QFile::rename(srcFilePath, dstFilePath);  // This moves the file
        }

        canvas->setSaveFolder(selectedFolder);
        // updateTabLabel(); // No longer needed here, handled by the close button lambda
    }

    return;
}
    RecentNotebooksManager *recentNotebooksManager;
    int pdfRenderDPI = 192;
    void setupUi() {

    // Ensure IME is properly enabled for the application
    QInputMethod *inputMethod = QGuiApplication::inputMethod();
    if (inputMethod) {
        inputMethod->show();
        inputMethod->reset();
    }

    // Create theme-aware button style
    bool darkMode = isDarkMode();
    QString buttonStyle = createButtonStyle(darkMode);

    loadPdfButton = new QPushButton(this);
    clearPdfButton = new QPushButton(this);
    loadPdfButton->setFixedSize(26, 30);
    clearPdfButton->setFixedSize(26, 30);
    QIcon pdfIcon(loadThemedIcon("pdf"));  // Path to your icon in resources
    QIcon pdfDeleteIcon(loadThemedIcon("pdfdelete"));  // Path to your icon in resources
    loadPdfButton->setIcon(pdfIcon);
    clearPdfButton->setIcon(pdfDeleteIcon);
    loadPdfButton->setStyleSheet(buttonStyle);
    clearPdfButton->setStyleSheet(buttonStyle);
    loadPdfButton->setToolTip(tr("Load PDF"));
    clearPdfButton->setToolTip(tr("Clear PDF"));
    connect(loadPdfButton, &QPushButton::clicked, this, &MainWindow::loadPdf);
    connect(clearPdfButton, &QPushButton::clicked, this, &MainWindow::clearPdf);

    pdfTextSelectButton = new QPushButton(this);
    pdfTextSelectButton->setFixedSize(26, 30);
    QIcon pdfTextIcon(loadThemedIcon("ibeam"));
    pdfTextSelectButton->setIcon(pdfTextIcon);
    pdfTextSelectButton->setStyleSheet(buttonStyle);
    pdfTextSelectButton->setToolTip(tr("Toggle PDF Text Selection"));
    connect(pdfTextSelectButton, &QPushButton::clicked, this, [this]() {
        if (!currentCanvas()) return;

        bool newMode = !currentCanvas()->isPdfTextSelectionEnabled();
        currentCanvas()->setPdfTextSelectionEnabled(newMode);
        updatePdfTextSelectButtonState();
        updateBookmarkButtonState();

        // Clear any existing selection when toggling
        if (!newMode) {
            currentCanvas()->clearPdfTextSelection();
        }
    });

    exportNotebookButton = new QPushButton(this);
    exportNotebookButton->setFixedSize(26, 30);
    QIcon exportIcon(loadThemedIcon("export"));  // Path to your icon in resources
    exportNotebookButton->setIcon(exportIcon);
    exportNotebookButton->setStyleSheet(buttonStyle);
    exportNotebookButton->setToolTip(tr("Export Notebook Into .SNPKG File"));
    importNotebookButton = new QPushButton(this);
    importNotebookButton->setFixedSize(26, 30);
    QIcon importIcon(loadThemedIcon("import"));  // Path to your icon in resources
    importNotebookButton->setIcon(importIcon);
    importNotebookButton->setStyleSheet(buttonStyle);
    importNotebookButton->setToolTip(tr("Import Notebook From .SNPKG File"));

    connect(exportNotebookButton, &QPushButton::clicked, this, [=]() {
        QString filename = QFileDialog::getSaveFileName(this, tr("Export Notebook"), "", "SpeedyNote Package (*.snpkg)");
        if (!filename.isEmpty()) {
            if (!filename.endsWith(".snpkg")) filename += ".snpkg";
            currentCanvas()->exportNotebook(filename);
        }
    });

    connect(importNotebookButton, &QPushButton::clicked, this, [=]() {
        QString filename = QFileDialog::getOpenFileName(this, tr("Import Notebook"), "", "SpeedyNote Package (*.snpkg)");
        if (!filename.isEmpty()) {
            currentCanvas()->importNotebook(filename);
        }
    });

    benchmarkButton = new QPushButton(this);
    QIcon benchmarkIcon(loadThemedIcon("benchmark"));  // Path to your icon in resources
    benchmarkButton->setIcon(benchmarkIcon);
    benchmarkButton->setFixedSize(26, 30); // Make the benchmark button smaller
    benchmarkButton->setStyleSheet(buttonStyle);
    benchmarkButton->setToolTip(tr("Toggle Benchmark"));
    benchmarkLabel = new QLabel("PR:N/A", this);
    benchmarkLabel->setFixedHeight(30);  // Make the benchmark bar smaller

    toggleTabBarButton = new QPushButton(this);
    toggleTabBarButton->setIcon(loadThemedIcon("tabs"));  // You can design separate icons for "show" and "hide"
    toggleTabBarButton->setToolTip(tr("Show/Hide Tab Bar"));
    toggleTabBarButton->setFixedSize(26, 30);
    toggleTabBarButton->setStyleSheet(buttonStyle);
    toggleTabBarButton->setProperty("selected", true); // Initially visible

    // PDF Outline Toggle Button
    toggleOutlineButton = new QPushButton(this);
    toggleOutlineButton->setIcon(loadThemedIcon("outline"));  // Icon to be added later
    toggleOutlineButton->setToolTip(tr("Show/Hide PDF Outline"));
    toggleOutlineButton->setFixedSize(26, 30);
    toggleOutlineButton->setStyleSheet(buttonStyle);
    toggleOutlineButton->setProperty("selected", false); // Initially hidden

    // Bookmarks Toggle Button
    toggleBookmarksButton = new QPushButton(this);
    toggleBookmarksButton->setIcon(loadThemedIcon("bookmark"));  // Using bookpage icon for bookmarks
    toggleBookmarksButton->setToolTip(tr("Show/Hide Bookmarks"));
    toggleBookmarksButton->setFixedSize(26, 30);
    toggleBookmarksButton->setStyleSheet(buttonStyle);
    toggleBookmarksButton->setProperty("selected", false); // Initially hidden

    // Add/Remove Bookmark Toggle Button
    toggleBookmarkButton = new QPushButton(this);
    toggleBookmarkButton->setIcon(loadThemedIcon("star"));
    toggleBookmarkButton->setToolTip(tr("Add/Remove Bookmark"));
    toggleBookmarkButton->setFixedSize(26, 30);
    toggleBookmarkButton->setStyleSheet(buttonStyle);
    toggleBookmarkButton->setProperty("selected", false); // For toggle state styling

    // Touch Gestures Toggle Button
    touchGesturesButton = new QPushButton(this);
    touchGesturesButton->setIcon(loadThemedIcon("hand"));  // Using hand icon for touch/gesture
    touchGesturesButton->setToolTip(tr("Toggle Touch Gestures"));
    touchGesturesButton->setFixedSize(26, 30);
    touchGesturesButton->setStyleSheet(buttonStyle);
    touchGesturesButton->setProperty("selected", touchGesturesEnabled); // For toggle state styling

    selectFolderButton = new QPushButton(this);
    selectFolderButton->setFixedSize(26, 30);
    QIcon folderIcon(loadThemedIcon("folder"));  // Path to your icon in resources
    selectFolderButton->setIcon(folderIcon);
    selectFolderButton->setStyleSheet(buttonStyle);
    selectFolderButton->setToolTip(tr("Select Save Folder"));
    connect(selectFolderButton, &QPushButton::clicked, this, &MainWindow::selectFolder);

    saveButton = new QPushButton(this);
    saveButton->setFixedSize(26, 30);
    QIcon saveIcon(loadThemedIcon("save"));  // Path to your icon in resources
    saveButton->setIcon(saveIcon);
    saveButton->setStyleSheet(buttonStyle);
    saveButton->setToolTip(tr("Save Current Page"));
    connect(saveButton, &QPushButton::clicked, this, &MainWindow::saveCurrentPage);

    saveAnnotatedButton = new QPushButton(this);
    saveAnnotatedButton->setFixedSize(26, 30);
    QIcon saveAnnotatedIcon(loadThemedIcon("saveannotated"));  // Path to your icon in resources
    saveAnnotatedButton->setIcon(saveAnnotatedIcon);
    saveAnnotatedButton->setStyleSheet(buttonStyle);
    saveAnnotatedButton->setToolTip(tr("Save Page with Background"));
    connect(saveAnnotatedButton, &QPushButton::clicked, this, &MainWindow::saveAnnotated);

    fullscreenButton = new QPushButton(this);
    fullscreenButton->setIcon(loadThemedIcon("fullscreen"));  // Load from resources
    fullscreenButton->setFixedSize(26, 30);
    fullscreenButton->setToolTip(tr("Toggle Fullscreen"));
    fullscreenButton->setStyleSheet(buttonStyle);

    // ✅ Connect button click to toggleFullscreen() function
    connect(fullscreenButton, &QPushButton::clicked, this, &MainWindow::toggleFullscreen);

    // Use the darkMode variable already declared at the beginning of setupUi()

    redButton = new QPushButton(this);
    redButton->setFixedSize(18, 30);  // Half width
    QString redIconPath = darkMode ? ":/resources/icons/pen_light_red.png" : ":/resources/icons/pen_dark_red.png";
    QIcon redIcon(redIconPath);
    redButton->setIcon(redIcon);
    redButton->setStyleSheet(buttonStyle);
    connect(redButton, &QPushButton::clicked, [this]() {
        handleColorButtonClick();
        currentCanvas()->setPenColor(getPaletteColor("red"));
        updateDialDisplay();
        updateColorButtonStates();
    });

    blueButton = new QPushButton(this);
    blueButton->setFixedSize(18, 30);  // Half width
    QString blueIconPath = darkMode ? ":/resources/icons/pen_light_blue.png" : ":/resources/icons/pen_dark_blue.png";
    QIcon blueIcon(blueIconPath);
    blueButton->setIcon(blueIcon);
    blueButton->setStyleSheet(buttonStyle);
    connect(blueButton, &QPushButton::clicked, [this]() {
        handleColorButtonClick();
        currentCanvas()->setPenColor(getPaletteColor("blue"));
        updateDialDisplay();
        updateColorButtonStates();
    });

    yellowButton = new QPushButton(this);
    yellowButton->setFixedSize(18, 30);  // Half width
    QString yellowIconPath = darkMode ? ":/resources/icons/pen_light_yellow.png" : ":/resources/icons/pen_dark_yellow.png";
    QIcon yellowIcon(yellowIconPath);
    yellowButton->setIcon(yellowIcon);
    yellowButton->setStyleSheet(buttonStyle);
    connect(yellowButton, &QPushButton::clicked, [this]() {
        handleColorButtonClick();
        currentCanvas()->setPenColor(getPaletteColor("yellow"));
        updateDialDisplay();
        updateColorButtonStates();
    });

    greenButton = new QPushButton(this);
    greenButton->setFixedSize(18, 30);  // Half width
    QString greenIconPath = darkMode ? ":/resources/icons/pen_light_green.png" : ":/resources/icons/pen_dark_green.png";
    QIcon greenIcon(greenIconPath);
    greenButton->setIcon(greenIcon);
    greenButton->setStyleSheet(buttonStyle);
    connect(greenButton, &QPushButton::clicked, [this]() {
        handleColorButtonClick();
        currentCanvas()->setPenColor(getPaletteColor("green"));
        updateDialDisplay();
        updateColorButtonStates();
    });

    blackButton = new QPushButton(this);
    blackButton->setFixedSize(18, 30);  // Half width
    QString blackIconPath = darkMode ? ":/resources/icons/pen_light_black.png" : ":/resources/icons/pen_dark_black.png";
    QIcon blackIcon(blackIconPath);
    blackButton->setIcon(blackIcon);
    blackButton->setStyleSheet(buttonStyle);
    connect(blackButton, &QPushButton::clicked, [this]() {
        handleColorButtonClick();
        currentCanvas()->setPenColor(QColor("#000000"));
        updateDialDisplay();
        updateColorButtonStates();
    });

    whiteButton = new QPushButton(this);
    whiteButton->setFixedSize(18, 30);  // Half width
    QString whiteIconPath = darkMode ? ":/resources/icons/pen_light_white.png" : ":/resources/icons/pen_dark_white.png";
    QIcon whiteIcon(whiteIconPath);
    whiteButton->setIcon(whiteIcon);
    whiteButton->setStyleSheet(buttonStyle);
    connect(whiteButton, &QPushButton::clicked, [this]() {
        handleColorButtonClick();
        currentCanvas()->setPenColor(QColor("#FFFFFF"));
        updateDialDisplay();
        updateColorButtonStates();
    });

    customColorInput = new QLineEdit(this);
    customColorInput->setPlaceholderText("Custom HEX");
    customColorInput->setFixedSize(85, 30);

    // Enable IME support for multi-language input
    customColorInput->setAttribute(Qt::WA_InputMethodEnabled, true);
    customColorInput->setInputMethodHints(Qt::ImhNone); // Allow all input methods
    customColorInput->installEventFilter(this); // Install event filter for IME handling

    connect(customColorInput, &QLineEdit::returnPressed, this, &MainWindow::applyCustomColor);

    thicknessButton = new QPushButton(this);
    thicknessButton->setIcon(loadThemedIcon("thickness"));
    thicknessButton->setFixedSize(26, 30);
    thicknessButton->setStyleSheet(buttonStyle);
    connect(thicknessButton, &QPushButton::clicked, this, &MainWindow::toggleThicknessSlider);

    thicknessFrame = new QFrame(this);
    thicknessFrame->setFrameShape(QFrame::StyledPanel);
    thicknessFrame->setStyleSheet(R"(
        background-color: black;
        border: 1px solid black;
        padding: 5px;
    )");
    thicknessFrame->setVisible(false);
    thicknessFrame->setFixedSize(220, 40); // Adjust width/height as needed

    thicknessSlider = new QSlider(Qt::Horizontal, this);
    thicknessSlider->setRange(1, 50);
    thicknessSlider->setValue(5);
    thicknessSlider->setMaximumWidth(200);

    connect(thicknessSlider, &QSlider::valueChanged, this, &MainWindow::updateThickness);

    QVBoxLayout *popupLayoutThickness = new QVBoxLayout();
    popupLayoutThickness->setContentsMargins(10, 5, 10, 5);
    popupLayoutThickness->addWidget(thicknessSlider);
    thicknessFrame->setLayout(popupLayoutThickness);

    toolSelector = new QComboBox(this);
    toolSelector->addItem(loadThemedIcon("pen"), "");
    toolSelector->addItem(loadThemedIcon("marker"), "");
    toolSelector->addItem(loadThemedIcon("eraser"), "");
    toolSelector->setFixedWidth(43);
    toolSelector->setFixedHeight(30);
    connect(toolSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &MainWindow::changeTool);

    // ✅ Individual tool buttons
    penToolButton = new QPushButton(this);
    penToolButton->setFixedSize(26, 30);
    penToolButton->setIcon(loadThemedIcon("pen"));
    penToolButton->setStyleSheet(buttonStyle);
    penToolButton->setToolTip(tr("Pen Tool"));
    connect(penToolButton, &QPushButton::clicked, this, &MainWindow::setPenTool);

    markerToolButton = new QPushButton(this);
    markerToolButton->setFixedSize(26, 30);
    markerToolButton->setIcon(loadThemedIcon("marker"));
    markerToolButton->setStyleSheet(buttonStyle);
    markerToolButton->setToolTip(tr("Marker Tool"));
    connect(markerToolButton, &QPushButton::clicked, this, &MainWindow::setMarkerTool);

    eraserToolButton = new QPushButton(this);
    eraserToolButton->setFixedSize(26, 30);
    eraserToolButton->setIcon(loadThemedIcon("eraser"));
    eraserToolButton->setStyleSheet(buttonStyle);
    eraserToolButton->setToolTip(tr("Eraser Tool"));
    connect(eraserToolButton, &QPushButton::clicked, this, &MainWindow::setEraserTool);

    backgroundButton = new QPushButton(this);
    backgroundButton->setFixedSize(26, 30);
    QIcon bgIcon(loadThemedIcon("background"));  // Path to your icon in resources
    backgroundButton->setIcon(bgIcon);
    backgroundButton->setStyleSheet(buttonStyle);
    backgroundButton->setToolTip(tr("Set Background Pic"));
    connect(backgroundButton, &QPushButton::clicked, this, &MainWindow::selectBackground);

    // Initialize straight line toggle button
    straightLineToggleButton = new QPushButton(this);
    straightLineToggleButton->setFixedSize(26, 30);
    QIcon straightLineIcon(loadThemedIcon("straightLine"));  // Make sure this icon exists or use a different one
    straightLineToggleButton->setIcon(straightLineIcon);
    straightLineToggleButton->setStyleSheet(buttonStyle);
    straightLineToggleButton->setToolTip(tr("Toggle Straight Line Mode"));
    connect(straightLineToggleButton, &QPushButton::clicked, this, [this]() {
        if (!currentCanvas()) return;

        // If we're turning on straight line mode, first disable rope tool
        if (!currentCanvas()->isStraightLineMode()) {
            currentCanvas()->setRopeToolMode(false);
            updateRopeToolButtonState();
        }

        bool newMode = !currentCanvas()->isStraightLineMode();
        currentCanvas()->setStraightLineMode(newMode);
        updateStraightLineButtonState();
    });

    ropeToolButton = new QPushButton(this);
    ropeToolButton->setFixedSize(26, 30);
    QIcon ropeToolIcon(loadThemedIcon("rope")); // Make sure this icon exists
    ropeToolButton->setIcon(ropeToolIcon);
    ropeToolButton->setStyleSheet(buttonStyle);
    ropeToolButton->setToolTip(tr("Toggle Rope Tool Mode"));
    connect(ropeToolButton, &QPushButton::clicked, this, [this]() {
        if (!currentCanvas()) return;

        // If we're turning on rope tool mode, first disable straight line
        if (!currentCanvas()->isRopeToolMode()) {
            currentCanvas()->setStraightLineMode(false);
            updateStraightLineButtonState();
        }

        bool newMode = !currentCanvas()->isRopeToolMode();
        currentCanvas()->setRopeToolMode(newMode);
        updateRopeToolButtonState();
    });

    markdownButton = new QPushButton(this);
    markdownButton->setFixedSize(26, 30);
    QIcon markdownIcon(loadThemedIcon("markdown")); // Using text icon for markdown
    markdownButton->setIcon(markdownIcon);
    markdownButton->setStyleSheet(buttonStyle);
    markdownButton->setToolTip(tr("Add Markdown Window"));
    connect(markdownButton, &QPushButton::clicked, this, [this]() {
        if (!currentCanvas()) return;

        // Toggle markdown selection mode
        bool newMode = !currentCanvas()->isMarkdownSelectionMode();
        currentCanvas()->setMarkdownSelectionMode(newMode);
        updateMarkdownButtonState();
    });

    deletePageButton = new QPushButton(this);
    deletePageButton->setFixedSize(26, 30);
    QIcon trashIcon(loadThemedIcon("trash"));  // Path to your icon in resources
    deletePageButton->setIcon(trashIcon);
    deletePageButton->setStyleSheet(buttonStyle);
    deletePageButton->setToolTip(tr("Delete Current Page"));
    connect(deletePageButton, &QPushButton::clicked, this, &MainWindow::deleteCurrentPage);

    zoomButton = new QPushButton(this);
    zoomButton->setIcon(loadThemedIcon("zoom"));
    zoomButton->setFixedSize(26, 30);
    zoomButton->setStyleSheet(buttonStyle);
    connect(zoomButton, &QPushButton::clicked, this, &MainWindow::toggleZoomSlider);

    // ✅ Create the floating frame (Initially Hidden)
    zoomFrame = new QFrame(this);
    zoomFrame->setFrameShape(QFrame::StyledPanel);
    zoomFrame->setStyleSheet(R"(
        background-color: black;
        border: 1px solid black;
        padding: 5px;
    )");
    zoomFrame->setVisible(false);
    zoomFrame->setFixedSize(440, 40); // Adjust width/height as needed

    zoomSlider = new QSlider(Qt::Horizontal, this);
    zoomSlider->setRange(10, 400);
    zoomSlider->setValue(100);
    zoomSlider->setMaximumWidth(405);

    connect(zoomSlider, &QSlider::valueChanged, this, &MainWindow::onZoomSliderChanged);

    QVBoxLayout *popupLayout = new QVBoxLayout();
    popupLayout->setContentsMargins(10, 5, 10, 5);
    popupLayout->addWidget(zoomSlider);
    zoomFrame->setLayout(popupLayout);

    zoom50Button = new QPushButton("0.5x", this);
    zoom50Button->setFixedSize(35, 30);
    zoom50Button->setStyleSheet(buttonStyle);
    zoom50Button->setToolTip(tr("Set Zoom to 50%"));
    connect(zoom50Button, &QPushButton::clicked, [this]() { zoomSlider->setValue(50 / initialDpr); updateDialDisplay(); });

    dezoomButton = new QPushButton("1x", this);
    dezoomButton->setFixedSize(26, 30);
    dezoomButton->setStyleSheet(buttonStyle);
    dezoomButton->setToolTip(tr("Set Zoom to 100%"));
    connect(dezoomButton, &QPushButton::clicked, [this]() { zoomSlider->setValue(100 / initialDpr); updateDialDisplay(); });

    zoom200Button = new QPushButton("2x", this);
    zoom200Button->setFixedSize(31, 30);
    zoom200Button->setStyleSheet(buttonStyle);
    zoom200Button->setToolTip(tr("Set Zoom to 200%"));
    connect(zoom200Button, &QPushButton::clicked, [this]() { zoomSlider->setValue(200 / initialDpr); updateDialDisplay(); });

    panXSlider = new QScrollBar(Qt::Horizontal, this);
    panYSlider = new QScrollBar(Qt::Vertical, this);
    panYSlider->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);

    // Set scrollbar styling
    QString scrollBarStyle = R"(
        QScrollBar {
            background: rgba(200, 200, 200, 80);
            border: none;
            margin: 0px;
        }
        QScrollBar:hover {
            background: rgba(200, 200, 200, 120);
        }
        QScrollBar:horizontal {
            height: 16px !important;  /* Force narrow height */
            max-height: 16px !important;
        }
        QScrollBar:vertical {
            width: 16px !important;   /* Force narrow width */
            max-width: 16px !important;
        }
        QScrollBar::handle {
            background: rgba(100, 100, 100, 150);
            border-radius: 2px;
            min-height: 120px;  /* Longer handle for vertical scrollbar */
            min-width: 120px;   /* Longer handle for horizontal scrollbar */
        }
        QScrollBar::handle:hover {
            background: rgba(80, 80, 80, 210);
        }
        /* Hide scroll buttons */
        QScrollBar::add-line,
        QScrollBar::sub-line {
            width: 0px;
            height: 0px;
            background: none;
            border: none;
        }
        /* Disable scroll page buttons */
        QScrollBar::add-page,
        QScrollBar::sub-page {
            background: transparent;
        }
    )";

    panXSlider->setStyleSheet(scrollBarStyle);
    panYSlider->setStyleSheet(scrollBarStyle);

    // Force fixed dimensions programmatically
    panXSlider->setFixedHeight(16);
    panYSlider->setFixedWidth(16);

    // Set up auto-hiding
    panXSlider->setMouseTracking(true);
    panYSlider->setMouseTracking(true);
    panXSlider->installEventFilter(this);
    panYSlider->installEventFilter(this);
    panXSlider->setVisible(false);
    panYSlider->setVisible(false);

    // Create timer for auto-hiding
    scrollbarHideTimer = new QTimer(this);
    scrollbarHideTimer->setSingleShot(true);
    scrollbarHideTimer->setInterval(200); // Hide after 0.2 seconds
    connect(scrollbarHideTimer, &QTimer::timeout, this, [this]() {
        panXSlider->setVisible(false);
        panYSlider->setVisible(false);
        scrollbarsVisible = false;
    });

    // panXSlider->setFixedHeight(30);
    // panYSlider->setFixedWidth(30);

    connect(panXSlider, &QScrollBar::valueChanged, this, &MainWindow::updatePanX);

    connect(panYSlider, &QScrollBar::valueChanged, this, &MainWindow::updatePanY);

    // 🌟 PDF Outline Sidebar
    outlineSidebar = new QWidget(this);
    outlineSidebar->setFixedWidth(250);
    outlineSidebar->setVisible(false); // Hidden by default

    QVBoxLayout *outlineLayout = new QVBoxLayout(outlineSidebar);
    outlineLayout->setContentsMargins(5, 5, 5, 5);

    QLabel *outlineLabel = new QLabel(tr("PDF Outline"), outlineSidebar);
    outlineLabel->setStyleSheet("font-weight: bold; padding: 5px;");
    outlineLayout->addWidget(outlineLabel);

    outlineTree = new QTreeWidget(outlineSidebar);
    outlineTree->setHeaderHidden(true);
    outlineTree->setRootIsDecorated(true);
    outlineTree->setIndentation(15);
    outlineLayout->addWidget(outlineTree);

    // Connect outline tree item clicks
    connect(outlineTree, &QTreeWidget::itemClicked, this, &MainWindow::onOutlineItemClicked);

    // 🌟 Bookmarks Sidebar
    bookmarksSidebar = new QWidget(this);
    bookmarksSidebar->setFixedWidth(250);
    bookmarksSidebar->setVisible(false); // Hidden by default

    QVBoxLayout *bookmarksLayout = new QVBoxLayout(bookmarksSidebar);
    bookmarksLayout->setContentsMargins(5, 5, 5, 5);

    QLabel *bookmarksLabel = new QLabel(tr("Bookmarks"), bookmarksSidebar);
    bookmarksLabel->setStyleSheet("font-weight: bold; padding: 5px;");
    bookmarksLayout->addWidget(bookmarksLabel);

    bookmarksTree = new QTreeWidget(bookmarksSidebar);
    bookmarksTree->setHeaderHidden(true);
    bookmarksTree->setRootIsDecorated(false);
    bookmarksTree->setIndentation(0);
    bookmarksLayout->addWidget(bookmarksTree);

    // Connect bookmarks tree item clicks
    connect(bookmarksTree, &QTreeWidget::itemClicked, this, &MainWindow::onBookmarkItemClicked);

    // 🌟 Horizontal Tab Bar (like modern web browsers)
    tabList = new QListWidget(this);
    tabList->setFlow(QListView::LeftToRight);  // Make it horizontal
    tabList->setFixedHeight(32);  // Increased to accommodate scrollbar below tabs
    tabList->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    tabList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    tabList->setSelectionMode(QAbstractItemView::SingleSelection);

    // Style the tab bar like a modern browser with transparent scrollbar
    tabList->setStyleSheet(R"(
        QListWidget {
            background-color: rgba(240, 240, 240, 255);
            border: none;
            border-bottom: 1px solid rgba(200, 200, 200, 255);
            outline: none;
        }
        QListWidget::item {
            background-color: rgba(220, 220, 220, 255);
            border: 1px solid rgba(180, 180, 180, 255);
            border-bottom: none;
            margin-right: 1px;
            margin-top: 2px;
            padding: 0px;
            min-width: 80px;
            max-width: 120px;
        }
        QListWidget::item:selected {
            background-color: white;
            border: 1px solid rgba(180, 180, 180, 255);
            border-bottom: 1px solid white;
            margin-top: 1px;
        }
        QListWidget::item:hover:!selected {
            background-color: rgba(230, 230, 230, 255);
        }
        QScrollBar:horizontal {
            background: rgba(240, 240, 240, 255);
            height: 8px;
            border: none;
            margin: 0px;
            border-top: 1px solid rgba(200, 200, 200, 255);
        }
        QScrollBar::handle:horizontal {
            background: rgba(150, 150, 150, 120);
            border-radius: 4px;
            min-width: 20px;
            margin: 1px;
        }
        QScrollBar::handle:horizontal:hover {
            background: rgba(120, 120, 120, 200);
        }
        QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {
            width: 0px;
            height: 0px;
            background: none;
            border: none;
        }
        QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {
            background: transparent;
        }
    )");

    // 🌟 Add Button for New Tab (styled like browser + button)
    addTabButton = new QPushButton(this);
    QIcon addTab(loadThemedIcon("addtab"));  // Path to your icon in resources
    addTabButton->setIcon(addTab);
    addTabButton->setFixedSize(30, 30);  // Even smaller to match thinner layout
    addTabButton->setStyleSheet(R"(
        QPushButton {
            background-color: rgba(220, 220, 220, 255);
            border: 1px solid rgba(180, 180, 180, 255);
            border-radius: 15px;
            margin: 2px;
        }
        QPushButton:hover {
            background-color: rgba(200, 200, 200, 255);
        }
        QPushButton:pressed {
            background-color: rgba(180, 180, 180, 255);
        }
    )");
    addTabButton->setToolTip(tr("Add New Tab"));
    connect(addTabButton, &QPushButton::clicked, this, &MainWindow::addNewTab);

    if (!canvasStack) {
        canvasStack = new QStackedWidget(this);
    }

    connect(tabList, &QListWidget::currentRowChanged, this, &MainWindow::switchTab);

    // Create horizontal tab container
    tabBarContainer = new QWidget(this);
    tabBarContainer->setObjectName("tabBarContainer");
    tabBarContainer->setFixedHeight(38);  // Increased to accommodate scrollbar below tabs

    QHBoxLayout *tabBarLayout = new QHBoxLayout(tabBarContainer);
    tabBarLayout->setContentsMargins(5, 5, 5, 5);
    tabBarLayout->setSpacing(5);
    tabBarLayout->addWidget(tabList, 1);  // Tab list takes most space
    tabBarLayout->addWidget(addTabButton, 0);  // Add button stays at the end

    connect(toggleTabBarButton, &QPushButton::clicked, this, [=]() {
        bool isVisible = tabBarContainer->isVisible();
        tabBarContainer->setVisible(!isVisible);

        // Update button toggle state
        toggleTabBarButton->setProperty("selected", !isVisible);
        toggleTabBarButton->style()->unpolish(toggleTabBarButton);
        toggleTabBarButton->style()->polish(toggleTabBarButton);

        QTimer::singleShot(0, this, [this]() {
            if (auto *canvas = currentCanvas()) {
                canvas->setMaximumSize(canvas->getCanvasSize());
            }
        });
    });

    connect(toggleOutlineButton, &QPushButton::clicked, this, &MainWindow::toggleOutlineSidebar);
    connect(toggleBookmarksButton, &QPushButton::clicked, this, &MainWindow::toggleBookmarksSidebar);
    connect(toggleBookmarkButton, &QPushButton::clicked, this, &MainWindow::toggleCurrentPageBookmark);
    connect(touchGesturesButton, &QPushButton::clicked, this, [this]() {
        setTouchGesturesEnabled(!touchGesturesEnabled);
        touchGesturesButton->setProperty("selected", touchGesturesEnabled);
        touchGesturesButton->style()->unpolish(touchGesturesButton);
        touchGesturesButton->style()->polish(touchGesturesButton);
    });

    // Previous page button
    prevPageButton = new QPushButton(this);
    prevPageButton->setFixedSize(26, 30);
    prevPageButton->setText("◀");
    prevPageButton->setStyleSheet(buttonStyle);
    prevPageButton->setToolTip(tr("Previous Page"));
    connect(prevPageButton, &QPushButton::clicked, this, &MainWindow::goToPreviousPage);

    pageInput = new QSpinBox(this);
    pageInput->setFixedSize(42, 30);
    pageInput->setMinimum(1);
    pageInput->setMaximum(9999);
    pageInput->setValue(1);
    pageInput->setMaximumWidth(100);
    connect(pageInput, QOverload<int>::of(&QSpinBox::valueChanged), this, &MainWindow::onPageInputChanged);

    // Next page button
    nextPageButton = new QPushButton(this);
    nextPageButton->setFixedSize(26, 30);
    nextPageButton->setText("▶");
    nextPageButton->setStyleSheet(buttonStyle);
    nextPageButton->setToolTip(tr("Next Page"));
    connect(nextPageButton, &QPushButton::clicked, this, &MainWindow::goToNextPage);

    jumpToPageButton = new QPushButton(this);
    // QIcon jumpIcon(":/resources/icons/bookpage.png");  // Path to your icon in resources
    jumpToPageButton->setFixedSize(26, 30);
    jumpToPageButton->setStyleSheet(buttonStyle);
    jumpToPageButton->setIcon(loadThemedIcon("bookpage"));
    connect(jumpToPageButton, &QPushButton::clicked, this, &MainWindow::showJumpToPageDialog);

    // ✅ Dial Toggle Button
    dialToggleButton = new QPushButton(this);
    dialToggleButton->setIcon(loadThemedIcon("dial"));  // Icon for dial
    dialToggleButton->setFixedSize(26, 30);
    dialToggleButton->setToolTip(tr("Toggle Magic Dial"));
    dialToggleButton->setStyleSheet(buttonStyle);

    // ✅ Connect to toggle function
    connect(dialToggleButton, &QPushButton::clicked, this, &MainWindow::toggleDial);

    // toggleDial();

    fastForwardButton = new QPushButton(this);
    fastForwardButton->setFixedSize(26, 30);
    // QIcon ffIcon(":/resources/icons/fastforward.png");  // Path to your icon in resources
    fastForwardButton->setIcon(loadThemedIcon("fastforward"));
    fastForwardButton->setToolTip(tr("Toggle Fast Forward 8x"));
    fastForwardButton->setStyleSheet(buttonStyle);

    // ✅ Toggle fast-forward mode
    connect(fastForwardButton, &QPushButton::clicked, [this]() {
        fastForwardMode = !fastForwardMode;
        updateFastForwardButtonState();
    });

    QComboBox *dialModeSelector = new QComboBox(this);
    dialModeSelector->addItem("Page Switch", PageSwitching);
    dialModeSelector->addItem("Zoom", ZoomControl);
    dialModeSelector->addItem("Thickness", ThicknessControl);

    dialModeSelector->addItem("Tool Switch", ToolSwitching);
    dialModeSelector->setFixedWidth(120);

    connect(dialModeSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
        [this](int index) { changeDialMode(static_cast<DialMode>(index)); });

    colorPreview = new QPushButton(this);
    colorPreview->setFixedSize(26, 30);
    colorPreview->setStyleSheet("border-radius: 15px; border: 1px solid gray;");
    colorPreview->setEnabled(false);  // ✅ Prevents it from being clicked

    btnPageSwitch = new QPushButton(loadThemedIcon("bookpage"), "", this);
    btnPageSwitch->setStyleSheet(buttonStyle);
    btnPageSwitch->setFixedSize(26, 30);
    btnPageSwitch->setToolTip(tr("Set Dial Mode to Page Switching"));
    btnZoom = new QPushButton(loadThemedIcon("zoom"), "", this);
    btnZoom->setStyleSheet(buttonStyle);
    btnZoom->setFixedSize(26, 30);
    btnZoom->setToolTip(tr("Set Dial Mode to Zoom Ctrl"));
    btnThickness = new QPushButton(loadThemedIcon("thickness"), "", this);
    btnThickness->setStyleSheet(buttonStyle);
    btnThickness->setFixedSize(26, 30);
    btnThickness->setToolTip(tr("Set Dial Mode to Pen Tip Thickness Ctrl"));

    btnTool = new QPushButton(loadThemedIcon("pen"), "", this);
    btnTool->setStyleSheet(buttonStyle);
    btnTool->setFixedSize(26, 30);
    btnTool->setToolTip(tr("Set Dial Mode to Tool Switching"));
    btnPresets = new QPushButton(loadThemedIcon("preset"), "", this);
    btnPresets->setStyleSheet(buttonStyle);
    btnPresets->setFixedSize(26, 30);
    btnPresets->setToolTip(tr("Set Dial Mode to Color Preset Selection"));
    btnPannScroll = new QPushButton(loadThemedIcon("scroll"), "", this);
    btnPannScroll->setStyleSheet(buttonStyle);
    btnPannScroll->setFixedSize(26, 30);
    btnPannScroll->setToolTip(tr("Slide and turn pages with the dial"));

    connect(btnPageSwitch, &QPushButton::clicked, this, [this]() { changeDialMode(PageSwitching); });
    connect(btnZoom, &QPushButton::clicked, this, [this]() { changeDialMode(ZoomControl); });
    connect(btnThickness, &QPushButton::clicked, this, [this]() { changeDialMode(ThicknessControl); });

    connect(btnTool, &QPushButton::clicked, this, [this]() { changeDialMode(ToolSwitching); });
    connect(btnPresets, &QPushButton::clicked, this, [this]() { changeDialMode(PresetSelection); });
    connect(btnPannScroll, &QPushButton::clicked, this, [this]() { changeDialMode(PanAndPageScroll); });

    // ✅ Initialize color presets based on palette mode (will be updated after UI setup)
    colorPresets.enqueue(getDefaultPenColor());
    colorPresets.enqueue(QColor("#AA0000"));  // Temporary - will be updated later
    colorPresets.enqueue(QColor("#997700"));
    colorPresets.enqueue(QColor("#0000AA"));
    colorPresets.enqueue(QColor("#007700"));
    colorPresets.enqueue(QColor("#000000"));
    colorPresets.enqueue(QColor("#FFFFFF"));

    // ✅ Button to add current color to presets
    addPresetButton = new QPushButton(loadThemedIcon("savepreset"), "", this);
    addPresetButton->setStyleSheet(buttonStyle);
    addPresetButton->setToolTip(tr("Add Current Color to Presets"));
    addPresetButton->setFixedSize(26, 30);
    connect(addPresetButton, &QPushButton::clicked, this, &MainWindow::addColorPreset);

    openControlPanelButton = new QPushButton(this);
    openControlPanelButton->setIcon(loadThemedIcon("settings"));  // Replace with your actual settings icon
    openControlPanelButton->setStyleSheet(buttonStyle);
    openControlPanelButton->setToolTip(tr("Open Control Panel"));
    openControlPanelButton->setFixedSize(26, 30);  // Adjust to match your other buttons

    connect(openControlPanelButton, &QPushButton::clicked, this, [=]() {
        InkCanvas *canvas = currentCanvas();
        if (canvas) {
            ControlPanelDialog dialog(this, canvas, this);
            dialog.exec();  // Modal
        }
    });

    openRecentNotebooksButton = new QPushButton(this); // Create button
    openRecentNotebooksButton->setIcon(loadThemedIcon("recent")); // Replace with actual icon if available
    openRecentNotebooksButton->setStyleSheet(buttonStyle);
    openRecentNotebooksButton->setToolTip(tr("Open Recent Notebooks"));
    openRecentNotebooksButton->setFixedSize(26, 30);
    connect(openRecentNotebooksButton, &QPushButton::clicked, this, &MainWindow::openRecentNotebooksDialog);

    customColorButton = new QPushButton(this);
    customColorButton->setFixedSize(62, 30);
    QColor initialColor = getDefaultPenColor();  // Theme-aware default color
    customColorButton->setText(initialColor.name().toUpper());

    if (currentCanvas()) {
        initialColor = currentCanvas()->getPenColor();
    }

    updateCustomColorButtonStyle(initialColor);

    QTimer::singleShot(0, this, [=]() {
        connect(customColorButton, &QPushButton::clicked, this, [=]() {
            if (!currentCanvas()) return;

            handleColorButtonClick();

            // Get the current custom color from the button text
            QString buttonText = customColorButton->text();
            QColor customColor(buttonText);

            // Check if the custom color is already the current pen color
            if (currentCanvas()->getPenColor() == customColor) {
                // Second click - show color picker dialog
            QColor chosen = QColorDialog::getColor(currentCanvas()->getPenColor(), this, "Select Pen Color");
            if (chosen.isValid()) {
                currentCanvas()->setPenColor(chosen);
                    updateCustomColorButtonStyle(chosen);
                updateDialDisplay();
                    updateColorButtonStates();
                }
            } else {
                // First click - apply the custom color
                currentCanvas()->setPenColor(customColor);
                updateDialDisplay();
                updateColorButtonStates();
            }
        });
    });

    QHBoxLayout *controlLayout = new QHBoxLayout;

    controlLayout->addWidget(toggleOutlineButton);
    controlLayout->addWidget(toggleBookmarksButton);
    controlLayout->addWidget(toggleBookmarkButton);
    controlLayout->addWidget(touchGesturesButton);
    controlLayout->addWidget(toggleTabBarButton);
    controlLayout->addWidget(selectFolderButton);

    controlLayout->addWidget(exportNotebookButton);
    controlLayout->addWidget(importNotebookButton);
    controlLayout->addWidget(loadPdfButton);
    controlLayout->addWidget(clearPdfButton);
    controlLayout->addWidget(pdfTextSelectButton);
    controlLayout->addWidget(backgroundButton);
    controlLayout->addWidget(saveButton);
    controlLayout->addWidget(saveAnnotatedButton);
    controlLayout->addWidget(openControlPanelButton);
    controlLayout->addWidget(openRecentNotebooksButton); // Add button to layout
    controlLayout->addWidget(redButton);
    controlLayout->addWidget(blueButton);
    controlLayout->addWidget(yellowButton);
    controlLayout->addWidget(greenButton);
    controlLayout->addWidget(blackButton);
    controlLayout->addWidget(whiteButton);
    controlLayout->addWidget(customColorButton);
    controlLayout->addWidget(straightLineToggleButton);
    controlLayout->addWidget(ropeToolButton); // Add rope tool button to layout
    controlLayout->addWidget(markdownButton); // Add markdown button to layout
    // controlLayout->addWidget(colorPreview);
    // controlLayout->addWidget(thicknessButton);
    // controlLayout->addWidget(jumpToPageButton);
    controlLayout->addWidget(dialToggleButton);
    controlLayout->addWidget(fastForwardButton);
    // controlLayout->addWidget(channelSelector);
    controlLayout->addWidget(btnPageSwitch);
    controlLayout->addWidget(btnPannScroll);
    controlLayout->addWidget(btnZoom);
    controlLayout->addWidget(btnThickness);

    controlLayout->addWidget(btnTool);
    controlLayout->addWidget(btnPresets);
    controlLayout->addWidget(addPresetButton);
    // controlLayout->addWidget(dialModeSelector);
    // controlLayout->addStretch();

    // controlLayout->addWidget(toolSelector);
    controlLayout->addWidget(fullscreenButton);
    // controlLayout->addWidget(zoomButton);
    controlLayout->addWidget(zoom50Button);
    controlLayout->addWidget(dezoomButton);
    controlLayout->addWidget(zoom200Button);
    controlLayout->addStretch();

    controlLayout->addWidget(prevPageButton);
    controlLayout->addWidget(pageInput);
    controlLayout->addWidget(nextPageButton);
    controlLayout->addWidget(benchmarkButton);
    controlLayout->addWidget(benchmarkLabel);
    controlLayout->addWidget(deletePageButton);

    controlBar = new QWidget;  // Use member variable instead of local
    controlBar->setObjectName("controlBar");
    // controlBar->setLayout(controlLayout);  // Commented out - responsive layout will handle this
    controlBar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);

    // Theme will be applied later in loadUserSettings -> updateTheme()
    controlBar->setStyleSheet("");

    canvasStack = new QStackedWidget();
    canvasStack->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    // Create a container for the canvas and scrollbars with relative positioning
    QWidget *canvasContainer = new QWidget;
    QVBoxLayout *canvasLayout = new QVBoxLayout(canvasContainer);
    canvasLayout->setContentsMargins(0, 0, 0, 0);
    canvasLayout->addWidget(canvasStack);

    // Enable context menu for the workaround
    canvasContainer->setContextMenuPolicy(Qt::CustomContextMenu);

    // Set up the scrollbars to overlay the canvas
    panXSlider->setParent(canvasContainer);
    panYSlider->setParent(canvasContainer);

    // Raise scrollbars to ensure they're visible above the canvas
    panXSlider->raise();
    panYSlider->raise();

    // Handle scrollbar intersection
    connect(canvasContainer, &QWidget::customContextMenuRequested, this, [this]() {
        // This connection is just to make sure the container exists
        // and can receive signals - a workaround for some Qt versions
    });

    // Position the scrollbars at the bottom and right edges
    canvasContainer->installEventFilter(this);

    // Update scrollbar positions initially
    QTimer::singleShot(0, this, [this, canvasContainer]() {
        updateScrollbarPositions();
    });

    // Main layout: toolbar -> tab bar -> canvas (vertical stack)
    QWidget *container = new QWidget;
    container->setObjectName("container");
    QVBoxLayout *mainLayout = new QVBoxLayout(container);
    mainLayout->setContentsMargins(0, 0, 0, 0);  // ✅ Remove extra margins
    mainLayout->setSpacing(0); // ✅ Remove spacing between components

    // Add components in vertical order
    mainLayout->addWidget(controlBar);        // Toolbar at top
    mainLayout->addWidget(tabBarContainer);   // Tab bar below toolbar

    // Content area with sidebars and canvas
    QHBoxLayout *contentLayout = new QHBoxLayout;
    contentLayout->setContentsMargins(0, 0, 0, 0);
    contentLayout->setSpacing(0);
    contentLayout->addWidget(outlineSidebar, 0); // Fixed width outline sidebar
    contentLayout->addWidget(bookmarksSidebar, 0); // Fixed width bookmarks sidebar
    contentLayout->addWidget(canvasContainer, 1); // Canvas takes remaining space

    QWidget *contentWidget = new QWidget;
    contentWidget->setLayout(contentLayout);
    mainLayout->addWidget(contentWidget, 1);

    setCentralWidget(container);

    benchmarkTimer = new QTimer(this);
    connect(benchmarkButton, &QPushButton::clicked, this, &MainWindow::toggleBenchmark);
    connect(benchmarkTimer, &QTimer::timeout, this, &MainWindow::updateBenchmarkDisplay);

    QString tempDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/temp_session";
    QDir dir(tempDir);

    // Remove all contents (but keep the directory itself)
    if (dir.exists()) {
        dir.removeRecursively();  // Careful: this wipes everything inside
    }
    QDir().mkpath(tempDir);  // Recreate clean directory

    addNewTab();

    // Initialize responsive toolbar layout
    createSingleRowLayout();  // Start with single row layout

    // Now that all UI components are created, update the color palette
    updateColorPalette();

}
    void loadUserSettings() {
    QSettings settings("SpeedyNote", "App");

    // Load low-res toggle
    lowResPreviewEnabled = settings.value("lowResPreviewEnabled", true).toBool();
    setLowResPreviewEnabled(lowResPreviewEnabled);

    zoomButtonsVisible = settings.value("zoomButtonsVisible", true).toBool();
    setZoomButtonsVisible(zoomButtonsVisible);

    scrollOnTopEnabled = settings.value("scrollOnTopEnabled", true).toBool();
    setScrollOnTopEnabled(scrollOnTopEnabled);

    touchGesturesEnabled = settings.value("touchGesturesEnabled", true).toBool();
    setTouchGesturesEnabled(touchGesturesEnabled);

    // Update button visual state to match loaded setting
    touchGesturesButton->setProperty("selected", touchGesturesEnabled);
    touchGesturesButton->style()->unpolish(touchGesturesButton);
    touchGesturesButton->style()->polish(touchGesturesButton);

    // Initialize default background settings if they don't exist
    if (!settings.contains("defaultBackgroundStyle")) {
        saveDefaultBackgroundSettings(BackgroundStyle::Grid, Qt::white, 30);
    }

    // Load keyboard mappings
    loadKeyboardMappings();

    // Load theme settings
    loadThemeSettings();
}
    bool scrollbarsVisible = false;
    QTimer *scrollbarHideTimer = nullptr;
    bool eventFilter(QObject *obj, QEvent *event) override {
    static bool dragging = false;
    static QPoint lastMousePos;
    static QTimer *longPressTimer = nullptr;

    // Handle IME focus events for text input widgets
    QLineEdit *lineEdit = qobject_cast<QLineEdit*>(obj);
    if (lineEdit) {
        if (event->type() == QEvent::FocusIn) {
            // Ensure IME is enabled when text field gets focus
            lineEdit->setAttribute(Qt::WA_InputMethodEnabled, true);
            QInputMethod *inputMethod = QGuiApplication::inputMethod();
            if (inputMethod) {
                inputMethod->show();
            }
        }
        else if (event->type() == QEvent::FocusOut) {
            // Keep IME available but reset state
            QInputMethod *inputMethod = QGuiApplication::inputMethod();
            if (inputMethod) {
                inputMethod->reset();
            }
        }
    }

    // Handle resize events for canvas container
    QWidget *container = canvasStack ? canvasStack->parentWidget() : nullptr;
    if (obj == container && event->type() == QEvent::Resize) {
        updateScrollbarPositions();
        return false; // Let the event propagate
    }

    // Handle scrollbar visibility
    if (obj == panXSlider || obj == panYSlider) {
        if (event->type() == QEvent::Enter) {
            // Mouse entered scrollbar area
            if (scrollbarHideTimer->isActive()) {
                scrollbarHideTimer->stop();
            }
            return false;
        }
        else if (event->type() == QEvent::Leave) {
            // Mouse left scrollbar area - start timer to hide
            if (!scrollbarHideTimer->isActive()) {
                scrollbarHideTimer->start();
            }
            return false;
        }
    }

    // Check if this is a canvas event for scrollbar handling
    InkCanvas* canvas = qobject_cast<InkCanvas*>(obj);
    if (canvas) {
        // Handle mouse movement for scrollbar visibility
        if (event->type() == QEvent::MouseMove) {
            QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
            handleEdgeProximity(canvas, mouseEvent->pos());
        }
        // Handle tablet events for stylus hover (safely)
        else if (event->type() == QEvent::TabletMove) {
            try {
                QTabletEvent* tabletEvent = static_cast<QTabletEvent*>(event);
                handleEdgeProximity(canvas, tabletEvent->position().toPoint());
            } catch (...) {
                // Ignore tablet event errors to prevent crashes
            }
        }
        // Handle mouse button press events for forward/backward navigation
        else if (event->type() == QEvent::MouseButtonPress) {
            QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);

            // Mouse button 4 (Back button) - Previous page
            if (mouseEvent->button() == Qt::BackButton) {
                if (prevPageButton) {
                    prevPageButton->click();
                }
                return true; // Consume the event
            }
            // Mouse button 5 (Forward button) - Next page
            else if (mouseEvent->button() == Qt::ForwardButton) {
                if (nextPageButton) {
                    nextPageButton->click();
                }
                return true; // Consume the event
            }
        }
        // Handle wheel events for scrolling
        else if (event->type() == QEvent::Wheel) {
            QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event);

            // Check if we need scrolling
            bool needHorizontalScroll = panXSlider->maximum() > 0;
            bool needVerticalScroll = panYSlider->maximum() > 0;

            // Handle vertical scrolling (Y axis)
            if (wheelEvent->angleDelta().y() != 0 && needVerticalScroll) {
                // Calculate scroll amount (negative because wheel up should scroll up)
                int scrollDelta = -wheelEvent->angleDelta().y() / 8; // Convert from 1/8 degree units
                scrollDelta = scrollDelta / 15; // Convert to steps (typical wheel step is 15 degrees)

                // Much faster base scroll speed - aim for ~3-5 scrolls to reach bottom
                int baseScrollAmount = panYSlider->maximum() / 8; // 1/8 of total range per scroll
                scrollDelta = scrollDelta * qMax(baseScrollAmount, 50); // Minimum 50 units per scroll

                // Apply the scroll
                int currentPan = panYSlider->value();
                int newPan = qBound(panYSlider->minimum(), currentPan + scrollDelta, panYSlider->maximum());
                panYSlider->setValue(newPan);

                // Show scrollbar temporarily
                panYSlider->setVisible(true);
                scrollbarsVisible = true;
                if (scrollbarHideTimer->isActive()) {
                    scrollbarHideTimer->stop();
                }
                scrollbarHideTimer->start();

                // Consume the event
                return true;
            }

            // Handle horizontal scrolling (X axis) - for completeness
            if (wheelEvent->angleDelta().x() != 0 && needHorizontalScroll) {
                // Calculate scroll amount
                int scrollDelta = wheelEvent->angleDelta().x() / 8; // Convert from 1/8 degree units
                scrollDelta = scrollDelta / 15; // Convert to steps

                // Much faster base scroll speed - same logic as vertical
                int baseScrollAmount = panXSlider->maximum() / 8; // 1/8 of total range per scroll
                scrollDelta = scrollDelta * qMax(baseScrollAmount, 50); // Minimum 50 units per scroll

                // Apply the scroll
                int currentPan = panXSlider->value();
                int newPan = qBound(panXSlider->minimum(), currentPan + scrollDelta, panXSlider->maximum());
                panXSlider->setValue(newPan);

                // Show scrollbar temporarily
                panXSlider->setVisible(true);
                scrollbarsVisible = true;
                if (scrollbarHideTimer->isActive()) {
                    scrollbarHideTimer->stop();
                }
                scrollbarHideTimer->start();

                // Consume the event
                return true;
            }

            // If no scrolling was needed, let the event propagate
            return false;
        }
    }

    // Handle dial container drag events
    if (obj == dialContainer) {
        if (event->type() == QEvent::MouseButtonPress) {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
            lastMousePos = mouseEvent->globalPos();
            dragging = false;

            if (!longPressTimer) {
                longPressTimer = new QTimer(this);
                longPressTimer->setSingleShot(true);
                connect(longPressTimer, &QTimer::timeout, [this]() {
                    dragging = true;  // ✅ Allow movement after long press
                });
            }
            longPressTimer->start(1500);  // ✅ Start long press timer (500ms)
            return true;
        }

        if (event->type() == QEvent::MouseMove && dragging) {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
            QPoint delta = mouseEvent->globalPos() - lastMousePos;
            dialContainer->move(dialContainer->pos() + delta);
            lastMousePos = mouseEvent->globalPos();
            return true;
        }

        if (event->type() == QEvent::MouseButtonRelease) {
            if (longPressTimer) longPressTimer->stop();
            dragging = false;  // ✅ Stop dragging on release
            return true;
        }
    }

    return QObject::eventFilter(obj, event);
}
    void updateScrollbarPositions() {
    QWidget *container = canvasStack->parentWidget();
    if (!container || !panXSlider || !panYSlider) return;

    // Add small margins for better visibility
    const int margin = 3;

    // Get scrollbar dimensions
    const int scrollbarWidth = panYSlider->width();
    const int scrollbarHeight = panXSlider->height();

    // Calculate sizes based on container
    int containerWidth = container->width();
    int containerHeight = container->height();

    // Leave a bit of space for the corner
    int cornerOffset = 15;

    // Position horizontal scrollbar at top
    panXSlider->setGeometry(
        cornerOffset + margin,  // Leave space at left corner
        margin,
        containerWidth - cornerOffset - margin*2,  // Full width minus corner and right margin
        scrollbarHeight
    );

    // Position vertical scrollbar at left
    panYSlider->setGeometry(
        margin,
        cornerOffset + margin,  // Leave space at top corner
        scrollbarWidth,
        containerHeight - cornerOffset - margin*2  // Full height minus corner and bottom margin
    );
}
    void handleEdgeProximity(InkCanvas* canvas, const QPoint& pos) {
    if (!canvas) return;

    // Get canvas dimensions
    int canvasWidth = canvas->width();
    int canvasHeight = canvas->height();

    // Edge detection zones - show scrollbars when pointer is within 50px of edges
    bool nearLeftEdge = pos.x() < 25;  // For vertical scrollbar
    bool nearTopEdge = pos.y() < 25;   // For horizontal scrollbar - entire top edge

    // Only show scrollbars if canvas is larger than viewport
    bool needHorizontalScroll = panXSlider->maximum() > 0;
    bool needVerticalScroll = panYSlider->maximum() > 0;

    // Show/hide scrollbars based on pointer position
    if (nearLeftEdge && needVerticalScroll) {
        panYSlider->setVisible(true);
        scrollbarsVisible = true;
        if (scrollbarHideTimer->isActive()) {
            scrollbarHideTimer->stop();
        }
        scrollbarHideTimer->start();
    }

    if (nearTopEdge && needHorizontalScroll) {
        panXSlider->setVisible(true);
        scrollbarsVisible = true;
        if (scrollbarHideTimer->isActive()) {
            scrollbarHideTimer->stop();
        }
        scrollbarHideTimer->start();
    }
}
    bool isToolbarTwoRows = false;
    QVBoxLayout *controlLayoutVertical = nullptr;
    QHBoxLayout *controlLayoutSingle = nullptr;
    QHBoxLayout *controlLayoutFirstRow = nullptr;
    QHBoxLayout *controlLayoutSecondRow = nullptr;
    void updateToolbarLayout() {
    // Calculate scaled width using device pixel ratio
    QScreen *screen = QGuiApplication::primaryScreen();
    // qreal dpr = screen ? screen->devicePixelRatio() : 1.0;
    int scaledWidth = width();

    // Dynamic threshold based on zoom button visibility
    int threshold = areZoomButtonsVisible() ? 1548 : 1438;

    // Debug output to understand what's happening
    // qDebug() << "Window width:" << scaledWidth << "Threshold:" << threshold << "Zoom buttons visible:" << areZoomButtonsVisible();

    bool shouldBeTwoRows = scaledWidth <= threshold;

    // qDebug() << "Should be two rows:" << shouldBeTwoRows << "Currently is two rows:" << isToolbarTwoRows;

    if (shouldBeTwoRows != isToolbarTwoRows) {
        isToolbarTwoRows = shouldBeTwoRows;

        // qDebug() << "Switching to" << (isToolbarTwoRows ? "two rows" : "single row");

        if (isToolbarTwoRows) {
            createTwoRowLayout();
        } else {
            createSingleRowLayout();
        }
    }
}
    void createSingleRowLayout() {
    // Delete separator line if it exists (from previous 2-row layout)
    if (separatorLine) {
        delete separatorLine;
        separatorLine = nullptr;
    }

    // Create new single row layout first
    QHBoxLayout *newLayout = new QHBoxLayout;

    // Add all widgets to single row (same order as before)
    newLayout->addWidget(toggleTabBarButton);
            newLayout->addWidget(toggleOutlineButton);
        newLayout->addWidget(toggleBookmarksButton);
        newLayout->addWidget(toggleBookmarkButton);
        newLayout->addWidget(touchGesturesButton);
        newLayout->addWidget(selectFolderButton);
    newLayout->addWidget(exportNotebookButton);
    newLayout->addWidget(importNotebookButton);
    newLayout->addWidget(loadPdfButton);
    newLayout->addWidget(clearPdfButton);
    newLayout->addWidget(pdfTextSelectButton);
    newLayout->addWidget(backgroundButton);
    newLayout->addWidget(saveButton);
    newLayout->addWidget(saveAnnotatedButton);
    newLayout->addWidget(openControlPanelButton);
    newLayout->addWidget(openRecentNotebooksButton);
    newLayout->addWidget(redButton);
    newLayout->addWidget(blueButton);
    newLayout->addWidget(yellowButton);
    newLayout->addWidget(greenButton);
    newLayout->addWidget(blackButton);
    newLayout->addWidget(whiteButton);
    newLayout->addWidget(customColorButton);
    newLayout->addWidget(penToolButton);
    newLayout->addWidget(markerToolButton);
    newLayout->addWidget(eraserToolButton);
    newLayout->addWidget(straightLineToggleButton);
    newLayout->addWidget(ropeToolButton);
    newLayout->addWidget(markdownButton);
    newLayout->addWidget(dialToggleButton);
    newLayout->addWidget(fastForwardButton);
    newLayout->addWidget(btnPageSwitch);
    newLayout->addWidget(btnPannScroll);
    newLayout->addWidget(btnZoom);
    newLayout->addWidget(btnThickness);

    newLayout->addWidget(btnTool);
    newLayout->addWidget(btnPresets);
    newLayout->addWidget(addPresetButton);
    newLayout->addWidget(fullscreenButton);

    // Only add zoom buttons if they're visible
    if (areZoomButtonsVisible()) {
        newLayout->addWidget(zoom50Button);
        newLayout->addWidget(dezoomButton);
        newLayout->addWidget(zoom200Button);
    }

    newLayout->addStretch();
    newLayout->addWidget(prevPageButton);
    newLayout->addWidget(pageInput);
    newLayout->addWidget(nextPageButton);
    newLayout->addWidget(benchmarkButton);
    newLayout->addWidget(benchmarkLabel);
    newLayout->addWidget(deletePageButton);

    // Safely replace the layout
    QLayout* oldLayout = controlBar->layout();
    if (oldLayout) {
        // Remove all items from old layout (but don't delete widgets)
        QLayoutItem* item;
        while ((item = oldLayout->takeAt(0)) != nullptr) {
            // Just removing, not deleting widgets
        }
        delete oldLayout;
    }

    // Set the new layout
    controlBar->setLayout(newLayout);
    controlLayoutSingle = newLayout;

    // Clean up other layout pointers
    controlLayoutVertical = nullptr;
    controlLayoutFirstRow = nullptr;
    controlLayoutSecondRow = nullptr;

    // Update pan range after layout change
    updatePanRange();
}
    void createTwoRowLayout() {
    // Create new layouts first
    QVBoxLayout *newVerticalLayout = new QVBoxLayout;
    QHBoxLayout *newFirstRowLayout = new QHBoxLayout;
    QHBoxLayout *newSecondRowLayout = new QHBoxLayout;

    // Add comfortable spacing and margins for 2-row layout
    newFirstRowLayout->setContentsMargins(8, 8, 8, 6);  // More generous margins
    newFirstRowLayout->setSpacing(3);  // Add spacing between buttons for less cramped feel
    newSecondRowLayout->setContentsMargins(8, 6, 8, 8);  // More generous margins
    newSecondRowLayout->setSpacing(3);  // Add spacing between buttons for less cramped feel

    // First row: up to customColorButton
    newFirstRowLayout->addWidget(toggleTabBarButton);
            newFirstRowLayout->addWidget(toggleOutlineButton);
        newFirstRowLayout->addWidget(toggleBookmarksButton);
        newFirstRowLayout->addWidget(toggleBookmarkButton);
        newFirstRowLayout->addWidget(touchGesturesButton);
        newFirstRowLayout->addWidget(selectFolderButton);
    newFirstRowLayout->addWidget(exportNotebookButton);
    newFirstRowLayout->addWidget(importNotebookButton);
    newFirstRowLayout->addWidget(loadPdfButton);
    newFirstRowLayout->addWidget(clearPdfButton);
    newFirstRowLayout->addWidget(pdfTextSelectButton);
    newFirstRowLayout->addWidget(backgroundButton);
    newFirstRowLayout->addWidget(saveButton);
    newFirstRowLayout->addWidget(saveAnnotatedButton);
    newFirstRowLayout->addWidget(openControlPanelButton);
    newFirstRowLayout->addWidget(openRecentNotebooksButton);
    newFirstRowLayout->addWidget(redButton);
    newFirstRowLayout->addWidget(blueButton);
    newFirstRowLayout->addWidget(yellowButton);
    newFirstRowLayout->addWidget(greenButton);
    newFirstRowLayout->addWidget(blackButton);
    newFirstRowLayout->addWidget(whiteButton);
    newFirstRowLayout->addWidget(customColorButton);
    newFirstRowLayout->addWidget(penToolButton);
    newFirstRowLayout->addWidget(markerToolButton);
    newFirstRowLayout->addWidget(eraserToolButton);
    newFirstRowLayout->addStretch();

    // Create a separator line
    if (!separatorLine) {
        separatorLine = new QFrame();
        separatorLine->setFrameShape(QFrame::HLine);
        separatorLine->setFrameShadow(QFrame::Sunken);
        separatorLine->setLineWidth(1);
        separatorLine->setStyleSheet("QFrame { color: rgba(255, 255, 255, 255); }");
    }

    // Second row: everything after customColorButton
    newSecondRowLayout->addWidget(straightLineToggleButton);
    newSecondRowLayout->addWidget(ropeToolButton);
    newSecondRowLayout->addWidget(markdownButton);
    newSecondRowLayout->addWidget(dialToggleButton);
    newSecondRowLayout->addWidget(fastForwardButton);
    newSecondRowLayout->addWidget(btnPageSwitch);
    newSecondRowLayout->addWidget(btnPannScroll);
    newSecondRowLayout->addWidget(btnZoom);
    newSecondRowLayout->addWidget(btnThickness);

    newSecondRowLayout->addWidget(btnTool);
    newSecondRowLayout->addWidget(btnPresets);
    newSecondRowLayout->addWidget(addPresetButton);
    newSecondRowLayout->addWidget(fullscreenButton);

    // Only add zoom buttons if they're visible
    if (areZoomButtonsVisible()) {
        newSecondRowLayout->addWidget(zoom50Button);
        newSecondRowLayout->addWidget(dezoomButton);
        newSecondRowLayout->addWidget(zoom200Button);
    }

    newSecondRowLayout->addStretch();
    newSecondRowLayout->addWidget(prevPageButton);
    newSecondRowLayout->addWidget(pageInput);
    newSecondRowLayout->addWidget(nextPageButton);
    newSecondRowLayout->addWidget(benchmarkButton);
    newSecondRowLayout->addWidget(benchmarkLabel);
    newSecondRowLayout->addWidget(deletePageButton);

    // Add layouts to vertical layout with separator
    newVerticalLayout->addLayout(newFirstRowLayout);
    newVerticalLayout->addWidget(separatorLine);
    newVerticalLayout->addLayout(newSecondRowLayout);
    newVerticalLayout->setContentsMargins(0, 0, 0, 0);
    newVerticalLayout->setSpacing(0); // No spacing since we have our own separator

    // Safely replace the layout
    QLayout* oldLayout = controlBar->layout();
    if (oldLayout) {
        // Remove all items from old layout (but don't delete widgets)
        QLayoutItem* item;
        while ((item = oldLayout->takeAt(0)) != nullptr) {
            // Just removing, not deleting widgets
        }
        delete oldLayout;
    }

    // Set the new layout
    controlBar->setLayout(newVerticalLayout);
    controlLayoutVertical = newVerticalLayout;
    controlLayoutFirstRow = newFirstRowLayout;
    controlLayoutSecondRow = newSecondRowLayout;

    // Clean up other layout pointer
    controlLayoutSingle = nullptr;

    // Update pan range after layout change
    updatePanRange();
}
    QString elideTabText(const QString &text, int maxWidth) {
    // Create a font metrics object using the default font
    QFontMetrics fontMetrics(QApplication::font());

    // Elide the text from the right (showing the beginning)
    return fontMetrics.elidedText(text, Qt::ElideRight, maxWidth);
}
    QTimer *layoutUpdateTimer = nullptr;
    QFrame *separatorLine = nullptr;
  protected:
    void resizeEvent(QResizeEvent *event) override {
    QMainWindow::resizeEvent(event);

    // Use a timer to delay layout updates during resize to prevent excessive switching
    if (!layoutUpdateTimer) {
        layoutUpdateTimer = new QTimer(this);
        layoutUpdateTimer->setSingleShot(true);
        connect(layoutUpdateTimer, &QTimer::timeout, this, [this]() {
            updateToolbarLayout();
            // Also reposition dial after resize finishes
            if (dialContainer && dialContainer->isVisible()) {
                positionDialContainer();
            }
        });
    }

    layoutUpdateTimer->stop();
    layoutUpdateTimer->start(100); // Wait 100ms after resize stops
}
    void keyPressEvent(QKeyEvent *event) override {
    // Don't intercept keyboard events when text input widgets have focus
    // This prevents conflicts with Windows TextInputFramework
    QWidget *focusWidget = QApplication::focusWidget();
    if (focusWidget) {
        bool isTextInputWidget = qobject_cast<QLineEdit*>(focusWidget) ||
                               qobject_cast<QSpinBox*>(focusWidget) ||
                               qobject_cast<QTextEdit*>(focusWidget) ||
                               qobject_cast<QPlainTextEdit*>(focusWidget) ||
                               qobject_cast<QComboBox*>(focusWidget);

        if (isTextInputWidget) {
            // Let text input widgets handle their own keyboard events
            QMainWindow::keyPressEvent(event);
            return;
        }
    }

    // Don't intercept IME-related keyboard shortcuts
    // These are reserved for Windows Input Method Editor
    if (event->modifiers() & Qt::ControlModifier) {
        if (event->key() == Qt::Key_Space ||           // Ctrl+Space (IME toggle)
            event->key() == Qt::Key_Shift ||           // Ctrl+Shift (language switch)
            event->key() == Qt::Key_Alt) {             // Ctrl+Alt (IME functions)
            // Let Windows handle IME shortcuts
            QMainWindow::keyPressEvent(event);
            return;
        }
    }

    // Don't intercept Shift+Alt (another common IME shortcut)
    if ((event->modifiers() & Qt::ShiftModifier) && (event->modifiers() & Qt::AltModifier)) {
        QMainWindow::keyPressEvent(event);
        return;
    }

    // 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";

    QString keyString = QKeySequence(event->key()).toString();

    QString fullSequence;
    if (!modifiers.isEmpty()) {
        fullSequence = modifiers.join("+") + "+" + keyString;
    } else {
        fullSequence = keyString;
    }

    // Check if this sequence is mapped
    if (keyboardMappings.contains(fullSequence)) {
        handleKeyboardShortcut(fullSequence);
        event->accept();
        return;
    }

    // If not handled, pass to parent
    QMainWindow::keyPressEvent(event);
}
    void tabletEvent(QTabletEvent *event) override {
    // Since tablet tracking is disabled to prevent crashes, we now only handle
    // basic tablet events that come through when stylus is touching the surface
    if (!event) {
        return;
    }

    // Just pass tablet events to parent safely without custom hover handling
    // (hover tooltips will work through normal mouse events instead)
    try {
        QMainWindow::tabletEvent(event);
    } catch (...) {
        // Catch any exceptions and just accept the event
        event->accept();
    }
}
    void inputMethodEvent(QInputMethodEvent *event) override {
    // Forward IME events to the focused widget
    QWidget *focusWidget = QApplication::focusWidget();
    if (focusWidget && focusWidget != this) {
        QApplication::sendEvent(focusWidget, event);
        event->accept();
        return;
    }

    // Default handling
    QMainWindow::inputMethodEvent(event);
}
    QVariant inputMethodQuery(Qt::InputMethodQuery query) const override {
    // Forward IME queries to the focused widget
    QWidget *focusWidget = QApplication::focusWidget();
    if (focusWidget && focusWidget != this) {
        return focusWidget->inputMethodQuery(query);
    }

    // Default handling
    return QMainWindow::inputMethodQuery(query);
}
};

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;
};

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 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 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() {}
private:
void setupUi() {}
void populateGrid() {}
RecentNotebooksManager *notebookManager;
QGridLayout *gridLayout;
MainWindow *mainWindowRef;
};

This code segment functions to: Manage user interaction with a grid of recent notebook entries by creating visual elements for selection, handling click events to initiate notebook loading processes, and coordinating data persistence operations with associated UI updates.

```cpp
[TASK_BEGIN]
class RecentNotebooksDialog
{
    Q_OBJECT

public:
    explicit RecentNotebooksDialog(MainWindow *mainWindow, RecentNotebooksManager *manager, QWidget *parent = nullptr)
        : mainWindowRef(mainWindow), notebookManager(manager), gridLayout(new QGridLayout(this)) {
        setupUi();
    }

private:
    void onNotebookClicked() {
        QPushButton *button = qobject_cast<QPushButton*>(sender());
        if (button) {
            mainWindowRef->openNotebook(button->property("notebookPath").toString());
        }
    }

    void setupUi() {
        setLayout(gridLayout);
        populateGrid();
    }

    void populateGrid() {
        int row = 0;
        for (const QString &path : notebookManager->getRecentNotebookPaths()) {
            QPushButton *btn = new QPushButton(QFileInfo(path).baseName(), this);
            btn->setProperty("notebookPath", path);
            btn->setToolTip(path);
            gridLayout->addWidget(btn, row++, 0);
            connect(btn, &QPushButton::clicked, this, &RecentNotebooksDialog::onNotebookClicked);
        }
    }

    RecentNotebooksManager *notebookManager;
    QGridLayout *gridLayout;
    MainWindow *mainWindowRef;
};
[TASK_END]
```