# acmenra_cv/render/style/stroke.py

import logging
from typing import List, Dict, Any


logger = logging.getLogger(__name__)


class Stroke:
    """
    Configuration for shape outlines (strokes).

    If `Style.stroke` is set to `None`, no outline is drawn.
    When present, uses `palette[class_id]` for color combined with these parameters.

    Attributes:
        thickness (int): Line thickness in pixels.
        segment (float): Segment length factor for "firmware-style" rounded corners [0.0, 0.5].
        alpha (float): Stroke opacity [0.0, 1.0].
    """

    def __init__(self, thickness: int = 5, segment: float = 0.2, alpha: float = 1.0): # tested (good)
        """
        Initializes stroke configuration with comprehensive validation.

        Args:
            thickness (int, optional): Line thickness in pixels. Defaults to 5.
            segment (float, optional): Segment factor [0.0, 0.5]. Defaults to 0.2.
            alpha (float, optional): Stroke opacity [0.0, 1.0]. Defaults to 1.0.

        Raises:
            TypeError: If any parameter has incorrect type.
            ValueError: If numeric parameters are outside valid ranges.
        """
        self.thickness = thickness
        self.segment = segment
        self.alpha = alpha

    def __str__(self) -> str: # tested (good)
        """
        Returns a human-readable string representation of the stroke configuration.

        Returns:
            str: Concise summary with thickness, segment length, and opacity.
                 Format: "Stroke(thickness=..., segment=..., alpha=...)"

        Note:
            - Optimized for console logging, operator dashboards, and alert systems
            - Uses fixed-point precision for immediate visual clarity
            - Omits internal validation state for brevity
        """
        return (f"Stroke(thickness={self.thickness}, "
                f"segment={self.segment:.2f}, "
                f"alpha={self.alpha:.2f})")

    def __repr__(self) -> str: # tested (good)
        """
        Returns a detailed string representation suitable for debugging and reproduction.

        Returns:
            str: Exact representation including all canonical parameters.
                 Uses repr-safe formatting (!r) for unambiguous parsing.

        Note:
            - Includes all styling parameters with full floating-point precision
            - Output can theoretically be used to recreate the object in interactive sessions
            - Essential for debugging pipeline style overrides and validation failures
        """
        return (f"Stroke(thickness={self.thickness!r}, "
                f"segment={self.segment!r}, "
                f"alpha={self.alpha!r})")

    @property
    def thickness(self) -> int: # tested (good)
        """
        int: Gets the line thickness for stroke outlines.

        Returns:
            int: Line thickness in pixels. Always non-negative.

        Note:
            - Controls the visual weight of bounding box and polygon outlines.
            - Value 0 renders an invisible line (but maintains geometric validity).
            - Larger values increase anti-aliasing footprint on embedded displays.
        """
        return self._thickness

    @thickness.setter
    def thickness(self, thickness: int) -> None: # tested (good)
        """
        Sets the stroke thickness with validation.

        Args:
            thickness (int): Line thickness in pixels. Must be >= 0.

        Raises:
            TypeError: If thickness is not an integer or is a boolean.
            ValueError: If thickness is negative.

        Note:
            - Thickness is applied uniformly across all stroke elements.
            - Extremely large values may cause performance degradation on Raspberry Pi.
            - Zero thickness is valid for hidden/styled overlays.
        """
        if not isinstance(thickness, int) or isinstance(thickness, bool):
            raise TypeError(f"'thickness' must be '<int>', but got {type(thickness).__name__}")
        if thickness < 0:
            raise ValueError(f"'thickness' must be >= 0, got {thickness}")
        self._thickness = thickness

    @property
    def segment(self) -> float: # tested (good)
        """
        float: Gets the segment length factor for box corners.

        Returns:
            float: Segment factor in range [0.0, 0.5].

        Note:
            - 0.0 = continuous rounded corner (no straight segments).
            - 0.5 = maximum straight segment length (firmware-style break).
            - Controls the visual signature of the bounding box styling.
        """
        return self._segment

    @segment.setter
    def segment(self, segment: float) -> None: # tested (good)
        """
        Sets the segment length factor with range validation.

        Args:
            segment (float): Segment factor to set. Must be in [0.0, 0.5].

        Raises:
            TypeError: If segment is not a float.
            ValueError: If segment is outside [0.0, 0.5].

        Note:
            - Values outside range are rejected to prevent visual artifacts.
            - Intermediate values (e.g., 0.2) provide balanced firmware styling.
            - Changes affect all subsequent stroke rendering operations.
        """
        if not isinstance(segment, float):
            raise TypeError(f"'segment' must be '<float>', but got {type(segment).__name__}")
        if not 0.0 <= segment <= 0.5:
            raise ValueError(f"'segment' must be in [0.0, 0.5], got {segment}")
        self._segment = segment

    @property
    def alpha(self) -> float: # tested (good)
        """
        float: Gets the stroke opacity level.

        Returns:
            float: Opacity value in range [0.0, 1.0].

        Note:
            - 0.0 = fully transparent (invisible stroke).
            - 1.0 = fully opaque.
            - This value is multiplied with global Style.alpha during compositing.
        """
        return self._alpha

    @alpha.setter
    def alpha(self, alpha: float) -> None: # tested (good)
        """
        Sets the stroke opacity with range validation.

        Args:
            alpha (float): Opacity value to set. Must be in [0.0, 1.0].

        Raises:
            TypeError: If alpha is not a float.
            ValueError: If alpha is outside [0.0, 1.0].

        Note:
            - Values > 1.0 or < 0.0 are rejected as invalid.
            - Lower values allow underlying video frame to show through.
            - Combined with fill.alpha for layered transparency effects.
        """
        if not isinstance(alpha, float):
            raise TypeError(f"'alpha' must be '<float>', but got {type(alpha).__name__}")
        if not 0.0 <= alpha <= 1.0:
            raise ValueError(f"'alpha' must be in [0.0, 1.0], got {alpha}")
        self._alpha = alpha

    def to_dict(self) -> Dict[str, Any]: # tested (good)
        """
        Serializes the Stroke configuration to a JSON-compatible dictionary.

        Returns:
            Dict[str, Any]: Dictionary containing all stroke rendering parameters.

        Note:
            - Ready for direct DB insertion or API transmission.
            - All values are primitive types (int, float) requiring no complex conversion.
            - Maintains perfect round-trip fidelity with from_dict().
        """
        return {
            "thickness": self.thickness,
            "segment": self.segment,
            "alpha": self.alpha
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'Stroke': # tested (good)
        """
        Reconstructs a Stroke instance from a serialized dictionary.

        Args:
            data: Dictionary with stroke configuration. Required: thickness, segment, alpha.

        Returns:
            Stroke: Fully initialized and validated instance.

        Raises:
            KeyError: If any required field is missing from input dictionary.
            TypeError: If any parameter has incorrect type.
            ValueError: If any parameter is outside valid range.

        Note:
            - Delegates validation to property setters.
            - Handles missing keys safely with None defaults (except required fields).
            - Maintains perfect round-trip fidelity with to_dict().
            - Explicit type conversion ensures compatibility with JSON deserialization.
        """
        required_fields = ['thickness', 'segment', 'alpha']
        for field in required_fields:
            if field not in data:
                raise KeyError(f"Required field '{field}' is missing from input dictionary: {data}")

        return cls(
            thickness=int(data.get("thickness")),
            segment=float(data.get("segment")),
            alpha=float(data.get("alpha"))
        )

