#
#   Imandra Inc.
#
#   speclogician/tui/main_screen.py
#

from __future__ import annotations

from typing import Any

from rich.console import Group
from rich.panel import Panel
from rich.syntax import Syntax
from rich.text import Text

from textual.app import ComposeResult
from textual.containers import Container, Horizontal
from textual.screen import Screen
from textual.widgets import (
    Footer,
    Header,
    ListItem,
    ListView,
    RichLog,
    Static,
    TabbedContent,
    TabPane,
)

from ..data.refs import DocRef, SrcCodeRef
from ..data.traces import LogTrace, TestTrace
from ..modeling.predicates import ActionPredicate, StatePredicate, Transition
from ..modeling.scenario import Scenario
from ..state.inst import StateInstance
from ..presentation.api import (
    present_data_artifact,
    present_predicate,
    present_scenario,
    present_state_instance,
    present_trace,
    present_transition,
    present_scenario_complement,  # NEW
)
from ..presentation.ctx import RenderCtx

ctx = RenderCtx()


class InstanceItem(ListItem):
    """Single-line list entry for a StateInstance."""

    def __init__(self, inst: StateInstance) -> None:
        self.inst = inst
        super().__init__(Static(self._render_line()))

    def _render_line(self) -> str:
        """
        Format:
          2025-02-03 14:22:11 | base=OK sc(miss=1 unsat=0) rec(e=1 w=2 n=3)
        """

        # --- timestamp ---
        ts = self.inst.created_at.strftime("%Y-%m-%d %H:%M:%S")

        parts: list[str] = []

        # --- base status ---
        base_status = None
        try:
            base_status = self.inst.spec.domain_model.base_status
        except Exception:
            pass

        if base_status is not None:
            parts.append(f"base={base_status.name}")
        else:
            parts.append("base=—")

        # --- scenario health (from state_diff if present) ---
        sd = getattr(self.inst, "state_diff", None)
        if sd is not None:
            miss = getattr(getattr(sd, "num_sc_missing", None), "after", None)
            unsat = getattr(getattr(sd, "num_sc_inconsistent", None), "after", None)

            if isinstance(miss, int) or isinstance(unsat, int):
                parts.append(f"sc(miss={miss or 0} unsat={unsat or 0})")

        # --- recommendations summary ---
        recs = getattr(self.inst, "recommendations", None)
        if recs is not None:
            e = getattr(recs, "num_error", 0) or 0
            w = getattr(recs, "num_warning", 0) or 0
            n = getattr(recs, "num_next", 0) or 0
            parts.append(f"rec(e={e} w={w} n={n})")

        return f"{ts} | " + " ".join(parts)


class MainScreen(Screen[Any]):
    """Main TUI screen."""

    def compose(self) -> ComposeResult:
        yield Header()

        with Container(id="body"):
            # ----- split: instances list + details -----
            with Horizontal(id="instances_split"):

                # left: instances list only
                with Container(id="instances_panel"):
                    self.instances_list = ListView(id="instances_list")
                    yield self.instances_list

                # ----- right: tabbed details -----
                with TabbedContent(initial="summary"):
                    with TabPane("Summary", id="summary"):
                        self.instance_summary = RichLog(
                            id="instance_summary",
                            wrap=False,
                            highlight=True,
                            auto_scroll=False,
                        )
                        yield self.instance_summary

                    with TabPane("Full IML Model", id="model"):
                        with Container(id="full_iml_panel"):
                            with Horizontal(id="full_iml_toolbar"):
                                yield Static("Press 'c' to copy", id="copy_hint")

                            self.full_iml_model = RichLog(
                                id="full_iml_model",
                                wrap=False,
                                highlight=True,
                                auto_scroll=False,
                            )
                            yield self.full_iml_model

                    with TabPane("Predicates", id="predicates"):
                        self.predicates = RichLog(
                            id="predicates_view",
                            wrap=True,
                            highlight=True,
                            auto_scroll=False,
                        )
                        yield self.predicates

                    with TabPane("Transitions", id="transitions"):
                        self.transitions = RichLog(
                            id="transitions_view",
                            wrap=True,
                            highlight=True,
                            auto_scroll=False,
                        )
                        yield self.transitions

                    with TabPane("Scenarios", id="scenarios"):
                        self.scenarios = RichLog(
                            id="scenarios_view",
                            wrap=True,
                            highlight=True,
                            auto_scroll=False,
                        )
                        yield self.scenarios

                    # NEW: Scenario complement tab
                    with TabPane("Complement", id="complement"):
                        self.complement = RichLog(
                            id="complement_view",
                            wrap=True,
                            highlight=True,
                            auto_scroll=False,
                        )
                        yield self.complement

                    with TabPane("Test Traces", id="test_traces"):
                        self.test_traces = RichLog(
                            id="test_traces_view",
                            wrap=True,
                            highlight=True,
                            auto_scroll=False,
                        )
                        yield self.test_traces

                    with TabPane("Log Traces", id="log_traces"):
                        self.log_traces = RichLog(
                            id="log_traces_view",
                            wrap=True,
                            highlight=True,
                            auto_scroll=False,
                        )
                        yield self.log_traces

                    with TabPane("Src Artifacts", id="src_artifacts"):
                        self.src_refs = RichLog(
                            id="src_artifacts_view",
                            wrap=True,
                            highlight=True,
                            auto_scroll=False,
                        )
                        yield self.src_refs

                    with TabPane("Doc Artifacts", id="doc_artifacts"):
                        self.doc_refs = RichLog(
                            id="doc_artifacts_view",
                            wrap=True,
                            highlight=True,
                            auto_scroll=False,
                        )
                        yield self.doc_refs

        # --- status line above Footer ---
        self.status_line = Static("", id="status_line")
        yield self.status_line

        yield Footer()

    # -------------------------------------------------------------------------
    # UI plumbing
    # -------------------------------------------------------------------------

    def set_status(self, msg: str = "") -> None:
        """Set footer status line text."""
        self.status_line.update(msg)

    def clear_status(self) -> None:
        self.set_status("")

    def refresh_from_state(self) -> None:
        """
        Rebuild instances list from self.app.state and reselect an instance.
        """
        prev_created_at: str | None = None
        try:
            if self.instances_list.index is not None and self.instances_list.index >= 0:
                item = self.instances_list.children[self.instances_list.index]
                if isinstance(item, InstanceItem):
                    prev_created_at = item.inst.created_at.isoformat()
        except Exception:
            prev_created_at = None

        self.instances_list.clear()
        for inst in self.app.state.instances:
            self.instances_list.append(InstanceItem(inst))

        if not self.app.state.instances:
            self.clear_status()
            return

        idx = 0
        if prev_created_at is not None:
            for i, inst in enumerate(self.app.state.instances):
                if inst.created_at.isoformat() == prev_created_at:
                    idx = i
                    break

        self.instances_list.index = idx
        self._select_instance(self.app.state.instances[idx])

        # reflect current update-available status
        if getattr(self.app, "_update_available", False):
            sig = getattr(self.app, "_pending_state_sig", None)
            if sig is not None:
                mtime_ns, _ = sig
                self.set_status(
                    f"[yellow]Update available[/yellow] — press [bold]u[/bold] to reload ({self.app._fmt_mtime(mtime_ns)})"
                )
        else:
            self.clear_status()

    # -------------------------------------------------------------------------
    # rendering helpers
    # -------------------------------------------------------------------------

    def _show_instance_summary(self, inst: StateInstance) -> None:
        self.instance_summary.clear()
        self.instance_summary.write(present_state_instance(inst, ctx=ctx, json_only=False))
        self.instance_summary.scroll_to(y=0, animate=False, immediate=True, force=True)

    def _show_full_iml_model(self, iml_code: str) -> None:
        self._full_iml_text = iml_code or ""
        self.full_iml_model.clear()
        self.full_iml_model.write(Syntax(self._full_iml_text, "ocaml", line_numbers=True, word_wrap=False))

    def _show_predicates(
        self,
        state_preds: list[StatePredicate],
        action_preds: list[ActionPredicate],
    ) -> None:
        self.predicates.clear()

        state_r = [present_predicate(p, ctx=ctx, json_only=False) for p in state_preds]
        action_r = [present_predicate(p, ctx=ctx, json_only=False) for p in action_preds]

        self.predicates.write(
            Panel(
                Group(*state_r) if state_r else Text("— none —"),
                title=f"State predicates ({len(state_r)})",
            )
        )

        self.predicates.write(
            Panel(
                Group(*action_r) if action_r else Text("— none —"),
                title=f"Action predicates ({len(action_r)})",
            )
        )
        self.predicates.scroll_home(animate=False)

    def _show_transitions(self, transitions: list[Transition]) -> None:
        self.transitions.clear()
        trs = [present_transition(t, ctx=ctx, json_only=False) for t in transitions]
        self.transitions.write(
            Panel(
                Group(*trs) if trs else Text("— none —"),
                title=f"Transitions ({len(trs)})",
            )
        )
        self.transitions.scroll_home(animate=False)

    def _show_scenarios(self, scenarios: list[Scenario]) -> None:
        self.scenarios.clear()
        scs = [present_scenario(s, ctx=ctx, json_only=False) for s in scenarios]
        self.scenarios.write(
            Panel(
                Group(*scs) if scs else Text("— none —"),
                title=f"Scenarios ({len(scs)})",
            )
        )
        self.scenarios.scroll_home(animate=False)

    def _show_scenario_complement(self, inst: StateInstance) -> None:
        """
        Render spec.scenario_comp via the new presentation API.
        """
        self.complement.clear()

        comp = getattr(getattr(inst, "spec", None), "scenario_comp", None)
        if comp is None:
            self.complement.write(Panel(Text("— no scenario complement —", style="dim"), title="Complement"))
            self.complement.scroll_home(animate=False)
            return

        try:
            rend = present_scenario_complement(comp, ctx=ctx, json_only=False)
        except Exception as e:
            self.complement.write(
                Panel(
                    Text(f"Failed to render complement: {type(e).__name__}: {e}", style="red"),
                    title="Complement",
                )
            )
            self.complement.scroll_home(animate=False)
            return

        # `rend` is a rich renderable
        self.complement.write(rend)
        self.complement.scroll_home(animate=False)

    def _show_test_traces(self, test_traces: list[TestTrace]) -> None:
        self.test_traces.clear()
        tts = [present_trace(tt, ctx=ctx, json_only=False) for tt in test_traces]
        self.test_traces.write(
            Panel(
                Group(*tts) if tts else Text("— none —"),
                title=f"Test Traces ({len(tts)})",
            )
        )
        self.test_traces.scroll_home(animate=False)

    def _show_log_traces(self, log_traces: list[LogTrace]) -> None:
        self.log_traces.clear()
        lts = [present_trace(lt, ctx=ctx, json_only=False) for lt in log_traces]
        self.log_traces.write(
            Panel(
                Group(*lts) if lts else Text("— none —"),
                title=f"Log Traces ({len(lts)})",
            )
        )
        self.log_traces.scroll_home(animate=False, immediate=True)

    def _show_src_artifacts(self, src_refs: list[SrcCodeRef]) -> None:
        self.src_refs.clear()
        srefs = [present_data_artifact(sr, ctx=ctx, json_only=False) for sr in src_refs]
        self.src_refs.write(
            Panel(
                Group(*srefs) if srefs else Text("— none —"),
                title=f"Source Code References ({len(srefs)})",
            )
        )
        self.src_refs.scroll_home(animate=False, immediate=True)

    def _show_doc_artifacts(self, doc_refs: list[DocRef]) -> None:
        self.doc_refs.clear()
        drefs = [present_data_artifact(dr, ctx=ctx, json_only=False) for dr in doc_refs]
        self.doc_refs.write(
            Panel(
                Group(*drefs) if drefs else Text("— none —"),
                title=f"Documentation References ({len(drefs)})",
            )
        )
        self.doc_refs.scroll_home(animate=False, immediate=True)

    def _select_instance(self, inst: StateInstance) -> None:
        self._show_instance_summary(inst)
        self._show_full_iml_model(inst.spec.full_model())
        self._show_predicates(inst.spec.domain_model.state_preds, inst.spec.domain_model.action_preds)
        self._show_transitions(inst.spec.domain_model.transitions)
        self._show_scenarios(inst.spec.scenarios)
        self._show_scenario_complement(inst)  # NEW
        self._show_test_traces(inst.art_container.test_traces)
        self._show_log_traces(inst.art_container.log_traces)
        self._show_src_artifacts(inst.art_container.src_code)
        self._show_doc_artifacts(inst.art_container.doc_ref)

    # -------------------------------------------------------------------------
    # events
    # -------------------------------------------------------------------------

    def on_mount(self) -> None:
        for inst in self.app.state.instances:
            self.instances_list.append(InstanceItem(inst))

        if self.app.state and self.app.state.instances:
            self.instances_list.index = 0
            self._select_instance(self.app.state.instances[0])

        self.instances_list.border_title = "State Instances"

        # reflect update status on first mount
        if getattr(self.app, "_update_available", False):
            sig = getattr(self.app, "_pending_state_sig", None)
            if sig is not None:
                mtime_ns, _ = sig
                self.set_status(
                    f"[yellow]Update available[/yellow] — press [bold]u[/bold] to reload ({self.app._fmt_mtime(mtime_ns)})"
                )
        else:
            self.clear_status()

    def action_show_tab(self, tab: str) -> None:
        tc = self.query_one(TabbedContent)
        tc.active = tab

    def on_list_view_highlighted(self, event: ListView.Highlighted) -> None:
        if event.list_view is not self.instances_list:
            return
        if isinstance(event.item, InstanceItem):
            self._select_instance(event.item.inst)

    def on_list_view_selected(self, event: ListView.Selected) -> None:
        if event.list_view is not self.instances_list:
            return
        if isinstance(event.item, InstanceItem):
            self._select_instance(event.item.inst)