"""Form value parsing utilities.

Provides reusable parsing methods for common form input types with
validation and user-friendly error messages.

Usage:
    from cli.infrastructure.forms.parsers import FormParsers

    class MyWidget(Widget, FormParsers):
        def _parse_values(self):
            temperature = self.parse_float(
                self.query_one("#temperature", Input).value,
                "Temperature",
                min_val=0.0,
                max_val=2.0
            )
            user_id = self.parse_uuid(self.query_one("#user_id", Input).value, "User ID")
            config = self.parse_json(self.query_one("#config", TextArea).text, "Config")
"""

import json
from typing import Any, cast
from uuid import UUID


class FormParsers:
    """Mixin providing common value parsing methods for form apps.

    All parsing methods follow the same pattern:
    - Return None for empty strings
    - Raise ValueError with user-friendly message on invalid input
    - Support optional bounds/constraints
    """

    def parse_uuid(self, value: str, field_name: str) -> UUID | None:
        """Parse UUID string or return None for empty input.

        Args:
            value: String value to parse (may be empty)
            field_name: Human-readable field name for error messages

        Returns:
            Parsed UUID or None if input is empty

        Raises:
            ValueError: If value is non-empty but not a valid UUID
        """
        if not value:
            return None
        try:
            return UUID(value)
        except ValueError:
            raise ValueError(f"{field_name} must be a valid UUID")

    def parse_float(
        self,
        value: str,
        field_name: str,
        min_val: float | None = None,
        max_val: float | None = None,
    ) -> float | None:
        """Parse float with optional bounds validation.

        Args:
            value: String value to parse (may be empty)
            field_name: Human-readable field name for error messages
            min_val: Optional minimum value (inclusive)
            max_val: Optional maximum value (inclusive)

        Returns:
            Parsed float or None if input is empty

        Raises:
            ValueError: If value is non-empty but not a valid float,
                       or if value is outside specified bounds
        """
        if not value:
            return None
        try:
            f = float(value)
            if min_val is not None and f < min_val:
                raise ValueError(f"{field_name} must be >= {min_val}")
            if max_val is not None and f > max_val:
                raise ValueError(f"{field_name} must be <= {max_val}")
            return f
        except ValueError as e:
            if "must be" in str(e):
                raise
            raise ValueError(f"{field_name} must be a number")

    def parse_int(
        self,
        value: str,
        field_name: str,
        min_val: int | None = None,
        max_val: int | None = None,
    ) -> int | None:
        """Parse integer with optional bounds validation.

        Args:
            value: String value to parse (may be empty)
            field_name: Human-readable field name for error messages
            min_val: Optional minimum value (inclusive)
            max_val: Optional maximum value (inclusive)

        Returns:
            Parsed integer or None if input is empty

        Raises:
            ValueError: If value is non-empty but not a valid integer,
                       or if value is outside specified bounds
        """
        if not value:
            return None
        try:
            i = int(value)
            if min_val is not None and i < min_val:
                raise ValueError(f"{field_name} must be >= {min_val}")
            if max_val is not None and i > max_val:
                raise ValueError(f"{field_name} must be <= {max_val}")
            return i
        except ValueError as e:
            if "must be" in str(e):
                raise
            raise ValueError(f"{field_name} must be an integer")

    def parse_json(self, value: str, field_name: str) -> dict[str, Any] | None:
        """Parse JSON string to dict.

        Args:
            value: JSON string to parse (may be empty)
            field_name: Human-readable field name for error messages

        Returns:
            Parsed dict or None if input is empty

        Raises:
            ValueError: If value is non-empty but not valid JSON,
                       or if JSON is not an object (dict)
        """
        if not value:
            return None
        try:
            result = json.loads(value)
            if not isinstance(result, dict):
                raise ValueError(f"{field_name} must be a JSON object")
            return cast(dict[str, Any], result)
        except json.JSONDecodeError as e:
            raise ValueError(f"{field_name} has invalid JSON: {e}")

    def parse_json_list(self, value: str, field_name: str) -> list[Any] | None:
        """Parse JSON string to list.

        Args:
            value: JSON string to parse (may be empty)
            field_name: Human-readable field name for error messages

        Returns:
            Parsed list or None if input is empty

        Raises:
            ValueError: If value is non-empty but not valid JSON,
                       or if JSON is not an array (list)
        """
        if not value:
            return None
        try:
            result = json.loads(value)
            if not isinstance(result, list):
                raise ValueError(f"{field_name} must be a JSON array")
            return cast(list[Any], result)
        except json.JSONDecodeError as e:
            raise ValueError(f"{field_name} has invalid JSON: {e}")
