"""Component-related models."""

from __future__ import annotations

from typing import Any, List, Optional, Union

from pydantic import BaseModel, ConfigDict, Field, field_validator

from filare.models.dataclasses import Component, GraphicalComponent  # noqa: F401
from filare.models.hypertext import MultilineHypertext
from filare.models.numbers import NumberAndUnit
from filare.models.types import (  # noqa: F401
    AUTOGENERATED_PREFIX,
    BomCategory,
    Designator,
    QtyMultiplierCable,
    QtyMultiplierConnector,
    Side,
)
from filare.models.colors import SingleColor


class ComponentModel(BaseModel):
    """Pydantic representation of a Component with conversion helpers."""

    category: Optional[Union[BomCategory, str]] = None
    type: Optional[Union[MultilineHypertext, str, List[str]]] = None
    subtype: Optional[Union[MultilineHypertext, str, List[str]]] = None
    pn: Optional[str] = None
    manufacturer: Optional[str] = None
    mpn: Optional[str] = None
    supplier: Optional[str] = None
    spn: Optional[str] = None
    qty: NumberAndUnit = NumberAndUnit(number=1, unit=None)
    amount: Optional[NumberAndUnit] = None
    ignore_in_bom: bool = False
    id: Optional[str] = None
    designators: List[str] = Field(default_factory=list)
    parent: Optional[str] = None
    additional_components: List[Any] = Field(default_factory=list)
    qty_multiplier: Union[
        QtyMultiplierConnector, QtyMultiplierCable, int, float, str
    ] = 1
    bgcolor: Optional[SingleColor] = None

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

    @field_validator("type", "subtype", mode="before")
    def _to_multiline(cls, value: Any) -> Optional[MultilineHypertext]:
        if value is None:
            return None
        return MultilineHypertext.to(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:
            return value

    @field_validator("qty", "amount", mode="before")
    def _to_number_and_unit(cls, value: Any) -> Optional[NumberAndUnit]:
        if value is None:
            return None
        return NumberAndUnit.to_number_and_unit(value)

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

    @field_validator("additional_components", mode="before")
    def _coerce_additional(cls, value: Any):
        vals = value if isinstance(value, list) else [value]
        out = []
        for v in vals:
            if isinstance(v, ComponentModel):
                out.append(v)
            elif isinstance(v, Component):
                out.append(ComponentModel.from_component(v))
            elif isinstance(v, dict):
                out.append(ComponentModel(**v))
            else:
                out.append(v)
        return out if isinstance(value, list) else out[0]

    @classmethod
    def from_component(cls, comp: Component) -> "ComponentModel":
        additional_models: List[Any] = []
        for sub in comp.additional_components or []:
            if isinstance(sub, Component):
                sub_model = cls.from_component(sub)
                sub_model.parent = None  # avoid nested object references
                additional_models.append(sub_model)
            else:
                additional_models.append(sub)
        parent_val = getattr(comp, "parent", None)
        if parent_val is not None and not isinstance(parent_val, (str, int, float)):
            parent_val = None
        return cls(
            category=comp.category,
            type=comp.type,
            subtype=comp.subtype,
            pn=comp.pn,
            manufacturer=comp.manufacturer,
            mpn=comp.mpn,
            supplier=comp.supplier,
            spn=comp.spn,
            qty=comp.qty,
            amount=comp.amount,
            ignore_in_bom=comp.ignore_in_bom,
            id=comp.id,
            designators=list(comp.designators),
            parent=parent_val,
            additional_components=additional_models,
            qty_multiplier=comp.qty_multiplier,
            bgcolor=comp.bgcolor,
        )

    def to_component(self) -> Component:
        additional = [
            item.to_component() if isinstance(item, ComponentModel) else item
            for item in self.additional_components
        ]
        comp = Component(
            category=self.category,
            type=self.type,
            subtype=self.subtype,
            pn=self.pn,
            manufacturer=self.manufacturer,
            mpn=self.mpn,
            supplier=self.supplier,
            spn=self.spn,
            qty=self.qty,
            amount=self.amount,
            ignore_in_bom=self.ignore_in_bom,
            id=self.id,
            designators=list(self.designators),
            parent=self.parent,
            additional_components=additional,
            qty_multiplier=self.qty_multiplier,
            bgcolor=self.bgcolor,
        )
        return comp
