"""Behave step definitions for the RendererRegistry / ElementRenderer
architecture (PR #1193).

Step patterns here are additive — they do not collide with the existing
step definitions in ``output_rendering_steps.py``.  Two step functions
that read the JSON envelope use the master HEAD schema key ``"data"``
(post-M4 rename) rather than the legacy key ``"elements"``.
"""

from __future__ import annotations

from behave import given, then, when
from behave.runner import Context

from cleveragents.cli.output.handles import ColumnDef
from cleveragents.cli.output.materializers import (
    JsonMaterializer,
    PlainMaterializer,
)
from cleveragents.cli.output.selection import TerminalCapabilities


# ===========================================================================
# RendererRegistry tests
# ===========================================================================


@given("the default renderer registry")
def step_default_registry(context: Context) -> None:
    from cleveragents.cli.output.registry import default_registry

    context.registry = default_registry


@then('the registry has format "{fmt}" registered')
def step_registry_has_format(context: Context, fmt: str) -> None:
    assert context.registry.is_registered(fmt), f"Format {fmt!r} not registered"


@then('the registry does not have format "{fmt}" registered')
def step_registry_no_format(context: Context, fmt: str) -> None:
    assert not context.registry.is_registered(fmt), f"Format {fmt!r} is registered"


@then('the available formats are "{expected}"')
def step_available_formats(context: Context, expected: str) -> None:
    expected_list = sorted(expected.split(","))
    actual = context.registry.available_formats()
    assert actual == expected_list, f"Expected {expected_list}, got {actual}"


@when('I resolve format "{fmt}" from the registry')
def step_resolve_format(context: Context, fmt: str) -> None:
    caps = TerminalCapabilities(
        is_tty=True,
        supports_ansi=True,
        supports_cursor_movement=True,
        term_program="xterm-256color",
    )
    strategy, renderer = context.registry.resolve(fmt, caps)
    context.resolved_strategy = strategy
    context.resolved_renderer = renderer


@when('I resolve format "{fmt}" from the registry with terminal caps')
def step_resolve_format_with_caps(context: Context, fmt: str) -> None:
    caps = getattr(context, "terminal_caps", None)
    if caps is None:
        caps = TerminalCapabilities(
            is_tty=True,
            supports_ansi=True,
            supports_cursor_movement=True,
            term_program="xterm-256color",
        )
    strategy, renderer = context.registry.resolve(fmt, caps)
    context.resolved_strategy = strategy
    context.resolved_renderer = renderer


@then('the resolved renderer format name is "{expected}"')
def step_resolved_renderer_name(context: Context, expected: str) -> None:
    assert context.resolved_renderer.format_name == expected, (
        f"Expected {expected!r}, got {context.resolved_renderer.format_name!r}"
    )


@given("a new empty renderer registry")
def step_empty_registry(context: Context) -> None:
    from cleveragents.cli.output.registry import RendererRegistry

    context.registry = RendererRegistry()


@when('I register a custom "{fmt}" format in the registry')
def step_register_custom_format(context: Context, fmt: str) -> None:
    from cleveragents.cli.output.registry import PlainElementRenderer

    context.registry.register(
        fmt,
        strategy_factory=lambda caps: PlainMaterializer(),
        renderer_factory=lambda caps: PlainElementRenderer(),
        fallback="plain",
    )


# ===========================================================================
# ElementRenderer tests
# ===========================================================================


@given("a PlainElementRenderer")
def step_plain_renderer(context: Context) -> None:
    from cleveragents.cli.output.registry import PlainElementRenderer

    context.element_renderer = PlainElementRenderer()


@given("a ColorElementRenderer")
def step_color_renderer(context: Context) -> None:
    from cleveragents.cli.output.registry import ColorElementRenderer

    context.element_renderer = ColorElementRenderer()


@given("a RichElementRenderer")
def step_rich_renderer(context: Context) -> None:
    from cleveragents.cli.output.registry import RichElementRenderer

    context.element_renderer = RichElementRenderer()


@given("a JsonElementRenderer")
def step_json_renderer(context: Context) -> None:
    from cleveragents.cli.output.registry import JsonElementRenderer

    context.element_renderer = JsonElementRenderer()


@given("a YamlElementRenderer")
def step_yaml_renderer(context: Context) -> None:
    from cleveragents.cli.output.registry import YamlElementRenderer

    context.element_renderer = YamlElementRenderer()


@when('I render a panel with title "{title}" through the renderer')
def step_render_panel_via_renderer(context: Context, title: str) -> None:
    from cleveragents.cli.output.handles import Panel

    panel = Panel(title=title)
    context.renderer_output = context.element_renderer.render_panel(panel)


@then('the renderer output contains "{text}"')
def step_renderer_output_contains(context: Context, text: str) -> None:
    assert text in context.renderer_output, (
        f"Expected {text!r} in renderer output, got: {context.renderer_output!r}"
    )


@then("the renderer can_render returns True for any terminal")
def step_renderer_can_render_true(context: Context) -> None:
    caps = TerminalCapabilities(
        is_tty=False,
        supports_ansi=False,
        supports_cursor_movement=False,
        term_program="",
    )
    assert context.element_renderer.can_render(caps) is True


@then("the renderer can_render returns False without ANSI")
def step_renderer_can_render_no_ansi(context: Context) -> None:
    caps = TerminalCapabilities(
        is_tty=True,
        supports_ansi=False,
        supports_cursor_movement=False,
        term_program="dumb",
    )
    assert context.element_renderer.can_render(caps) is False


@then("the renderer can_render returns False without cursor movement")
def step_renderer_can_render_no_cursor(context: Context) -> None:
    caps = TerminalCapabilities(
        is_tty=True,
        supports_ansi=True,
        supports_cursor_movement=False,
        term_program="ansi",
    )
    assert context.element_renderer.can_render(caps) is False


@when("I serialize a StructuredOutput through the renderer")
def step_serialize_via_renderer(context: Context) -> None:
    from cleveragents.cli.output.session import StructuredOutput

    output = StructuredOutput(
        command="test",
        session_id="ses-test",
        elements=[],
        exit_code=0,
    )
    context.serialized_output = context.element_renderer.serialize(output)


@then("the serialized output is valid JSON")
def step_serialized_json(context: Context) -> None:
    import json

    json.loads(context.serialized_output)


@then("the serialized output is valid YAML")
def step_serialized_yaml(context: Context) -> None:
    import yaml

    yaml.safe_load(context.serialized_output)


@then("the serialized output has sorted keys")
def step_serialized_sorted_keys(context: Context) -> None:
    import yaml

    data = yaml.safe_load(context.serialized_output)
    if isinstance(data, dict):
        keys = list(data.keys())
        assert keys == sorted(keys), f"Keys not sorted: {keys}"


# ===========================================================================
# TerminalCapabilities extended fields
# ===========================================================================


@when("I create a full TerminalCapabilities")
def step_full_caps(context: Context) -> None:
    context.full_caps = TerminalCapabilities(
        is_tty=True,
        width=120,
        height=40,
        supports_ansi=True,
        supports_256_color=True,
        supports_truecolor=True,
        supports_unicode=True,
        supports_cursor_movement=True,
        supports_alternate_screen=True,
        no_color=False,
        term_program="iTerm2",
    )


@then("the capabilities has width {value:d}")
def step_caps_width(context: Context, value: int) -> None:
    assert context.full_caps.width == value


@then("the capabilities has height {value:d}")
def step_caps_height(context: Context, value: int) -> None:
    assert context.full_caps.height == value


@then("the capabilities has supports_256_color {value}")
def step_caps_256color(context: Context, value: str) -> None:
    expected = value == "True"
    assert context.full_caps.supports_256_color == expected


@then("the capabilities has supports_truecolor {value}")
def step_caps_truecolor(context: Context, value: str) -> None:
    expected = value == "True"
    assert context.full_caps.supports_truecolor == expected


@then("the capabilities has supports_unicode {value}")
def step_caps_unicode(context: Context, value: str) -> None:
    expected = value == "True"
    assert context.full_caps.supports_unicode == expected


@then("the capabilities has supports_alternate_screen {value}")
def step_caps_alt_screen(context: Context, value: str) -> None:
    expected = value == "True"
    assert context.full_caps.supports_alternate_screen == expected


@then("the capabilities has no_color {value}")
def step_caps_no_color(context: Context, value: str) -> None:
    expected = value == "True"
    assert context.full_caps.no_color == expected


@then('the capabilities has term_program "{value}"')
def step_caps_term_program(context: Context, value: str) -> None:
    assert context.full_caps.term_program == value


@then("the capabilities supports_cursor property matches supports_cursor_movement")
def step_caps_cursor_alias(context: Context) -> None:
    assert (
        context.full_caps.supports_cursor == context.full_caps.supports_cursor_movement
    )


@then("the capabilities term property matches term_program")
def step_caps_term_alias(context: Context) -> None:
    assert context.full_caps.term == (context.full_caps.term_program or "")


# ===========================================================================
# ColumnDef.type alias
# ===========================================================================


@when('I create a ColumnDef with type alias "{type_val}"')
def step_create_coldef_type_alias(context: Context, type_val: str) -> None:
    context.coldef = ColumnDef(type=type_val, name="test")


@then('the ColumnDef col_type is "{expected}"')
def step_coldef_col_type(context: Context, expected: str) -> None:
    assert context.coldef.col_type == expected


# ===========================================================================
# Progress omitted from JSON — reads master HEAD's "data" envelope key
# ===========================================================================


@then('the json output does not contain element type "{etype}"')
def step_json_no_element_type(context: Context, etype: str) -> None:
    import json

    output = context.strategy.get_output()
    data = json.loads(output)
    for elem in data.get("data", []):
        assert elem.get("type") != etype, f"Found element type {etype!r} in JSON output"


# ===========================================================================
# MaterializationStrategy.bind()
# ===========================================================================


@given("a PlainMaterializer instance")
def step_plain_mat_instance(context: Context) -> None:
    context.bind_mat = PlainMaterializer()


@given("a JsonMaterializer instance")
def step_json_mat_instance(context: Context) -> None:
    context.bind_mat = JsonMaterializer()


@when("I call bind on the materializer")
def step_call_bind(context: Context) -> None:
    from cleveragents.cli.output.registry import PlainElementRenderer

    caps = TerminalCapabilities(
        is_tty=True,
        supports_ansi=True,
        supports_cursor_movement=True,
        term_program="xterm",
    )
    context.bind_mat.bind(PlainElementRenderer(), terminal_caps=caps)
    context.bind_error = None


@when("I call bind on the json materializer")
def step_call_bind_json(context: Context) -> None:
    from cleveragents.cli.output.registry import JsonElementRenderer

    caps = TerminalCapabilities(
        is_tty=True,
        supports_ansi=True,
        supports_cursor_movement=True,
        term_program="xterm",
    )
    context.bind_mat.bind(JsonElementRenderer(), terminal_caps=caps)
    context.bind_error = None


@then("no error is raised")
def step_no_bind_error(context: Context) -> None:
    assert getattr(context, "bind_error", None) is None


# ===========================================================================
# ColumnDef "type" key assertion — reads master HEAD's "data" envelope key
# ===========================================================================


@then("the json output ColumnDef uses type key not col_type")
def step_json_coldef_type_key(context: Context) -> None:
    import json

    output = context.strategy.get_output()
    data = json.loads(output)
    for elem in data.get("data", []):
        if elem.get("type") == "table":
            for col in elem.get("columns", []):
                assert "type" in col, f"Missing 'type' key in ColumnDef: {col}"
                assert "col_type" not in col, (
                    f"Found 'col_type' key (should be 'type'): {col}"
                )


# ===========================================================================
# YAML does not contain assertion
# ===========================================================================


@then('the yaml output does not contain "{text}"')
def step_yaml_not_contains(context: Context, text: str) -> None:
    output = context.strategy.get_output()
    assert text not in output, f"Found {text!r} in YAML output: {output[:200]}"


# ===========================================================================
# TableElementRenderer given
# ===========================================================================


@given("a TableElementRenderer")
def step_table_renderer(context: Context) -> None:
    from cleveragents.cli.output.registry import TableElementRenderer

    context.element_renderer = TableElementRenderer()


# ===========================================================================
# Generic render_element dispatch — covers all render_* methods
# ===========================================================================


@when("I call render_element with each element type")
def step_render_all_element_types(context: Context) -> None:
    from cleveragents.cli.output.handles import (
        ActionHint,
        CodeBlock,
        DiffBlock,
        ProgressIndicator,
        Separator,
        StatusMessage,
        Table,
        TextBlock,
        Tree,
        TreeNode,
    )

    elements = [
        TextBlock(content="hello world"),
        StatusMessage(message="all ok"),
        CodeBlock(content="x = 1"),
        DiffBlock(),
        Separator(),
        ActionHint(commands=["ls", "pwd"]),
        Table(title="data"),
        Tree(root=TreeNode(label="root")),
        ProgressIndicator(label="loading"),
    ]
    context.render_dispatch_error = None
    try:
        for elem in elements:
            context.element_renderer.render_element(elem)
    except Exception as exc:
        context.render_dispatch_error = exc


@then("no render dispatch error occurred")
def step_no_dispatch_error(context: Context) -> None:
    err = getattr(context, "render_dispatch_error", None)
    assert err is None, f"Unexpected render error: {err}"


# ===========================================================================
# _BaseElementRenderer.serialize() via PlainElementRenderer
# ===========================================================================


@when("I call base serialize with a text element")
def step_base_serialize_text(context: Context) -> None:
    from cleveragents.cli.output.handles import TextBlock
    from cleveragents.cli.output.session import StructuredOutput

    output = StructuredOutput(
        command="test-base",
        session_id="ses-base-test",
        elements=[TextBlock(content="hello from base serialize")],
        exit_code=0,
    )
    context.base_serialized = context.element_renderer.serialize(output)


@then("the base serialized output is a string")
def step_base_serialized_is_string(context: Context) -> None:
    assert isinstance(context.base_serialized, str)


# ===========================================================================
# can_render True paths
# ===========================================================================


@then("color renderer can_render is True with ANSI support")
def step_color_can_render_true(context: Context) -> None:
    caps = TerminalCapabilities(
        is_tty=True,
        supports_ansi=True,
        supports_cursor_movement=True,
        term_program="xterm-256color",
    )
    assert context.element_renderer.can_render(caps) is True


@then("table renderer can_render is True on a TTY terminal")
def step_table_can_render_true(context: Context) -> None:
    caps = TerminalCapabilities(
        is_tty=True,
        supports_ansi=True,
        supports_cursor_movement=True,
        term_program="xterm",
    )
    assert context.element_renderer.can_render(caps) is True


@then("table renderer can_render is False on a non-TTY terminal")
def step_table_can_render_false(context: Context) -> None:
    caps = TerminalCapabilities(
        is_tty=False,
        supports_ansi=False,
        supports_cursor_movement=False,
        term_program="",
    )
    assert context.element_renderer.can_render(caps) is False


@then("rich renderer can_render is True with cursor movement")
def step_rich_can_render_true(context: Context) -> None:
    caps = TerminalCapabilities(
        is_tty=True,
        supports_ansi=True,
        supports_cursor_movement=True,
        term_program="xterm",
    )
    assert context.element_renderer.can_render(caps) is True


# ===========================================================================
# RendererRegistry error paths
# ===========================================================================


@given("a registry with no plain and a non-renderable format")
def step_registry_no_plain_non_renderable(context: Context) -> None:
    from cleveragents.cli.output.materializers import PlainMaterializer
    from cleveragents.cli.output.registry import RendererRegistry

    class _NeverRenders:
        format_name = "norender"

        def can_render(self, terminal_caps: TerminalCapabilities) -> bool:
            return False

    context.registry = RendererRegistry()
    context.registry.register(
        "norender",
        strategy_factory=lambda caps: PlainMaterializer(),
        renderer_factory=lambda caps: _NeverRenders(),
        fallback=None,
    )
    context.no_plain_format = "norender"


@when("I try to resolve the non-renderable format")
def step_resolve_non_renderable(context: Context) -> None:
    caps = TerminalCapabilities(
        is_tty=False,
        supports_ansi=False,
        supports_cursor_movement=False,
        term_program="",
    )
    context.resolve_error = None
    try:
        context.registry.resolve(context.no_plain_format, caps)
    except ValueError as exc:
        context.resolve_error = exc


@then("a ValueError is raised from resolve")
def step_valueerror_raised(context: Context) -> None:
    assert context.resolve_error is not None, "Expected ValueError but none was raised"
    assert isinstance(context.resolve_error, ValueError)
