"""Hub-and-spoke diagram renderer."""

from __future__ import annotations

import math

from pptx.enum.shapes import MSO_SHAPE
from pptx.enum.text import MSO_ANCHOR, PP_ALIGN
from pptx.slide import Slide
from pptx.util import Inches, Pt

from pptx_mcp.engine.diagrams._common import (
    add_freeform_connector,
    add_textbox,
    brand_color_at,
    text_color_for_shape,
)
from pptx_mcp.engine.layouts import _add_slide_title
from pptx_mcp.engine.shapes import parse_hex_color
from pptx_mcp.models.brand import BrandConfig
from pptx_mcp.models.slides import SlideDefinition


def render_hub_spoke_slide(
    slide: Slide,
    definition: SlideDefinition,
    brand: BrandConfig,
    slide_width: float,
) -> None:
    """Render a hub-and-spoke diagram with a central oval and radial spokes."""
    _add_slide_title(slide, definition.title, brand, slide_width)

    spokes = definition.spokes or []
    hub_label = definition.hub_spoke_center
    if not spokes or not hub_label:
        return

    n = len(spokes)
    sp = brand.spacing
    content_w = slide_width - sp.margin_left - sp.margin_right

    # Center of the diagram area
    cx = sp.margin_left + content_w / 2
    cy = sp.content_top + 2.6

    hub_r = 0.7  # hub oval radius (semi-axis)
    spoke_r = 2.3  # distance from center to spoke centers
    spoke_node_r = 0.55  # spoke oval radius

    # --- hub oval ---
    hub = slide.shapes.add_shape(
        MSO_SHAPE.OVAL,
        Inches(cx - hub_r), Inches(cy - hub_r),
        Inches(hub_r * 2), Inches(hub_r * 2),
    )
    hub.fill.solid()
    hub.fill.fore_color.rgb = parse_hex_color(brand.colors.primary)
    hub.line.fill.background()

    tf = hub.text_frame
    tf.word_wrap = True
    tf.vertical_anchor = MSO_ANCHOR.MIDDLE
    p = tf.paragraphs[0]
    p.text = hub_label
    p.alignment = PP_ALIGN.CENTER
    run = p.runs[0]
    run.font.name = brand.fonts.heading_font
    run.font.size = Pt(13)
    run.font.bold = True
    run.font.color.rgb = parse_hex_color("#FFFFFF")

    # --- spoke nodes ---
    for i, spoke in enumerate(spokes):
        angle = 2 * math.pi * i / n - math.pi / 2  # start from top
        sx = cx + spoke_r * math.cos(angle)
        sy = cy + spoke_r * math.sin(angle)

        # Connector line from hub edge to spoke center
        # Compute point on hub border towards spoke
        hx = cx + hub_r * math.cos(angle)
        hy = cy + hub_r * math.sin(angle)
        # Compute point on spoke border towards hub
        nx = sx - spoke_node_r * math.cos(angle)
        ny = sy - spoke_node_r * math.sin(angle)

        add_freeform_connector(
            slide, hx, hy, nx, ny,
            color=brand.colors.text_secondary,
            width=1.0,
        )

        # Spoke oval
        color_hex = spoke.color or brand_color_at(brand, i)
        node = slide.shapes.add_shape(
            MSO_SHAPE.OVAL,
            Inches(sx - spoke_node_r), Inches(sy - spoke_node_r),
            Inches(spoke_node_r * 2), Inches(spoke_node_r * 2),
        )
        node.fill.solid()
        node.fill.fore_color.rgb = parse_hex_color(color_hex)
        node.line.fill.background()

        tf = node.text_frame
        tf.word_wrap = True
        tf.vertical_anchor = MSO_ANCHOR.MIDDLE
        p = tf.paragraphs[0]
        p.text = spoke.label
        p.alignment = PP_ALIGN.CENTER
        run = p.runs[0]
        run.font.name = brand.fonts.body_font
        run.font.size = Pt(10)
        run.font.bold = True
        run.font.color.rgb = parse_hex_color(text_color_for_shape(color_hex, brand))

        # Optional description near the spoke (below for top-half, above for bottom-half)
        if spoke.description:
            desc_w = spoke_node_r * 2.2
            if math.sin(angle) >= 0:
                desc_y = sy + spoke_node_r + 0.05
            else:
                desc_y = sy - spoke_node_r - 0.38
            add_textbox(
                slide,
                sx - desc_w / 2,
                desc_y,
                desc_w,
                0.35,
                text=spoke.description,
                font_name=brand.fonts.body_font,
                font_size=10,
                color=brand.colors.text_secondary,
            )
