"""Style recipes for deriving colors from theme slots.

This module defines "recipes" - patterns of tint/shade that derive colors
from base theme slots. Recipes can be resolved in two ways:

1. **Theme references** (OOXML - Word, PowerPoint, Excel)
   - Output: themeFill="accent1", themeFillTint="99"
   - Colors computed at render time by Office
   - Responsive to theme changes

2. **Computed colors** (ODF - LibreOffice, Google Docs)
   - Output: fo:background-color="#D6E1F0"
   - Colors computed at generation time
   - Static but consistent across suites

This dual approach ensures visual consistency across all office suites
while leveraging theme responsiveness where available.

Example:
    recipe = ColorRecipe(ThemeSlot.ACCENT1, tint=40)

    # For Word (.docx):
    attrs = recipe.to_word_attrs()
    # {"themeFill": "accent1", "themeFillTint": "99"}

    # For LibreOffice (.odt):
    hex_color = recipe.resolve(theme_colors)
    # "#D6E1F0"
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Dict, List, Optional

from tokenmoulds.colors.palette import ThemeSlot


def _apply_tint(r: int, g: int, b: int, tint_pct: float) -> tuple[int, int, int]:
    """Apply tint (mix with white) to RGB color.

    Args:
        r, g, b: RGB values (0-255)
        tint_pct: Tint percentage (0-100, 0=no change, 100=white)

    Returns:
        Tinted RGB tuple
    """
    factor = tint_pct / 100.0
    return (
        int(r + (255 - r) * factor),
        int(g + (255 - g) * factor),
        int(b + (255 - b) * factor),
    )


def _apply_shade(r: int, g: int, b: int, shade_pct: float) -> tuple[int, int, int]:
    """Apply shade (mix with black) to RGB color.

    Args:
        r, g, b: RGB values (0-255)
        shade_pct: Shade percentage (0-100, 0=no change, 100=black)

    Returns:
        Shaded RGB tuple
    """
    factor = 1.0 - (shade_pct / 100.0)
    return (
        int(r * factor),
        int(g * factor),
        int(b * factor),
    )


def _hex_to_rgb(hex_color: str) -> tuple[int, int, int]:
    """Convert hex color to RGB tuple."""
    hex_color = hex_color.lstrip("#")
    return (
        int(hex_color[0:2], 16),
        int(hex_color[2:4], 16),
        int(hex_color[4:6], 16),
    )


def _rgb_to_hex(r: int, g: int, b: int) -> str:
    """Convert RGB tuple to hex color."""
    return f"#{r:02X}{g:02X}{b:02X}"


@dataclass
class ColorRecipe:
    """A recipe for deriving a color from a theme slot.

    This represents a color that references a theme slot and applies
    optional tint/shade modifiers. The actual color is computed at
    render time by the Office application.

    Attributes:
        slot: Theme color slot to reference
        tint: Lighten by mixing with white (0-100, 0=no change, 100=white)
        shade: Darken by mixing with black (0-100, 0=no change, 100=black)

    Note: tint and shade are mutually exclusive in practice.

    Examples:
        # Solid accent1
        ColorRecipe(ThemeSlot.ACCENT1)

        # Light tint of accent1 (40% white mixed in)
        ColorRecipe(ThemeSlot.ACCENT1, tint=40)

        # Dark shade of accent1 (25% black mixed in)
        ColorRecipe(ThemeSlot.ACCENT1, shade=25)
    """

    slot: ThemeSlot
    tint: Optional[float] = None  # 0-100 (percent)
    shade: Optional[float] = None  # 0-100 (percent)

    def to_word_attrs(self) -> dict[str, str]:
        """Convert to Word XML attributes for w:shd element.

        Word uses hex values for themeFillTint/Shade:
        - FF = full color (0% tint)
        - 00 = pure white (100% tint)

        Conversion: hex = int(255 * (1 - percent/100))
        """
        attrs = {"themeFill": self.slot.value}

        if self.tint is not None and self.tint > 0:
            # Word: smaller hex = more tint (lighter)
            hex_val = int(255 * (1 - self.tint / 100))
            attrs["themeFillTint"] = f"{hex_val:02X}"

        if self.shade is not None and self.shade > 0:
            # Word: smaller hex = more shade (darker)
            hex_val = int(255 * (1 - self.shade / 100))
            attrs["themeFillShade"] = f"{hex_val:02X}"

        return attrs

    def to_drawingml_units(self) -> dict[str, str | int]:
        """Convert to DrawingML units for PowerPoint/shapes.

        DrawingML uses 0-100000 (100000 = 100%).
        """
        result: dict[str, str | int] = {"slot": self.slot.value}

        if self.tint is not None and self.tint > 0:
            result["tint"] = int(self.tint * 1000)

        if self.shade is not None and self.shade > 0:
            result["shade"] = int(self.shade * 1000)

        return result

    def resolve(self, theme_colors: Dict[str, str]) -> str:
        """Resolve recipe to actual hex color using theme palette.

        This computes the final color by:
        1. Looking up the base color from theme_colors
        2. Applying tint (mix with white) or shade (mix with black)

        Args:
            theme_colors: Dict mapping slot names to hex colors
                          e.g., {"accent1": "#4472C4", "dk1": "#000000", ...}

        Returns:
            Computed hex color (e.g., "#D6E1F0")

        Example:
            theme = {"accent1": "#4472C4", "lt1": "#FFFFFF"}
            recipe = ColorRecipe(ThemeSlot.ACCENT1, tint=40)
            color = recipe.resolve(theme)  # "#A9B9D9"
        """
        # Get base color from theme
        base_hex = theme_colors.get(self.slot.value)
        if not base_hex:
            # Fallback to common defaults
            defaults = {
                "dk1": "#000000",
                "lt1": "#FFFFFF",
                "dk2": "#44546A",
                "lt2": "#E7E6E6",
                "accent1": "#4472C4",
                "accent2": "#ED7D31",
                "accent3": "#A5A5A5",
                "accent4": "#FFC000",
                "accent5": "#5B9BD5",
                "accent6": "#70AD47",
                "hlink": "#0563C1",
                "folHlink": "#954F72",
            }
            base_hex = defaults.get(self.slot.value, "#000000")

        r, g, b = _hex_to_rgb(base_hex)

        # Apply tint or shade
        if self.tint is not None and self.tint > 0:
            r, g, b = _apply_tint(r, g, b, self.tint)
        elif self.shade is not None and self.shade > 0:
            r, g, b = _apply_shade(r, g, b, self.shade)

        return _rgb_to_hex(r, g, b)


@dataclass
class TableBandingRecipe:
    """Recipe for table row banding colors.

    Defines the tint/shade pattern for alternating rows.
    """

    band1: ColorRecipe  # Odd rows
    band2: ColorRecipe  # Even rows (often transparent/none)

    @classmethod
    def from_brand_tone(
        cls, brand_tone: float, accent: ThemeSlot = ThemeSlot.ACCENT1
    ) -> TableBandingRecipe:
        """Create banding recipe based on brand tone.

        Args:
            brand_tone: 0.0 (conservative) to 1.0 (expressive)
            accent: Theme slot to use for banding

        Returns:
            TableBandingRecipe with appropriate tint levels
        """
        if brand_tone < 0.3:
            # Conservative: very subtle banding
            return cls(
                band1=ColorRecipe(accent, tint=90),  # Very light
                band2=ColorRecipe(ThemeSlot.LT1),  # Background
            )
        elif brand_tone < 0.7:
            # Balanced: moderate banding
            return cls(
                band1=ColorRecipe(accent, tint=80),
                band2=ColorRecipe(accent, tint=95),
            )
        else:
            # Expressive: visible banding
            return cls(
                band1=ColorRecipe(accent, tint=60),
                band2=ColorRecipe(accent, tint=85),
            )


@dataclass
class TableHeaderRecipe:
    """Recipe for table header styling.

    Defines fill and text colors for the header row.
    """

    fill: ColorRecipe
    text: ColorRecipe

    @classmethod
    def from_brand_tone(
        cls, brand_tone: float, accent: ThemeSlot = ThemeSlot.ACCENT1
    ) -> TableHeaderRecipe:
        """Create header recipe based on brand tone.

        Args:
            brand_tone: 0.0 (conservative) to 1.0 (expressive)
            accent: Theme slot to use for header
        """
        if brand_tone < 0.3:
            # Conservative: subtle header, dark text
            return cls(
                fill=ColorRecipe(accent, tint=80),
                text=ColorRecipe(ThemeSlot.DK1),
            )
        elif brand_tone < 0.7:
            # Balanced: medium fill, white text
            return cls(
                fill=ColorRecipe(accent, tint=40),
                text=ColorRecipe(ThemeSlot.LT1),
            )
        else:
            # Expressive: solid accent, white text
            return cls(
                fill=ColorRecipe(accent),  # Full strength
                text=ColorRecipe(ThemeSlot.LT1),
            )


@dataclass
class TableBorderRecipe:
    """Recipe for table border styling.

    Defines colors for horizontal rules (typographically correct tables
    have horizontal rules only - no outer vertical borders).
    """

    top: ColorRecipe
    bottom: ColorRecipe
    header_separator: ColorRecipe
    # Vertical borders: None for typographic tables

    @classmethod
    def from_brand_tone(
        cls, brand_tone: float, accent: ThemeSlot = ThemeSlot.ACCENT1
    ) -> TableBorderRecipe:
        """Create border recipe based on brand tone.

        Follows typographic best practices:
        - Horizontal rules only (no outer vertical borders)
        - Header separator is most prominent
        - Top and bottom rules frame the content
        """
        if brand_tone < 0.3:
            # Conservative: thin, subtle rules using dk2
            return cls(
                top=ColorRecipe(ThemeSlot.DK2, tint=50),
                bottom=ColorRecipe(ThemeSlot.DK2, tint=50),
                header_separator=ColorRecipe(ThemeSlot.DK2),
            )
        elif brand_tone < 0.7:
            # Balanced: accent-colored rules
            return cls(
                top=ColorRecipe(accent, tint=40),
                bottom=ColorRecipe(accent, tint=40),
                header_separator=ColorRecipe(accent),
            )
        else:
            # Expressive: prominent accent rules
            return cls(
                top=ColorRecipe(accent),
                bottom=ColorRecipe(accent),
                header_separator=ColorRecipe(accent, shade=20),
            )


@dataclass
class TableStyleRecipe:
    """Complete recipe for a table style.

    Combines header, banding, and border recipes into a cohesive style.
    """

    name: str
    header: TableHeaderRecipe
    banding: TableBandingRecipe
    borders: TableBorderRecipe

    @classmethod
    def from_brand_tone(
        cls,
        brand_tone: float,
        name: str = "Brand Table",
        accent: ThemeSlot = ThemeSlot.ACCENT1,
    ) -> TableStyleRecipe:
        """Create complete table recipe based on brand tone.

        Args:
            brand_tone: 0.0 (conservative) to 1.0 (expressive)
            name: Style name
            accent: Theme slot to use
        """
        return cls(
            name=name,
            header=TableHeaderRecipe.from_brand_tone(brand_tone, accent),
            banding=TableBandingRecipe.from_brand_tone(brand_tone, accent),
            borders=TableBorderRecipe.from_brand_tone(brand_tone, accent),
        )


@dataclass
class ChartSeriesRecipe:
    """Recipe for chart series colors.

    Defines how to derive 6 chart series colors from theme slots.
    This ensures charts look harmonious with any theme.
    """

    series: List[ColorRecipe] = field(default_factory=list)

    @classmethod
    def default(cls) -> ChartSeriesRecipe:
        """Create default chart series recipe.

        Uses accent1-6 with tint/shade variations for 6+ series.
        This follows Microsoft/Brandwares best practices:
        - Series 1-2: Primary colors (accent1, accent2)
        - Series 3-4: Tints of accent1
        - Series 5-6: Accents 5-6 (utility colors)
        """
        return cls(
            series=[
                ColorRecipe(ThemeSlot.ACCENT1),
                ColorRecipe(ThemeSlot.ACCENT2),
                ColorRecipe(ThemeSlot.ACCENT1, tint=40),
                ColorRecipe(ThemeSlot.ACCENT1, shade=25),
                ColorRecipe(ThemeSlot.ACCENT5),
                ColorRecipe(ThemeSlot.ACCENT6),
            ]
        )

    @classmethod
    def monochromatic(cls, accent: ThemeSlot = ThemeSlot.ACCENT1) -> ChartSeriesRecipe:
        """Create monochromatic chart series from single accent.

        All series are tints/shades of the same color.
        Professional and accessible.
        """
        return cls(
            series=[
                ColorRecipe(accent),
                ColorRecipe(accent, tint=25),
                ColorRecipe(accent, shade=25),
                ColorRecipe(accent, tint=50),
                ColorRecipe(accent, shade=50),
                ColorRecipe(accent, tint=75),
            ]
        )


@dataclass
class TextHierarchyRecipe:
    """Recipe for text color hierarchy.

    Defines primary, secondary, tertiary text colors using
    tint/shade of dk1 for subtle hierarchy.
    """

    primary: ColorRecipe  # Main body text
    secondary: ColorRecipe  # Captions, metadata
    tertiary: ColorRecipe  # Placeholders, disabled

    @classmethod
    def default(cls) -> TextHierarchyRecipe:
        """Create default text hierarchy.

        Uses dk1 with tints for lighter variants.
        """
        return cls(
            primary=ColorRecipe(ThemeSlot.DK1),
            secondary=ColorRecipe(ThemeSlot.DK1, tint=35),
            tertiary=ColorRecipe(ThemeSlot.DK1, tint=55),
        )


@dataclass
class StyleRecipes:
    """Collection of all style recipes for a design system.

    This is the top-level container for all recipe definitions.
    It replaces pre-computed color variations with parameterized
    recipes that are evaluated at render time.
    """

    table: TableStyleRecipe
    chart: ChartSeriesRecipe
    text: TextHierarchyRecipe

    @classmethod
    def from_brand_tone(cls, brand_tone: float) -> StyleRecipes:
        """Create complete recipe collection based on brand tone.

        Args:
            brand_tone: 0.0 (conservative) to 1.0 (expressive)
        """
        return cls(
            table=TableStyleRecipe.from_brand_tone(brand_tone),
            chart=ChartSeriesRecipe.default(),
            text=TextHierarchyRecipe.default(),
        )


__all__ = [
    "ThemeSlot",
    "ColorRecipe",
    "TableBandingRecipe",
    "TableHeaderRecipe",
    "TableBorderRecipe",
    "TableStyleRecipe",
    "ChartSeriesRecipe",
    "TextHierarchyRecipe",
    "StyleRecipes",
]
