#
#   Imandra Inc.
#
#   speclogician/commands/reach.py
#

from __future__ import annotations
from typing import Any

import json
import time
import typer

from speclogician.utils import console
from speclogician.logic.lib.reachability import ScenarioReachabilityGraph

from .utils import get_state

app = typer.Typer(help="Scenario reachability analysis (ImandraX-backed compute + graph queries).")

def _emit_json(payload: dict[str, Any]) -> None:
    typer.echo(json.dumps(payload, indent=2, sort_keys=True, default=str))

def check_graph(g:ScenarioReachabilityGraph|None, json_only:bool) -> ScenarioReachabilityGraph:
    if g is None:
        if json_only:
            _emit_json({"ok": False, "notes": "Scenario reachability not computed"})
        else:
            console.print("[dim]No reachability graph computed yet. Run: speclogician reach compute[/dim]")
        raise typer.Exit(0)
    return g

@app.command("summary")
def summary_cmd(
    ctx: typer.Context,
    json_only: bool = typer.Option(False, "--json")
) -> None:

    state = get_state(ctx)
    spec = state.curr_state().spec
    g = spec.scenario_reachability

    g = check_graph(g, json_only)

    if json_only:
        _emit_json(
            {
                "ok": True,
                "nodes": len(g.nodes),
                "edges": len(g.edges),
                "initials": g.initials,
                "reachable_from_initials": g.reachable_from_initials,
                "unreachable": g.unreachable,
            }
        )
        raise typer.Exit(0)

    console.print(f"[bold]Reachability graph[/bold] nodes={len(g.nodes)} edges={len(g.edges)}")
    if g.initials:
        console.print(f"[dim]initials[/dim] {', '.join(g.initials)}")
        console.print(f"[dim]unreachable[/dim] {len(g.unreachable)}")


@app.command("unreachable")
def unreachable_cmd(
    ctx: typer.Context,
    json_only: bool = typer.Option(False, "--json")
) -> None:
    
    state = get_state(ctx)

    g = state.curr_state().spec.scenario_reachability

    g = check_graph(g, json_only)

    if json_only:
        _emit_json({"unreachable": g.unreachable})
    else:
        for n in g.unreachable:
            console.print(n)

@app.command("succ")
def succ_cmd(
    ctx: typer.Context,
    name: str,
    json_only: bool = typer.Option(False, "--json")
) -> None:

    state = get_state(ctx)
    g = state.curr_state().spec.scenario_reachability

    g = check_graph(g, json_only)

    for n in g.successors(name):
        console.print(n)

@app.command("pred")
def pred_cmd(
    ctx: typer.Context,
    name: str,
    json_only: bool = typer.Option(False, "--json")
) -> None:
    state = get_state(ctx)

    g = state.curr_state().spec.scenario_reachability

    g = check_graph(g, json_only)

    for n in g.predecessors(name):
        console.print(n)


@app.command("path")
def path_cmd(
    ctx: typer.Context,
    frm: str,
    to: str, 
    json_only: bool = typer.Option(False, "--json")
) -> None:
    
    state = get_state(ctx)
    g = state.curr_state().spec.scenario_reachability

    g = check_graph(g, json_only)

    path = g.shortest_path(frm, to)  # helper on graph
    if json_only:
        _emit_json({"path": path})
    else:
        console.print(" -> ".join(path) if path else "[dim]no path[/dim]")


@app.command("explain-edge")
def explain_edge_cmd(
    ctx: typer.Context,
    src: str,
    dst: str,
    no_witness: bool = typer.Option(False, "--no-witness"),
    json_only: bool = typer.Option(False, "--json"),
) -> None:

    state = get_state(ctx)
    g = state.curr_state().spec.scenario_reachability

    g = check_graph(g, json_only)

    ex = g.explain_edge(src, dst, include_witness_src=not no_witness)  # helper on graph

    if json_only:
        _emit_json(ex)
        raise typer.Exit(0)

    if not ex.get("reachable"):
        console.print(f"[red]not reachable[/red] {src} -> {dst}")
        notes = ex.get("notes")
        if notes:
            console.print(f"[dim]{notes}[/dim]")
        raise typer.Exit(1)

    console.print(f"[green]reachable[/green] {src} -> {dst}")
    if ex.get("witness_fp"):
        console.print(f"[dim]witness_fp[/dim] {ex['witness_fp']}")
    if ex.get("witness_src"):
        console.print(ex["witness_src"])


@app.command("frontier")
def frontier_cmd(
    ctx: typer.Context,
    json_only: bool = typer.Option(False, "--json")
) -> None:
    
    state = get_state(ctx)
    g = state.curr_state().spec.scenario_reachability

    g = check_graph(g, json_only)

    gaps = g.frontier_gaps()

    if json_only:
        _emit_json({"frontier_gaps": [{"src": s, "missing_reason": r} for (s, r) in gaps]})
        raise typer.Exit(0)

    for s, r in gaps:
        console.print(f"{s}  [dim]{r}[/dim]")