"""Typography management for armonia."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING, Literal

if TYPE_CHECKING:
    from armonia.theme import Theme

ComputeFontFn = Callable[["Theme"], "Font"]


@dataclass(frozen=True, slots=True)
class Font:
    """Represents an immutable font definition.

    :param family: The font family name (e.g. ``"Inter"``, ``"Georgia"``).
    :type family: str
    :param size: The font size in pixels. Must be greater than 0.
    :type size: float
    :param weight: The font weight (1-1000, e.g. 400 for normal, 700 for bold).
    :type weight: int
    :param italic: Whether the font uses italic style.
    :type italic: bool
    :raises ValueError: If ``size`` is not greater than 0, or ``weight`` is outside 1-1000.

    Example::

        font = Font("Inter", 16.0, 400, False)
        bold = font.variant(weight=700)
    """

    family: str
    size: float
    weight: int
    italic: bool

    def __post_init__(self) -> None:
        if self.size <= 0:
            raise ValueError(f"Font size must be greater than 0, got {self.size!r}")
        if not (1 <= self.weight <= 1000):
            raise ValueError(f"Font weight must be between 1 and 1000, got {self.weight!r}")

    def variant(
        self,
        *,
        size: float | None = None,
        weight: int | None = None,
        italic: bool | None = None,
    ) -> Font:
        """Return a new Font with one or more properties changed.

        The font family is always preserved. Computed fonts may only vary
        size, weight, and italic style.

        :param size: New size in pixels, or ``None`` to keep current.
        :type size: float | None
        :param weight: New weight (1-1000), or ``None`` to keep current.
        :type weight: int | None
        :param italic: New italic flag, or ``None`` to keep current.
        :type italic: bool | None
        :return: A new Font with the requested changes applied.
        :rtype: Font
        :raises ValueError: If the resulting size or weight would be invalid.

        Example::

            body = Font("Inter", 16.0, 400, False)
            heading = body.variant(size=24.0, weight=700)
        """
        return Font(
            family=self.family,
            size=size if size is not None else self.size,
            weight=weight if weight is not None else self.weight,
            italic=italic if italic is not None else self.italic,
        )


@dataclass(frozen=True, slots=True)
class ThemeFontEntry:
    """A resolved font together with its name and source metadata.

    :param name: The registered name of the font.
    :type name: str
    :param font: The resolved Font value.
    :type font: Font
    :param source: Whether this font is manually defined or computed.
    :type source: Literal["manual", "computed"]
    """

    name: str
    font: Font
    source: Literal["manual", "computed"]


__all__ = [
    "ComputeFontFn",
    "Font",
    "ThemeFontEntry",
]
