#
#   Imandra Inc.
#
#   speclogician/shell/app.py
#

from __future__ import annotations

import json
import argparse
from pathlib import Path
from cmd2 import Cmd, with_argparser
from rich.text import Text
from rich.align import Align
from rich.console import RenderableType
from rich.rule import Rule

from speclogician.utils import console
from speclogician.state.state import State
from speclogician.shell.shell_ch import ChangeShell


SPEC_LOGICIAN_BANNER = r"""
.___                           .___                 _________                    .____                 .__       .__               
|   | _____ _____    ____    __| _/___________     /   _____/_____   ____   ____ |    |    ____   ____ |__| ____ |__|____    ____  
|   |/     \\__  \  /    \  / __ |\_  __ \__  \    \_____  \\____ \_/ __ \_/ ___\|    |   /  _ \ / ___\|  |/ ___\|  \__  \  /    \ 
|   |  Y Y  \/ __ \|   |  \/ /_/ | |  | \// __ \_  /        \  |_> >  ___/\  \___|    |__(  <_> ) /_/  >  \  \___|  |/ __ \|   |  \
|___|__|_|  (____  /___|  /\____ | |__|  (____  / /_______  /   __/ \___  >\___  >_______ \____/\___  /|__|\___  >__(____  /___|  /
          \/     \/     \/      \/            \/          \/|__|        \/     \/        \/    /_____/         \/        \/     \/ 
"""

def _print_result(res: object) -> None:
    """Best-effort printing for ProcessChange* results."""
    # If your ProcessChange JSON-safe models have .model_dump(), use that.
    if hasattr(res, "model_dump"):
        console.print_json(json.dumps(res.model_dump(), indent=2))
        return

    # Otherwise fall back to repr / dict
    if isinstance(res, dict):
        console.print_json(json.dumps(res, indent=2, default=str))
        return

    console.print(res)


class SpecLogicianShell(Cmd):
    intro = "SpecLogician shell. Type 'help' or '?' to list commands.\n"
    prompt = "sl> "
    allow_cli_args = False

    def __init__(self, *args, **kwargs):
        # Important: prevent cmd2 from parsing outer CLI args
        self.allow_cli_args = False

        super().__init__(*args, **kwargs)

        # Rich console that writes to the SAME stream cmd2 uses
        # (cmd2 writes to self.stdout)
        self.rich = console

        self.state = None
        self.state_path = None


    def preloop(self) -> None:
        console.print(
            Align.left(
                Text(SPEC_LOGICIAN_BANNER, style="bold magenta")
            )
        )
        console.print(
            Align.left(
                Text("SpecLogician Interactive Shell - www.speclogician.dev", style="italic dim")
            )
        )
        console.print()

    # ---- helpers ----
    def rprint(self, *renderables: RenderableType) -> None:
        """Print rich renderables to cmd2 stdout."""
        for r in renderables:
            self.rich.print(r)

    def info(self, msg: str) -> None:
        self.rich.print(f"[info]{msg}[/info]")

    def ok(self, msg: str) -> None:
        self.rich.print(f"[ok]✓ {msg}[/ok]")

    def warn(self, msg: str) -> None:
        self.rich.print(f"[warn]⚠ {msg}[/warn]")

    def err(self, msg: str) -> None:
        self.rich.print(f"[err]✗ {msg}[/err]")

    # -------------------------
    # Session management
    # -------------------------

    # ✅ programmatic API: pass a real State object
    def open_state(self, st: State, *, path: str | Path | None = None) -> None:
        self.state = st
        self.state_path = Path(path) if path is not None else None
        if self.state_path:
            self.poutput(f"Loaded state object (path={self.state_path})")
        else:
            self.poutput("Loaded state object (in-memory)")

    open_parser = argparse.ArgumentParser()
    open_parser.add_argument("path", help="Path to state.json")
    @with_argparser(open_parser)
    def do_open(self, args) -> None:
        p = Path(args.path)
        st = State.from_json(p)  # your actual loader
        self.open_state(st, path=p)


    def do_status(self, _arg) -> None:
        """Show current state/spec summary."""
        if self.state is None:
            self.warn("No state loaded.")
            return

        cur = self.state.curr_state()

        # Header that won't render as a black rectangle
        self.rprint(Rule("[bold]Status[/bold]"))

        # Your DomainModel.__rich__ already returns a Table
        self.rprint(cur)

    def do_save(self, _args) -> None:
        if not self.state or not self.state_path:
            self.perror("No state loaded.")
            return
        self.state.save_to_path(self.state_path)  # adapt to your actual saver
        self.poutput(f"Saved {self.state_path}")


    # -------------------------
    # Output mode
    # -------------------------

    def do_json(self, arg) -> None:
        """
        Toggle JSON output mode (affects process_change json flag).
        Usage:
          json on
          json off
        """
        a = (arg or "").strip().lower()
        if a in ("on", "true", "1"):
            self.json_default = True
        elif a in ("off", "false", "0"):
            self.json_default = False
        else:
            self.poutput(f"json_default={self.json_default}")
            return
        self.poutput(f"json_default={self.json_default}")

    def do_ch(self, _arg) -> None:
        """Enter the change sub-shell (pred/trans/scenario/base/...)."""
        ChangeShell(self).cmdloop()

def main():
    SpecLogicianShell().cmdloop()

if __name__ == "__main__":
    main()