"""
Prettifiers that produce s-expression source for syntax-highlighted DOM.

``sx_to_pretty_sx(source)``  — parse sx, emit sx that renders as coloured DOM.
``json_to_pretty_sx(json_str)`` — parse JSON, emit sx that renders as coloured DOM.

The output is *not* HTML — it's sx source that, when evaluated and rendered by
the sx engine, produces ``<pre>`` blocks with ``<span>`` elements carrying
CSS classes for syntax highlighting.
"""
from __future__ import annotations

import json
from typing import Any

from .parser import parse, serialize
from .types import Keyword, Symbol, NIL


# ---------------------------------------------------------------------------
# Helpers — build sx AST (lists), then serialize once at the end
# ---------------------------------------------------------------------------

def _span(cls: str, text: str) -> list:
    """Build an sx AST node: (span :class "cls" "text")."""
    return [Symbol("span"), Keyword("class"), cls, text]


def _str_display(cls: str, value: str) -> list:
    """Build a span showing a quoted string value.

    Uses curly quotes so the display delimiters don't conflict with
    sx string syntax.
    """
    return [Symbol("span"), Keyword("class"), cls,
            [Symbol("span"), Keyword("class"), f"{cls}-q", "\u201c"],
            value,
            [Symbol("span"), Keyword("class"), f"{cls}-q", "\u201d"]]


# ---------------------------------------------------------------------------
# S-expression prettifier
# ---------------------------------------------------------------------------

def sx_to_pretty_sx(source: str) -> str:
    """Parse *source* as sx and return sx source that renders as highlighted DOM."""
    try:
        expr = parse(source)
    except Exception:
        return serialize([Symbol("pre"), Keyword("class"), "sx-pretty", source])
    inner = _sx_node(expr, depth=0)
    return serialize([Symbol("pre"), Keyword("class"), "sx-pretty", inner])


def _sx_node(expr: Any, depth: int) -> list:
    """Recursively convert a parsed sx value to an sx AST for pretty display."""
    if isinstance(expr, list):
        if not expr:
            return [_span("sx-paren", "("), _span("sx-paren", ")")]
        return _sx_list(expr, depth)
    if isinstance(expr, Symbol):
        return _span("sx-sym", expr.name)
    if isinstance(expr, Keyword):
        return _span("sx-kw", f":{expr.name}")
    if isinstance(expr, str):
        return _str_display("sx-str", expr)
    if isinstance(expr, bool):
        return _span("sx-bool", "true" if expr else "false")
    if isinstance(expr, (int, float)):
        return _span("sx-num", str(expr))
    if expr is None or expr is NIL:
        return _span("sx-sym", "nil")
    return _span("sx-sym", str(expr))


def _sx_list(items: list, depth: int) -> list:
    """Format a list as a prettified sx AST node."""
    children: list = []
    for item in items:
        children.append(_sx_node(item, depth + 1))
    indent_style = f"margin-left: {depth * 16}px"
    return [Symbol("div"), Keyword("class"), "sx-list",
            Keyword("style"), indent_style,
            _span("sx-paren", "("),
            *children,
            _span("sx-paren", ")")]


# ---------------------------------------------------------------------------
# JSON prettifier
# ---------------------------------------------------------------------------

def json_to_pretty_sx(json_str: str) -> str:
    """Parse *json_str* as JSON and return sx source that renders as highlighted DOM."""
    try:
        data = json.loads(json_str)
    except (json.JSONDecodeError, TypeError):
        return serialize([Symbol("pre"), Keyword("class"), "json-pretty",
                          json_str or ""])
    inner = _json_node(data, depth=0)
    return serialize([Symbol("pre"), Keyword("class"), "json-pretty", inner])


def _json_node(val: Any, depth: int) -> list:
    """Recursively convert a JSON value to an sx AST for pretty display."""
    if isinstance(val, dict):
        return _json_object(val, depth)
    if isinstance(val, list):
        return _json_array(val, depth)
    if isinstance(val, str):
        return _str_display("json-str", val)
    if isinstance(val, bool):
        return _span("json-lit", "true" if val else "false")
    if val is None:
        return _span("json-lit", "null")
    if isinstance(val, (int, float)):
        return _span("json-num", str(val))
    return _span("json-str", str(val))


def _json_object(obj: dict, depth: int) -> list:
    if not obj:
        return [_span("json-brace", "{"), _span("json-brace", "}")]
    indent_style = f"margin-left: {depth * 16}px"
    fields: list = []
    for key, val in obj.items():
        key_node = _str_display("json-key", key)
        val_node = _json_node(val, depth + 1)
        fields.append([Symbol("div"), Keyword("class"), "json-field",
                        key_node, ": ", val_node])
    return [Symbol("div"), Keyword("class"), "json-obj",
            Keyword("style"), indent_style,
            _span("json-brace", "{"),
            *fields,
            _span("json-brace", "}")]


def _json_array(arr: list, depth: int) -> list:
    if not arr:
        return [_span("json-bracket", "["), _span("json-bracket", "]")]
    indent_style = f"margin-left: {depth * 16}px"
    items: list = []
    for item in arr:
        items.append(_json_node(item, depth + 1))
    return [Symbol("div"), Keyword("class"), "json-arr",
            Keyword("style"), indent_style,
            _span("json-bracket", "["),
            *items,
            _span("json-bracket", "]")]
