# Copyright (c) 2026 동일비전(Dongil Vision Korea). All Rights Reserved.
import threading
from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING

if TYPE_CHECKING:
    from .device_property import DeviceProperty

class DeviceTaskMixin:
    """Infrastructure injected by @task for device management.
    
    Provides cache, timers, resync, and session management.
    """
    
    # Internal infrastructure
    _cache: Dict[str, Any]
    _resync_guard: bool
    _delay_timers: Dict[str, threading.Timer]
    _resync_open_fn: Optional[str]
    _resync_close_fn: Optional[str]

    def _device_init(self):
        """Initialize device infrastructure. Called by @task."""
        if not hasattr(self, "_cache"):
            self._cache = {}
            self._resync_guard = False
            self._delay_timers = {}
            # _resync_open_fn and _resync_close_fn should be set by @task
            
    def snapshot(self) -> Dict[str, Any]:
        """Return a copy of the current logic cache."""
        return self._cache.copy()

    def _resync_open(self):
        """Execute session open callback."""
        if hasattr(self, "_resync_open_fn") and self._resync_open_fn:
            fn = getattr(self, self._resync_open_fn, None)
            if fn and callable(fn):
                fn()

    def _resync_close(self):
        """Execute session close callback."""
        if hasattr(self, "_resync_close_fn") and self._resync_close_fn:
            fn = getattr(self, self._resync_close_fn, None)
            if fn and callable(fn):
                fn()

    def _flush_delayed(self, name: str):
        """Timer callback for debounced HW writing."""
        # This is called by threading.Timer, so it's in a separate thread.
        # Ensure thread safety for _cache if needed, but ALASKA mostly uses it for local task.
        if name in self._delay_timers:
            del self._delay_timers[name]
        
        # Get descriptor to execute HW write
        prop = getattr(type(self), name, None)
        if prop and hasattr(prop, "_apply_hw"):
            val = self._cache.get(name)
            prop._apply_hw(self, val)
            # Re-check resync after flush (might have enabled other conditions)
            self._resync_dependents()

    def _resync_dependents(self):
        """Resync all dependent properties whose opstate is met.
        
        Includes Force Flush of pending timers.
        """
        if getattr(self, "_resync_guard", False):
            return
        
        self._resync_guard = True
        try:
            # 0. Force Flush: Apply all pending timers now
            pending_names = list(self._delay_timers.keys())
            for name in pending_names:
                timer = self._delay_timers.get(name)
                if timer:
                    timer.cancel()
                self._flush_delayed(name)

            # 1. Collect targets (opstate met)
            from .device_property import DeviceProperty
            cls = type(self)
            
            # Use cached property list for efficiency
            props = self._get_all_device_properties()
            
            targets = []
            for name, prop in props:
                # Target for resync must have a setter and a condition (opstate)
                if prop.setter_ref and prop.opstate and prop.opstate(self):
                    targets.append((name, prop))

            if not targets:
                return

            # 2. Batch Execution with Session
            self._resync_open()
            try:
                for name, prop in targets:
                    val = self._cache.get(name, prop.default)
                    try:
                        # Call setter directly (no signal emission for resync)
                        setter = prop._get_callback(self, prop.setter_ref)
                        if setter:
                            setter(val)
                    except Exception as e:
                        print(f"[Resync] Error applying {name}: {e}")
            finally:
                self._resync_close()
                
        finally:
            self._resync_guard = False

    def read_all(self):
        """Read all properties that have a getter defined."""
        from .device_property import DeviceProperty
        props = self._get_all_device_properties()
        
        targets = []
        for name, prop in props:
            if prop.getter_ref:
                targets.append((name, prop))
                
        if not targets:
            return

        self._resync_open()
        try:
            for name, prop in targets:
                getter = prop._get_callback(self, prop.getter_ref)
                if getter:
                    try:
                        val = getter()
                        self._cache[name] = val
                    except Exception as e:
                        print(f"[ReadAll] Error reading {name}: {e}")
        finally:
            self._resync_close()

    def _get_all_device_properties(self) -> List[Tuple[str, "DeviceProperty"]]:
        """Collect all DeviceProperty descriptors from the class hierarchy."""
        cls = type(self)
        if hasattr(cls, "_dp_cache"):
            return cls._dp_cache
            
        from .device_property import DeviceProperty
        props = []
        for name in dir(cls):
            try:
                # getattr(cls, name) might trigger descriptors. 
                # We need the descriptor object itself.
                attr = getattr(cls, name)
                if isinstance(attr, DeviceProperty):
                    props.append((name, attr))
            except:
                continue
                
        # Sort by order, then by name
        props.sort(key=lambda x: (x[1].order, x[0]))
        cls._dp_cache = props
        return props
