from __future__ import annotations

import json

from .interactive_keyboard import second_port_fallback_description
from .interactive_session import InteractiveSessionEvent, InteractiveSnapshot


def render_startup_panel(session) -> str:
    lines = [
        f"GrowNet interactive mode",
        f"profile={session.profile.name} normalize_inputs={session.normalize_inputs} output={session.output_mode} trace={'on' if session.trace_enabled else 'off'}",
        f"description={session.profile.description}",
        f"ports={', '.join(session.profile.input_ports)} current_inputs={format_inputs(session.input_values)}",
        "controls: digits=input, space=repeat, r=reset, s=summary, t=toggle compact/verbose, c=panel, h=help, q=quit",
    ]
    if bool(getattr(session.profile.focus_config, "enabled", False)):
        lines.append(f"focus=enabled policy={session.profile.focus_config.policy} candidate_limit={session.profile.focus_config.candidate_limit}")
    if session.profile.supports_dual_input():
        lines.append(
            "dual-input mapping: top-row digits -> port a; keypad tokens -> port b when distinguishable; "
            f"fallback {second_port_fallback_description()} -> port b"
        )
    return "\n".join(lines)


def render_help_text(session) -> str:
    lines = [
        "Interactive controls",
        "  digits 0-9: update the primary input port and run one tick",
        "  space: run one tick again with the current input state",
        "  r: rebuild the original scaffold and reset tick/input state",
        "  s: print the current metrics summary without advancing the tick",
        "  t: toggle compact/verbose summary output",
        "  c: reprint the startup panel",
        "  h: print this help",
        "  q: quit",
        f"normalize_inputs={session.normalize_inputs} output={session.output_mode} trace={'on' if session.trace_enabled else 'off'}",
    ]
    if bool(getattr(session.profile.focus_config, "enabled", False)):
        lines.append(
            f"focus policy={session.profile.focus_config.policy} candidate_limit={session.profile.focus_config.candidate_limit} "
            f"anchor_map={'on' if session.profile.focus_config.allow_anchor_map else 'off'}"
        )
    if session.profile.supports_dual_input():
        lines.extend(
            [
                "Dual-input behavior",
                "  top-row digits update port a",
                "  keypad digits update port b when the terminal exposes distinct keypad tokens",
                f"  fallback row for port b: {second_port_fallback_description()}",
                "  when only one port changes, the other port keeps its previous value for that tick",
            ]
        )
    return "\n".join(lines)


def render_event_lines(session, event: InteractiveSessionEvent) -> list[str]:
    if event.kind == "snapshot" and event.snapshot is not None:
        return render_snapshot_lines(session, event.snapshot)
    if event.kind == "help":
        return [render_help_text(session)]
    if event.kind == "panel" or event.should_reprint_panel:
        panel_lines = [render_startup_panel(session)]
        if event.message:
            panel_lines.append(render_message_line(session, event.message))
        return panel_lines
    if event.kind == "message" and event.message is not None:
        return [render_message_line(session, event.message)]
    if event.kind == "quit":
        return [render_message_line(session, "Quitting interactive session.")]
    return []


def render_snapshot_lines(session, snapshot: InteractiveSnapshot) -> list[str]:
    rendered_lines = list(snapshot.trace_lines)
    if session.output_mode == "jsonl":
        rendered_lines.append(render_snapshot_json(snapshot))
        return rendered_lines
    if session.output_mode == "verbose":
        rendered_lines.append(render_snapshot_verbose(snapshot))
        return rendered_lines
    rendered_lines.append(render_snapshot_compact(snapshot))
    return rendered_lines


def render_snapshot_compact(snapshot: InteractiveSnapshot) -> str:
    base_line = (
        "tick={tick} profile={profile} inputs={inputs} updated_port={updated_port} updated_value={updated_value} "
        "fired={fired} delivered={delivered_events} total_slots={total_slots} total_synapses={total_synapses} "
        "neurons={neuron_count} layers={layer_count} regions={region_count} slot_delta={slot_delta} "
        "neuron_delta={neuron_delta} layer_delta={layer_delta}"
    ).format(
        tick=snapshot.tick,
        profile=snapshot.profile,
        inputs=format_inputs(snapshot.input_values),
        updated_port=snapshot.updated_port or "-",
        updated_value=format_optional_float(snapshot.updated_value),
        fired=str(snapshot.fired).lower(),
        delivered_events=snapshot.delivered_events,
        total_slots=snapshot.total_slots,
        total_synapses=snapshot.total_synapses,
        neuron_count=snapshot.neuron_count,
        layer_count=snapshot.layer_count,
        region_count=snapshot.region_count,
        slot_delta=snapshot.slot_delta,
        neuron_delta=snapshot.neuron_delta,
        layer_delta=snapshot.layer_delta,
    )
    focus_suffix = format_focus_suffix(snapshot)
    if focus_suffix:
        return f"{base_line} {focus_suffix}"
    return base_line


def render_snapshot_verbose(snapshot: InteractiveSnapshot) -> str:
    lines = [
        f"event={snapshot.event} tick={snapshot.tick} profile={snapshot.profile}",
        f"trigger_key={snapshot.trigger_key or '-'} updated_port={snapshot.updated_port or '-'} updated_value={format_optional_float(snapshot.updated_value)}",
        f"inputs={format_inputs(snapshot.input_values)} fired={str(snapshot.fired).lower()} delivered_events={snapshot.delivered_events}",
        (
            f"total_slots={snapshot.total_slots} total_synapses={snapshot.total_synapses} "
            f"neurons={snapshot.neuron_count} layers={snapshot.layer_count} regions={snapshot.region_count}"
        ),
        f"slot_delta={snapshot.slot_delta} neuron_delta={snapshot.neuron_delta} layer_delta={snapshot.layer_delta}",
    ]
    focus_line = format_focus_line(snapshot)
    if focus_line:
        lines.append(focus_line)
    return "\n".join(lines)


def render_snapshot_json(snapshot: InteractiveSnapshot) -> str:
    payload = {
        "event": snapshot.event,
        "tick": snapshot.tick,
        "profile": snapshot.profile,
        "trigger_key": snapshot.trigger_key,
        "updated_port": snapshot.updated_port,
        "updated_value": round(snapshot.updated_value, 3) if snapshot.updated_value is not None else None,
        "fired": snapshot.fired,
        "delivered_events": snapshot.delivered_events,
        "total_slots": snapshot.total_slots,
        "total_synapses": snapshot.total_synapses,
        "neuron_count": snapshot.neuron_count,
        "layer_count": snapshot.layer_count,
        "region_count": snapshot.region_count,
        "slot_delta": snapshot.slot_delta,
        "neuron_delta": snapshot.neuron_delta,
        "layer_delta": snapshot.layer_delta,
        "focus_policy": snapshot.focus_policy,
        "anchor_count": snapshot.anchor_count,
        "focus_candidate_count": snapshot.focus_candidate_count,
    }
    if snapshot.focus_point is not None:
        payload["focus_point"] = [int(snapshot.focus_point[0]), int(snapshot.focus_point[1])]
    if snapshot.focus_value is not None:
        payload["focus_value"] = round(snapshot.focus_value, 3)
    if snapshot.anchor_point is not None:
        payload["anchor_point"] = [int(snapshot.anchor_point[0]), int(snapshot.anchor_point[1])]
    if snapshot.anchor_value is not None:
        payload["anchor_value"] = round(snapshot.anchor_value, 3)
    for port_name, port_value in snapshot.input_values.items():
        payload[f"input_{port_name}"] = round(port_value, 3)
    compact_payload = {field_name: field_value for field_name, field_value in payload.items() if field_value is not None}
    return json.dumps(compact_payload, separators=(",", ":"))


def render_message_line(session, message: str) -> str:
    if session.output_mode == "jsonl":
        payload = {
            "event": "interactive.message",
            "tick": session.tick_number,
            "profile": session.profile.name,
            "message": message,
        }
        return json.dumps(payload, separators=(",", ":"))
    return message


def format_inputs(input_values: dict[str, float]) -> str:
    if not input_values:
        return "-"
    parts = [f"{port_name}={port_value:.3f}" for port_name, port_value in input_values.items()]
    return ",".join(parts)


def format_optional_float(value: float | None) -> str:
    if value is None:
        return "-"
    return f"{value:.3f}"


def format_focus_suffix(snapshot: InteractiveSnapshot) -> str:
    parts: list[str] = []
    if snapshot.focus_policy:
        parts.append(f"policy={snapshot.focus_policy}")
    if snapshot.focus_point is not None:
        parts.append(f"focus={format_point(snapshot.focus_point)}")
    elif snapshot.focus_value is not None:
        parts.append(f"focus={snapshot.focus_value:.3f}")
    if snapshot.anchor_point is not None:
        parts.append(f"anchor={format_point(snapshot.anchor_point)}")
    elif snapshot.anchor_value is not None:
        parts.append(f"anchor={snapshot.anchor_value:.3f}")
    if snapshot.anchor_count > 0:
        parts.append(f"anchor_count={snapshot.anchor_count}")
    if snapshot.focus_candidate_count > 0:
        parts.append(f"candidate_count={snapshot.focus_candidate_count}")
    return " ".join(parts)


def format_focus_line(snapshot: InteractiveSnapshot) -> str:
    focus_suffix = format_focus_suffix(snapshot)
    if not focus_suffix:
        return ""
    return f"focus={focus_suffix}"


def format_point(point_value: tuple[int, int]) -> str:
    return f"({int(point_value[0])},{int(point_value[1])})"
