import os
from typing import Any, ClassVar, Dict, List, Literal, Optional

from pydantic import BaseModel, field_validator, model_validator

from .constants import Constants


def dict_to_class(data: Dict[Any, Any]) -> Any:
    return type("DictClass", (object,), data)


def onoff(value: Any) -> Literal["on", "off"]:
    if str(value).lower() in [1, "1", "on", "true", "enable", "yes"]:
        return "on"
    else:
        return "off"


class SBEvent(BaseModel):
    _: Optional[str] = None
    info: Optional[str] = None
    name: str = ""
    sender: Optional[str] = None
    bar_name: Optional[str] = None
    config_dir: Optional[str] = None
    button: Optional[str] = None
    modifier: Optional[str] = None
    scroll_delta: Optional[str] = None

    @property
    def script(self):
        return self._

    def __init__(self, **data: Any | None):
        super().__init__(**data)
        for key in Constants.SB_ENV_VARS:
            self.__setattr__(key.lower(), os.environ.get(key))


class ARGB(BaseModel):
    value: int = 0
    alpha: int = 255
    red: int = 255
    green: int = 255
    blue: int = 255

    def __init__(self, value: int | str, **data: Any):
        """
        Initialize ARGB color.
        Accepts int (0x11223344) or str ('#11223344' or '11223344')
        """
        if isinstance(value, str):
            value = int(value.replace("#", ""), 16)

        processed_value = value & 0xFFFFFFFF  # Ensure 32-bit value
        super().__init__(value=processed_value, **data)


class Bar(BaseModel):
    color: str
    border_color: str
    position: Literal["top", "bottom"] = "top"
    height: int = 25
    notch_display_height: int = 0
    margin: int = 0
    y_offset: int = 0
    corner_radius: int = 0
    border_width: int = 0
    blur_radius: int = 0
    padding_left: int = 0
    padding_right: int = 0
    notch_width: int = 200
    notch_offset: int = 0
    display: Literal["main", "all"] | list[int] = "main"

    hidden: Literal["on", "off", "current"]
    topmost: Literal["on", "off", "window"]
    sticky: Literal["on", "off"]
    font_smoothing: Literal["on", "off"]
    shadow: Literal["on", "off"]

    show_in_fullscreen: Literal["on", "off"]
    drawing: Literal["on", "off"]

    items: list[str] = list()


### autogenerated


class Font(BaseModel):
    """
    Represents a font configuration in the format "family:style:size"
    Example: "FiraCode Nerd Font:Bold:12.00"
    """

    family: str
    style: str
    size: float

    # Common font styles
    STYLE_REGULAR: ClassVar[str] = "Regular"
    STYLE_BOLD: ClassVar[str] = "Bold"
    STYLE_ITALIC: ClassVar[str] = "Italic"
    STYLE_BOLD_ITALIC: ClassVar[str] = "Bold Italic"

    @model_validator(mode="before")
    @classmethod
    def parse_font_string(cls, data: Any) -> dict[str, str | float]:
        """
        Parse input data. If it's a string, convert it to dict format.
        Example: "FiraCode Nerd Font:Bold:12.00" ->
                {"family": "FiraCode Nerd Font", "style": "Bold", "size": 12.00}
        """
        if isinstance(data, str):
            try:
                family, style, size = data.split(":")
                return {"family": family.strip(), "style": style.strip(), "size": float(size)}
            except ValueError:
                raise ValueError("Font string must be in format 'family:style:size'")
        return data

    @field_validator("size")
    @classmethod
    def validate_size(cls, v: float) -> float:
        """Validate that font size is positive"""
        if v <= 0:
            raise ValueError("Font size must be positive")
        return v

    def __str__(self) -> str:
        """Convert font to string representation"""
        return f"{self.family}:{self.style}:{self.size:.2f}"

    def with_size(self, new_size: float) -> "Font":
        """Create a new Font instance with a different size"""
        return Font(family=self.family, style=self.style, size=new_size)

    def with_style(self, new_style: str) -> "Font":
        """Create a new Font instance with a different style"""
        return Font(family=self.family, style=new_style, size=self.size)


class Shadow(BaseModel):
    drawing: Optional[Literal["on", "off"]] = None
    color: Optional[str] = None
    angle: Optional[int] = None
    distance: Optional[int] = None


class Image(BaseModel):
    value: Optional[str] = None
    drawing: Optional[Literal["on", "off"]] = None
    scale: Optional[float] = None


class Background(BaseModel):
    drawing: Optional[Literal["on", "off"]] = None
    color: Optional[str] = None
    border_color: Optional[str] = None
    border_width: Optional[int] = None
    height: Optional[int] = None
    corner_radius: Optional[int] = None
    padding_left: Optional[int] = None
    padding_right: Optional[int] = None
    y_offset: Optional[int] = None
    clip: Optional[float] = None
    image: Image = Image()
    shadow: Shadow = Shadow()


class TextElement(BaseModel):
    value: str = ""
    drawing: Optional[Literal["on", "off"]] = None
    highlight: Optional[Literal["on", "off"]] = None
    color: Optional[str] = None
    highlight_color: Optional[str] = None
    padding_left: Optional[int] = None
    padding_right: Optional[int] = None
    y_offset: Optional[int] = None
    font: Optional[Font] = None
    width: Optional[int | Literal["dynamic"]] = None
    scroll_duration: Optional[int] = None
    align: Optional[Literal["left", "center", "right"]] = None
    background: Background = Background()
    shadow: Shadow = Shadow()


class Geometry(BaseModel):
    drawing: Optional[Literal["on", "off"]] = None
    position: Optional[Literal["left", "center", "right", "q", "e"]] = None
    associated_space_mask: Optional[int] = None
    associated_display_mask: Optional[int] = None
    ignore_association: Optional[Literal["on", "off"]] = None
    y_offset: Optional[int] = None
    padding_left: Optional[int] = None
    padding_right: Optional[int] = None
    scroll_texts: Optional[Literal["on", "off"]] = None
    width: Optional[int] = None
    background: Background = Background()


class BoundingRect(BaseModel):
    origin: Optional[List[float]] = None
    size: Optional[List[float]] = None


class Scripting(BaseModel):
    script: Optional[str] = None
    click_script: Optional[str] = None
    update_freq: Optional[int] = None
    update_mask: Optional[int] = None
    updates: Optional[Literal["on", "off", "when_shown"]] = None


class SBItemRaw(BaseModel):
    name: str = ""
    type: Literal["item"] = "item"
    geometry: Geometry = Geometry()
    icon: TextElement = TextElement(value="")
    label: TextElement = TextElement(value="")
    scripting: Scripting = Scripting()
    bounding_rects: Optional[Dict[str, BoundingRect]] = None


class SBItem(BaseModel):
    _raw: SBItemRaw = SBItemRaw()

    def __repr__(self):
        return self._raw.model_dump_json(indent=2)
        # return pformat(self.__dict__)

    __str__ = __repr__

    @property
    def name(self):
        return str(self._raw.name)

    @property
    def label(self):
        return str(self._raw.label.value)

    @label.setter
    def label(self, value: str):
        self._raw.label.value = value

    @property
    def icon(self):
        return str(self._raw.icon.value)

    @icon.setter
    def icon(self, value: str):
        self._raw.icon.value = value

    @property
    def scripting(self):
        return str(self._raw.scripting)

    @property
    def geometry(self):
        return self._raw.geometry
