"""Bottom-up measure pass — compute min/preferred width and height for each node."""

from __future__ import annotations

from uframe.ir import (
    IRNode, Page, Box, Row, Col, Spacer, Pad,
    Heading, Text, Label, Divider, Link, ListNode, ListItem,
    Gauge, Sparkline, Status, Table,
    Form, Field, Password, Radio, Checkbox, FormButton,
    Let, Source, IfBlock, ForLoop, CacheControl, OnSubmit, StateDecl,
    BigTitle, ImageNode, HNav, VNav,
)
from uframe.fonts import FONT_HEIGHTS, get_text_width
from uframe.imaging import get_image_height


def _text_height(text: str, width: int) -> int:
    """Compute how many lines a text string needs at a given width."""
    if not text or width <= 0:
        return 1
    words = text.split()
    lines = 1
    col = 0
    for word in words:
        wlen = len(word)
        if col > 0 and col + 1 + wlen > width:
            lines += 1
            col = wlen
        elif col == 0:
            col = wlen
        else:
            col += 1 + wlen
    return max(lines, 1)


def measure(node: IRNode, available_width: int) -> None:
    """Recursively compute min/preferred sizes for an IR subtree.

    This is a bottom-up pass: children are measured before their parent.
    """
    # Dispatch to node type
    if isinstance(node, Page):
        w = node.width
        node.pref_width = w
        node.min_width = w
        # Measure children with full page width
        total_h = 0
        for child in node.children:
            measure(child, w)
            total_h += child.pref_height
        node.pref_height = total_h
        node.min_height = total_h

    elif isinstance(node, Box):
        # Box adds 2 chars for borders on each axis (left+right, top+bottom)
        inner_w = available_width - 2
        total_h = 0
        for child in node.children:
            measure(child, inner_w)
            total_h += child.pref_height
        node.pref_width = available_width
        node.min_width = 4  # minimum: border + 2 chars + border
        node.pref_height = total_h + 2  # +2 for top/bottom border
        node.min_height = 3  # top border + 1 line + bottom border

    elif isinstance(node, Row):
        # Children laid out horizontally, split available width
        n = len(node.children)
        if n == 0:
            node.pref_width = available_width
            node.pref_height = 0
            node.min_width = 0
            node.min_height = 0
            return

        gap_total = node.gap * (n - 1)
        usable = available_width - gap_total

        # First pass: measure children to get their preferred sizes
        # Distribute width proportionally or equally
        fixed_width_children = []
        flex_children = []
        fixed_total = 0

        for child in node.children:
            if isinstance(child, Col) and child.col_width is not None:
                fixed_width_children.append(child)
                fixed_total += child.col_width
            else:
                flex_children.append(child)

        flex_each = 0
        if flex_children:
            flex_each = max(1, (usable - fixed_total) // len(flex_children))

        max_h = 0
        for child in node.children:
            if isinstance(child, Col) and child.col_width is not None:
                child_w = child.col_width
            else:
                child_w = flex_each
            measure(child, child_w)
            max_h = max(max_h, child.pref_height)

        node.pref_width = available_width
        node.min_width = n  # at minimum 1 char per child
        node.pref_height = max_h
        node.min_height = max_h

    elif isinstance(node, Col):
        w = node.col_width if node.col_width is not None else available_width
        total_h = 0
        for child in node.children:
            measure(child, w)
            total_h += child.pref_height
        node.pref_width = w
        node.min_width = min(w, 1)
        node.pref_height = total_h
        node.min_height = total_h

    elif isinstance(node, Spacer):
        node.pref_width = available_width
        node.min_width = 0
        node.pref_height = node.lines
        node.min_height = node.lines

    elif isinstance(node, Pad):
        inner_w = available_width - node.left - node.right
        total_h = 0
        for child in node.children:
            measure(child, inner_w)
            total_h += child.pref_height
        node.pref_width = available_width
        node.min_width = node.left + node.right + 1
        node.pref_height = total_h + node.top + node.bottom
        node.min_height = node.top + node.bottom

    elif isinstance(node, Heading):
        node.pref_width = available_width
        node.min_width = len(node.text) + 1
        node.pref_height = 1
        node.min_height = 1

    elif isinstance(node, Text):
        node.pref_width = available_width
        node.min_width = 1
        node.pref_height = _text_height(node.content, available_width)
        node.min_height = 1

    elif isinstance(node, Label):
        node.pref_width = available_width
        node.min_width = len(node.key) + 2 + len(node.value)
        node.pref_height = 1
        node.min_height = 1

    elif isinstance(node, Divider):
        node.pref_width = available_width
        node.min_width = 1
        node.pref_height = 1
        node.min_height = 1

    elif isinstance(node, Link):
        node.pref_width = available_width
        node.min_width = len(node.display) + 2
        node.pref_height = 1
        node.min_height = 1

    elif isinstance(node, ListNode):
        total_h = 0
        for child in node.children:
            measure(child, available_width - 2)  # indent for bullet
            total_h += child.pref_height
        node.pref_width = available_width
        node.min_width = 4
        node.pref_height = total_h
        node.min_height = total_h

    elif isinstance(node, ListItem):
        node.pref_width = available_width
        node.min_width = len(node.content) + 1
        node.pref_height = _text_height(node.content, available_width)
        node.min_height = 1

    elif isinstance(node, Gauge):
        node.pref_width = available_width
        node.min_width = node.bar_width + len(node.label) + 6
        node.pref_height = 1
        node.min_height = 1

    elif isinstance(node, Sparkline):
        node.pref_width = available_width
        node.min_width = node.spark_width + len(node.label) + 4
        node.pref_height = 1
        node.min_height = 1

    elif isinstance(node, Status):
        node.pref_width = available_width
        node.min_width = len(node.label) + 4
        node.pref_height = 1
        node.min_height = 1

    elif isinstance(node, Table):
        num_rows = len(node.rows)
        node.pref_width = available_width
        node.min_width = len(node.columns) * 3 + 1
        node.pref_height = num_rows + 4
        node.min_height = 4

    elif isinstance(node, Form):
        total_h = 0
        for child in node.children:
            measure(child, available_width)
            total_h += child.pref_height
        node.pref_width = available_width
        node.min_width = 10
        node.pref_height = total_h
        node.min_height = total_h

    elif isinstance(node, Field):
        node.pref_width = available_width
        node.min_width = len(node.field_name) + node.field_width + 6
        node.pref_height = 1
        node.min_height = 1

    elif isinstance(node, Password):
        node.pref_width = available_width
        node.min_width = len(node.field_name) + node.field_width + 6
        node.pref_height = 1
        node.min_height = 1

    elif isinstance(node, Radio):
        node.pref_width = available_width
        node.min_width = len(node.group) + sum(len(o) + 6 for o in node.options)
        node.pref_height = 1
        node.min_height = 1

    elif isinstance(node, Checkbox):
        node.pref_width = available_width
        node.min_width = len(node.checkbox_label) + 6
        node.pref_height = 1
        node.min_height = 1

    elif isinstance(node, FormButton):
        node.pref_width = available_width
        node.min_width = len(node.button_label) + 6
        node.pref_height = 1
        node.min_height = 1

    elif isinstance(node, HNav):
        # Height: 3 for bar/tabs/pills (top border + items + bottom border), 2 for underline/breadcrumb
        if node.nav_style in ("underline",):
            node.pref_height = 2
        elif node.nav_style in ("breadcrumb",):
            node.pref_height = 1
        else:
            node.pref_height = 3
        node.pref_width = available_width
        node.min_width = 10
        node.min_height = 1

    elif isinstance(node, VNav):
        count = sum(1 for it in node.items if it.kind in ("item", "heading"))
        sep_count = sum(1 for it in node.items if it.kind == "separator")
        h = count + sep_count
        if node.nav_style == "boxed":
            h += 2  # top + bottom border
        node.pref_height = max(h, 1)
        if node.nav_width > 0:
            node.pref_width = node.nav_width
        else:
            max_label = max((len(it.label) for it in node.items if it.label), default=8)
            node.pref_width = max_label + 6  # marker + padding
        node.min_width = 8
        node.min_height = 1

    elif isinstance(node, ImageNode):
        h = get_image_height(node.path, node.mode, node.img_width)
        if node.caption:
            h += 1  # extra line for caption
        node.pref_width = available_width
        node.min_width = node.img_width
        node.pref_height = h
        node.min_height = 1

    elif isinstance(node, BigTitle):
        # Try the requested font, fall back to smaller if too wide
        tw = get_text_width(node.text, node.font)
        if tw <= available_width:
            h = FONT_HEIGHTS.get(node.font, 6)
        elif get_text_width(node.text, "pixel") <= available_width:
            h = FONT_HEIGHTS.get("pixel", 3)
        elif get_text_width(node.text, "thin") <= available_width:
            h = FONT_HEIGHTS.get("thin", 3)
        else:
            h = 1  # fallback to single styled line
        node.pref_width = available_width
        node.min_width = 1
        node.pref_height = h
        node.min_height = 1

    elif isinstance(node, (Let, Source, CacheControl, StateDecl)):
        # Zero-height metadata nodes — no visual output
        node.pref_width = 0
        node.min_width = 0
        node.pref_height = 0
        node.min_height = 0

    elif isinstance(node, (IfBlock, ForLoop, OnSubmit)):
        # Container nodes — height = sum of children
        total_h = 0
        for child in node.children:
            measure(child, available_width)
            total_h += child.pref_height
        node.pref_width = available_width
        node.min_width = 1
        node.pref_height = total_h
        node.min_height = 0

    else:
        # Generic: just measure children
        total_h = 0
        for child in node.children:
            measure(child, available_width)
            total_h += child.pref_height
        node.pref_width = available_width
        node.min_width = 1
        node.pref_height = max(total_h, 1)
        node.min_height = 1
