from typing import Dict

from PySide6.QtCore import QObject, QPoint, QSize, QPropertyAnimation, QParallelAnimationGroup, QAbstractAnimation, QTimer
from PySide6.QtWidgets import QWidget
from PySide6.QtGui import QColor

from .anim_effect import AnimEffect
from .motion_variant import parse_sx
from .motion_variant import compile_variant_timeline
from .anim_easing_curve_type import chooseEasing

class AnimationController(QObject):
    def __init__(self, widget: QWidget, variants: Dict, parent=None):
        super().__init__(parent)
        
        self.widget = widget
        self.style = parse_sx(getattr(widget, "_sx") or {})
        self.variants = variants or {}
        self.anim_group = None

        # Lưu trạng thái gốc (sẽ được set trong apply_initial)
        self.base_pos = None      # QPoint
        self.base_size = None     # QSize
        
        # scale state
        self._scaleX = 1.0
        self._scaleY = 1.0
        
        self.effect = AnimEffect(widget=self.widget, style=self.style, src=getattr(self.widget, "src"), variants=variants)
        
        self.widget.setGraphicsEffect(self.effect)

        self._played = False

    def apply_initial(self, variants=None):
        """Should be called after layout assigned positions (i.e. after show)."""
        initial = self.variants.get("initial", {}) or {}
        
        self.base_pos = QPoint(self.widget.pos())
        # self.base_pos = QPoint(0, 0)
        
        self.base_size = self.widget.size()

        # === Rotation X/Y ===
        if "rotationX" in initial:
            self._rotationX = float(initial.get("rotationX", 0.0))
            self.effect.setRotationX(self._rotationX)
                        
        if "rotationY" in initial:
            self._rotationY = float(initial.get("rotationY", 0.0))
            self.effect.setRotationY(self._rotationY)
            
        if "rotation" in initial:
            self._angle = float(initial.get("rotation", 0.0))
            self.effect.setAngle(self._angle)


        # apply initial Y offset if provided (move widget visually from base)
        if "y" in initial:
            try:
                dy = int(initial["y"])
            except Exception:
                dy = 0
            # move widget to base_pos + dy
            self.widget.move(self.base_pos.x(), self.base_pos.y() + dy)
            # print("Applying initial y offset:", dy)
            
        # apply initial Y offset if provided (move widget visually from base)
        if "x" in initial:
            try:
                dx = int(initial["x"])
            except Exception:
                dx = 0
            # move widget to base_pos + dx
            # print("Applying initial x offset:", dx)
            self.widget.move(self.base_pos.x() + dx, self.base_pos.y())


        # === Scale (giả lập bằng resize) ===
        if initial.get("scale") is not None  or initial.get("scaleX") is not None or initial.get("scaleY") is not None:
            scale_x = float(initial.get("scaleX", initial.get("scale", 1.0)))
            scale_y = float(initial.get("scaleY", initial.get("scale", 1.0)))

            if abs(scale_x - 1.0) > 0.01 or abs(scale_y - 1.0) > 0.01:
                new_width = max(1, int(self.base_size.width() * scale_x))
                new_height = max(1, int(self.base_size.height() * scale_y))
                self.widget.resize(new_width, new_height)
                
        # === Scale X/Y ===
        if "scaleX" in initial:
            self._scaleX = float(initial.get("scaleX", 1.0))
            self.effect.setScaleX(self._scaleX)
                        
        if "scaleY" in initial:
            self._scaleY = float(initial.get("scaleY", 1.0))
            self.effect.setScaleY(self._scaleY)
            
        if "scale" in initial:
            self.effect.setScaleX(float(initial.get("scale", 1.0)))
            self.effect.setScaleY(float(initial.get("scale", 1.0)))

        if initial.get("opacity") is not None:
            self._opacity = float(initial.get("opacity", 1.0))
            self.effect.setOpacity(self._opacity)

    def play(self, delay_ms: int = 0):
        """Start animate from current widget pos -> base_pos + animate.y (usually base_pos)"""
        if self._played:
            return
        
        self._played = True

        initial = self.variants.get("initial", {}) or {}
        anim_def = self.variants.get("animate", {}) or {}
        
        if not anim_def:
            return
        
        # Ensure base_pos set (should be set in apply_initial). If not, compute now.
        if self.base_pos is None:
            self.base_pos = QPoint(self.widget.pos())

        transition = anim_def.get("transition", {}) or {}
        loop_bg_pan = anim_def.get("loop", True)
        duration_sec = transition.get("duration", 0.6)
        # allow duration either seconds (float) or ms (int)
        try:
            duration_ms = int(duration_sec * 1000) if duration_sec < 1000 else int(duration_sec)
        except Exception:
            duration_ms = 600

        ease_data = transition.get("ease", None)
        
        if isinstance(ease_data, list):
            if self.anim_group is not None:
                self.anim_group.stop()
                self.anim_group.deleteLater()
                self.anim_group = None
                
            if "backgroundGradient" in anim_def: self.effect.setGradientColors(anim_def["backgroundGradient"].get("colors") or [
                QColor("#ff4545"),
                QColor("#00ff99"),
                QColor("#006aff"),
                QColor("#ff0095"),
                QColor("#ff4545"),
            ])
            
            # Map values từ compiler -> motion state
            def apply(values: Dict[str, float]):
                if "x" in values: self.effect.setX(values["x"])
                if "y" in values: self.effect.setY(values["y"])
                if "borderWidth" in values: self.effect.setBorderWidth(values["borderWidth"])
                if "borderRadius" in values: self.effect.setBorderRadius(values["borderRadius"])
                if "gradientStartAngle" in values: self.effect.setGradientStartAngle(values["gradientStartAngle"])

                
                if "opacity" in values: self.effect.setOpacity(values["opacity"])

                # scale shorthand
                if "scale" in values:
                    self.effect.setScaleX(values["scale"])
                    self.effect.setScaleY(values["scale"])
                if "scaleX" in values: self.effect.setScaleX(values["scaleX"])
                if "scaleY" in values: self.effect.setScaleY(values["scaleY"])

                # rotate keys (2D)
                if "rotate" in values:
                    self.effect.setRotation(values["rotate"])
                elif "rotateZ" in values:
                    self.effect.setRotation(values["rotateZ"])
                elif "rotateX" in values:
                    # 3D rotateX -> map sang 2D rotation demo
                    self.effect.setRotation(values["rotateX"])
                    
            self.anim_group = compile_variant_timeline(self.variants, "animate", apply, parent=self.widget)
            self.anim_group.start()
        else:

            easing = chooseEasing(ease_data)

            # Build animations
            animations = []

            # position (Y)
            if "y" in anim_def:
                # start is current widget position (probably base_pos + initial_y)
                start_pos = QPoint(self.widget.pos())
                # end is base_pos + animate['y'] (usually animate['y'] == 0)
                try:
                    anim_y = int(anim_def.get("y", 0))
                except Exception:
                    anim_y = 0
                end_pos = QPoint(self.base_pos.x(), self.base_pos.y() + anim_y)

                pos_anim = QPropertyAnimation(self.widget, b"pos")
                pos_anim.setStartValue(start_pos)
                pos_anim.setEndValue(end_pos)
                pos_anim.setDuration(duration_ms)
                pos_anim.setEasingCurve(easing)
                animations.append(pos_anim)
                
            # position (X)
            if "x" in anim_def:
                # start is current widget position (probably base_pos + initial_x)
                start_pos = QPoint(self.widget.pos())
                # end is base_pos + animate['x'] (usually animate['x'] == 0)
                try:
                    anim_x = int(anim_def.get("x", 0))
                except Exception:
                    anim_x = 0
                end_pos = QPoint(self.base_pos.x() + anim_x, self.base_pos.y())

                pos_anim = QPropertyAnimation(self.widget, b"pos")
                pos_anim.setStartValue(start_pos)
                pos_anim.setEndValue(end_pos)
                pos_anim.setDuration(duration_ms)
                pos_anim.setEasingCurve(easing)
                animations.append(pos_anim)

            # === Scale (giả lập bằng resize + move để giữ tâm) ===
            if anim_def.get("scaleX") is not None or anim_def.get("scaleY") is not None:
                target_scale_x = float(anim_def.get("scaleX", anim_def.get("scale", 1.0)))
                target_scale_y = float(anim_def.get("scaleY", anim_def.get("scale", 1.0)))

                current_w = self.widget.width()
                current_h = self.widget.height()
                target_w = max(1, int(self.base_size.width() * target_scale_x))
                target_h = max(1, int(self.base_size.height() * target_scale_y))

                if current_w != target_w or current_h != target_h:
                    # Animation resize
                    size_anim = QPropertyAnimation(self.widget, b"size")
                    # size_anim.setStartValue(QSize(0,0))
                    size_anim.setStartValue(self.widget.size())
                    size_anim.setEndValue(QSize(target_w, target_h))
                    size_anim.setDuration(duration_ms)
                    size_anim.setEasingCurve(easing)
                    animations.append(size_anim)

                    # # Giữ tâm khi scale → tính toán vị trí mới
                    # if anim_def.get("x") is None and anim_def.get("y") is None:
                    #     print("Adding center move for scale")
                    #     def update_center(value):
                    #         progress = size_anim.currentValue().width() / target_w if target_w > 0 else 1.0
                    #         new_w = int(self.base_size.width() * target_scale_x * progress)
                    #         new_h = int(self.base_size.height() * target_scale_y * progress)
                    #         center_x = self.base_pos.x() + self.base_size.width() // 2
                    #         center_y = self.base_pos.y() + self.base_size.height() // 2
                    #         new_x = center_x - new_w // 2
                    #         new_y = center_y - new_h // 2
                    #         self.widget.move(new_x, new_y)

                    #     size_anim.valueChanged.connect(update_center)

            # scale
            if "scale" in anim_def and isinstance(anim_def.get("scale"), float):
                anim_sclX = QPropertyAnimation(self.effect, b"scaleX")
                anim_sclX.setStartValue(float(initial.get("scale", 1.0)))
                anim_sclX.setEndValue(float(anim_def.get("scale", 1.0)))
                anim_sclX.setDuration(duration_ms)
                anim_sclX.setEasingCurve(easing)
                animations.append(anim_sclX)
                
                anim_sclY = QPropertyAnimation(self.effect, b"scaleY")
                anim_sclY.setStartValue(float(initial.get("scale", 1.0)))
                anim_sclY.setEndValue(float(anim_def.get("scale", 1.0)))
                anim_sclY.setDuration(duration_ms)
                anim_sclY.setEasingCurve(easing)
                animations.append(anim_sclY)
            elif "scale" in anim_def and isinstance(anim_def.get("scale"), list):
                anim_scl = QPropertyAnimation(self.effect, b"scale")
                anim_scl.setStartValue(float(anim_def["scale"][0]))
                anim_scl.setEndValue(float(anim_def["scale"][1]))
                anim_scl.setDuration(duration_ms)
                anim_scl.setEasingCurve(easing)
                animations.append(anim_scl)
                
                
            if "scaleX" in anim_def:
                anim_sclX = QPropertyAnimation(self.effect, b"scaleX")
                anim_sclX.setStartValue(float(initial.get("scaleX", self._scaleX)))
                anim_sclX.setEndValue(float(anim_def.get("scaleX", self._scaleX)))
                anim_sclX.setDuration(duration_ms)
                anim_sclX.setEasingCurve(easing)
                animations.append(anim_sclX)
                
            if "scaleY" in anim_def:
                anim_sclY = QPropertyAnimation(self.effect, b"scaleY")
                anim_sclY.setStartValue(float(initial.get("scaleY", self._scaleY)))
                anim_sclY.setEndValue(float(anim_def.get("scaleY", self._scaleY)))
                anim_sclY.setDuration(duration_ms)
                anim_sclY.setEasingCurve(easing)
                animations.append(anim_sclY)
                

            # rotationX
            if "rotationX" in anim_def:
                anim_rotX = QPropertyAnimation(self.effect, b"rotationX")
                anim_rotX.setStartValue(float(initial.get("rotationX", self._rotationX)))
                anim_rotX.setEndValue(float(anim_def.get("rotationX", self._rotationX)))
                anim_rotX.setDuration(duration_ms)
                anim_rotX.setEasingCurve(easing)
                animations.append(anim_rotX)
                

            # rotationY
            if "rotationY" in anim_def:
                anim_rotY = QPropertyAnimation(self.effect, b"rotationY")
                anim_rotY.setStartValue(float(initial.get("rotationY", self._rotationY)))
                anim_rotY.setEndValue(float(anim_def.get("rotationY", self._rotationY)))
                anim_rotY.setDuration(duration_ms)
                anim_rotY.setEasingCurve(easing)
                animations.append(anim_rotY)
                
            # rotation
            if "rotation" in anim_def:
                anim_rotate = QPropertyAnimation(self.effect, b"angle")
                anim_rotate.setStartValue(float(initial.get("rotation", 0.0)))
                anim_rotate.setEndValue(float(anim_def.get("rotation", 0.0)))
                anim_rotate.setDuration(duration_ms)
                anim_rotate.setEasingCurve(easing)
                animations.append(anim_rotate)
                
            # opacity
            if "opacity" in anim_def:
                op_anim = QPropertyAnimation(self.effect, b"opacity")
                op_anim.setStartValue(float(initial.get("opacity", 1.0)))
                op_anim.setEndValue(float(anim_def.get("opacity", 1.0)))
                op_anim.setDuration(duration_ms)
                op_anim.setEasingCurve(easing)
                animations.append(op_anim)
                
            # opacity
            if "gradient" in anim_def:
                gradient_anim = QPropertyAnimation(self.effect, b"bgProgress", parent=self.effect)
                gradient_anim.setStartValue(0.0)
                gradient_anim.setEndValue(1.0)
                gradient_anim.setDuration(duration_ms)
                if loop_bg_pan:
                    gradient_anim.setLoopCount(-1)
                gradient_anim.setEasingCurve(easing)
                animations.append(gradient_anim)
                
            if not animations:
                return

            # group them and play after delay
            self.anim_group = QParallelAnimationGroup(self.widget)
            for a in animations:
                self.anim_group.addAnimation(a)

            if delay_ms <= 0:
                self.anim_group.start(QAbstractAnimation.DeleteWhenStopped)
            else:
                # self.anim_group.start()
                QTimer.singleShot(delay_ms, lambda: self.anim_group.start(QAbstractAnimation.DeleteWhenStopped))
                # QTimer.singleShot(delay_ms, lambda: self.anim_group.start())

    def onDestroy(self):
        print('desktroyyyyyyyyyyyyyyyyyyyyyyyyyyy')
