"""Color manipulation helpers for OOXML DrawingML."""

from __future__ import annotations

from typing import TYPE_CHECKING, Dict

from lxml import etree

from tokenmoulds.emitters.xml_constants import A_NS
from tokenmoulds.emitters.xml_helpers_search import find_element

if TYPE_CHECKING:
    from tokenmoulds.ir.style_primitives import ColorRef


def normalize_hex_color(hex_color: str) -> str:
    """Normalize a hex color string.

    Args:
        hex_color: Hex color with or without # prefix

    Returns:
        Uppercase hex color without # prefix
    """
    return hex_color.lstrip("#").upper()


def set_color_value(
    root: etree._Element,
    color_name: str,
    hex_color: str,
    namespaces: Dict[str, str] | None = None,
) -> bool:
    """Set an OOXML color scheme value.

    Finds the color element by name and updates its srgbClr/@val attribute.
    Handles both sysClr (with lastClr) and srgbClr elements.

    Args:
        root: Theme root element (or clrScheme element)
        color_name: Color name (dk1, lt1, accent1, etc.)
        hex_color: Hex color (with or without #)
        namespaces: Namespace map (defaults to DrawingML)

    Returns:
        True if color was updated

    Example:
        >>> set_color_value(root, 'accent1', '#FF5733')
    """
    ns = namespaces or {"a": A_NS}
    clean_hex = normalize_hex_color(hex_color)

    # Find the color element
    color_el = find_element(root, f".//a:{color_name}", ns)
    if color_el is None:
        return False

    # Update srgbClr if present
    srgb = find_element(color_el, ".//a:srgbClr", ns)
    if srgb is not None:
        srgb.set("val", clean_hex)
        return True

    # Update sysClr lastClr if present
    sys_clr = find_element(color_el, ".//a:sysClr", ns)
    if sys_clr is not None:
        sys_clr.set("lastClr", clean_hex)
        return True

    # Fallback: If color element exists but has no srgbClr/sysClr child,
    # create srgbClr. This is an intentional SSOT exception for handling
    # malformed or minimal templates.
    for child in list(color_el):
        color_el.remove(child)
    srgb = etree.SubElement(color_el, f"{{{A_NS}}}srgbClr")
    srgb.set("val", clean_hex)
    return True


def set_color_slot(
    slot: etree._Element,
    hex_color: str,
    namespaces: Dict[str, str] | None = None,
) -> None:
    """Set color on a specific color slot element (dk1, lt1, accent1, etc.).

    This is a lower-level function that operates directly on a color slot element
    rather than searching for it by name.

    Args:
        slot: The color slot element (e.g., <a:accent1>)
        hex_color: Hex color (with or without #)
        namespaces: Namespace map
    """
    normalized = normalize_hex_color(hex_color)

    # Try sysClr first (dk1/lt1 often use this)
    sys_clr = slot.find(f"{{{A_NS}}}sysClr")
    if sys_clr is not None:
        sys_clr.set("lastClr", normalized)
        return

    # Try srgbClr
    srgb = slot.find(f"{{{A_NS}}}srgbClr")
    if srgb is None:
        # Remove any existing children and create srgbClr
        for child in list(slot):
            slot.remove(child)
        srgb = etree.SubElement(slot, f"{{{A_NS}}}srgbClr")

    srgb.set("val", normalized)


def add_color_modifiers(
    color_el: etree._Element,
    tint: int | None = None,
    shade: int | None = None,
    alpha: int | None = None,
    sat_mod: int | None = None,
    lum_mod: int | None = None,
    sat_off: int | None = None,
    lum_off: int | None = None,
    alpha_off: int | None = None,
) -> None:
    """Add color modifier child elements to a color element.

    OOXML color modifiers are child elements of schemeClr/srgbClr:
    - <a:tint val="50000"/> - Lighten (0-100000)
    - <a:shade val="50000"/> - Darken (0-100000)
    - <a:alpha val="50000"/> - Opacity (0-100000, 100000=opaque)
    - <a:satMod val="150000"/> - Saturation multiplier (100000=100%)
    - <a:lumMod val="75000"/> - Luminance multiplier
    - <a:satOff val="10000"/> - Saturation offset
    - <a:lumOff val="-10000"/> - Luminance offset
    - <a:alphaOff val="10000"/> - Alpha offset

    Args:
        color_el: The color element (schemeClr, srgbClr, or sysClr)
        tint: Tint percentage in OOXML units (0-100000)
        shade: Shade percentage in OOXML units (0-100000)
        alpha: Alpha/opacity in OOXML units (0-100000)
        sat_mod: Saturation modifier percentage (100000=100%)
        lum_mod: Luminance modifier percentage
        sat_off: Saturation offset (-100000 to 100000)
        lum_off: Luminance offset (-100000 to 100000)
        alpha_off: Alpha offset (-100000 to 100000)

    Example:
        >>> # Add 50% tint and 80% opacity to a color
        >>> add_color_modifiers(scheme_clr, tint=50000, alpha=80000)
    """
    # Order matters in OOXML - modifiers are applied in document order
    # Common order: tint/shade, satMod, lumMod, alpha
    if tint is not None:
        el = etree.SubElement(color_el, f"{{{A_NS}}}tint")
        el.set("val", str(tint))

    if shade is not None:
        el = etree.SubElement(color_el, f"{{{A_NS}}}shade")
        el.set("val", str(shade))

    if sat_mod is not None:
        el = etree.SubElement(color_el, f"{{{A_NS}}}satMod")
        el.set("val", str(sat_mod))

    if lum_mod is not None:
        el = etree.SubElement(color_el, f"{{{A_NS}}}lumMod")
        el.set("val", str(lum_mod))

    if sat_off is not None:
        el = etree.SubElement(color_el, f"{{{A_NS}}}satOff")
        el.set("val", str(sat_off))

    if lum_off is not None:
        el = etree.SubElement(color_el, f"{{{A_NS}}}lumOff")
        el.set("val", str(lum_off))

    if alpha is not None:
        el = etree.SubElement(color_el, f"{{{A_NS}}}alpha")
        el.set("val", str(alpha))

    if alpha_off is not None:
        el = etree.SubElement(color_el, f"{{{A_NS}}}alphaOff")
        el.set("val", str(alpha_off))


def create_color_element(
    parent: etree._Element,
    theme_color: str | None = None,
    hex_rgb: str | None = None,
    tint: int | None = None,
    shade: int | None = None,
    alpha: int | None = None,
    sat_mod: int | None = None,
    lum_mod: int | None = None,
) -> etree._Element:
    """Create a complete color element with optional modifiers.

    Creates either <a:schemeClr> (for theme colors) or <a:srgbClr> (for RGB)
    with child modifier elements.

    Args:
        parent: Parent element to append color to
        theme_color: Theme color name (dk1, lt1, accent1, phClr, etc.)
        hex_rgb: Direct RGB hex color (without #)
        tint: Tint percentage in OOXML units
        shade: Shade percentage in OOXML units
        alpha: Alpha/opacity in OOXML units
        sat_mod: Saturation modifier percentage
        lum_mod: Luminance modifier percentage

    Returns:
        The created color element

    Example:
        >>> # Theme color with 20% tint and 50% opacity
        >>> create_color_element(fill_el, theme_color='accent1', tint=20000, alpha=50000)

        >>> # Direct RGB with 30% opacity
        >>> create_color_element(fill_el, hex_rgb='FF5733', alpha=30000)
    """
    if theme_color:
        color_el = etree.SubElement(parent, f"{{{A_NS}}}schemeClr")
        color_el.set("val", theme_color)
    elif hex_rgb:
        color_el = etree.SubElement(parent, f"{{{A_NS}}}srgbClr")
        color_el.set("val", normalize_hex_color(hex_rgb))
    else:
        raise ValueError("Either theme_color or hex_rgb must be provided")

    add_color_modifiers(
        color_el,
        tint=tint,
        shade=shade,
        alpha=alpha,
        sat_mod=sat_mod,
        lum_mod=lum_mod,
    )

    return color_el


def create_color_from_ref(
    parent: etree._Element,
    color_ref: ColorRef,
) -> etree._Element:
    """Create a color element from a ColorRef style primitive.

    This is the primary function for rendering ColorRef objects (from the IR
    style primitives) to OOXML XML elements with full modifier support.

    Args:
        parent: Parent element to append color to
        color_ref: ColorRef from style_primitives with optional modifiers

    Returns:
        The created color element (schemeClr or srgbClr)

    Example:
        >>> from tokenmoulds.ir.style_primitives import ColorRef, ThemeColor
        >>> # Theme color with 20% tint
        >>> color_ref = ColorRef.theme(ThemeColor.ACCENT1, tint=20)
        >>> create_color_from_ref(solid_fill, color_ref)
        >>> # Produces: <a:schemeClr val="accent1"><a:tint val="20000"/></a:schemeClr>
    """
    # Extract modifiers
    mods = color_ref.modifiers

    if color_ref.theme_color:
        return create_color_element(
            parent,
            theme_color=color_ref.theme_color.value,
            tint=mods.tint,
            shade=mods.shade,
            alpha=mods.alpha,
            sat_mod=mods.sat_mod,
            lum_mod=mods.lum_mod,
        )
    elif color_ref.hex_rgb:
        return create_color_element(
            parent,
            hex_rgb=color_ref.hex_rgb,
            tint=mods.tint,
            shade=mods.shade,
            alpha=mods.alpha,
            sat_mod=mods.sat_mod,
            lum_mod=mods.lum_mod,
        )
    else:
        raise ValueError("ColorRef must have either theme_color or hex_rgb set")


# =============================================================================
# EFFECT PRIMITIVES (DrawingML effectLst)
# =============================================================================
