"""
Decorator system for pyPhotoAlbum ribbon UI
"""

import copy
from functools import wraps
from typing import Any, Optional, Callable


class RibbonAction:
    """
    Decorator to mark methods as ribbon actions.

    This decorator stores metadata about UI actions that should appear in the ribbon.
    The metadata is used to auto-generate the ribbon configuration.

    Example:
        @RibbonAction(
            label="New",
            tooltip="Create a new project",
            tab="Home",
            group="File",
            icon="new.png",
            shortcut="Ctrl+N"
        )
        def new_project(self):
            ...
    """

    def __init__(
        self,
        label: str,
        tooltip: str,
        tab: str,
        group: str,
        icon: Optional[str] = None,
        shortcut: Optional[str] = None,
        requires_page: bool = False,
        requires_selection: bool = False,
        min_selection: int = 0,
    ):
        """
        Initialize the ribbon action decorator.

        Args:
            label: Button label text
            tooltip: Tooltip text shown on hover
            tab: Ribbon tab name (e.g., "Home", "Insert", "Layout")
            group: Group name within the tab (e.g., "File", "Edit")
            icon: Optional icon filename or path
            shortcut: Optional keyboard shortcut (e.g., "Ctrl+N", "Ctrl+Shift+S")
            requires_page: Whether this action requires an active page
            requires_selection: Whether this action requires selected elements
            min_selection: Minimum number of selected elements required
        """
        self.label = label
        self.tooltip = tooltip
        self.tab = tab
        self.group = group
        self.icon = icon
        self.shortcut = shortcut
        self.requires_page = requires_page
        self.requires_selection = requires_selection
        self.min_selection = min_selection

    def __call__(self, func: Callable) -> Callable:
        """
        Decorate the function with ribbon action metadata.

        Args:
            func: The function to decorate

        Returns:
            The decorated function with metadata attached
        """

        @wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            return func(*args, **kwargs)

        # Store metadata on wrapper function
        wrapper._ribbon_action = {  # type: ignore[attr-defined]
            "label": self.label,
            "tooltip": self.tooltip,
            "tab": self.tab,
            "group": self.group,
            "icon": self.icon,
            "shortcut": self.shortcut,
            "action": func.__name__,
            "requires_page": self.requires_page,
            "requires_selection": self.requires_selection,
            "min_selection": self.min_selection,
        }

        return wrapper


def ribbon_action(
    label: str,
    tooltip: str,
    tab: str,
    group: str,
    icon: Optional[str] = None,
    shortcut: Optional[str] = None,
    requires_page: bool = False,
    requires_selection: bool = False,
    min_selection: int = 0,
) -> Callable:
    """
    Convenience function for the RibbonAction decorator.

    This provides a lowercase function-style interface to the decorator.

    Args:
        label: Button label text
        tooltip: Tooltip text shown on hover
        tab: Ribbon tab name
        group: Group name within the tab
        icon: Optional icon filename or path
        shortcut: Optional keyboard shortcut
        requires_page: Whether this action requires an active page
        requires_selection: Whether this action requires selected elements
        min_selection: Minimum number of selected elements required

    Returns:
        RibbonAction decorator instance
    """
    return RibbonAction(
        label=label,
        tooltip=tooltip,
        tab=tab,
        group=group,
        icon=icon,
        shortcut=shortcut,
        requires_page=requires_page,
        requires_selection=requires_selection,
        min_selection=min_selection,
    )


class NumericalInput:
    """
    Decorator to mark methods that require numerical width/height inputs.

    This decorator stores metadata about numerical input fields that should
    be presented in dialogs for methods that work with page dimensions.

    Example:
        @numerical_input(
            fields=[
                ('width', 'Width', 'mm', 10, 1000),
                ('height', 'Height', 'mm', 10, 1000)
            ]
        )
        def set_page_size(self, width, height):
            ...
    """

    def __init__(self, fields: list):
        """
        Initialize the numerical input decorator.

        Args:
            fields: List of tuples, each containing:
                    (param_name, label, unit, min_value, max_value)
        """
        self.fields = fields

    def __call__(self, func: Callable) -> Callable:
        """
        Decorate the function with numerical input metadata.

        Args:
            func: The function to decorate

        Returns:
            The decorated function with metadata attached
        """

        @wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            return func(*args, **kwargs)

        # Store metadata on wrapper function
        wrapper._numerical_input = {"fields": self.fields}  # type: ignore[attr-defined]

        return wrapper


def numerical_input(fields: list) -> Callable:
    """
    Convenience function for the NumericalInput decorator.

    This provides a lowercase function-style interface to the decorator.

    Args:
        fields: List of tuples, each containing:
                (param_name, label, unit, min_value, max_value)

    Returns:
        NumericalInput decorator instance
    """
    return NumericalInput(fields=fields)


class UndoableOperation:
    """
    Decorator to automatically create undo/redo commands for operations.

    This decorator captures state before and after an operation, then creates
    a StateChangeCommand for undo/redo functionality.

    Example:
        @undoable_operation(capture='page_elements')
        def apply_template(self):
            # Just implement the operation
            self.template_manager.apply_template(...)
            # Decorator handles undo/redo automatically
    """

    def __init__(self, capture: str = "page_elements", description: Optional[str] = None):
        """
        Initialize the undoable operation decorator.

        Args:
            capture: What to capture for undo/redo:
                - 'page_elements': Capture elements of current page
                - 'custom': Operation provides its own capture logic
            description: Human-readable description (defaults to function name)
        """
        self.capture = capture
        self.description = description

    def __call__(self, func: Callable) -> Callable:
        """
        Decorate the function with automatic undo/redo.

        Args:
            func: The function to decorate

        Returns:
            The decorated function
        """

        @wraps(func)
        def wrapper(self_instance, *args, **kwargs):
            # Get description
            description = self.description or func.__name__.replace("_", " ").title()

            # Capture before state
            before_state = self._capture_state(self_instance, self.capture)

            # Execute the operation
            result = func(self_instance, *args, **kwargs)

            # Capture after state
            after_state = self._capture_state(self_instance, self.capture)

            # Create restore function
            def restore_state(state):
                self._restore_state(self_instance, self.capture, state)
                # Update view after restoring
                if hasattr(self_instance, "update_view"):
                    self_instance.update_view()

            # Create and execute command
            from pyPhotoAlbum.commands import StateChangeCommand

            cmd = StateChangeCommand(description, restore_state, before_state, after_state)

            if hasattr(self_instance, "project") and hasattr(self_instance.project, "history"):
                self_instance.project.history.execute(cmd)
                print(f"Undoable operation '{description}' executed")

            return result

        return wrapper

    def _capture_state(self, instance, capture_type: str):
        """Capture current state based on capture type"""
        if capture_type == "page_elements":
            # Capture elements from current page
            current_page = instance.get_current_page() if hasattr(instance, "get_current_page") else None
            if current_page:
                # Deep copy elements
                return [copy.deepcopy(elem.serialize()) for elem in current_page.layout.elements]
            return []

        return None

    def _restore_state(self, instance, capture_type: str, state):
        """Restore state based on capture type"""
        if capture_type == "page_elements":
            # Restore elements to current page
            current_page = instance.get_current_page() if hasattr(instance, "get_current_page") else None
            if current_page and state is not None:
                # Clear existing elements
                current_page.layout.elements.clear()

                # Restore elements from serialized state
                from pyPhotoAlbum.models import BaseLayoutElement, ImageData, PlaceholderData, TextBoxData

                for elem_data in state:
                    elem_type = elem_data.get("type")
                    elem: BaseLayoutElement
                    if elem_type == "image":
                        elem = ImageData()
                    elif elem_type == "placeholder":
                        elem = PlaceholderData()
                    elif elem_type == "textbox":
                        elem = TextBoxData()
                    else:
                        continue

                    elem.deserialize(elem_data)
                    current_page.layout.add_element(elem)


def undoable_operation(capture: str = "page_elements", description: Optional[str] = None) -> Callable:
    """
    Convenience function for the UndoableOperation decorator.

    This provides a lowercase function-style interface to the decorator.

    Args:
        capture: What to capture for undo/redo
        description: Human-readable description of the operation

    Returns:
        UndoableOperation decorator instance
    """
    return UndoableOperation(capture=capture, description=description)


class DialogAction:
    """
    Decorator to mark methods that should open a dialog.

    This decorator automatically handles dialog creation and result processing,
    separating UI presentation from business logic.

    Example:
        @dialog_action(dialog_class=PageSetupDialog)
        def page_setup(self, values):
            # Just implement the business logic
            # Dialog presentation is handled automatically
            self.apply_page_setup(values)
    """

    def __init__(self, dialog_class: type, requires_pages: bool = True):
        """
        Initialize the dialog action decorator.

        Args:
            dialog_class: The dialog class to instantiate
            requires_pages: Whether this action requires pages to exist
        """
        self.dialog_class = dialog_class
        self.requires_pages = requires_pages

    def __call__(self, func: Callable) -> Callable:
        """
        Decorate the function with automatic dialog handling.

        Args:
            func: The function to decorate (receives dialog values)

        Returns:
            The decorated function
        """

        @wraps(func)
        def wrapper(self_instance, *args, **kwargs):
            # Check preconditions
            if self.requires_pages and not self_instance.project.pages:
                return

            # Get initial page index if available
            initial_page_index = 0
            if hasattr(self_instance, "_get_most_visible_page_index"):
                initial_page_index = self_instance._get_most_visible_page_index()

            # Create and show dialog
            from pyPhotoAlbum.mixins.dialog_mixin import DialogMixin

            # Create dialog
            dialog = self.dialog_class(
                parent=self_instance, project=self_instance.project, initial_page_index=initial_page_index, **kwargs
            )

            # Show dialog and get result
            from PyQt6.QtWidgets import QDialog

            if dialog.exec() == QDialog.DialogCode.Accepted:
                # Get values from dialog
                if hasattr(dialog, "get_values"):
                    values = dialog.get_values()
                    # Call the decorated function with values
                    return func(self_instance, values, *args, **kwargs)
                else:
                    return func(self_instance, *args, **kwargs)

            return None

        return wrapper


def dialog_action(dialog_class: type, requires_pages: bool = True) -> Callable:
    """
    Convenience function for the DialogAction decorator.

    This provides a lowercase function-style interface to the decorator.

    Args:
        dialog_class: The dialog class to instantiate
        requires_pages: Whether this action requires pages to exist

    Returns:
        DialogAction decorator instance
    """
    return DialogAction(dialog_class=dialog_class, requires_pages=requires_pages)
