#
#   Imandra Inc.
#
#   speclogician/main.py
#

__version__ = "1.0"

import json
import typer
from typing import Final
from pathlib import Path

from speclogician.commands import ch_app, view_app, find_app, reach_app
from speclogician.presentation.preview import preview_app
from speclogician.demos.cmd_demo import app as demo_app
from speclogician.utils import console, require_imandra_key
from speclogician.utils.load import load_state
from speclogician.state.state import State


SL_HELP : Final = """
:robot: [bold deep_sky_blue1][italic]SpecLogician[/italic] is an AI framework for data-driven
formal program specification synthesis, verification, and analysis.[/bold deep_sky_blue1] :rocket:

[bold]Quick start:[/bold]
  speclogician prompt            Generate the full agent instruction guide
  speclogician ch batch --json   Apply changes (predicates, transitions, scenarios)
  speclogician view state        Inspect the current specification state
  speclogician reach summary     Check scenario reachability
  speclogician undo              Roll back the last change

Use [bold]--json[/bold] on any command for machine-readable output.
Run [bold]speclogician <command> --help[/bold] for details on each command.

Learn more at [bold italic deep_sky_blue1]https://www.speclogician.dev![/bold italic deep_sky_blue1]
"""

app = typer.Typer(
    name="SpecLogician",
    help=SL_HELP,
    rich_markup_mode="rich",
    no_args_is_help=True
)

class AppCtx:
    state_json_path: Path | None = None
    state: State | None = None

@app.callback()
def main(ctx: typer.Context, state_json: Path | None = typer.Option(None, "--state-json", help="...")):
    require_imandra_key()
    ctx.obj = ctx.ensure_object(AppCtx)

    ctx.obj.state_json_path = state_json

    # Only eagerly load if the file already exists; defer creation to the
    # subcommand so it can pass json_mode for non-interactive auto-creation.
    if state_json and state_json.expanduser().exists():
        try:
            ctx.obj.state = load_state(path=state_json)
        except Exception:
            ctx.obj.state = None
    else:
        ctx.obj.state = None

app.add_typer(view_app      , name="view"       , help="View components of the state")
app.add_typer(ch_app        , name="ch"         , help="Apply change to the state")
app.add_typer(find_app      , name="find"       , help="Search for a components of the state")
app.add_typer(demo_app      , name="demo"       , help="A collection of SpecLogician demos")
app.add_typer(reach_app     , name="reach"      , help="Scenario reachability analysis (ImandraX-backed compute + graph queries).")
app.add_typer(preview_app   , name="preview"    , hidden=True) # This is an internal tool for manually inspecting renderers


@app.command(help="Run the TUI")
def tui (ctx: typer.Context):
    """ Run the TUI to help us navigate and explore the state """
    from speclogician.tui.app import SpecLogicianApp

    obj: AppCtx = ctx.ensure_object(AppCtx)
    state_path = obj.state_json_path

    tui_app = SpecLogicianApp(
        state=obj.state,
        state_path=state_path,
        watch_state_file=state_path is not None,
    )
    tui_app.run()

@app.command(help="Generate a prompt for helping agents use SpecLogician")
def prompt(
    out: Path | None = typer.Option(
        None,
        "--out",
        help="Write the prompt to a file instead of stdout.",
    ),
):
    """
    Generate a prompt for helping CLI agents use SpecLogician.
    """

    here = Path(__file__).resolve()
    repo_root = here.parents[0]
    prompt_path = repo_root / "utils" / "prompt.md"

    try:
        txt = prompt_path.read_text()
    except Exception as e:
        console.print(f"🛑 Failed to read prompt file at {prompt_path}: {e}")
        raise typer.Exit(2)

    if out:
        try:
            out.write_text(txt)
        except Exception as e:
            console.print(f"🛑 Failed to write prompt to {out}: {e}")
            raise typer.Exit(2)
        typer.echo(str(out))
    else:
        typer.echo(txt)
    

@app.command(help="Undo the last N state changes, reverting to a previous state.")
def undo(
    ctx: typer.Context,
    steps: int = typer.Option(
        1,
        "--steps",
        "-n",
        min=1,
        help="Number of state instances to undo.",
    ),
    json_only: bool = typer.Option(False, "--json", help="Emit JSON only."),
) -> None:
    """
    Roll back the latest state instance(s), moving them to the stash.

    Examples:

      speclogician undo              # undo the last change
      speclogician undo --steps 3    # undo the last 3 changes
      speclogician undo --json       # undo with JSON output
    """
    from speclogician.commands.utils import get_state

    st = get_state(ctx)

    num_before = len(st.instances)

    try:
        actually_undone = st.undo(steps=steps)
    except ValueError as e:
        if json_only:
            payload = {"ok": False, "error": {"type": "ValueError", "message": str(e)}}
            typer.echo(json.dumps(payload, indent=2))
            raise typer.Exit(code=1)
        console.print(f"[bold red]Error:[/bold red] {e}")
        raise typer.Exit(code=1)

    st.save()

    current = st.curr_state()

    if json_only:
        payload = {
            "ok": True,
            "undone": actually_undone,
            "requested": steps,
            "instances_before": num_before,
            "instances_after": len(st.instances),
            "stash_size": len(st.stash),
            "current_created_at": current.created_at.isoformat(),
        }
        typer.echo(json.dumps(payload, indent=2))
        raise typer.Exit(code=0)

    console.print(f"[bold green]Undone {actually_undone} state(s).[/bold green]")
    if actually_undone < steps:
        console.print(f"[dim](Requested {steps}, but only {actually_undone} were available to undo.)[/dim]")
    console.print(f"Current state: created at {current.created_at.isoformat()}")
    console.print(f"Instances: {len(st.instances)}, stash: {len(st.stash)}")


@app.command()
def version(
    json_only: bool = typer.Option(False, "--json", help="Output JSON"),
):
    """Print SpecLogician version"""
    if json_only:
        typer.echo(f'{{"version": "{__version__}"}}')
    else:
        typer.echo(__version__)

if __name__ == '__main__':
    app()
