"""Org chart renderer — hierarchical tree layout with cards and connectors."""

from __future__ import annotations

from dataclasses import dataclass, field

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

from pptx_mcp.engine.diagrams._common import add_freeform_connector, 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, set_corner_radius
from pptx_mcp.models.brand import BrandConfig
from pptx_mcp.models.diagrams import OrgNode
from pptx_mcp.models.slides import SlideDefinition

# Card dimensions
CARD_W = 2.0
CARD_H = 0.85
LEVEL_GAP = 1.2
SIBLING_GAP = 0.3

MAX_DEPTH = 6
MAX_NODES = 30


# ---- Buchheim-style tree layout ------------------------------------------------

@dataclass
class _LayoutNode:
    """Internal node with computed layout coordinates."""

    node: OrgNode
    depth: int = 0
    x: float = 0.0
    y: float = 0.0
    children: list[_LayoutNode] = field(default_factory=list)
    mod: float = 0.0  # modifier for subtree shift
    thread: _LayoutNode | None = None
    ancestor: _LayoutNode | None = None
    prelim: float = 0.0
    change: float = 0.0
    shift: float = 0.0
    number: int = 0  # position among siblings (1-based)


def _build_tree(node: OrgNode, depth: int = 0, counter: list[int] | None = None) -> _LayoutNode | None:
    """Recursively build layout tree, enforcing depth and node limits."""
    if counter is None:
        counter = [0]
    if depth > MAX_DEPTH or counter[0] >= MAX_NODES:
        return None
    counter[0] += 1
    ln = _LayoutNode(node=node, depth=depth)
    ln.ancestor = ln
    for child in node.children:
        child_ln = _build_tree(child, depth + 1, counter)
        if child_ln is not None:
            ln.children.append(child_ln)
    return ln


def _first_walk(v: _LayoutNode, distance: float = 1.0) -> None:
    """Buchheim first walk — compute preliminary x positions."""
    if not v.children:
        if v.number > 1:
            # Has a left sibling — handled by caller
            pass
        v.prelim = 0.0
        return

    for i, child in enumerate(v.children):
        child.number = i + 1
        _first_walk(child, distance)

    midpoint = (v.children[0].prelim + v.children[-1].prelim) / 2

    if v.number > 1:
        v.prelim = 0.0  # will be set relative to sibling
    v.prelim = midpoint
    v.mod = v.prelim - midpoint  # 0 for root subtrees


def _compute_positions(root: _LayoutNode) -> None:
    """Simplified Buchheim — assign x/y positions to all nodes.

    Uses a simpler approach: assign positions bottom-up based on
    the widths of subtrees so siblings don't overlap.
    """
    _assign_y(root, 0)
    _assign_x(root)


def _assign_y(node: _LayoutNode, depth: int) -> None:
    """Assign y coordinates based on depth."""
    node.y = depth
    node.depth = depth
    for child in node.children:
        _assign_y(child, depth + 1)


def _subtree_width(node: _LayoutNode) -> float:
    """Compute the width of the subtree rooted at node (in node units)."""
    if not node.children:
        return 1.0
    child_widths = [_subtree_width(c) for c in node.children]
    return sum(child_widths) + SIBLING_GAP / (CARD_W + SIBLING_GAP) * (len(child_widths) - 1)


def _assign_x(node: _LayoutNode, left: float = 0.0) -> None:
    """Assign x coordinates so subtrees don't overlap."""
    if not node.children:
        node.x = left
        return

    # Distribute children across the available width
    child_widths = [_subtree_width(c) for c in node.children]
    total_width = sum(child_widths)
    gap_units = SIBLING_GAP / (CARD_W + SIBLING_GAP)
    total_with_gaps = total_width + gap_units * (len(child_widths) - 1)

    # Center the parent over its children
    start_x = left - total_with_gaps / 2 + child_widths[0] / 2
    # But first assign children
    cursor = left - total_with_gaps / 2

    for i, child in enumerate(node.children):
        cw = child_widths[i]
        child_center = cursor + cw / 2
        _assign_x(child, child_center)
        cursor += cw + gap_units

    # Parent is centered over children
    node.x = (node.children[0].x + node.children[-1].x) / 2


def _collect_nodes(node: _LayoutNode, result: list[_LayoutNode]) -> None:
    """Flatten tree into list."""
    result.append(node)
    for child in node.children:
        _collect_nodes(child, result)


def _normalize_positions(nodes: list[_LayoutNode]) -> None:
    """Shift all x values so minimum is 0."""
    if not nodes:
        return
    min_x = min(n.x for n in nodes)
    for n in nodes:
        n.x -= min_x


# ---- Renderer ------------------------------------------------------------------


def render_org_chart_slide(
    slide: Slide,
    definition: SlideDefinition,
    brand: BrandConfig,
    slide_width: float,
) -> None:
    """Render an org chart from a hierarchical OrgNode tree."""
    _add_slide_title(slide, definition.title, brand, slide_width)

    root_data = definition.org_chart
    if root_data is None:
        return

    root = _build_tree(root_data)
    if root is None:
        return

    _compute_positions(root)

    all_nodes: list[_LayoutNode] = []
    _collect_nodes(root, all_nodes)
    _normalize_positions(all_nodes)

    if not all_nodes:
        return

    # Compute bounding box in node-units
    max_x = max(n.x for n in all_nodes)
    max_y = max(n.y for n in all_nodes)

    sp = brand.spacing
    content_left = sp.margin_left
    content_top = sp.content_top + 0.2
    content_w = slide_width - sp.margin_left - sp.margin_right
    content_h = 7.5 - content_top - sp.margin_bottom

    # Scale node positions into content area
    x_range = max_x if max_x > 0 else 1.0
    y_range = max_y if max_y > 0 else 1.0

    # Available space minus card dimensions
    avail_w = content_w - CARD_W
    avail_h = content_h - CARD_H

    x_scale = avail_w / x_range if x_range > 0 else 0.0
    y_scale = min(LEVEL_GAP, avail_h / y_range) if y_range > 0 else 0.0

    # Center horizontally
    actual_w = x_range * x_scale + CARD_W
    h_offset = content_left + (content_w - actual_w) / 2

    for node in all_nodes:
        px = h_offset + node.x * x_scale
        py = content_top + node.y * y_scale

        # Pick color
        if node.depth == 0:
            fill_hex = node.node.color or brand.colors.accent
        else:
            fill_hex = node.node.color or brand_color_at(brand, node.depth - 1)

        # Draw card
        card = slide.shapes.add_shape(
            MSO_SHAPE.ROUNDED_RECTANGLE,
            Inches(px), Inches(py),
            Inches(CARD_W), Inches(CARD_H),
        )
        card.fill.solid()
        card.fill.fore_color.rgb = parse_hex_color(fill_hex)
        card.line.fill.background()
        set_corner_radius(card, brand.shape_style.card_corner_radius)

        # Text: name (bold) + role
        tf = card.text_frame
        tf.word_wrap = True
        tf.vertical_anchor = MSO_ANCHOR.MIDDLE

        node_text_color = text_color_for_shape(fill_hex, brand)

        p_name = tf.paragraphs[0]
        p_name.text = node.node.name
        p_name.alignment = PP_ALIGN.CENTER
        run_name = p_name.runs[0]
        run_name.font.name = brand.fonts.heading_font
        run_name.font.size = Pt(10)
        run_name.font.bold = True
        run_name.font.color.rgb = parse_hex_color(node_text_color)

        if node.node.role:
            p_role = tf.add_paragraph()
            p_role.text = node.node.role
            p_role.alignment = PP_ALIGN.CENTER
            run_role = p_role.runs[0]
            run_role.font.name = brand.fonts.body_font
            run_role.font.size = Pt(10)
            run_role.font.color.rgb = parse_hex_color(node_text_color)

        # Draw connectors to children
        parent_bottom_x = px + CARD_W / 2
        parent_bottom_y = py + CARD_H

        for child_ln in node.children:
            child_px = h_offset + child_ln.x * x_scale
            child_py = content_top + child_ln.y * y_scale
            child_top_x = child_px + CARD_W / 2
            child_top_y = child_py

            connector_color = brand.colors.text_secondary
            add_freeform_connector(
                slide,
                parent_bottom_x, parent_bottom_y,
                child_top_x, child_top_y,
                color=connector_color,
                width=1.0,
            )
