"""Color primitives for unified OOXML styling.

Provides theme color references, color modifiers, and color modification
stacking for DrawingML color manipulation.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from typing import List, Optional


__all__ = [
    "ThemeColor",
    "ColorModifiers",
    "ColorModType",
    "ColorMod",
    "ColorModStack",
    "ColorRef",
    "ExtraColorSlot",
    "ExtraColorScheme",
]


# =============================================================================
# UNIT CONVERSION CONSTANTS
# =============================================================================


# =============================================================================
# COLOR PRIMITIVES
# =============================================================================


class ThemeColor(Enum):
    """OOXML theme color slots."""

    DK1 = "dk1"
    LT1 = "lt1"
    DK2 = "dk2"
    LT2 = "lt2"
    ACCENT1 = "accent1"
    ACCENT2 = "accent2"
    ACCENT3 = "accent3"
    ACCENT4 = "accent4"
    ACCENT5 = "accent5"
    ACCENT6 = "accent6"
    HLINK = "hlink"
    FOL_HLINK = "folHlink"
    # Special placeholder color (used in format schemes)
    PH_CLR = "phClr"
    # Text colors
    TX1 = "tx1"
    TX2 = "tx2"
    BG1 = "bg1"
    BG2 = "bg2"


@dataclass
class ColorModifiers:
    """Color modification values (OOXML percentage units: 100000 = 100%).

    These map directly to DrawingML child elements of schemeClr/srgbClr.
    All values are in OOXML percentage units (1/1000th of a percent).

    Common modifiers:
    - tint: Lighten by mixing with white (0-100000)
    - shade: Darken by mixing with black (0-100000)
    - alpha: Transparency (0-100000, 100000 = fully opaque)
    - satMod: Saturation multiplier (100000 = 100%)
    - lumMod: Luminance multiplier (100000 = 100%)

    Advanced modifiers (not in Office GUI):
    - hue: Absolute hue (0-21600000 = 0°-360°)
    - hueOff: Hue rotation offset
    - hueMod: Hue multiplier
    - satOff/lumOff: Additive offsets for sat/lum
    - red/green/blue: Absolute channel values (0-100000)
    - redMod/greenMod/blueMod: Channel multipliers
    - redOff/greenOff/blueOff: Channel offsets
    - comp: Complement (180° hue flip)
    - inv: Invert RGB
    - gray: Convert to grayscale
    - gamma/invGamma: Gamma correction
    """

    # HSL modifiers
    tint: Optional[int] = None  # 0-100000 (mix with white)
    shade: Optional[int] = None  # 0-100000 (mix with black)
    alpha: Optional[int] = None  # 0-100000 (opacity)

    # Saturation
    sat: Optional[int] = None  # 0-100000 (absolute)
    sat_mod: Optional[int] = None  # percentage (100000 = 100%)
    sat_off: Optional[int] = None  # -100000 to 100000

    # Luminance
    lum: Optional[int] = None  # 0-100000 (absolute)
    lum_mod: Optional[int] = None  # percentage
    lum_off: Optional[int] = None  # -100000 to 100000

    # Hue (not in GUI)
    hue: Optional[int] = None  # 0-21600000 (0°-360°)
    hue_off: Optional[int] = None  # rotation offset
    hue_mod: Optional[int] = None  # percentage

    # Alpha modifiers
    alpha_off: Optional[int] = None  # -100000 to 100000
    alpha_mod: Optional[int] = None  # percentage

    # RGB channel control (not in GUI)
    red: Optional[int] = None  # 0-100000
    red_off: Optional[int] = None
    red_mod: Optional[int] = None
    green: Optional[int] = None  # 0-100000
    green_off: Optional[int] = None
    green_mod: Optional[int] = None
    blue: Optional[int] = None  # 0-100000
    blue_off: Optional[int] = None
    blue_mod: Optional[int] = None

    # Special transforms (not in GUI)
    comp: bool = False  # Complement (180° hue flip)
    inv: bool = False  # Invert RGB
    gray: bool = False  # Convert to grayscale
    gamma: bool = False  # Gamma correction
    inv_gamma: bool = False  # Inverse gamma


class ColorModType(Enum):
    """Types of color modifications for stacking.

    In OOXML, order matters. For example:
    <a:schemeClr val="accent1">
      <a:tint val="50000"/>      <!-- Applied first -->
      <a:satMod val="150000"/>   <!-- Applied second -->
      <a:lumMod val="90000"/>    <!-- Applied third -->
    </a:schemeClr>
    """

    # Tint/shade
    TINT = "tint"
    SHADE = "shade"
    # Alpha
    ALPHA = "alpha"
    ALPHA_OFF = "alphaOff"
    ALPHA_MOD = "alphaMod"
    # Saturation
    SAT = "sat"
    SAT_OFF = "satOff"
    SAT_MOD = "satMod"
    # Luminance
    LUM = "lum"
    LUM_OFF = "lumOff"
    LUM_MOD = "lumMod"
    # Hue
    HUE = "hue"
    HUE_OFF = "hueOff"
    HUE_MOD = "hueMod"
    # RGB channels
    RED = "red"
    RED_OFF = "redOff"
    RED_MOD = "redMod"
    GREEN = "green"
    GREEN_OFF = "greenOff"
    GREEN_MOD = "greenMod"
    BLUE = "blue"
    BLUE_OFF = "blueOff"
    BLUE_MOD = "blueMod"
    # Special transforms
    COMP = "comp"  # Complement (180° hue flip)
    INV = "inv"  # Invert RGB
    GRAY = "gray"  # Grayscale
    GAMMA = "gamma"
    INV_GAMMA = "invGamma"


@dataclass
class ColorMod:
    """A single color modification with type and value.

    For stacking multiple modifications in order.
    """

    mod_type: ColorModType
    value: Optional[int] = None  # OOXML units (percentage * 1000 or hue * 60000)

    @classmethod
    def tint(cls, percent: float) -> ColorMod:
        """Tint by percentage (0-100)."""
        return cls(ColorModType.TINT, int(percent * 1000))

    @classmethod
    def shade(cls, percent: float) -> ColorMod:
        """Shade by percentage (0-100)."""
        return cls(ColorModType.SHADE, int(percent * 1000))

    @classmethod
    def alpha(cls, percent: float) -> ColorMod:
        """Alpha/opacity percentage (0-100)."""
        return cls(ColorModType.ALPHA, int(percent * 1000))

    @classmethod
    def sat_mod(cls, percent: float) -> ColorMod:
        """Saturation modifier (100 = no change)."""
        return cls(ColorModType.SAT_MOD, int(percent * 1000))

    @classmethod
    def lum_mod(cls, percent: float) -> ColorMod:
        """Luminance modifier (100 = no change)."""
        return cls(ColorModType.LUM_MOD, int(percent * 1000))

    @classmethod
    def hue(cls, degrees: float) -> ColorMod:
        """Absolute hue (0-360 degrees)."""
        return cls(ColorModType.HUE, int(degrees * 60000))

    @classmethod
    def complement(cls) -> ColorMod:
        """Complement (180° hue flip)."""
        return cls(ColorModType.COMP, None)

    @classmethod
    def grayscale(cls) -> ColorMod:
        """Convert to grayscale."""
        return cls(ColorModType.GRAY, None)


@dataclass
class ColorModStack:
    """Ordered stack of color modifications.

    OOXML applies modifications in order. This allows effects like:
    - Tint first, then saturate
    - Desaturate, then shade

    Example:
        >>> stack = ColorModStack([
        ...     ColorMod.tint(50),       # 50% tint
        ...     ColorMod.sat_mod(150),   # 150% saturation
        ...     ColorMod.lum_mod(90),    # 90% luminance
        ... ])
    """

    mods: List[ColorMod] = field(default_factory=list)

    def add(self, mod: ColorMod) -> ColorModStack:
        """Add a modifier and return self for chaining."""
        self.mods.append(mod)
        return self

    def tint(self, percent: float) -> ColorModStack:
        """Add tint and return self."""
        return self.add(ColorMod.tint(percent))

    def shade(self, percent: float) -> ColorModStack:
        """Add shade and return self."""
        return self.add(ColorMod.shade(percent))

    def saturate(self, percent: float) -> ColorModStack:
        """Add saturation modifier and return self."""
        return self.add(ColorMod.sat_mod(percent))


def _pct_to_modifiers(
    *,
    tint: Optional[float] = None,
    shade: Optional[float] = None,
    alpha: Optional[float] = None,
    sat_mod: Optional[float] = None,
) -> ColorModifiers:
    """Convert percentage values (0-100) to OOXML thousandths units."""
    return ColorModifiers(
        tint=int(tint * 1000) if tint is not None else None,
        shade=int(shade * 1000) if shade is not None else None,
        alpha=int(alpha * 1000) if alpha is not None else None,
        sat_mod=int(sat_mod * 1000) if sat_mod is not None else None,
    )


@dataclass
class ColorRef:
    """A color reference that can be a theme color or direct RGB.

    This is the core building block for all color usage in OOXML.
    It can represent:
    - Theme color: ColorRef.theme(ThemeColor.ACCENT1)
    - Theme color with mods: ColorRef.theme(ThemeColor.ACCENT1, tint=20)
    - Direct RGB: ColorRef.rgb("#FF5733")
    - RGB with alpha: ColorRef.rgb("#FF5733", alpha=50)
    - Stacked modifiers: ColorRef.theme_stacked(ThemeColor.ACCENT1, stack)

    Examples:
        >>> # Theme color reference
        >>> ColorRef.theme(ThemeColor.ACCENT1)

        >>> # Theme color with 20% tint
        >>> ColorRef.theme(ThemeColor.ACCENT1, tint=20)

        >>> # Direct RGB color
        >>> ColorRef.rgb("#4472C4")

        >>> # RGB with 50% opacity
        >>> ColorRef.rgb("#000000", alpha=50)

        >>> # Stacked modifiers (order matters!)
        >>> stack = ColorModStack().tint(50).saturate(150)
        >>> ColorRef.theme_stacked(ThemeColor.ACCENT1, stack)
    """

    # Either theme_color OR hex_rgb should be set, not both
    theme_color: Optional[ThemeColor] = None
    hex_rgb: Optional[str] = None  # Without # prefix, uppercase
    modifiers: ColorModifiers = field(default_factory=ColorModifiers)
    # For ordered modifier stacking (takes precedence over modifiers if set)
    mod_stack: Optional[ColorModStack] = None

    @classmethod
    def theme(
        cls,
        color: ThemeColor,
        tint: Optional[float] = None,
        shade: Optional[float] = None,
        alpha: Optional[float] = None,
        saturation: Optional[float] = None,
    ) -> ColorRef:
        """Create a theme color reference.

        Args:
            color: Theme color slot
            tint: Optional tint percentage (0-100)
            shade: Optional shade percentage (0-100)
            alpha: Optional alpha/opacity percentage (0-100)
            saturation: Optional saturation modifier percentage
        """
        mods = _pct_to_modifiers(
            tint=tint, shade=shade, alpha=alpha, sat_mod=saturation
        )
        return cls(theme_color=color, modifiers=mods)

    @classmethod
    def rgb(
        cls,
        hex_color: str,
        alpha: Optional[float] = None,
        tint: Optional[float] = None,
        shade: Optional[float] = None,
    ) -> ColorRef:
        """Create a direct RGB color reference.

        Args:
            hex_color: Hex color (with or without #)
            alpha: Optional alpha/opacity percentage (0-100)
            tint: Optional tint percentage (0-100)
            shade: Optional shade percentage (0-100)
        """
        normalized = hex_color.lstrip("#").upper()
        mods = _pct_to_modifiers(tint=tint, shade=shade, alpha=alpha)
        return cls(hex_rgb=normalized, modifiers=mods)

    def with_tint(self, percent: float) -> ColorRef:
        """Return a copy with tint applied."""
        new_mods = ColorModifiers(
            tint=int(percent * 1000),
            shade=self.modifiers.shade,
            alpha=self.modifiers.alpha,
            sat_mod=self.modifiers.sat_mod,
            lum_mod=self.modifiers.lum_mod,
        )
        return ColorRef(
            theme_color=self.theme_color,
            hex_rgb=self.hex_rgb,
            modifiers=new_mods,
        )

    def with_shade(self, percent: float) -> ColorRef:
        """Return a copy with shade applied."""
        new_mods = ColorModifiers(
            tint=self.modifiers.tint,
            shade=int(percent * 1000),
            alpha=self.modifiers.alpha,
            sat_mod=self.modifiers.sat_mod,
            lum_mod=self.modifiers.lum_mod,
        )
        return ColorRef(
            theme_color=self.theme_color,
            hex_rgb=self.hex_rgb,
            modifiers=new_mods,
        )

    def with_alpha(self, percent: float) -> ColorRef:
        """Return a copy with alpha applied."""
        new_mods = ColorModifiers(
            tint=self.modifiers.tint,
            shade=self.modifiers.shade,
            alpha=int(percent * 1000),
            sat_mod=self.modifiers.sat_mod,
            lum_mod=self.modifiers.lum_mod,
        )
        return ColorRef(
            theme_color=self.theme_color,
            hex_rgb=self.hex_rgb,
            modifiers=new_mods,
        )


# =============================================================================
# EXTRA COLOR SCHEME (for extended brand palettes)
# =============================================================================


@dataclass
class ExtraColorSlot:
    """An extra color slot beyond the standard 12.

    OOXML allows additional colors via extLst in theme.xml.
    These can be referenced by name in styles.
    """

    name: str
    color: ColorRef


@dataclass
class ExtraColorScheme:
    """Extended color scheme beyond the standard 12 theme colors.

    The standard theme has 12 colors (dk1, lt1, dk2, lt2, accent1-6, hlink, folHlink).
    This allows adding more colors via the extLst mechanism.

    Useful for:
    - Extended brand palettes (7+ accent colors)
    - Semantic colors (success, warning, error)
    - Department/division colors
    """

    name: str
    colors: List[ExtraColorSlot] = field(default_factory=list)
