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

from __future__ import annotations

import os
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import pyperclip
from textual.app import App
from textual.screen import Screen

from ..state.state import State
from ..utils.load import load_state
from .demo import DemoScreen
from .splash_screen import SplashScreen
from .stats_screen import StatsScreen
from .main_screen import MainScreen


class SpecLogicianApp(App[Any]):
    """SpecLogician TUI app with optional file-watch + manual update."""

    TITLE = "Imandra SpecLogician"
    SUB_TITLE = "AI-powered spec synthesis, verification & analysis"

    CSS_PATH = "app.tcss"

    SCREENS = {
        "splash": SplashScreen,
        "demo": DemoScreen,
        "stats": StatsScreen,
        "main": MainScreen,
    }

    BINDINGS = [
        ("s", "stats", "Statistics"),
        ("m", "main", "Main"),
        ("d", "demo", "Demo Artifacts"),
        ("c", "copy_full_iml", "Copy IML"),
        ("u", "update_state", "Update State"),
        ("q", "quit", "Quit"),
    ]

    def __init__(
        self,
        state: State | None = None,
        demo_md: str | None = None,
        demo_title: str = "Demo artifacts",
        *,
        state_path: Path | None = None,
        watch_state_file: bool = False,
        poll_interval_s: float = 0.5,
    ) -> None:
        super().__init__()

        self.demo_md = demo_md
        self.demo_title = demo_title

        self.state_path = state_path
        self.watch_state_file = watch_state_file
        self.poll_interval_s = poll_interval_s

        # manual-update mode fields
        self._pending_state_sig: tuple[int, int] | None = None
        self._update_available: bool = False

        # state
        self.state = load_state() if state is None else state

        # file watch bookkeeping
        self._state_sig: tuple[int, int] | None = None
        self._last_reload_ts: float = 0.0


    # -------------------------------------------------------------------------
    # mount + nav
    # -------------------------------------------------------------------------

    def on_mount(self) -> None:
        # If demo isn't available,
        # action_demo() will show a popup instead.

        # start watch timer if enabled
        if self.watch_state_file and self.state_path:
            self._state_sig = self._get_state_file_signature()
            self._set_last_updated_indicator(self._state_sig)
            self.set_interval(self.poll_interval_s, self._poll_state_file)
        else:
            self._set_last_updated_indicator(None)

        self.push_screen("splash")

    def action_stats(self) -> None:
        self.switch_screen("stats")

    def action_main(self) -> None:
        self.switch_screen("main")

    def action_demo(self) -> None:
        # If not in demo mode, show a popup (mirrors "No update available")
        if not self.demo_md:
            self.notify("No demo artifacts available (run in demo mode to enable).", timeout=2.0)
            return
        self.push_screen(DemoScreen(self.demo_md, title=self.demo_title))

    def action_noop(self) -> None:
        pass

    # -------------------------------------------------------------------------
    # copy helper
    # -------------------------------------------------------------------------

    def action_copy_full_iml(self) -> None:
        screen: Screen[Any] = self.screen
        if not isinstance(screen, MainScreen):
            self.notify("Copy is only available on the Main screen.", timeout=1.0)
            return

        iml_text = getattr(screen, "_full_iml_text", "")
        if not iml_text:
            self.notify("No IML model to copy.", timeout=1.0)
            return

        pyperclip.copy(iml_text)
        self.notify("Full IML model copied to clipboard.", timeout=1.2)

    # -------------------------------------------------------------------------
    # state apply / refresh
    # -------------------------------------------------------------------------

    def _apply_new_state(self, new_state: State) -> None:
        self.state = new_state

        screen: Screen[Any] = self.screen
        if isinstance(screen, MainScreen):
            screen.refresh_from_state()

    # -------------------------------------------------------------------------
    # file watching (polling) + subtitle indicator
    # -------------------------------------------------------------------------

    def _fmt_mtime(self, mtime_ns: int) -> str:
        dt = datetime.fromtimestamp(mtime_ns / 1e9, tz=timezone.utc)
        return dt.strftime("%Y-%m-%d %H:%M:%S UTC")

    def _set_last_updated_indicator(self, sig: tuple[int, int] | None) -> None:
        base = "AI-powered spec synthesis, verification & analysis"
        if not self.watch_state_file or not self.state_path or sig is None:
            self.sub_title = base
            return
        mtime_ns, _ = sig
        self.sub_title = f"{base} • state updated {self._fmt_mtime(mtime_ns)}"

    def _set_update_indicator_pending(self, sig: tuple[int, int]) -> None:
        base = "AI-powered spec synthesis, verification & analysis"
        mtime_ns, _ = sig
        self.sub_title = f"{base} • update available ({self._fmt_mtime(mtime_ns)})"

    def _get_state_file_signature(self) -> tuple[int, int] | None:
        if not self.state_path:
            return None
        try:
            st = os.stat(self.state_path)
            return (st.st_mtime_ns, st.st_size)
        except FileNotFoundError:
            return None

    def _poll_state_file(self) -> None:
        if not self.watch_state_file or not self.state_path:
            return

        sig = self._get_state_file_signature()
        if sig is None:
            return

        if self._state_sig is None:
            self._state_sig = sig
            self._set_last_updated_indicator(sig)
            return

        if sig == self._state_sig or sig == self._pending_state_sig:
            return

        # file changed: mark pending update (manual)
        self._pending_state_sig = sig
        self._update_available = True
        self._set_update_indicator_pending(sig)

        # update footer status if MainScreen is visible
        screen: Screen[Any] = self.screen
        if isinstance(screen, MainScreen):
            mtime_ns, _ = sig
            screen.set_status(
                f"[yellow]Update available[/yellow] — press [bold]u[/bold] to reload ({self._fmt_mtime(mtime_ns)})"
            )

        # popup hint for each new change
        self.notify("Update available — press 'u' to reload.", timeout=2.0)

    # -------------------------------------------------------------------------
    # manual update action
    # -------------------------------------------------------------------------

    def action_update_state(self) -> None:
        if not self._update_available or not self.state_path or not self._pending_state_sig:
            self.notify("No update available.", timeout=1.0)
            return

        # Debounce: avoid repeated clicks in quick succession
        now = time.time()
        if now - self._last_reload_ts < 0.25:
            return

        try:
            data = self.state_path.read_text(encoding="utf-8", errors="replace")
            new_state = State.model_validate_json(data)  # type: ignore[attr-defined]
        except Exception:
            self.notify(
                "Failed to load updated state (file may be mid-write).",
                severity="warning",
                timeout=1.5,
            )
            return

        self._last_reload_ts = now

        # accept update
        self._state_sig = self._pending_state_sig
        self._pending_state_sig = None
        self._update_available = False

        # update subtitle back to last-updated
        self._set_last_updated_indicator(self._state_sig)

        # apply + refresh UI
        self._apply_new_state(new_state)

        # clear footer status if on main
        screen: Screen[Any] = self.screen
        if isinstance(screen, MainScreen):
            screen.clear_status()

        self.notify("State updated.", timeout=1.2)