"""µFrame — A DSL for rich terminal UIs rendered as ASCII and Micron.

Public API:
    compile(source, width=64) → CompileResult
"""

from __future__ import annotations

from dataclasses import dataclass, field

from uframe.errors import CompileWarning, UFrameError
from uframe.parser import parse
from uframe.measure import measure
from uframe.layout import layout
from uframe.paint import paint
from uframe.borders import merge_borders
from uframe.grid import CharGrid
from uframe.emit_ascii import emit_ascii
from uframe.emit_micron import emit_micron
from uframe.ir import (
    IRNode, Field, Password, Radio, Checkbox, FormButton,
    Source, IfBlock, ForLoop, OnSubmit, StateDecl, CacheControl,
)
from uframe.themes import get_theme, ThemeDef


@dataclass
class CompileResult:
    """Result of compiling a .uf source."""
    ascii: str = ""
    micron: str = ""
    script: str = ""   # generated Python script (dynamic mode only)
    is_dynamic: bool = False
    warnings: list[CompileWarning] = field(default_factory=list)


def _has_dynamic_nodes(node: IRNode) -> bool:
    """Check if the IR tree contains any dynamic nodes."""
    if isinstance(node, (Source, IfBlock, ForLoop, OnSubmit, StateDecl, CacheControl)):
        return True
    return any(_has_dynamic_nodes(child) for child in node.children)


def _collect_form_nodes(node: IRNode) -> dict[int, IRNode]:
    """Walk the IR tree and collect form nodes keyed by their y position."""
    result: dict[int, IRNode] = {}
    if isinstance(node, (Field, Password, Radio, Checkbox, FormButton)):
        result[node.rect.y] = node
    for child in node.children:
        result.update(_collect_form_nodes(child))
    return result


def _micron_form_line(node: IRNode) -> str:
    """Generate the Micron form tag for a form node."""
    if isinstance(node, Field):
        w = node.field_width
        name = node.field_name
        ph = node.placeholder or name
        return f"{name}: `<{w}|{name}`{ph}>"
    elif isinstance(node, Password):
        w = node.field_width
        name = node.field_name
        ph = node.placeholder or name
        return f"{name}: `<!{w}|{name}`{ph}>"
    elif isinstance(node, Radio):
        parts = []
        for i, opt in enumerate(node.options):
            val = opt.lower().replace(" ", "_")
            checked = "|*" if i == 0 else ""
            parts.append(f"`<^|{node.group}|{val}{checked}`{opt}>")
        return f"{node.group}: {'  '.join(parts)}"
    elif isinstance(node, Checkbox):
        name = node.field_name
        label = node.checkbox_label
        checked = "|*" if node.checked else ""
        return f"`<?|{name}|yes{checked}`{label}>"
    elif isinstance(node, FormButton):
        return f"`[`!{node.button_label}`!`:{node.dest}]"
    return ""


def compile(source: str, width: int = 64, theme: str = "") -> CompileResult:
    """Compile a µFrame .uf source string into ASCII and Micron output.

    Args:
        source: the .uf DSL source text
        width: page width in characters (default 64)

    Returns:
        CompileResult with ascii, micron, and any warnings

    Raises:
        ParseError: if the source cannot be parsed
        LayoutError: if layout constraints fail
    """
    warnings: list[CompileWarning] = []

    # 1. Parse
    page = parse(source)
    if page.width == 64 and width != 64:
        page.width = width

    w = page.width

    # 1b. Resolve theme (CLI flag overrides source directive)
    theme_name = theme or page.theme_name or "default"
    theme_def = get_theme(theme_name)

    # 2. Measure
    measure(page, w)

    # 3. Layout
    total_h = layout(page, 0, 0, w, page.pref_height + 100)

    # 4. Create grid and paint
    grid = CharGrid(w, max(total_h, 1))
    paint(page, grid, theme_def)

    # 5. Merge borders
    merge_borders(grid)

    # 6. Emit
    ascii_out = emit_ascii(grid)
    micron_out = emit_micron(grid, page_title=page.title)

    # 7. Post-pass: replace form element lines in Micron output
    form_nodes = _collect_form_nodes(page)
    if form_nodes:
        micron_lines = micron_out.split("\n")
        for row_y, form_node in form_nodes.items():
            if 0 <= row_y < len(micron_lines):
                micron_lines[row_y] = _micron_form_line(form_node)
        micron_out = "\n".join(micron_lines)

    # 8. Check if this page has dynamic features
    is_dynamic = _has_dynamic_nodes(page)
    script = ""
    if is_dynamic:
        from uframe.codegen import compile_dynamic
        script = compile_dynamic(page)

    return CompileResult(
        ascii=ascii_out,
        micron=micron_out,
        script=script,
        is_dynamic=is_dynamic,
        warnings=warnings,
    )
