"""Connector-related models."""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Union, cast

from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator

from filare.models.colors import MultiColor, SingleColor
from filare.models.component import ComponentModel
from filare.models.connections import LoopModel, PinModel
from filare.models.hypertext import MultilineHypertext
from filare.models.image import Image
from filare.models.types import AUTOGENERATED_PREFIX, BomCategory, Side  # noqa: F401
from filare.models.utils import remove_links

if TYPE_CHECKING:  # pragma: no cover
    from filare.models.dataclasses import Component as ComponentDC
    from filare.models.dataclasses import Connector as ConnectorDC
    from filare.models.dataclasses import GraphicalComponent as GraphicalComponentDC
    from filare.models.dataclasses import Loop as LoopDC
    from filare.models.dataclasses import PinClass as PinClassDC
else:  # pragma: no cover
    try:
        from filare.models.dataclasses import Component as ComponentDC
        from filare.models.dataclasses import Connector as ConnectorDC
        from filare.models.dataclasses import GraphicalComponent as GraphicalComponentDC
        from filare.models.dataclasses import Loop as LoopDC
        from filare.models.dataclasses import PinClass as PinClassDC
    except Exception:
        ComponentDC = ConnectorDC = GraphicalComponentDC = LoopDC = PinClassDC = None  # type: ignore

# Types used only for annotations to avoid pyright type-form errors when dataclasses
# are not available at runtime.
if TYPE_CHECKING:
    from filare.models.dataclasses import Connector as ConnectorType
    from filare.models.dataclasses import GraphicalComponent as GraphicalComponentType
else:
    ConnectorType = Any
    GraphicalComponentType = Any

# Compatibility dataclass aliases
Component = ComponentDC  # type: ignore
Connector = ConnectorDC  # type: ignore
GraphicalComponent = GraphicalComponentDC  # type: ignore
Loop = LoopDC  # type: ignore
PinClass = PinClassDC  # type: ignore


class GraphicalComponentModel(BaseModel):
    """Pydantic representation mirroring GraphicalComponent fields."""

    designator: str
    category: Optional[Union[BomCategory, str]] = None
    type: Optional[MultilineHypertext] = None
    subtype: Optional[MultilineHypertext] = None
    color: Optional[MultiColor] = None
    image: Optional[Image] = None
    notes: Optional[MultilineHypertext] = None
    additional_components: List[Any] = Field(default_factory=list)
    bgcolor: Optional[SingleColor] = None
    bgcolor_title: Optional[SingleColor] = None
    show_name: Optional[bool] = None
    show_pincount: Optional[bool] = None

    model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)

    @field_validator("type", "subtype", "notes", mode="before")
    def _to_multiline(
        cls, value: Optional[Union[str, MultilineHypertext, Sequence[str]]]
    ) -> Optional[MultilineHypertext]:
        if value is None:
            return value
        return MultilineHypertext.to(value)

    @field_validator("color", mode="before")
    def _to_multicolor(
        cls, value: Optional[Union[str, Sequence[str], MultiColor]]
    ) -> Optional[MultiColor]:
        if value is None:
            return value
        return MultiColor(value)

    @field_validator("bgcolor", "bgcolor_title", mode="before")
    def _to_single_color(
        cls, value: Optional[Union[str, SingleColor]]
    ) -> Optional[SingleColor]:
        if value is None:
            return value
        return SingleColor(value)

    @field_validator("image", mode="before")
    def _to_image(
        cls, value: Optional[Union[Image, Dict[str, Any]]]
    ) -> Optional[Image]:
        if value is None:
            return None
        if isinstance(value, Image):
            return value
        if isinstance(value, dict):
            return Image(**value)
        return value

    @field_validator("category", mode="before")
    def _coerce_category(cls, value: Any):
        if value is None:
            return None
        if isinstance(value, BomCategory):
            return value
        try:
            return BomCategory(value)
        except Exception as exc:
            logging.debug(
                "Connector category %r not recognized; using raw value. error=%s",
                value,
                exc,
            )
            return value

    def to_graphical_component(self) -> GraphicalComponentType:
        if GraphicalComponentDC is None:  # pragma: no cover
            raise TypeError("GraphicalComponent dataclass not available")
        additional_components = [
            item.to_component() if isinstance(item, ComponentModel) else item
            for item in self.additional_components
        ]
        return GraphicalComponentDC(
            designator=remove_links(self.designator),
            type=self.type,
            subtype=self.subtype,
            color=self.color,
            image=self.image,
            notes=self.notes,
            additional_components=additional_components,
            bgcolor=self.bgcolor,
            bgcolor_title=self.bgcolor_title,
            show_name=self.show_name,
            category=self.category or BomCategory.ADDITIONAL,
        )


class ConnectorModel(GraphicalComponentModel):
    """Pydantic representation of Connector with conversion helpers."""

    pincount: Optional[int] = None
    pins: List[Any] = Field(default_factory=list)
    pinlabels: List[Any] = Field(default_factory=list)
    pincolors: List[Any] = Field(default_factory=list)
    loops: List[Any] = Field(default_factory=list)
    style: Optional[str] = None
    images: List[str] = Field(default_factory=list)
    hide_disconnected_pins: bool = False

    @model_validator(mode="before")
    def _normalize_pincount(cls, values: Dict[str, Any]) -> Dict[str, Any]:
        data = dict(values or {})
        if data.get("pincount") is None:
            pins = data.get("pins") or []
            pinlabels = data.get("pinlabels") or []
            data["pincount"] = len(pins) or len(pinlabels) or None
        return data

    @field_validator("loops", mode="before")
    def _coerce_loops(cls, value: Any):
        if value is None:
            return []
        if isinstance(value, dict):
            return [value]
        return list(value)

    def to_connector(self) -> ConnectorType:
        if (
            ConnectorDC is None or LoopDC is None or PinClassDC is None
        ):  # pragma: no cover
            raise TypeError("Connector dataclass not available")
        normalized_pins = []
        for pin in self.pins:
            if isinstance(pin, dict):
                normalized_pins.append(pin.get("id") or pin.get("label"))
            else:
                normalized_pins.append(pin)
        additional_components = [
            item.to_component() if isinstance(item, ComponentModel) else item
            for item in self.additional_components
        ]

        def _pin_value(value: Any) -> Any:
            if isinstance(value, PinModel):
                return value.id or value.label
            if isinstance(value, PinClassDC):
                return value.id or value.label
            if isinstance(value, dict):
                return value.get("id") or value.get("label") or value
            return value

        loop_objects: List[Any] = []
        for loop in self.loops:
            if isinstance(loop, LoopModel):
                loop_objects.append(
                    {
                        "first": _pin_value(loop.first),
                        "second": _pin_value(loop.second),
                        "side": loop.side,
                        "show_label": loop.show_label,
                        "color": loop.color,
                    }
                )
            elif isinstance(loop, dict):
                loop_objects.append(
                    {
                        "first": _pin_value(loop.get("first")),
                        "second": _pin_value(loop.get("second")),
                        "side": loop.get("side"),
                        "show_label": loop.get("show_label", True),
                        "color": loop.get("color"),
                    }
                )
            else:
                loop_objects.append(loop)

        return ConnectorDC(
            designator=self.designator,
            type=self.type,
            subtype=self.subtype,
            pincount=self.pincount,
            pins=normalized_pins,
            pinlabels=self.pinlabels,
            pincolors=self.pincolors,
            loops=loop_objects,
            style=self.style,
            notes=self.notes,
            color=self.color,
            bgcolor=self.bgcolor,
            bgcolor_title=self.bgcolor_title,
            show_pincount=self.show_pincount,
            show_name=self.show_name,
            hide_disconnected_pins=self.hide_disconnected_pins,
            additional_components=additional_components,
        )


__all__ = [
    "ConnectorModel",
    "GraphicalComponentModel",
    "AUTOGENERATED_PREFIX",
    "Side",
    "Connector",
    "Loop",
    "PinClass",
    "GraphicalComponent",
]

# Expose dataclass names for compatibility
Connector = ConnectorDC  # type: ignore
Loop = LoopDC  # type: ignore
PinClass = PinClassDC  # type: ignore
GraphicalComponent = GraphicalComponentDC  # type: ignore
