"""Type-safe measurement class for OOXML dimensions.

Provides a single canonical representation (EMU) with constructors and
accessors for all common units, preventing unit mixing and conversion creep.

Example:
    # Create from any unit
    margin = Measurement.from_inches(1.0)
    padding = Measurement.from_pt(12.0)
    spacing = Measurement.from_twips(240)

    # Access in any unit (conversion happens here, at the edge)
    emu_value = margin.emu          # For DrawingML
    twips_value = padding.twips     # For WordprocessingML
    pt_value = spacing.pt           # For display

    # Arithmetic preserves type safety
    total = margin + padding
    half = margin / 2

    # Comparison
    if padding > Measurement.from_pt(10):
        ...
"""

from __future__ import annotations

import math
from dataclasses import dataclass
from typing import Union

from .scalars import (
    EMU_PER_CM,
    EMU_PER_INCH,
    EMU_PER_MM,
    EMU_PER_POINT,
    EMU_PER_TWIP,
)


@dataclass(frozen=True, slots=True)
class Measurement:
    """A type-safe dimension measurement.

    Internally stores value in EMU (English Metric Units) as an integer
    for maximum precision. EMU is the native unit for OOXML DrawingML.

    Key insight: Store in EMU (integer), convert at boundaries.
    This prevents cumulative rounding errors from repeated conversions.

    Attributes:
        _emu: Internal EMU value (use .emu property for access)
    """

    _emu: int

    # ─────────────────────────────────────────────────────────────────────────
    # CONSTRUCTORS - Create from any unit
    # ─────────────────────────────────────────────────────────────────────────

    @classmethod
    def from_emu(cls, emu: int) -> Measurement:
        """Create from EMU (English Metric Units)."""
        return cls(_emu=emu)

    @classmethod
    def from_pt(cls, points: float) -> Measurement:
        """Create from points (1/72 inch)."""
        return cls(_emu=int(round(points * EMU_PER_POINT)))

    @classmethod
    def from_twips(cls, twips: int) -> Measurement:
        """Create from twips (1/20 point, 1/1440 inch)."""
        return cls(_emu=int(round(twips * EMU_PER_TWIP)))

    @classmethod
    def from_half_points(cls, half_points: int) -> Measurement:
        """Create from half-points (OOXML font size unit)."""
        return cls(_emu=int(round(half_points * EMU_PER_POINT / 2)))

    @classmethod
    def from_hundredths(cls, hundredths: int) -> Measurement:
        """Create from hundredths of a point (OOXML spacing unit)."""
        return cls(_emu=int(round(hundredths * EMU_PER_POINT / 100)))

    @classmethod
    def from_inches(cls, inches: float) -> Measurement:
        """Create from inches."""
        return cls(_emu=int(round(inches * EMU_PER_INCH)))

    @classmethod
    def from_mm(cls, mm: float) -> Measurement:
        """Create from millimeters."""
        return cls(_emu=int(round(mm * EMU_PER_MM)))

    @classmethod
    def from_cm(cls, cm: float) -> Measurement:
        """Create from centimeters."""
        return cls(_emu=int(round(cm * EMU_PER_CM)))

    @classmethod
    def zero(cls) -> Measurement:
        """Create a zero measurement."""
        return cls(_emu=0)

    # ─────────────────────────────────────────────────────────────────────────
    # ACCESSORS - Convert to any unit (at the boundary)
    # ─────────────────────────────────────────────────────────────────────────

    @property
    def emu(self) -> int:
        """Value in EMU (native DrawingML unit)."""
        return self._emu

    @property
    def pt(self) -> float:
        """Value in points."""
        return self._emu / EMU_PER_POINT

    @property
    def twips(self) -> int:
        """Value in twips (native WordprocessingML unit)."""
        return int(round(self._emu / EMU_PER_TWIP))

    @property
    def half_points(self) -> int:
        """Value in half-points (OOXML font size sz attribute)."""
        return int(round(self._emu / EMU_PER_POINT * 2))

    @property
    def hundredths(self) -> int:
        """Value in hundredths of a point (OOXML spacing)."""
        return int(round(self._emu / EMU_PER_POINT * 100))

    @property
    def inches(self) -> float:
        """Value in inches."""
        return self._emu / EMU_PER_INCH

    @property
    def mm(self) -> float:
        """Value in millimeters."""
        return self._emu / EMU_PER_MM

    @property
    def cm(self) -> float:
        """Value in centimeters."""
        return self._emu / EMU_PER_CM

    # ─────────────────────────────────────────────────────────────────────────
    # ARITHMETIC - Type-safe operations
    # ─────────────────────────────────────────────────────────────────────────

    def __add__(self, other: Measurement) -> Measurement:
        """Add two measurements."""
        if not isinstance(other, Measurement):
            return NotImplemented
        return Measurement(_emu=self._emu + other._emu)

    def __sub__(self, other: Measurement) -> Measurement:
        """Subtract two measurements."""
        if not isinstance(other, Measurement):
            return NotImplemented
        return Measurement(_emu=self._emu - other._emu)

    def __mul__(self, scalar: Union[int, float]) -> Measurement:
        """Multiply by a scalar."""
        if not isinstance(scalar, (int, float)):
            return NotImplemented
        return Measurement(_emu=int(round(self._emu * scalar)))

    def __rmul__(self, scalar: Union[int, float]) -> Measurement:
        """Multiply by a scalar (reverse)."""
        return self.__mul__(scalar)

    def __truediv__(
        self, other: Union[int, float, Measurement]
    ) -> Union[Measurement, float]:
        """Divide by a scalar or another measurement.

        Division by scalar returns Measurement.
        Division by Measurement returns ratio (float).
        """
        if isinstance(other, Measurement):
            if other._emu == 0:
                raise ZeroDivisionError("Cannot divide by zero measurement")
            return self._emu / other._emu
        if isinstance(other, (int, float)):
            if other == 0:
                raise ZeroDivisionError("Cannot divide by zero")
            return Measurement(_emu=int(round(self._emu / other)))
        return NotImplemented

    def __floordiv__(self, scalar: Union[int, float]) -> Measurement:
        """Floor divide by a scalar."""
        if not isinstance(scalar, (int, float)):
            return NotImplemented
        if scalar == 0:
            raise ZeroDivisionError("Cannot divide by zero")
        return Measurement(_emu=int(self._emu // scalar))

    def __neg__(self) -> Measurement:
        """Negate measurement."""
        return Measurement(_emu=-self._emu)

    def __abs__(self) -> Measurement:
        """Absolute value."""
        return Measurement(_emu=abs(self._emu))

    # ─────────────────────────────────────────────────────────────────────────
    # COMPARISON
    # ─────────────────────────────────────────────────────────────────────────

    def __lt__(self, other: Measurement) -> bool:
        if not isinstance(other, Measurement):
            return NotImplemented
        return self._emu < other._emu

    def __le__(self, other: Measurement) -> bool:
        if not isinstance(other, Measurement):
            return NotImplemented
        return self._emu <= other._emu

    def __gt__(self, other: Measurement) -> bool:
        if not isinstance(other, Measurement):
            return NotImplemented
        return self._emu > other._emu

    def __ge__(self, other: Measurement) -> bool:
        if not isinstance(other, Measurement):
            return NotImplemented
        return self._emu >= other._emu

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Measurement):
            return NotImplemented
        return self._emu == other._emu

    def __hash__(self) -> int:
        return hash(self._emu)

    def __bool__(self) -> bool:
        """True if non-zero."""
        return self._emu != 0

    # ─────────────────────────────────────────────────────────────────────────
    # UTILITIES
    # ─────────────────────────────────────────────────────────────────────────

    def snap_to_grid(self, grid: Measurement) -> Measurement:
        """Snap this measurement to the nearest grid line."""
        if grid._emu <= 0:
            return self
        return Measurement(_emu=int(round(self._emu / grid._emu) * grid._emu))

    def snap_up(self, grid: Measurement) -> Measurement:
        """Snap up to the next grid line."""
        if grid._emu <= 0:
            return self
        return Measurement(_emu=int(math.ceil(self._emu / grid._emu) * grid._emu))

    def snap_down(self, grid: Measurement) -> Measurement:
        """Snap down to the previous grid line."""
        if grid._emu <= 0:
            return self
        return Measurement(_emu=int(math.floor(self._emu / grid._emu) * grid._emu))

    def clamp(self, min_val: Measurement, max_val: Measurement) -> Measurement:
        """Clamp value between min and max."""
        if self._emu < min_val._emu:
            return min_val
        if self._emu > max_val._emu:
            return max_val
        return self

    def __repr__(self) -> str:
        """Readable representation with automatic unit selection."""
        # Show in the most readable unit
        if abs(self._emu) >= EMU_PER_INCH:
            return f"Measurement({self.inches:.3f}in)"
        elif abs(self._emu) >= EMU_PER_POINT:
            return f"Measurement({self.pt:.2f}pt)"
        else:
            return f"Measurement({self._emu}emu)"


# ─────────────────────────────────────────────────────────────────────────────
# CONVENIENCE CONSTANTS
# ─────────────────────────────────────────────────────────────────────────────

# Common measurements
ZERO = Measurement.zero()
ONE_INCH = Measurement.from_inches(1.0)
ONE_PT = Measurement.from_pt(1.0)
ONE_TWIP = Measurement.from_twips(1)

# Standard page margins
MARGIN_NORMAL = Measurement.from_inches(1.0)
MARGIN_NARROW = Measurement.from_inches(0.5)
MARGIN_WIDE = Measurement.from_inches(1.25)

# A4 dimensions (210mm x 297mm)
A4_WIDTH = Measurement.from_mm(210)
A4_HEIGHT = Measurement.from_mm(297)

# US Letter dimensions (8.5in x 11in)
LETTER_WIDTH = Measurement.from_inches(8.5)
LETTER_HEIGHT = Measurement.from_inches(11.0)


__all__ = [
    "Measurement",
    "ZERO",
    "ONE_INCH",
    "ONE_PT",
    "ONE_TWIP",
    "MARGIN_NORMAL",
    "MARGIN_NARROW",
    "MARGIN_WIDE",
    "A4_WIDTH",
    "A4_HEIGHT",
    "LETTER_WIDTH",
    "LETTER_HEIGHT",
]
