"""Standalone Image Editor for ShotX."""

from __future__ import annotations

from PySide6.QtCore import QPointF, QRectF, Qt, QTimer, Signal
from PySide6.QtGui import QImage, QKeySequence, QPainter, QPixmap, QShortcut, QUndoCommand
from PySide6.QtWidgets import (
    QApplication,
    QCheckBox,
    QDialog,
    QDialogButtonBox,
    QDoubleSpinBox,
    QFileDialog,
    QFormLayout,
    QGraphicsPixmapItem,
    QGraphicsView,
    QMainWindow,
    QMessageBox,
    QSpinBox,
    QToolBar,
    QVBoxLayout,
    QWidget,
)

from shotx.ui.annotations.scene import AnnotationScene, AnnotationTool
from shotx.ui.annotations.toolbar import AnnotationToolbar
from shotx.ui.beautify import BeautifyCommand, BeautifyDialog, CombineCommand, CombineDialog
from shotx.ui.effects import EffectsCommand, EffectsDialog

# ---------------------------------------------------------------------------
# Undo Commands for Flattening Vectors
# ---------------------------------------------------------------------------

class CropCommand(QUndoCommand):
    """Crops the image and flattens current vectors."""

    def __init__(self, editor: ImageEditorWindow, crop_rect: QRectF) -> None:
        super().__init__("Crop Image")
        self.editor = editor

        if editor.image_item:
            self.old_pixmap = editor.image_item.pixmap()
        else:
            self.old_pixmap = QPixmap()
        self.old_scene_rect = editor.scene.sceneRect()
        self.old_backdrop = editor.scene.backdrop_crop

        self.vector_items = []
        for item in editor.scene.items():
            if item != editor.image_item:
                self.vector_items.append(item)

        target_rect = crop_rect.intersected(self.old_scene_rect)
        if target_rect.width() < 10 or target_rect.height() < 10:
            target_rect = self.old_scene_rect

        new_image = QImage(target_rect.size().toSize(), QImage.Format.Format_ARGB32)
        new_image.fill(Qt.GlobalColor.transparent)
        painter = QPainter(new_image)
        editor.scene.render(painter, QRectF(new_image.rect()), target_rect)
        painter.end()

        self.new_pixmap = QPixmap.fromImage(new_image)
        self.new_scene_rect = QRectF(new_image.rect())
        self.new_backdrop = new_image

    def redo(self) -> None:
        for item in self.vector_items:
            self.editor.scene.removeItem(item)
        if self.editor.image_item:
            self.editor.image_item.setPixmap(self.new_pixmap)
        self.editor.scene.setSceneRect(self.new_scene_rect)
        self.editor.scene.backdrop_crop = self.new_backdrop
        self.editor.view.fitInView(self.new_scene_rect, Qt.AspectRatioMode.KeepAspectRatio)

    def undo(self) -> None:
        if self.editor.image_item:
            self.editor.image_item.setPixmap(self.old_pixmap)
        self.editor.scene.setSceneRect(self.old_scene_rect)
        self.editor.scene.backdrop_crop = self.old_backdrop
        for item in self.vector_items:
            if item.scene() is None:
                self.editor.scene.addItem(item)
        self.editor.view.fitInView(self.old_scene_rect, Qt.AspectRatioMode.KeepAspectRatio)


class ResizeCommand(QUndoCommand):
    """Resizes the image and flattens current vectors."""

    def __init__(self, editor: ImageEditorWindow, new_width: int, new_height: int) -> None:
        super().__init__("Resize Image")
        self.editor = editor

        assert editor.image_item is not None
        self.old_pixmap = editor.image_item.pixmap()
        self.old_scene_rect = editor.scene.sceneRect()
        self.old_backdrop = editor.scene.backdrop_crop

        self.vector_items = []
        for item in editor.scene.items():
            if item != editor.image_item:
                self.vector_items.append(item)

        flattened = QImage(self.old_scene_rect.size().toSize(), QImage.Format.Format_ARGB32)
        flattened.fill(Qt.GlobalColor.transparent)
        painter = QPainter(flattened)
        editor.scene.render(painter, QRectF(flattened.rect()), self.old_scene_rect)
        painter.end()

        scaled_image = flattened.scaled(
            new_width, new_height,
            Qt.AspectRatioMode.IgnoreAspectRatio,
            Qt.TransformationMode.SmoothTransformation
        )

        self.new_pixmap = QPixmap.fromImage(scaled_image)
        self.new_scene_rect = QRectF(scaled_image.rect())
        self.new_backdrop = scaled_image

    def redo(self) -> None:
        for item in self.vector_items:
            self.editor.scene.removeItem(item)
        if self.editor.image_item:
            self.editor.image_item.setPixmap(self.new_pixmap)
        self.editor.scene.setSceneRect(self.new_scene_rect)
        self.editor.scene.backdrop_crop = self.new_backdrop
        self.editor.view.fitInView(self.new_scene_rect, Qt.AspectRatioMode.KeepAspectRatio)

    def undo(self) -> None:
        if self.editor.image_item:
            self.editor.image_item.setPixmap(self.old_pixmap)
        self.editor.scene.setSceneRect(self.old_scene_rect)
        self.editor.scene.backdrop_crop = self.old_backdrop
        for item in self.vector_items:
            if item.scene() is None:
                self.editor.scene.addItem(item)
        self.editor.view.fitInView(self.old_scene_rect, Qt.AspectRatioMode.KeepAspectRatio)


# ---------------------------------------------------------------------------
# UI Components
# ---------------------------------------------------------------------------

class ResizeDialog(QDialog):
    def __init__(self, current_width: int, current_height: int, orig_size: int, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Resize Image")
        self.setMinimumWidth(350)

        self._orig_width = current_width
        self._orig_height = current_height
        self._orig_size = orig_size

        layout = QFormLayout(self)

        self.width_spin = QSpinBox(self)
        self.width_spin.setRange(1, 20000)
        self.width_spin.setValue(current_width)

        self.height_spin = QSpinBox(self)
        self.height_spin.setRange(1, 20000)
        self.height_spin.setValue(current_height)

        self.percent_spin = QDoubleSpinBox(self)
        self.percent_spin.setRange(1.0, 500.0)
        self.percent_spin.setSuffix(" %")
        self.percent_spin.setValue(100.0)

        self.aspect_ratio_cb = QCheckBox("Maintain Aspect Ratio", self)
        self.aspect_ratio_cb.setChecked(True)

        self._aspect_ratio = current_width / current_height if current_height > 0 else 1

        self.width_spin.valueChanged.connect(self._on_width_changed)
        self.height_spin.valueChanged.connect(self._on_height_changed)
        self.percent_spin.valueChanged.connect(self._on_percent_changed)

        layout.addRow("Width (px):", self.width_spin)
        layout.addRow("Height (px):", self.height_spin)
        layout.addRow("Scale:", self.percent_spin)
        layout.addRow("", self.aspect_ratio_cb)

        from PySide6.QtWidgets import QLabel
        self.size_label = QLabel(self)
        self.size_label.setStyleSheet("color: #88c0d0; font-weight: bold;")
        self._update_size_estimate()
        layout.addRow("", self.size_label)

        buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, self)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        layout.addRow(buttons)

    def _on_width_changed(self, w: int):
        if self.width_spin.hasFocus():
            if self.aspect_ratio_cb.isChecked():
                self.height_spin.setValue(int(w / self._aspect_ratio))
            self.percent_spin.setValue((w / self._orig_width) * 100.0)
        self._update_size_estimate()

    def _on_height_changed(self, h: int):
        if self.height_spin.hasFocus():
            if self.aspect_ratio_cb.isChecked():
                self.width_spin.setValue(int(h * self._aspect_ratio))
            self.percent_spin.setValue((h / self._orig_height) * 100.0)
        self._update_size_estimate()

    def _on_percent_changed(self, p: float):
        if self.percent_spin.hasFocus():
            new_w = max(1, int(self._orig_width * (p / 100.0)))
            new_h = max(1, int(self._orig_height * (p / 100.0)))
            self.width_spin.setValue(new_w)
            self.height_spin.setValue(new_h)
        self._update_size_estimate()

    def _format_size(self, size_bytes: int) -> str:
        if size_bytes < 1024 * 1024:
            return f"{size_bytes / 1024:.1f} KB"
        return f"{size_bytes / (1024 * 1024):.2f} MB"

    def _update_size_estimate(self) -> None:
        w = self.width_spin.value()
        h = self.height_spin.value()
        ratio = (w * h) / (self._orig_width * self._orig_height) if self._orig_width > 0 else 1
        est_size = int(self._orig_size * ratio)
        self.size_label.setText(f"Est. Size: ~{self._format_size(est_size)}")

class EditorGraphicsView(QGraphicsView):
    """Custom view to handle zooming and specific canvas interactions."""

    # Emitted when zoom percentage changes (e.g. 100 for 100%)
    zoom_changed = Signal(int)

    def __init__(self, scene, parent=None):
        super().__init__(scene, parent)
        self._space_pressed = False
        self._previous_drag_mode = self.dragMode()
        self._current_zoom = 100  # percentage

        # Performance/rendering hints
        self.setOptimizationFlag(QGraphicsView.OptimizationFlag.DontAdjustForAntialiasing, True)
        self.setOptimizationFlag(QGraphicsView.OptimizationFlag.DontSavePainterState, True)
        self.setViewportUpdateMode(QGraphicsView.ViewportUpdateMode.SmartViewportUpdate)

    def set_zoom(self, zoom_level: int, anchor_point: QPointF | None = None) -> None:
        """Set the absolute zoom level as a percentage (10 to 500)."""
        # Clamp between 10% and 500%
        zoom_level = max(10, min(500, zoom_level))
        if zoom_level == self._current_zoom:
            return

        old_zoom = self._current_zoom
        self._current_zoom = zoom_level

        # Calculate the mathematical scale factor needed to go from exactly old_zoom to zoom_level
        scale_factor = self._current_zoom / old_zoom

        if anchor_point:
            # Map the viewport anchor to a scene coordinate *before* scaling
            scene_pos = self.mapToScene(anchor_point.toPoint())

            self.scale(scale_factor, scale_factor)

            # Map that same viewport anchor to a scene coordinate *after* scaling
            new_scene_pos = self.mapToScene(anchor_point.toPoint())

            # Translate the view so the scene coordinate stays under the anchor
            delta = new_scene_pos - scene_pos
            self.translate(delta.x(), delta.y())
        else:
            self.scale(scale_factor, scale_factor)

        self.zoom_changed.emit(self._current_zoom)

    def mousePressEvent(self, event) -> None:
        if self._space_pressed:
            # Force panning only, preventing clicking/moving items underneath
            self.setInteractive(False)
            super().mousePressEvent(event)
            event.accept()
        else:
            super().mousePressEvent(event)

    def mouseReleaseEvent(self, event) -> None:
        super().mouseReleaseEvent(event)
        # Restore interactivity after drag finishes
        if self._space_pressed:
            self.setInteractive(True)
            event.accept()

    def keyPressEvent(self, event) -> None:
        if event.key() == Qt.Key.Key_Space and not event.isAutoRepeat():
            self._space_pressed = True
            self._previous_drag_mode = self.dragMode()
            self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
            event.accept()
        else:
            super().keyPressEvent(event)

    def keyReleaseEvent(self, event) -> None:
        if event.key() == Qt.Key.Key_Space and not event.isAutoRepeat():
            self._space_pressed = False
            self.setDragMode(self._previous_drag_mode)
            event.accept()
        else:
            super().keyReleaseEvent(event)

    def wheelEvent(self, event) -> None:
        # Standard Ctrl + Scroll to Zoom
        if event.modifiers() == Qt.KeyboardModifier.ControlModifier:
            if event.angleDelta().y() > 0:
                # Zoom in: ~15% steps
                new_zoom = int(self._current_zoom * 1.15)
                # Snap to nice round numbers if close
                if 95 <= new_zoom <= 105:
                    new_zoom = 100
                if 195 <= new_zoom <= 205:
                    new_zoom = 200
            else:
                # Zoom out: ~15% steps
                new_zoom = int(self._current_zoom / 1.15)
                # Snap to nice round numbers if close
                if 95 <= new_zoom <= 105:
                    new_zoom = 100
                if 195 <= new_zoom <= 205:
                    new_zoom = 200

            self.set_zoom(new_zoom, anchor_point=event.position())
            event.accept()
        else:
            super().wheelEvent(event)

class ImageEditorWindow(QMainWindow):
    """Main window for the ShotX Image Editor."""

    # Emitted with hex color string when user picks a new annotation color.
    # Connect this to settings persistence (same pattern as RegionOverlay).
    annotation_color_changed = Signal(str)

    def __init__(
        self,
        parent=None,
        initial_image: QImage | None = None,
        last_annotation_color: str = "#ff0000",
    ) -> None:
        super().__init__(parent)
        self.setWindowTitle("ShotX — Image Editor")
        self.resize(1024, 768)
        self._last_annotation_color = last_annotation_color

        # Setup Scene and View
        self.scene = AnnotationScene(self)
        self.view = EditorGraphicsView(self.scene)
        self.view.setRenderHint(QPainter.RenderHint.Antialiasing)
        self.view.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
        self.view.setDragMode(QGraphicsView.DragMode.NoDrag)

        # Dark grid or gray background for the canvas
        self.view.setBackgroundBrush(Qt.GlobalColor.darkGray)

        central_widget = QWidget(self)
        self.setCentralWidget(central_widget)

        from PySide6.QtWidgets import QGridLayout, QHBoxLayout
        main_layout = QVBoxLayout(central_widget)
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(0)

        self.image_item: QGraphicsPixmapItem | None = None
        self.current_image_path: str | None = None

        self._setup_system_toolbar()

        # Dedicated horizontal container for the Annotation toolbar
        self.annot_container = QWidget()
        self.annot_container.setStyleSheet("background-color: #3b4252; border-bottom: 1px solid #434c5e;")
        annot_layout = QHBoxLayout(self.annot_container)
        annot_layout.setContentsMargins(8, 8, 8, 8)
        self._setup_annotation_toolbar(annot_layout)

        main_layout.addWidget(self.annot_container)

        # Container for the View and Zoom Overlays
        self.view_container = QWidget()
        view_grid = QGridLayout(self.view_container)
        view_grid.setContentsMargins(0, 0, 0, 0)

        # 1. Base layer is the view
        view_grid.addWidget(self.view, 0, 0)

        # 2. Setup Zoom overlay UI in the grid layout
        self._setup_zoom_ui(view_grid)

        main_layout.addWidget(self.view_container, stretch=1)

        # Setup global application shortcuts
        self._setup_shortcuts()

        # Apply dark theme specific to editor to match other UIs
        self._apply_theme()

        if initial_image and not initial_image.isNull():
            self.load_image_from_data(initial_image)

    def _setup_system_toolbar(self) -> None:
        self.system_toolbar = QToolBar("System", self)
        self.system_toolbar.setMovable(False)
        self.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.system_toolbar)

        action_open = self.system_toolbar.addAction("📁 Open")
        action_open.triggered.connect(self._on_open_file)

        action_resize = self.system_toolbar.addAction("📐 Resize...")
        action_resize.triggered.connect(self._on_resize_requested)

        action_effects = self.system_toolbar.addAction("✨ Effects...")
        action_effects.triggered.connect(self._on_effects_requested)

        action_beautify = self.system_toolbar.addAction("🪄 Beautify...")
        action_beautify.triggered.connect(self._on_beautify_requested)

        action_combine = self.system_toolbar.addAction("🔗 Combine...")
        action_combine.triggered.connect(self._on_combine_requested)

        self.system_toolbar.addSeparator()

        action_paste = self.system_toolbar.addAction("📋 Paste")
        action_paste.triggered.connect(self._on_paste_clipboard)

        self.action_save = self.system_toolbar.addAction("💾 Save As...")
        self.action_save.triggered.connect(self._on_save_as)

        self.system_toolbar.addSeparator()

        self.action_copy = self.system_toolbar.addAction("📄 Copy to Clipboard")
        self.action_copy.triggered.connect(self._on_copy_to_clipboard)

    def _setup_shortcuts(self) -> None:
        """Register keyboard shortcuts for the editor."""
        # File operations
        QShortcut(QKeySequence("Ctrl+O"), self).activated.connect(self._on_open_file)

        # View operations
        QShortcut(QKeySequence("Ctrl+="), self).activated.connect(lambda: self.view.set_zoom(int(self.view._current_zoom * 1.15)))
        QShortcut(QKeySequence("Ctrl+-"), self).activated.connect(lambda: self.view.set_zoom(int(self.view._current_zoom / 1.15)))
        QShortcut(QKeySequence("Ctrl+0"), self).activated.connect(lambda: self.view.set_zoom(100))

        # Undo / Redo
        QShortcut(QKeySequence("Ctrl+Z"), self).activated.connect(self.scene.undo_stack.undo)
        QShortcut(QKeySequence("Ctrl+Y"), self).activated.connect(self.scene.undo_stack.redo)

        # Copy / Paste
        QShortcut(QKeySequence("Ctrl+C"), self).activated.connect(self._on_copy_to_clipboard)
        QShortcut(QKeySequence("Ctrl+Shift+C"), self).activated.connect(self._on_copy_to_clipboard)
        QShortcut(QKeySequence("Ctrl+V"), self).activated.connect(self._on_paste_clipboard)
        QShortcut(QKeySequence("Ctrl+S"), self).activated.connect(self._on_save_as)

        # --- Tool selection shortcuts (single key, matches docs) ---
        def _select(tool):
            return lambda: self.annotation_toolbar.select_tool(tool)

        QShortcut(QKeySequence("A"), self).activated.connect(_select(AnnotationTool.ARROW))
        QShortcut(QKeySequence("R"), self).activated.connect(_select(AnnotationTool.RECTANGLE))
        QShortcut(QKeySequence("E"), self).activated.connect(_select(AnnotationTool.ELLIPSE))
        QShortcut(QKeySequence("T"), self).activated.connect(_select(AnnotationTool.TEXT))
        QShortcut(QKeySequence("F"), self).activated.connect(_select(AnnotationTool.FREEHAND))
        QShortcut(QKeySequence("C"), self).activated.connect(_select(AnnotationTool.CROP))
        QShortcut(QKeySequence("B"), self).activated.connect(_select(AnnotationTool.BLUR))
        QShortcut(QKeySequence("H"), self).activated.connect(_select(AnnotationTool.HIGHLIGHT))
        QShortcut(QKeySequence("S"), self).activated.connect(_select(AnnotationTool.STEP_NUMBER))
        QShortcut(QKeySequence("X"), self).activated.connect(_select(AnnotationTool.ERASER))
        QShortcut(QKeySequence("V"), self).activated.connect(_select(AnnotationTool.SELECT))


    def _setup_annotation_toolbar(self, parent_layout) -> None:
        from PySide6.QtGui import QColor
        self.annotation_toolbar = AnnotationToolbar(
            initial_color=QColor(self._last_annotation_color),
            parent=self.centralWidget(),
        )

        parent_layout.addWidget(self.annotation_toolbar, alignment=Qt.AlignmentFlag.AlignHCenter)

        self.annotation_toolbar.tool_selected.connect(self._on_tool_selected)
        self.scene.crop_requested.connect(self.apply_crop)
        self.annotation_toolbar.undo_requested.connect(self.scene.undo_stack.undo)
        self.annotation_toolbar.redo_requested.connect(self.scene.undo_stack.redo)
        self.annotation_toolbar.color_changed.connect(self._on_color_changed)
        self.annotation_toolbar.thickness_changed.connect(lambda t: setattr(self.scene, 'current_thickness', t))

        self.annotation_toolbar.cancel_requested.connect(self.close)

    def _setup_zoom_ui(self, grid_layout) -> None:
        from PySide6.QtWidgets import QHBoxLayout, QPushButton

        self.zoom_widget = QWidget(self.centralWidget())
        self.zoom_widget.setStyleSheet("""
            QWidget {
                background-color: rgba(30, 30, 30, 240);
                border-radius: 6px;
                border: 1px solid rgba(255, 255, 255, 40);
            }
            QPushButton {
                background-color: transparent;
                color: white;
                border: none;
                font-size: 16px;
                font-weight: bold;
                padding: 4px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: rgba(255, 255, 255, 30);
            }
            QLabel, QPushButton#zoom_label {
                color: white;
                font-size: 14px;
                font-weight: bold;
                background-color: transparent;
                border: none;
                padding: 0 8px;
            }
        """)

        layout = QHBoxLayout(self.zoom_widget)
        layout.setContentsMargins(4, 4, 4, 4)
        layout.setSpacing(4)

        btn_out = QPushButton("−")
        btn_out.setFixedSize(30, 30)
        btn_out.clicked.connect(lambda: self.view.set_zoom(int(self.view._current_zoom / 1.15)))
        layout.addWidget(btn_out)

        self.zoom_label = QPushButton("100%")
        self.zoom_label.setObjectName("zoom_label")
        # Buttons are centered by default, no need for setAlignment
        self.zoom_label.setFixedWidth(50)
        self.zoom_label.clicked.connect(lambda: self.view.set_zoom(100))
        layout.addWidget(self.zoom_label)

        btn_in = QPushButton("+")
        btn_in.setFixedSize(30, 30)
        btn_in.clicked.connect(lambda: self.view.set_zoom(int(self.view._current_zoom * 1.15)))
        layout.addWidget(btn_in)

        # Wrapper to add padding from the bottom-right edges
        self.zoom_container = QWidget(self.centralWidget())
        container_layout = QHBoxLayout(self.zoom_container)
        container_layout.setContentsMargins(0, 0, 16, 16)
        container_layout.addWidget(self.zoom_widget)

        grid_layout.addWidget(
            self.zoom_container,
            0, 0,
            Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignRight
        )

        # Connect view signal to label update
        self.view.zoom_changed.connect(lambda z: self.zoom_label.setText(f"{z}%"))

    def _on_tool_selected(self, tool: AnnotationTool) -> None:
        # Abort any in-progress draw (e.g. user hit a key shortcut mid-drag).
        # This prevents orphaned semi-drawn items from being left on the scene.
        if self.scene._active_item is not None:
            self.scene.removeItem(self.scene._active_item)
            self.scene._active_item = None

        self.scene.current_tool = tool

        if tool == AnnotationTool.SELECT:
            self.view.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
        else:
            # Clear selection so the dashed item border disappears when the
            # user switches tools via keyboard (mouse clicks do this implicitly
            # by moving focus away from the scene).
            self.scene.clearSelection()
            self.view.setDragMode(QGraphicsView.DragMode.NoDrag)

    def _on_color_changed(self, color) -> None:
        """Update the scene color and emit signal for settings persistence."""
        self.scene.current_color = color
        self.annotation_color_changed.emit(color.name())

    def _apply_theme(self) -> None:
        """Apply a modern dark theme to the Image Editor."""
        self.setStyleSheet("""
            QMainWindow {
                background-color: #2e3440;
            }
            QToolBar {
                background-color: #3b4252;
                border-bottom: 1px solid #434c5e;
            }
            QToolButton {
                color: #eceff4;
                padding: 6px;
                border-radius: 4px;
                font-weight: 500;
            }
            QToolButton:hover {
                background-color: #4c566a;
            }
        """)

    def load_image_from_file(self, path: str) -> None:
        image = QImage(path)
        if image.isNull():
            QMessageBox.critical(self, "Error", f"Failed to load image from\n{path}")
            return
        self.current_image_path = path
        self.load_image_from_data(image)

    def load_image_from_data(self, image: QImage) -> None:
        self.scene.clear()
        pixmap = QPixmap.fromImage(image)
        self.image_item = self.scene.addPixmap(pixmap)

        # Ensure the scene rect exactly matches the image size
        self.scene.setSceneRect(QRectF(pixmap.rect()))

        # Set backdrop crop into scene so blur tool works
        self.scene.backdrop_crop = image

        # Center the image in the view and scale it down if it's too big
        self.view.fitInView(self.scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio)

    def apply_crop(self, crop_rect: QRectF) -> None:
        if self.image_item:
            self.scene.undo_stack.push(CropCommand(self, crop_rect))

    def _on_resize_requested(self) -> None:
        if not self.image_item:
            return

        rect = self.scene.sceneRect()

        from PySide6.QtCore import QBuffer, QByteArray, QIODevice
        ba = QByteArray()
        buf = QBuffer(ba)
        buf.open(QIODevice.OpenModeFlag.WriteOnly)
        self.image_item.pixmap().save(buf, "PNG")
        orig_size = ba.size()

        dialog = ResizeDialog(int(rect.width()), int(rect.height()), orig_size, self)
        if dialog.exec():
            w = dialog.width_spin.value()
            h = dialog.height_spin.value()
            if w != int(rect.width()) or h != int(rect.height()):
                self.scene.undo_stack.push(ResizeCommand(self, w, h))

    def _on_effects_requested(self) -> None:
        if not self.image_item:
            return

        dialog = EffectsDialog(self)
        if dialog.exec():
            config = dialog.get_config()
            self.scene.undo_stack.push(EffectsCommand(self, config))

    def _on_beautify_requested(self) -> None:
        if not self.image_item:
            return

        dialog = BeautifyDialog(self)
        if dialog.exec():
            config = dialog.get_config()
            self.scene.undo_stack.push(
                BeautifyCommand(self, config['bg_style'], config['padding'], config['radius'])
            )

    def _on_combine_requested(self) -> None:
        if not self.image_item:
            return

        file_path, _ = QFileDialog.getOpenFileName(
            self, "Select Image to Combine", "", "Images (*.png *.jpg *.jpeg *.bmp *.webp)"
        )
        if not file_path:
            return

        dialog = CombineDialog(self)
        if dialog.exec():
            config = dialog.get_config()
            try:
                self.scene.undo_stack.push(CombineCommand(self, file_path, config))
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Failed to combine image: {str(e)}")

    def _on_open_file(self) -> None:
        file_path, _ = QFileDialog.getOpenFileName(
            self, "Open Image", "", "Images (*.png *.jpg *.jpeg *.bmp *.webp)"
        )
        if file_path:
            self.load_image_from_file(file_path)

    def _on_paste_clipboard(self) -> None:
        clipboard = QApplication.clipboard()
        image = clipboard.image()
        if not image.isNull():
            self.load_image_from_data(image)
        else:
            QMessageBox.warning(self, "No Image", "The clipboard does not contain a valid image.")

    def _on_save_as(self) -> None:
        if not self.image_item:
            return

        default_path = self.current_image_path or "edited_image.png"
        file_path, _ = QFileDialog.getSaveFileName(
            self, "Save Image", default_path, "Images (*.png *.jpg *.jpeg *.bmp *.webp)"
        )
        if file_path:
            # Render the scene to an image
            rect = self.scene.sceneRect().toRect()
            image = QImage(rect.size(), QImage.Format.Format_ARGB32)
            image.fill(Qt.GlobalColor.transparent)

            painter = QPainter(image)
            self.scene.render(painter)
            painter.end()

            if image.save(file_path):
                self.current_image_path = file_path
                # Visual feedback: Change text to "Saved!" for 1.5 seconds
                original_text = self.action_save.text()
                self.action_save.setText("✅ Saved!")
                QTimer.singleShot(1500, lambda: self.action_save.setText(original_text))
            else:
                QMessageBox.critical(self, "Error", "Failed to save image.")

    def _on_copy_to_clipboard(self) -> None:
        if not self.image_item:
            return

        rect = self.scene.sceneRect().toRect()
        image = QImage(rect.size(), QImage.Format.Format_ARGB32)
        image.fill(Qt.GlobalColor.transparent)

        painter = QPainter(image)
        self.scene.render(painter)
        painter.end()

        QApplication.clipboard().setImage(image)

        # Visual feedback: Change text to "Copied!" for 1.5 seconds
        original_text = self.action_copy.text()
        self.action_copy.setText("✅ Copied!")
        QTimer.singleShot(1500, lambda: self.action_copy.setText(original_text))
