"""Field types for genui form builder (Django ORM-style).

Fields are declared as class attributes on a Form subclass.
The attribute name becomes the field name automatically.
All constructor arguments are keyword-only.
"""

from typing import Any, Dict, List, Optional, TYPE_CHECKING

if TYPE_CHECKING:
    from ..attachment_client import AttachmentClient, AttachmentInfo


class Option:
    """Option for Select, MultiSelect, or CheckboxGroup fields."""

    __slots__ = ("value", "label")

    def __init__(self, *, value: str, label: str):
        self.value = value
        self.label = label

    def to_dict(self) -> Dict[str, str]:
        return {"value": self.value, "label": self.label}


class Field:
    """Base field class.

    Subclasses define a field type in a Form. The attribute name on the Form
    class becomes the field name automatically (set by Form.__init_subclass__).
    """

    _name: str = ""

    def __init__(self, *, label: str, required: bool = True):
        self.label = label
        self.required = required

    def _field_type(self) -> str:
        raise NotImplementedError

    def to_dict(self) -> Dict[str, Any]:
        return {
            "type": self._field_type(),
            "name": self._name,
            "label": self.label,
            "required": self.required,
        }

    def parse(self, raw_value: Any) -> Any:
        """Parse a raw JSON value into the Python type for this field."""
        return raw_value

    def validate(self, value: Any) -> Optional[str]:
        """Validate a parsed value. Returns an error message or None if valid."""
        if self.required and self._is_empty(value):
            return f"{self.label} is required"
        return None

    def _is_empty(self, value: Any) -> bool:
        """Check if a value is considered empty."""
        if value is None:
            return True
        if isinstance(value, str) and not value.strip():
            return True
        if isinstance(value, list) and len(value) == 0:
            return True
        return False


class Text(Field):
    """Text input field. Parses to str."""

    def __init__(
        self,
        *,
        label: str,
        required: bool = True,
        placeholder: Optional[str] = None,
        min_length: Optional[int] = None,
        max_length: Optional[int] = None,
        multiline: bool = False,
    ):
        super().__init__(label=label, required=required)
        self.placeholder = placeholder
        self.min_length = min_length
        self.max_length = max_length
        self.multiline = multiline

    def _field_type(self) -> str:
        return "text"

    def to_dict(self) -> Dict[str, Any]:
        d = super().to_dict()
        if self.placeholder is not None:
            d["placeholder"] = self.placeholder
        if self.min_length is not None:
            d["min_length"] = self.min_length
        if self.max_length is not None:
            d["max_length"] = self.max_length
        if self.multiline:
            d["multiline"] = True
        return d

    def parse(self, raw_value: Any) -> str:
        if raw_value is None:
            return ""
        return str(raw_value)

    def validate(self, value: Any) -> Optional[str]:
        base = super().validate(value)
        if base:
            return base
        if self._is_empty(value):
            return None
        s = str(value)
        if self.min_length is not None and len(s) < self.min_length:
            return f"{self.label} must be at least {self.min_length} characters"
        if self.max_length is not None and len(s) > self.max_length:
            return f"{self.label} must be at most {self.max_length} characters"
        return None


class Number(Field):
    """Numeric input field. Parses to float."""

    def __init__(
        self,
        *,
        label: str,
        required: bool = True,
        min: Optional[float] = None,
        max: Optional[float] = None,
        step: Optional[float] = None,
    ):
        super().__init__(label=label, required=required)
        self.min = min
        self.max = max
        self.step = step

    def _field_type(self) -> str:
        return "number"

    def to_dict(self) -> Dict[str, Any]:
        d = super().to_dict()
        if self.min is not None:
            d["min"] = self.min
        if self.max is not None:
            d["max"] = self.max
        if self.step is not None:
            d["step"] = self.step
        return d

    def parse(self, raw_value: Any) -> float:
        if raw_value is None:
            return 0.0
        return float(raw_value)

    def validate(self, value: Any) -> Optional[str]:
        base = super().validate(value)
        if base:
            return base
        if self._is_empty(value):
            return None
        try:
            num = float(value)
        except (TypeError, ValueError):
            return f"{self.label} must be a number"
        if self.min is not None and num < self.min:
            return f"{self.label} must be at least {self.min}"
        if self.max is not None and num > self.max:
            return f"{self.label} must be at most {self.max}"
        return None


class Select(Field):
    """Single-select dropdown/radio field. Parses to str."""

    def __init__(
        self,
        *,
        label: str,
        required: bool = True,
        options: Optional[List[Option]] = None,
        placeholder: Optional[str] = None,
        allow_custom: bool = False,
        is_searchable: bool = False,
    ):
        super().__init__(label=label, required=required)
        self.options = options or []
        self.placeholder = placeholder
        self.allow_custom = allow_custom
        self.is_searchable = is_searchable

    def _field_type(self) -> str:
        return "select"

    def to_dict(self) -> Dict[str, Any]:
        d = super().to_dict()
        d["options"] = [o.to_dict() for o in self.options]
        if self.placeholder is not None:
            d["placeholder"] = self.placeholder
        d["allow_custom"] = self.allow_custom
        d["is_searchable"] = self.is_searchable
        return d

    def parse(self, raw_value: Any) -> str:
        if raw_value is None:
            return ""
        return str(raw_value)

    def validate(self, value: Any) -> Optional[str]:
        base = super().validate(value)
        if base:
            return base
        if self._is_empty(value):
            return None
        s = str(value)
        valid_values = {o.value for o in self.options}
        if not self.allow_custom and valid_values and s not in valid_values:
            return f"{self.label} must be one of: {', '.join(valid_values)}"
        return None


class MultiSelect(Field):
    """Multi-select field. Parses to List[str]."""

    def __init__(
        self,
        *,
        label: str,
        required: bool = True,
        options: Optional[List[Option]] = None,
        min_selections: Optional[int] = None,
        max_selections: Optional[int] = None,
        allow_custom: bool = False,
        is_searchable: bool = False,
    ):
        super().__init__(label=label, required=required)
        self.options = options or []
        self.min_selections = min_selections
        self.max_selections = max_selections
        self.allow_custom = allow_custom
        self.is_searchable = is_searchable

    def _field_type(self) -> str:
        return "multi_select"

    def to_dict(self) -> Dict[str, Any]:
        d = super().to_dict()
        d["options"] = [o.to_dict() for o in self.options]
        if self.min_selections is not None:
            d["min_selections"] = self.min_selections
        if self.max_selections is not None:
            d["max_selections"] = self.max_selections
        d["allow_custom"] = self.allow_custom
        d["is_searchable"] = self.is_searchable
        return d

    def parse(self, raw_value: Any) -> List[str]:
        if raw_value is None:
            return []
        if isinstance(raw_value, list):
            return [str(v) for v in raw_value]
        return [str(raw_value)]

    def validate(self, value: Any) -> Optional[str]:
        base = super().validate(value)
        if base:
            return base
        items = value if isinstance(value, list) else []
        if not items and not self.required:
            return None
        if self.min_selections is not None and len(items) < self.min_selections:
            return f"{self.label} requires at least {self.min_selections} selection(s)"
        if self.max_selections is not None and len(items) > self.max_selections:
            return f"{self.label} allows at most {self.max_selections} selection(s)"
        if not self.allow_custom and self.options:
            valid_values = {o.value for o in self.options}
            invalid = [v for v in items if v not in valid_values]
            if invalid:
                return f"{self.label} contains invalid option(s): {', '.join(invalid)}"
        return None


class Checkbox(Field):
    """Single checkbox (boolean) field. Parses to bool."""

    def __init__(
        self,
        *,
        label: str,
        required: bool = True,
        default: bool = False,
    ):
        super().__init__(label=label, required=required)
        self.default = default

    def _field_type(self) -> str:
        return "checkbox"

    def to_dict(self) -> Dict[str, Any]:
        d = super().to_dict()
        d["default"] = self.default
        return d

    def parse(self, raw_value: Any) -> bool:
        if raw_value is None:
            return False
        return bool(raw_value)

    def validate(self, value: Any) -> Optional[str]:
        if self.required and not value:
            return f"{self.label} must be checked"
        return None


class CheckboxGroup(Field):
    """Multiple checkboxes field. Parses to List[str]."""

    def __init__(
        self,
        *,
        label: str,
        required: bool = True,
        options: Optional[List[Option]] = None,
        min_selections: Optional[int] = None,
        max_selections: Optional[int] = None,
    ):
        super().__init__(label=label, required=required)
        self.options = options or []
        self.min_selections = min_selections
        self.max_selections = max_selections

    def _field_type(self) -> str:
        return "checkbox_group"

    def to_dict(self) -> Dict[str, Any]:
        d = super().to_dict()
        d["options"] = [o.to_dict() for o in self.options]
        if self.min_selections is not None:
            d["min_selections"] = self.min_selections
        if self.max_selections is not None:
            d["max_selections"] = self.max_selections
        return d

    def parse(self, raw_value: Any) -> List[str]:
        if raw_value is None:
            return []
        if isinstance(raw_value, list):
            return [str(v) for v in raw_value]
        return [str(raw_value)]

    def validate(self, value: Any) -> Optional[str]:
        base = super().validate(value)
        if base:
            return base
        items = value if isinstance(value, list) else []
        if not items and not self.required:
            return None
        if self.min_selections is not None and len(items) < self.min_selections:
            return f"{self.label} requires at least {self.min_selections} selection(s)"
        if self.max_selections is not None and len(items) > self.max_selections:
            return f"{self.label} allows at most {self.max_selections} selection(s)"
        if self.options:
            valid_values = {o.value for o in self.options}
            invalid = [v for v in items if v not in valid_values]
            if invalid:
                return f"{self.label} contains invalid option(s): {', '.join(invalid)}"
        return None


class Switch(Field):
    """Toggle switch field. Parses to bool."""

    def __init__(
        self,
        *,
        label: str,
        required: bool = True,
        default: bool = False,
    ):
        super().__init__(label=label, required=required)
        self.default = default

    def _field_type(self) -> str:
        return "switch"

    def to_dict(self) -> Dict[str, Any]:
        d = super().to_dict()
        d["default"] = self.default
        return d

    def parse(self, raw_value: Any) -> bool:
        if raw_value is None:
            return False
        return bool(raw_value)

    def validate(self, value: Any) -> Optional[str]:
        # Switch is always valid (bool) — no extra constraints
        return None


class Slider(Field):
    """Range slider field. Parses to float."""

    def __init__(
        self,
        *,
        label: str,
        required: bool = True,
        min: float = 0,
        max: float = 100,
        step: float = 1,
        default: Optional[float] = None,
    ):
        super().__init__(label=label, required=required)
        self.min = min
        self.max = max
        self.step = step
        self.default = default

    def _field_type(self) -> str:
        return "slider"

    def to_dict(self) -> Dict[str, Any]:
        d = super().to_dict()
        d["min"] = self.min
        d["max"] = self.max
        d["step"] = self.step
        if self.default is not None:
            d["default"] = self.default
        return d

    def parse(self, raw_value: Any) -> float:
        if raw_value is None:
            return 0.0
        return float(raw_value)

    def validate(self, value: Any) -> Optional[str]:
        base = super().validate(value)
        if base:
            return base
        try:
            num = float(value)
        except (TypeError, ValueError):
            return f"{self.label} must be a number"
        if num < self.min:
            return f"{self.label} must be at least {self.min}"
        if num > self.max:
            return f"{self.label} must be at most {self.max}"
        return None


class DatePicker(Field):
    """Date picker field. Parses to str."""

    def __init__(
        self,
        *,
        label: str,
        required: bool = True,
        min_date: Optional[str] = None,
        max_date: Optional[str] = None,
        format: Optional[str] = None,
    ):
        super().__init__(label=label, required=required)
        self.min_date = min_date
        self.max_date = max_date
        self.format = format

    def _field_type(self) -> str:
        return "date_picker"

    def to_dict(self) -> Dict[str, Any]:
        d = super().to_dict()
        if self.min_date is not None:
            d["min_date"] = self.min_date
        if self.max_date is not None:
            d["max_date"] = self.max_date
        if self.format is not None:
            d["format"] = self.format
        return d

    def parse(self, raw_value: Any) -> str:
        if raw_value is None:
            return ""
        return str(raw_value)

    def validate(self, value: Any) -> Optional[str]:
        base = super().validate(value)
        if base:
            return base
        if self._is_empty(value):
            return None
        s = str(value)
        if self.min_date is not None and s < self.min_date:
            return f"{self.label} must be on or after {self.min_date}"
        if self.max_date is not None and s > self.max_date:
            return f"{self.label} must be on or before {self.max_date}"
        return None


class Hidden(Field):
    """Hidden field (not displayed to user). Parses to Any."""

    def __init__(
        self,
        *,
        label: str,
        required: bool = False,
        value: Any = None,
    ):
        super().__init__(label=label, required=required)
        self.value = value

    def _field_type(self) -> str:
        return "hidden"

    def to_dict(self) -> Dict[str, Any]:
        d = super().to_dict()
        if self.value is not None:
            d["value"] = self.value
        return d

    def parse(self, raw_value: Any) -> Any:
        return raw_value

    def validate(self, value: Any) -> Optional[str]:
        # Hidden fields are system-controlled, no user validation needed
        return None


class FileField(Field):
    """File upload field. Parses to AttachmentInfo via attachment_client."""

    def __init__(
        self,
        *,
        label: str,
        required: bool = True,
        accept: Optional[List[str]] = None,
        max_size: Optional[int] = None,
    ):
        super().__init__(label=label, required=required)
        self.accept = accept
        self.max_size = max_size

    def _field_type(self) -> str:
        return "file"

    def to_dict(self) -> Dict[str, Any]:
        d = super().to_dict()
        if self.accept is not None:
            d["accept"] = self.accept
        if self.max_size is not None:
            d["max_size"] = self.max_size
        return d

    def parse(self, raw_value: Any) -> Any:
        """Sync fallback — returns the raw value as-is."""
        return raw_value

    def validate(self, value: Any) -> Optional[str]:
        if self.required and not value:
            return f"{self.label} is required"
        return None

    async def async_parse(
        self, raw_value: Any, attachment_client: "AttachmentClient"
    ) -> Optional["AttachmentInfo"]:
        """Async parse — resolves attachment data into AttachmentInfo."""
        from ..attachment_client import AttachmentInfo

        if raw_value is None:
            return None
        if isinstance(raw_value, dict):
            return AttachmentInfo.from_dict(raw_value)
        # String value treated as URL/object_key
        return AttachmentInfo(
            id="",
            url=str(raw_value),
            filename="",
            content_type="application/octet-stream",
            size=0,
            object_key=str(raw_value),
        )
