"""DrawingML effect and geometry helpers."""

from __future__ import annotations

from typing import TYPE_CHECKING

from lxml import etree

from tokenmoulds.emitters.xml_constants import A_NS
from tokenmoulds.emitters.xml_helpers_colors import create_color_from_ref

if TYPE_CHECKING:
    from tokenmoulds.ir.style_primitives import (
        Bevel3D,
        CustomGeometry,
        Effects,
        Glow,
        Reflection,
        Shadow,
        SoftEdge,
    )


def create_outer_shadow(
    parent: etree._Element,
    shadow: "Shadow",
) -> etree._Element:
    """Create an outer shadow element (<a:outerShdw>).

    Args:
        parent: Parent element (usually <a:effectLst>)
        shadow: Shadow primitive with all values from design tokens

    Returns:
        The created outerShdw element

    Example:
        >>> # Shadow at 45° with 4pt blur
        >>> create_outer_shadow(effect_lst, shadow)
        >>> # Produces: <a:outerShdw blurRad="50800" dist="38100" dir="2700000" rotWithShape="0">
        >>>            <a:srgbClr val="000000"><a:alpha val="40000"/></a:srgbClr></a:outerShdw>
    """
    shdw = etree.SubElement(parent, f"{{{A_NS}}}outerShdw")
    shdw.set("blurRad", str(shadow.blur_radius))
    shdw.set("dist", str(shadow.distance))
    shdw.set("dir", str(shadow.direction))
    shdw.set("rotWithShape", "1" if shadow.rotate_with_shape else "0")

    # Add color
    create_color_from_ref(shdw, shadow.color)

    return shdw


def create_inner_shadow(
    parent: etree._Element,
    shadow: "Shadow",
) -> etree._Element:
    """Create an inner shadow element (<a:innerShdw>).

    Args:
        parent: Parent element (usually <a:effectLst>)
        shadow: Shadow primitive (must have inner=True)

    Returns:
        The created innerShdw element
    """
    shdw = etree.SubElement(parent, f"{{{A_NS}}}innerShdw")
    shdw.set("blurRad", str(shadow.blur_radius))
    shdw.set("dist", str(shadow.distance))
    shdw.set("dir", str(shadow.direction))

    # Add color
    create_color_from_ref(shdw, shadow.color)

    return shdw


def create_glow(
    parent: etree._Element,
    glow: "Glow",
) -> etree._Element:
    """Create a glow element (<a:glow>).

    Args:
        parent: Parent element (usually <a:effectLst>)
        glow: Glow primitive with radius and color

    Returns:
        The created glow element

    Example:
        >>> create_glow(effect_lst, glow)
        >>> # Produces: <a:glow rad="63500"><a:schemeClr val="accent1"><a:alpha val="40000"/></a:schemeClr></a:glow>
    """
    glow_el = etree.SubElement(parent, f"{{{A_NS}}}glow")
    glow_el.set("rad", str(glow.radius))

    # Add color
    create_color_from_ref(glow_el, glow.color)

    return glow_el


def create_reflection(
    parent: etree._Element,
    reflection: "Reflection",
) -> etree._Element:
    """Create a reflection element (<a:reflection>).

    This is a hidden feature - GUI only offers presets, but XML allows
    full control over all parameters.

    Args:
        parent: Parent element (usually <a:effectLst>)
        reflection: Reflection primitive with all values from design tokens

    Returns:
        The created reflection element

    Example:
        >>> create_reflection(effect_lst, reflection)
        >>> # Produces: <a:reflection blurRad="6350" stA="50000" endA="0" dist="0"
        >>>            dir="5400000" fadeDir="5400000" sx="100000" sy="-100000"
        >>>            algn="b" rotWithShape="1"/>
    """
    refl = etree.SubElement(parent, f"{{{A_NS}}}reflection")
    refl.set("blurRad", str(reflection.blur_radius))
    refl.set("stA", str(reflection.start_alpha))
    refl.set("endA", str(reflection.end_alpha))
    refl.set("dist", str(reflection.distance))
    refl.set("dir", str(reflection.direction))
    refl.set("fadeDir", str(reflection.fade_direction))
    refl.set("sx", str(reflection.scale_x))
    refl.set("sy", str(reflection.scale_y))
    refl.set("algn", reflection.align)
    refl.set("rotWithShape", "1" if reflection.rotate_with_shape else "0")

    return refl


def create_soft_edge(
    parent: etree._Element,
    soft_edge: "SoftEdge",
) -> etree._Element:
    """Create a soft edge element (<a:softEdge>).

    Args:
        parent: Parent element (usually <a:effectLst>)
        soft_edge: SoftEdge primitive with radius

    Returns:
        The created softEdge element

    Example:
        >>> create_soft_edge(effect_lst, soft_edge)
        >>> # Produces: <a:softEdge rad="25400"/>
    """
    edge = etree.SubElement(parent, f"{{{A_NS}}}softEdge")
    edge.set("rad", str(soft_edge.radius))

    return edge


def create_effect_list(
    parent: etree._Element,
    effects: "Effects",
) -> etree._Element:
    """Create an effect list element (<a:effectLst>) with all effects.

    OOXML allows multiple effects per element (compound effects).
    Effects are applied in document order.

    Args:
        parent: Parent element (effectStyle, spPr, etc.)
        effects: Effects primitive with optional shadow, glow, reflection, etc.

    Returns:
        The created effectLst element

    Example:
        >>> # Compound effects (shadow + glow + reflection)
        >>> create_effect_list(effect_style, effects)
    """
    effect_lst = etree.SubElement(parent, f"{{{A_NS}}}effectLst")

    # Add effects in DrawingML order
    # Order: glow, outerShdw, reflection, softEdge, innerShdw
    if effects.glow is not None:
        create_glow(effect_lst, effects.glow)

    if effects.shadow is not None and not effects.shadow.inner:
        create_outer_shadow(effect_lst, effects.shadow)

    if effects.reflection is not None:
        create_reflection(effect_lst, effects.reflection)

    if effects.soft_edge is not None:
        create_soft_edge(effect_lst, effects.soft_edge)

    if effects.inner_shadow is not None:
        create_inner_shadow(effect_lst, effects.inner_shadow)

    return effect_lst


def create_bevel(
    parent: etree._Element,
    bevel: "Bevel3D",
    position: str = "t",
) -> etree._Element:
    """Create a 3D bevel element (<a:bevelT> or <a:bevelB>).

    Args:
        parent: Parent element (usually <a:sp3d>)
        bevel: Bevel3D primitive with preset, width, height
        position: "t" for top bevel, "b" for bottom bevel

    Returns:
        The created bevel element

    Example:
        >>> create_bevel(sp3d, bevel, "t")
        >>> # Produces: <a:bevelT w="76200" h="76200" prst="relaxedInset"/>
    """
    tag = "bevelT" if position == "t" else "bevelB"
    bevel_el = etree.SubElement(parent, f"{{{A_NS}}}{tag}")
    bevel_el.set("w", str(bevel.width))
    bevel_el.set("h", str(bevel.height))
    bevel_el.set("prst", bevel.preset)

    return bevel_el


def create_sp3d(
    parent: etree._Element,
    effects: "Effects",
) -> etree._Element | None:
    """Create 3D shape properties element (<a:sp3d>) if bevels are defined.

    Args:
        parent: Parent element (effectStyle, spPr, etc.)
        effects: Effects primitive with optional bevel_top and bevel_bottom

    Returns:
        The created sp3d element, or None if no 3D effects
    """
    if effects.bevel_top is None and effects.bevel_bottom is None:
        return None

    sp3d = etree.SubElement(parent, f"{{{A_NS}}}sp3d")

    if effects.bevel_top is not None:
        create_bevel(sp3d, effects.bevel_top, "t")

    if effects.bevel_bottom is not None:
        create_bevel(sp3d, effects.bevel_bottom, "b")

    return sp3d


def create_scene3d(
    parent: etree._Element,
    effects: "Effects",
) -> etree._Element | None:
    """Create 3D scene element (<a:scene3d>) if 3D scene is defined.

    Args:
        parent: Parent element (effectStyle, spPr, etc.)
        effects: Effects primitive with optional scene_3d

    Returns:
        The created scene3d element, or None if no 3D scene
    """
    if effects.scene_3d is None:
        return None

    scene = effects.scene_3d
    scene3d = etree.SubElement(parent, f"{{{A_NS}}}scene3d")

    # Camera
    camera = etree.SubElement(scene3d, f"{{{A_NS}}}camera")
    camera.set("prst", scene.camera.preset.value)
    if scene.camera.rotation:
        rot = etree.SubElement(camera, f"{{{A_NS}}}rot")
        rot.set("lat", str(scene.camera.rotation.lat))
        rot.set("lon", str(scene.camera.rotation.lon))
        rot.set("rev", str(scene.camera.rotation.rev))

    # Light rig
    light_rig = etree.SubElement(scene3d, f"{{{A_NS}}}lightRig")
    light_rig.set("rig", scene.light_rig.preset.value)
    light_rig.set("dir", scene.light_rig.direction.value)
    if scene.light_rig.rotation:
        rot = etree.SubElement(light_rig, f"{{{A_NS}}}rot")
        rot.set("lat", str(scene.light_rig.rotation.lat))
        rot.set("lon", str(scene.light_rig.rotation.lon))
        rot.set("rev", str(scene.light_rig.rotation.rev))

    return scene3d


def create_effect_style(
    parent: etree._Element,
    effects: "Effects",
) -> etree._Element:
    """Create a complete effect style element (<a:effectStyle>).

    Used in theme.xml fmtScheme effectStyleLst.
    Includes effectLst, scene3d, and sp3d as needed.

    Args:
        parent: Parent element (effectStyleLst)
        effects: Effects primitive

    Returns:
        The created effectStyle element
    """
    effect_style = etree.SubElement(parent, f"{{{A_NS}}}effectStyle")

    # Effect list (shadow, glow, reflection, soft edge)
    create_effect_list(effect_style, effects)

    # 3D scene (camera, lighting)
    create_scene3d(effect_style, effects)

    # 3D shape properties (bevels)
    create_sp3d(effect_style, effects)

    return effect_style


# =============================================================================
# CUSTOM GEOMETRY (custGeom for shapes)
# =============================================================================


def create_custom_geometry(
    parent: etree._Element,
    geometry: "CustomGeometry",
) -> etree._Element:
    """Create a custom geometry element (<a:custGeom>).

    This is a hidden power feature - allows any vector path,
    not just the ~200 preset shapes in the GUI.

    Args:
        parent: Parent element (usually <p:spPr> or <a:spPr>)
        geometry: CustomGeometry primitive with paths and guides

    Returns:
        The created custGeom element

    Example:
        >>> create_custom_geometry(sp_pr, geometry)
        >>> # Produces: <a:custGeom><a:pathLst><a:path>...</a:path></a:pathLst></a:custGeom>
    """
    from tokenmoulds.ir.style_primitives import PathCommand

    cust_geom = etree.SubElement(parent, f"{{{A_NS}}}custGeom")

    # Adjustment values (adjust handles)
    if geometry.adjust_values:
        av_lst = etree.SubElement(cust_geom, f"{{{A_NS}}}avLst")
        for name, val in geometry.adjust_values.items():
            gd = etree.SubElement(av_lst, f"{{{A_NS}}}gd")
            gd.set("name", name)
            gd.set("fmla", f"val {val}")

    # Guide formulas
    if geometry.guides:
        gd_lst = etree.SubElement(cust_geom, f"{{{A_NS}}}gdLst")
        for name, formula in geometry.guides.items():
            gd = etree.SubElement(gd_lst, f"{{{A_NS}}}gd")
            gd.set("name", name)
            gd.set("fmla", formula)

    # Connection sites (empty for now)
    etree.SubElement(cust_geom, f"{{{A_NS}}}ahLst")
    etree.SubElement(cust_geom, f"{{{A_NS}}}cxnLst")

    # Text rectangle (full shape)
    rect = etree.SubElement(cust_geom, f"{{{A_NS}}}rect")
    rect.set("l", "l")
    rect.set("t", "t")
    rect.set("r", "r")
    rect.set("b", "b")

    # Path list
    path_lst = etree.SubElement(cust_geom, f"{{{A_NS}}}pathLst")

    for geom_path in geometry.paths:
        path = etree.SubElement(path_lst, f"{{{A_NS}}}path")
        path.set("w", "w")
        path.set("h", "h")
        if not geom_path.stroke:
            path.set("stroke", "0")
        if geom_path.fill_mode != "norm":
            path.set("fill", geom_path.fill_mode)

        for segment in geom_path.segments:
            if segment.command == PathCommand.MOVE_TO:
                pt = segment.points[0]
                move = etree.SubElement(path, f"{{{A_NS}}}moveTo")
                pt_el = etree.SubElement(move, f"{{{A_NS}}}pt")
                pt_el.set("x", str(pt.x))
                pt_el.set("y", str(pt.y))

            elif segment.command == PathCommand.LINE_TO:
                pt = segment.points[0]
                line = etree.SubElement(path, f"{{{A_NS}}}lnTo")
                pt_el = etree.SubElement(line, f"{{{A_NS}}}pt")
                pt_el.set("x", str(pt.x))
                pt_el.set("y", str(pt.y))

            elif segment.command == PathCommand.ARC_TO:
                if segment.arc_params:
                    arc = etree.SubElement(path, f"{{{A_NS}}}arcTo")
                    arc.set("wR", str(segment.arc_params.width_radius))
                    arc.set("hR", str(segment.arc_params.height_radius))
                    arc.set("stAng", str(segment.arc_params.start_angle))
                    arc.set("swAng", str(segment.arc_params.sweep_angle))

            elif segment.command == PathCommand.QUAD_BEZIER_TO:
                if len(segment.points) >= 2:
                    qbez = etree.SubElement(path, f"{{{A_NS}}}quadBezTo")
                    for pt in segment.points:
                        pt_el = etree.SubElement(qbez, f"{{{A_NS}}}pt")
                        pt_el.set("x", str(pt.x))
                        pt_el.set("y", str(pt.y))

            elif segment.command == PathCommand.CUBIC_BEZIER_TO:
                if len(segment.points) >= 3:
                    cbez = etree.SubElement(path, f"{{{A_NS}}}cubicBezTo")
                    for pt in segment.points:
                        pt_el = etree.SubElement(cbez, f"{{{A_NS}}}pt")
                        pt_el.set("x", str(pt.x))
                        pt_el.set("y", str(pt.y))

            elif segment.command == PathCommand.CLOSE:
                etree.SubElement(path, f"{{{A_NS}}}close")

    return cust_geom


# =============================================================================
# EXTRA COLOR SCHEMES (extended brand palettes)
# =============================================================================
