"""Application state management with signal-based reactive updates."""

from typing import Any, Dict, Optional, Callable
from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot
from dataclasses import dataclass, field
from functools import wraps
import logging

logger = logging.getLogger(__name__)


class AppState(QObject):
    """
    Centralized application state with Qt signal emission for reactive UI updates.
    
    Properties can be observed by connecting to state_changed signal.
    Provides fine-grained control over which properties trigger UI updates.
    """
    
    # Global state changed signal: (property_name: str, new_value: Any, old_value: Any)
    state_changed = pyqtSignal(str, object, object)
    
    def __init__(self):
        super().__init__()
        self._state: Dict[str, Any] = {}
        self._subscriptions: Dict[str, list[Callable]] = {}
    
    def set(self, key: str, value: Any) -> bool:
        """
        Set a state value and emit signal if changed.
        
        Returns True if value changed, False otherwise.
        """
        old_value = self._state.get(key)
        if old_value == value:
            return False
        
        self._state[key] = value
        self.state_changed.emit(key, value, old_value)
        
        # Call registered callbacks
        if key in self._subscriptions:
            for callback in self._subscriptions[key]:
                try:
                    callback(value, old_value)
                except Exception as e:
                    logger.error(f"Error in state subscription callback for {key}: {e}")
        
        return True
    
    def get(self, key: str, default: Any = None) -> Any:
        """Get state value with optional default."""
        return self._state.get(key, default)
    
    def subscribe(self, key: str, callback: Callable[[Any, Any], None]) -> Callable:
        """
        Subscribe to changes for a specific key.
        Callback receives (new_value, old_value).
        Returns unsubscribe function.
        """
        if key not in self._subscriptions:
            self._subscriptions[key] = []
        
        self._subscriptions[key].append(callback)
        
        # Return unsubscribe function
        def unsubscribe():
            if callback in self._subscriptions.get(key, []):
                self._subscriptions[key].remove(callback)
        
        return unsubscribe
    
    def update(self, updates: Dict[str, Any]) -> int:
        """Update multiple values at once. Returns count of changed values."""
        changed = sum(1 for k, v in updates.items() if self.set(k, v))
        return changed
    
    def reset(self):
        """Clear all state."""
        self._state.clear()
    
    def __getitem__(self, key: str) -> Any:
        """Dict-like access."""
        return self._state[key]
    
    def __setitem__(self, key: str, value: Any):
        """Dict-like access."""
        self.set(key, value)
    
    def __contains__(self, key: str) -> bool:
        """Check if key exists."""
        return key in self._state


@dataclass
class ChannelEditorState:
    """State for visual channel editor."""
    
    # Current OME-Zarr file being edited
    current_zarr_path: Optional[str] = None
    
    # Current indices for navigation
    t_index: int = 0
    z_index: int = 0
    c_index: int = 0
    
    # FOV (Field of View) parameters
    fov_center_x: int = 0
    fov_center_y: int = 0
    fov_size: int = 256  # Width/height in pixels
    
    # Pan offset in data space (pixels to shift from center)
    pan_offset_x: int = 0  # Positive = pan right (view shifts left)
    pan_offset_y: int = 0  # Positive = pan down (view shifts up)
    
    # 3D Rotation parameters (camera-centric, 0-180 degrees)
    rotation_roll: float = 0.0  # Spin around camera normal (Z) - in-plane rotation
    rotation_tilt: float = 0.0  # Tilt around camera right (X) - depth exploration
    
    # Plane orientation: 'XY', 'XZ', 'YZ'
    plane_orientation: str = 'XY'
    
    # Pyramid zoom level (0 = lowest res, higher = higher res)
    zoom_level: int = 0
    
    # Channel editing state
    editing_colors: Dict[int, str] = field(default_factory=dict)  # channel_idx -> hex color
    editing_labels: Dict[int, str] = field(default_factory=dict)  # channel_idx -> label
    editing_intensity_limits: Dict[int, tuple[float, float]] = field(default_factory=dict)  # channel_idx -> (min, max)
    
    # UI state
    changes_locked: bool = False
    selected_channels: list[int] = field(default_factory=list)
    intensity_range_mode: str = "Data Values"  # or "Data Type Range"
    show_histogram: bool = True
    
    # File browser state
    browse_path: str = "/Users"
    browser_mode: str = 'hidden'  # 'hidden', 'breadcrumb', or 'active'
    
    # Loading state
    is_loading_pyramid: bool = False
    is_loading_plane: bool = False


@dataclass
class MetadataEditorState:
    """State for pixel metadata editor."""
    
    current_zarr_path: Optional[str] = None
    is_loading: bool = False
    pending_changes: Dict[str, Any] = field(default_factory=dict)


@dataclass
class MainWindowState:
    """State for main window."""
    
    current_view: str = "Channel Editor"  # "Channel Editor" or "Metadata Editor"
    is_loading: bool = False
