"""PowerPoint Table Styles with Typographic Correctness.

This module provides table styling for PowerPoint templates:
- Table text defaults via otherStyle in slide masters
- Typographically correct table styles (no outer verticals, horizontal rules)
- Brand tone derivation for style intensity

Typographic principles (Tufte, Butterick):
- No outer vertical borders
- Horizontal rules: top, header separator, bottom
- No internal vertical gridlines
- Subtle banding for readability in data-heavy tables

Brand tone (0.0 → 1.0) controls visual intensity:
- Conservative (0.0): Thin rules only, no fills
- Balanced (0.5): Subtle header tint, light banding
- Expressive (1.0): Solid header fill, clear banding

All colors reference theme tokens so styles adapt to brand colors.
"""

from __future__ import annotations

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

from lxml import etree

from tokenmoulds.design.units import (
    pt_to_hundredths,
    pt_to_emu,
)
from tokenmoulds.ir.style_primitives import ColorRef, ThemeColor

# XML Namespaces
NS_A = "http://schemas.openxmlformats.org/drawingml/2006/main"
NS_P = "http://schemas.openxmlformats.org/presentationml/2006/main"


@dataclass
class TableTextTokens:
    """Typography tokens for table text.

    These values control the default appearance of text when users
    insert tables in PowerPoint. Without explicit defaults, Office
    uses Arial 18pt which looks unprofessional.

    All values should be derived from design tokens.
    """

    # Font settings
    font_family: str
    body_size_pt: float
    header_size_pt: float
    header_bold: bool = True

    # Alignment
    alignment: str = "l"  # l=left, ctr=center, r=right
    capitalization: str = "none"  # none, all, small

    # Spacing (from baseline grid)
    indent_per_level_emu: int = 457200  # 0.5 inch default
    space_before_pt: float = 0.0
    space_after_pt: float = 0.0

    # Text color
    text_color_scheme: str = "tx1"  # scheme color reference

    @property
    def body_size_hundredths(self) -> int:
        """Body font size in hundredths of points (OOXML format)."""
        return pt_to_hundredths(self.body_size_pt)

    @property
    def header_size_hundredths(self) -> int:
        """Header font size in hundredths of points."""
        return pt_to_hundredths(self.header_size_pt)

    @property
    def space_before_hundredths(self) -> int:
        """Space before in hundredths of points."""
        return pt_to_hundredths(self.space_before_pt)

    @property
    def space_after_hundredths(self) -> int:
        """Space after in hundredths of points."""
        return pt_to_hundredths(self.space_after_pt)


@dataclass
class TableBorderTokens:
    """Border configuration for typographically correct tables.

    Follows typographic principles:
    - Horizontal rules only (top, header separator, bottom)
    - No vertical borders
    - Color references theme tokens
    """

    # Border widths in EMU (0 = no border)
    top_width_emu: int = 0
    bottom_width_emu: int = 0
    header_separator_width_emu: int = 0

    # No vertical borders by default (typographic correctness)
    left_width_emu: int = 0
    right_width_emu: int = 0
    inside_vertical_width_emu: int = 0
    inside_horizontal_width_emu: int = 0

    # Border color (theme reference)
    color: Optional[ColorRef] = None


@dataclass
class TableCellTokens:
    """Token-driven cell style definition using unified ColorRef system."""

    # Text color (unified ColorRef)
    text_color: Optional[ColorRef] = None
    bold: bool = False

    # Fill color (unified ColorRef with full modifier support)
    fill: Optional[ColorRef] = None

    # Borders (typographically correct by default)
    borders: Optional[TableBorderTokens] = None


@dataclass
class TableStyleTokens:
    """Complete token-driven table style definition.

    Designed for typographic correctness:
    - No outer vertical borders
    - Horizontal rules at top, header separator, bottom
    - Theme color references for brand adaptability
    """

    style_id: str
    style_name: str

    # Whole table defaults
    whole_table: TableCellTokens = field(default_factory=TableCellTokens)

    # Header row (first row)
    first_row: Optional[TableCellTokens] = None

    # Last row (totals)
    last_row: Optional[TableCellTokens] = None

    # Banding for readability
    band1_horizontal: Optional[TableCellTokens] = None
    band2_horizontal: Optional[TableCellTokens] = None


def derive_table_style_from_brand_tone(
    brand_tone: float,
    border_width_pt: float = 0.75,
    accent: ThemeColor = ThemeColor.ACCENT1,
) -> TableStyleTokens:
    """Derive a typographically correct table style from brand tone.

    Args:
        brand_tone: 0.0 (conservative) to 1.0 (expressive)
        border_width_pt: Base border width in points
        accent: Theme accent color for fills

    Returns:
        TableStyleTokens with typographically correct configuration

    Brand tone effects:
        0.0 (conservative): Thin rules, no fills, minimal visual weight
        0.5 (balanced): Medium rules, subtle header tint, light banding
        1.0 (expressive): Solid header fill, prominent rules, clear banding
    """
    # Clamp brand_tone to valid range
    brand_tone = max(0.0, min(1.0, brand_tone))

    # Scale border width by brand tone (thinner = more conservative)
    # Range: 0.5pt (conservative) to 1.0pt (expressive)
    effective_border_pt = border_width_pt * (0.67 + 0.44 * brand_tone)
    border_emu = pt_to_emu(effective_border_pt)

    # Rule color: dk1 for conservative, accent for expressive
    if brand_tone < 0.3:
        rule_color = ColorRef.theme(ThemeColor.DK1, alpha=60)
    elif brand_tone < 0.7:
        rule_color = ColorRef.theme(ThemeColor.DK1)
    else:
        rule_color = ColorRef.theme(accent)

    # Typographically correct borders: horizontal rules only
    table_borders = TableBorderTokens(
        top_width_emu=border_emu,
        bottom_width_emu=border_emu,
        header_separator_width_emu=border_emu,
        # No vertical borders
        left_width_emu=0,
        right_width_emu=0,
        inside_vertical_width_emu=0,
        inside_horizontal_width_emu=0,
        color=rule_color,
    )

    # Whole table: text color, borders
    whole_table = TableCellTokens(
        text_color=ColorRef.theme(ThemeColor.DK1),
        borders=table_borders,
    )

    # Header row: varies by brand tone
    if brand_tone < 0.2:
        # Conservative: bold text only, no fill
        first_row = TableCellTokens(
            text_color=ColorRef.theme(ThemeColor.DK1),
            bold=True,
        )
    elif brand_tone < 0.6:
        # Balanced: subtle tint fill
        # Tint decreases (more color) as brand_tone increases
        tint_percent = 90 - (brand_tone * 40)  # 90% → 66%
        first_row = TableCellTokens(
            text_color=ColorRef.theme(ThemeColor.DK1),
            fill=ColorRef.theme(accent, tint=tint_percent),
            bold=True,
        )
    else:
        # Expressive: solid accent fill with light text
        shade_percent = 100 - ((brand_tone - 0.6) * 25)  # 100% → 90%
        first_row = TableCellTokens(
            text_color=ColorRef.theme(ThemeColor.LT1),
            fill=ColorRef.theme(accent, shade=shade_percent)
            if shade_percent < 100
            else ColorRef.theme(accent),
            bold=True,
        )

    # Banding: only for brand_tone > 0.3
    band1_horizontal = None
    if brand_tone > 0.3:
        # Subtle banding: very light tint of accent
        # Tint decreases (more visible) as brand_tone increases
        banding_tint = 95 - (brand_tone * 15)  # 95% → 80%
        band1_horizontal = TableCellTokens(
            fill=ColorRef.theme(accent, tint=banding_tint),
        )

    return TableStyleTokens(
        style_id="{5940675A-B579-460E-94D1-54222C63F5DA}",
        style_name="Professional",
        whole_table=whole_table,
        first_row=first_row,
        band1_horizontal=band1_horizontal,
    )


# Mapping from ColorModifiers field → OOXML element name.
# Order follows the OOXML spec (hue → sat → lum → tint/shade → RGB → alpha → special).
_COLOR_MOD_FIELDS: list[tuple[str, str]] = [
    # (dataclass field, DrawingML element)
    ("hue", "hue"),
    ("hue_off", "hueOff"),
    ("hue_mod", "hueMod"),
    ("sat", "sat"),
    ("sat_off", "satOff"),
    ("sat_mod", "satMod"),
    ("lum", "lum"),
    ("lum_off", "lumOff"),
    ("lum_mod", "lumMod"),
    ("tint", "tint"),
    ("shade", "shade"),
    ("red", "red"),
    ("red_off", "redOff"),
    ("red_mod", "redMod"),
    ("green", "green"),
    ("green_off", "greenOff"),
    ("green_mod", "greenMod"),
    ("blue", "blue"),
    ("blue_off", "blueOff"),
    ("blue_mod", "blueMod"),
    ("alpha", "alpha"),
    ("alpha_off", "alphaOff"),
    ("alpha_mod", "alphaMod"),
]
_COLOR_MOD_FLAGS: list[tuple[str, str]] = [
    ("comp", "comp"),
    ("inv", "inv"),
    ("gray", "gray"),
    ("gamma", "gamma"),
    ("inv_gamma", "invGamma"),
]


class TableStyleGenerator:
    """Generator for PowerPoint table styles.

    Generates:
    1. otherStyle XML for slide masters (table text defaults)
    2. tableStyles.xml with typographically correct styles

    Example:
        tokens = TableTextTokens(
            font_family=document.body_style.font_family,
            body_size_pt=document.body_style.font_size_pt,
            header_size_pt=document.body_style.font_size_pt + 1.0,
        )
        generator = TableStyleGenerator(tokens)
        other_style_xml = generator.generate_other_style()

        # With brand tone derivation
        style = derive_table_style_from_brand_tone(brand_tone=0.5)
        table_styles_xml = generator.generate_table_styles_xml([style])
    """

    def __init__(self, text_tokens: TableTextTokens) -> None:
        """Initialize with text tokens."""
        self.tokens = text_tokens

    def generate_other_style(self) -> bytes:
        """Generate otherStyle XML for slide master.

        Controls default table text formatting in PowerPoint.
        """
        other_style = etree.Element(f"{{{NS_P}}}otherStyle")

        for level in range(1, 10):
            lvl_ppr = self._create_level_ppr(level)
            other_style.append(lvl_ppr)

        return etree.tostring(other_style, encoding="UTF-8")

    def _create_level_ppr(self, level: int) -> etree._Element:
        """Create paragraph properties for a specific level."""
        indent_emu = (level - 1) * self.tokens.indent_per_level_emu

        lvl = etree.Element(
            f"{{{NS_A}}}lvl{level}pPr", marL=str(indent_emu), algn=self.tokens.alignment
        )

        spc_bef = etree.SubElement(lvl, f"{{{NS_A}}}spcBef")
        etree.SubElement(
            spc_bef, f"{{{NS_A}}}spcPts", val=str(self.tokens.space_before_hundredths)
        )

        spc_aft = etree.SubElement(lvl, f"{{{NS_A}}}spcAft")
        etree.SubElement(
            spc_aft, f"{{{NS_A}}}spcPts", val=str(self.tokens.space_after_hundredths)
        )

        def_rpr = etree.SubElement(
            lvl,
            f"{{{NS_A}}}defRPr",
            sz=str(self.tokens.body_size_hundredths),
            kern="1200",
        )

        if self.tokens.capitalization != "none":
            def_rpr.set("cap", self.tokens.capitalization)

        solid_fill = etree.SubElement(def_rpr, f"{{{NS_A}}}solidFill")
        etree.SubElement(
            solid_fill, f"{{{NS_A}}}schemeClr", val=self.tokens.text_color_scheme
        )

        etree.SubElement(def_rpr, f"{{{NS_A}}}latin", typeface="+mn-lt")
        etree.SubElement(def_rpr, f"{{{NS_A}}}ea", typeface="+mn-ea")
        etree.SubElement(def_rpr, f"{{{NS_A}}}cs", typeface="+mn-cs")

        return lvl

    def generate_table_styles_xml(
        self,
        styles: List[TableStyleTokens],
        default_style_id: Optional[str] = None,
    ) -> bytes:
        """Generate tableStyles.xml with typographically correct styles.

        Args:
            styles: List of table style definitions
            default_style_id: ID of default style (uses first if not specified)

        Returns:
            XML bytes for tableStyles.xml
        """
        if default_style_id is None and styles:
            default_style_id = styles[0].style_id

        root = etree.Element(f"{{{NS_A}}}tblStyleLst")
        if default_style_id:
            root.set("def", default_style_id)

        for style in styles:
            tbl_style = self._create_table_style(style)
            root.append(tbl_style)

        return etree.tostring(root, encoding="UTF-8", xml_declaration=True)

    def _create_table_style(self, style: TableStyleTokens) -> etree._Element:
        """Create a table style element with typographically correct borders."""
        tbl_style = etree.Element(
            f"{{{NS_A}}}tblStyle", styleId=style.style_id, styleName=style.style_name
        )

        # Whole table defaults
        whole_tbl = etree.SubElement(tbl_style, f"{{{NS_A}}}wholeTbl")
        self._add_cell_style(whole_tbl, style.whole_table, include_borders=True)

        # Horizontal banding (order matters for schema validation)
        if style.band1_horizontal:
            band1h = etree.SubElement(tbl_style, f"{{{NS_A}}}band1H")
            self._add_cell_style(band1h, style.band1_horizontal, text_style=False)

        if style.band2_horizontal:
            band2h = etree.SubElement(tbl_style, f"{{{NS_A}}}band2H")
            self._add_cell_style(band2h, style.band2_horizontal, text_style=False)

        # Last row (totals)
        if style.last_row:
            last_row = etree.SubElement(tbl_style, f"{{{NS_A}}}lastRow")
            self._add_cell_style(last_row, style.last_row)

        # First row (header)
        if style.first_row:
            first_row = etree.SubElement(tbl_style, f"{{{NS_A}}}firstRow")
            self._add_cell_style(first_row, style.first_row)

        return tbl_style

    def _add_cell_style(
        self,
        parent: etree._Element,
        cell: TableCellTokens,
        text_style: bool = True,
        include_borders: bool = False,
    ) -> None:
        """Add cell styling to a table region element."""
        # Text style
        if text_style and cell.text_color:
            tc_tx_style = etree.SubElement(parent, f"{{{NS_A}}}tcTxStyle")
            if cell.bold:
                tc_tx_style.set("b", "on")

            font_ref = etree.SubElement(tc_tx_style, f"{{{NS_A}}}fontRef", idx="minor")
            self._add_color_ref(font_ref, cell.text_color)

        # Cell style (fill and borders)
        tc_style = etree.SubElement(parent, f"{{{NS_A}}}tcStyle")

        # Fill
        if cell.fill:
            fill_el = etree.SubElement(tc_style, f"{{{NS_A}}}fill")
            solid_fill = etree.SubElement(fill_el, f"{{{NS_A}}}solidFill")
            self._add_color_ref(solid_fill, cell.fill)

        # Borders (typographically correct)
        if include_borders and cell.borders:
            self._add_typographic_borders(tc_style, cell.borders)

    def _add_typographic_borders(
        self,
        tc_style: etree._Element,
        borders: TableBorderTokens,
    ) -> None:
        """Add typographically correct borders (horizontal rules only)."""
        tc_bdr = etree.SubElement(tc_style, f"{{{NS_A}}}tcBdr")

        # Element order follows ECMA-376 CT_TableCellBorderStyle sequence.
        for side, width in (
            ("left", borders.left_width_emu),
            ("right", borders.right_width_emu),
        ):
            side_el = etree.SubElement(tc_bdr, f"{{{NS_A}}}{side}")
            if width > 0:
                self._add_border_line(side_el, width, borders.color)
            else:
                etree.SubElement(side_el, f"{{{NS_A}}}ln", w="0")

        # Top border
        if borders.top_width_emu > 0:
            top = etree.SubElement(tc_bdr, f"{{{NS_A}}}top")
            self._add_border_line(top, borders.top_width_emu, borders.color)

        # Bottom border
        if borders.bottom_width_emu > 0:
            bottom = etree.SubElement(tc_bdr, f"{{{NS_A}}}bottom")
            self._add_border_line(bottom, borders.bottom_width_emu, borders.color)

        # Inside horizontal (between rows, header separator)
        if borders.header_separator_width_emu > 0:
            insideH = etree.SubElement(tc_bdr, f"{{{NS_A}}}insideH")
            self._add_border_line(
                insideH, borders.header_separator_width_emu, borders.color
            )

        # Explicitly set no vertical borders
        inside_v = etree.SubElement(tc_bdr, f"{{{NS_A}}}insideV")
        if borders.inside_vertical_width_emu > 0:
            self._add_border_line(
                inside_v,
                borders.inside_vertical_width_emu,
                borders.color,
            )
        else:
            etree.SubElement(inside_v, f"{{{NS_A}}}ln", w="0")

    def _add_border_line(
        self,
        parent: etree._Element,
        width_emu: int,
        color: Optional[ColorRef],
    ) -> None:
        """Add a border line element."""
        ln = etree.SubElement(
            parent,
            f"{{{NS_A}}}ln",
            w=str(width_emu),
            cap="flat",
            cmpd="sng",
            algn="ctr",
        )

        if color:
            solid_fill = etree.SubElement(ln, f"{{{NS_A}}}solidFill")
            self._add_color_ref(solid_fill, color)

        etree.SubElement(ln, f"{{{NS_A}}}prstDash", val="solid")

    def _add_color_ref(self, parent: etree._Element, color_ref: ColorRef) -> None:
        """Add a ColorRef to an element with full OOXML modifier support."""
        if color_ref.theme_color:
            color_el = etree.SubElement(
                parent, f"{{{NS_A}}}schemeClr", val=color_ref.theme_color.value
            )
        elif color_ref.hex_rgb:
            color_el = etree.SubElement(
                parent, f"{{{NS_A}}}srgbClr", val=color_ref.hex_rgb
            )
        else:
            return

        mods = color_ref.modifiers
        for mod_field, tag in _COLOR_MOD_FIELDS:
            val = getattr(mods, mod_field)
            if val is not None:
                etree.SubElement(color_el, f"{{{NS_A}}}{tag}", val=str(val))
        for mod_field, tag in _COLOR_MOD_FLAGS:
            if getattr(mods, mod_field):
                etree.SubElement(color_el, f"{{{NS_A}}}{tag}")


__all__ = [
    # Token classes
    "TableTextTokens",
    "TableBorderTokens",
    "TableCellTokens",
    "TableStyleTokens",
    # Generator
    "TableStyleGenerator",
    # Derivation function
    "derive_table_style_from_brand_tone",
    # Constants
    "NS_A",
    "NS_P",
]
