#!/usr/bin/env python3
"""
PromptBridge TUI - Cross-platform Terminal UI
Uses Textual + PTY for Windows/macOS/Linux support

Platform handling:
- macOS/Linux: Uses pty module (native)
- Windows: Uses pywinpty (needs: pip install pywinpty)

Layout (like tmux):
- Top: PromptBridge panel (status, log, !commands)
- Bottom: Interactive CLI terminal (direct input)
"""

import os
import sys
import asyncio
import shutil
import select
import signal
import subprocess
from typing import Optional
from pathlib import Path

# Platform detection
IS_WINDOWS = sys.platform == "win32"

# Check for required packages
try:
    from textual.app import App, ComposeResult
    from textual.widgets import Header, Footer, Static, Input, RichLog
    from textual.containers import Vertical, Horizontal
    from textual.binding import Binding
    from textual.widget import Widget
    from textual.message import Message
    from textual import on, events
    TEXTUAL_AVAILABLE = True
except ImportError:
    TEXTUAL_AVAILABLE = False

# PTY support
if IS_WINDOWS:
    PTY_AVAILABLE = False
    WINPTY_HAS_PTY = False
    WINPTY_HAS_PTYPROCESS = False
    PYWINPTY_VERSION = None
    try:
        import winpty  # provided by the "pywinpty" distribution on Windows
        WINPTY_HAS_PTY = hasattr(winpty, "PTY")
        WINPTY_HAS_PTYPROCESS = hasattr(winpty, "PtyProcess")
        PTY_AVAILABLE = bool(WINPTY_HAS_PTY or WINPTY_HAS_PTYPROCESS)
        try:
            from importlib.metadata import version as _pkg_version
            PYWINPTY_VERSION = _pkg_version("pywinpty")
        except Exception:
            PYWINPTY_VERSION = None
    except ImportError:
        winpty = None
else:
    import pty
    import fcntl
    import termios
    import struct
    PTY_AVAILABLE = True
    WINPTY_HAS_PTY = False
    WINPTY_HAS_PTYPROCESS = False
    PYWINPTY_VERSION = None

# Auth client
try:
    from promptbridge.auth.client import AuthClient
    AUTH_AVAILABLE = True
except ImportError:
    AUTH_AVAILABLE = False


TUI_CSS = """
Screen {
    layout: grid;
    /* top-section + focus-indicator + terminal-container */
    grid-size: 1 3;
    grid-rows: auto auto 1fr;
}

#top-section {
    height: auto;
    max-height: 12;
    background: #121215;
    border: solid #7f19e6;
    padding: 0 1;
}

#status-bar {
    height: 1;
    background: #1c1c21;
    padding: 0 1;
    margin-bottom: 1;
}

#pb-log {
    height: auto;
    max-height: 5;
    background: #0d0d1a;
    padding: 0 1;
}

#prompt-input {
    margin-top: 1;
    display: none;
}

#prompt-input.visible {
    display: block;
}

#terminal-container {
    background: #0a0a0f;
    border: solid #333;
    padding: 0;
}

#terminal-container.focused {
    border: solid #7f19e6;
}

#cli-output {
    background: #0a0a0f;
    padding: 1;
    height: 100%;
}

#terminal-input {
    dock: bottom;
    background: #0a0a0f;
    border: none;
    padding: 0 1;
}

#focus-indicator {
    height: 1;
    background: #333;
    text-align: center;
}

#focus-indicator.top-focused {
    background: #7f19e6;
}

Header {
    background: #7f19e6;
}

Footer {
    background: #1c1c21;
}
"""


class PTYProcess:
    """Cross-platform PTY process wrapper"""

    def __init__(self, command: str):
        self.command = command
        self.process = None
        self.master_fd = None
        self.running = False
        self._winpty_mode = None  # "pty" | "ptyprocess"

    def start(self) -> bool:
        """Start the PTY process"""
        if IS_WINDOWS:
            return self._start_windows()
        else:
            return self._start_unix()

    def _start_unix(self) -> bool:
        """Start PTY on Unix/macOS"""
        try:
            # Create pseudo-terminal
            self.master_fd, slave_fd = pty.openpty()

            # Fork process
            pid = os.fork()
            if pid == 0:
                # Child process
                os.setsid()
                os.dup2(slave_fd, 0)
                os.dup2(slave_fd, 1)
                os.dup2(slave_fd, 2)
                os.close(slave_fd)
                os.close(self.master_fd)

                # Execute command
                os.execlp(self.command, self.command)
            else:
                # Parent process
                os.close(slave_fd)
                self.process = pid
                self.running = True

                # Set non-blocking
                flags = fcntl.fcntl(self.master_fd, fcntl.F_GETFL)
                fcntl.fcntl(self.master_fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)

                return True
        except Exception as e:
            print(f"PTY error: {e}")
            return False

    def _start_windows(self) -> bool:
        """Start PTY on Windows using winpty/pywinpty"""
        if not PTY_AVAILABLE or winpty is None:
            return False

        try:
            # Prefer the low-level PTY API when available:
            # - Gives us reliable non-blocking reads for a polling TUI loop.
            # - Avoids CreateProcess limitations on .cmd/.bat by spawning COMSPEC (cmd.exe)
            #   and then running the selected CLI inside that shell.
            if WINPTY_HAS_PTY:
                cols, rows = 120, 40  # will be resized once the UI is mounted
                self.process = winpty.PTY(cols, rows)  # (cols, rows)
                self._winpty_mode = "pty"

                comspec = os.environ.get("COMSPEC", "cmd.exe")
                try:
                    self.process.spawn(comspec)
                except TypeError:
                    self.process.spawn(comspec.encode("utf-8", errors="replace"))

                # Start the selected CLI inside cmd.exe
                self.process.write((self.command + "\r\n").encode("utf-8", errors="replace"))
                self.running = True
                return True

            # Fallback: legacy/high-level API (older builds).
            if WINPTY_HAS_PTYPROCESS:
                resolved = shutil.which(self.command) or self.command
                resolved_lower = str(resolved).lower()
                if resolved_lower.endswith((".cmd", ".bat")):
                    cmdline = f'cmd.exe /c "{resolved}"'
                else:
                    cmdline = str(resolved)

                try:
                    self.process = winpty.PtyProcess.spawn(cmdline)
                except TypeError:
                    self.process = winpty.PtyProcess.spawn([str(resolved)])
                self._winpty_mode = "ptyprocess"
                self.running = True
                return True

        except Exception as e:
            print(f"WinPTY error: {e}")
            return False
        return False

    def write(self, data: str) -> None:
        """Write data to the PTY"""
        if not self.running:
            return

        if IS_WINDOWS:
            if self.process:
                try:
                    # Mode-specific types (some winpty APIs take str, others bytes).
                    if self._winpty_mode == "pty":
                        self.process.write(data.encode("utf-8", errors="replace"))
                    else:
                        self.process.write(data)
                except Exception:
                    pass
        else:
            if self.master_fd:
                os.write(self.master_fd, data.encode())

    def read(self, size: int = 4096) -> Optional[str]:
        """Read data from the PTY (non-blocking)"""
        if not self.running:
            return None

        if IS_WINDOWS:
            if self.process:
                try:
                    if self._winpty_mode == "pty":
                        # pywinpty has multiple read() signatures across versions.
                        # - v2.x: read(num_bytes, blocking=False)
                        # - v3.x: read(blocking=False) (num_bytes removed)
                        try:
                            data = self.process.read(size, blocking=False)
                        except TypeError:
                            data = self.process.read(blocking=False)
                        if not data:
                            return None
                        if isinstance(data, bytes):
                            return data.decode("utf-8", errors="replace")
                        return str(data)

                    # PtyProcess-like API (text)
                    data = self.process.read(size)
                    return data if data else None
                except Exception:
                    return None
        else:
            if self.master_fd:
                try:
                    ready, _, _ = select.select([self.master_fd], [], [], 0.1)
                    if ready:
                        data = os.read(self.master_fd, size)
                        return data.decode('utf-8', errors='replace')
                except Exception:
                    return None
        return None

    def resize(self, rows: int, cols: int) -> None:
        """Resize the PTY"""
        if IS_WINDOWS:
            if self.process:
                try:
                    if hasattr(self.process, "set_size"):
                        # winpty.PTY: set_size(cols, rows)
                        self.process.set_size(cols, rows)
                    elif hasattr(self.process, "setwinsize"):
                        # winpty.PtyProcess: setwinsize(rows, cols)
                        self.process.setwinsize(rows, cols)
                except Exception:
                    pass
        else:
            if self.master_fd:
                try:
                    winsize = struct.pack('HHHH', rows, cols, 0, 0)
                    fcntl.ioctl(self.master_fd, termios.TIOCSWINSZ, winsize)
                except Exception:
                    pass

    def stop(self) -> None:
        """Stop the PTY process"""
        self.running = False

        if IS_WINDOWS:
            if self.process:
                try:
                    if hasattr(self.process, "close"):
                        self.process.close()
                    elif hasattr(self.process, "terminate"):
                        self.process.terminate()
                except Exception:
                    pass
        else:
            if self.process:
                try:
                    os.kill(self.process, signal.SIGTERM)
                except Exception:
                    pass
            if self.master_fd:
                try:
                    os.close(self.master_fd)
                except Exception:
                    pass


class TerminalPanel(Widget):
    """Interactive terminal panel that captures keyboard input"""

    can_focus = True

    class Focused(Message):
        """Terminal panel gained focus"""
        pass

    class Blurred(Message):
        """Terminal panel lost focus"""
        pass

    def __init__(self, pty_process: Optional[PTYProcess] = None, **kwargs):
        super().__init__(**kwargs)
        self.pty_process = pty_process
        self._input_buffer = ""

    def compose(self) -> ComposeResult:
        # CLI output is untrusted/raw; don't parse Rich markup (can break on '[' output).
        yield RichLog(id="cli-output", markup=False, auto_scroll=True, max_lines=1000)

    def on_focus(self, event: events.Focus) -> None:
        self.add_class("focused")
        self.post_message(self.Focused())

    def on_blur(self, event: events.Blur) -> None:
        self.remove_class("focused")
        self.post_message(self.Blurred())

    def on_key(self, event: events.Key) -> None:
        """Handle keyboard input and send to PTY"""
        if not self.pty_process or not self.pty_process.running:
            return

        # Special keys
        key_map = {
            "enter": "\r",
            "tab": "\t",
            # Windows console apps commonly expect BS(0x08); Unix PTYs often use DEL(0x7f).
            "backspace": "\x08" if IS_WINDOWS else "\x7f",
            "delete": "\x1b[3~",
            "escape": "\x1b",
            "up": "\x1b[A",
            "down": "\x1b[B",
            "right": "\x1b[C",
            "left": "\x1b[D",
            "home": "\x1b[H",
            "end": "\x1b[F",
            "pageup": "\x1b[5~",
            "pagedown": "\x1b[6~",
        }

        # Ctrl+C
        if event.key == "ctrl+c":
            self.pty_process.write("\x03")
            event.prevent_default()
            return

        # Ctrl+D
        if event.key == "ctrl+d":
            self.pty_process.write("\x04")
            event.prevent_default()
            return

        # Ctrl+Z
        if event.key == "ctrl+z":
            self.pty_process.write("\x1a")
            event.prevent_default()
            return

        # Ctrl+L (clear)
        if event.key == "ctrl+l":
            self.pty_process.write("\x0c")
            event.prevent_default()
            return

        # Map special keys
        if event.key in key_map:
            self.pty_process.write(key_map[event.key])
            event.prevent_default()
            return

        # Regular character
        if event.character and len(event.character) == 1:
            self.pty_process.write(event.character)
            event.prevent_default()

    def write_output(self, text: str) -> None:
        """Write output to the terminal display"""
        log = self.query_one("#cli-output", RichLog)
        # Split by lines and write each
        for line in text.split('\n'):
            if line:
                log.write(line)

    def clear(self) -> None:
        """Clear the terminal display"""
        self.query_one("#cli-output", RichLog).clear()


class PromptBridgeTUI(App):
    """PromptBridge Cross-platform TUI Application (tmux-like)"""

    CSS = TUI_CSS
    TITLE = "PromptBridge"

    BINDINGS = [
        Binding("ctrl+q", "quit", "Quit"),
        Binding("ctrl+b", "toggle_focus", "Switch Panel", priority=True),
        Binding("f1", "show_help", "Help"),
        Binding("f2", "toggle_pb_input", "PB Input"),
    ]

    def __init__(self, cli_name: str = "bash"):
        super().__init__()
        self.cli_name = cli_name
        self.auth_client = AuthClient() if AUTH_AVAILABLE else None
        self.pty_process: Optional[PTYProcess] = None
        self._read_task: Optional[asyncio.Task] = None
        self._terminal_focused = True  # Start with terminal focused

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

        with Vertical(id="top-section"):
            yield Static(self._get_status_text(), id="status-bar")
            yield RichLog(id="pb-log", markup=True, highlight=True, max_lines=30)
            yield Input(
                placeholder="!help for commands, !opt <prompt> to optimize",
                id="prompt-input"
            )

        yield Static("[Ctrl+B] Switch Panel | [F1] Help | [F2] PB Commands", id="focus-indicator")

        with Vertical(id="terminal-container"):
            yield TerminalPanel(id="terminal-panel")

        yield Footer()

    def _get_status_text(self) -> str:
        cli_part = f"[bold yellow]{self.cli_name.upper()}[/]"
        pty_status = "[green]PTY[/]" if PTY_AVAILABLE else "[red]No PTY[/]"

        if self.auth_client and self.auth_client.is_logged_in():
            user = self.auth_client.storage.get_user()
            username = user['username'] if user else "User"
            return f"{cli_part} | {pty_status} | [green]{username}[/]"
        else:
            return f"{cli_part} | {pty_status} | [red]Not logged in[/]"

    async def on_mount(self) -> None:
        self.pb_log("[bold #7f19e6]PromptBridge TUI[/] v1.1", "info")

        if not PTY_AVAILABLE:
            if IS_WINDOWS:
                self.pb_log("[red]PTY not available.[/] Install: pip install pywinpty", "error")
            else:
                self.pb_log("[red]PTY not available[/]", "error")
        else:
            self.pb_log(f"PTY ready for [yellow]{self.cli_name}[/]", "ok")

        # Check CLI exists
        if not shutil.which(self.cli_name):
            self.pb_log(f"[red]'{self.cli_name}' not found in PATH[/]", "error")
        else:
            # Start CLI in PTY
            await self.start_cli()

        # Check auth
        if self.auth_client and self.auth_client.is_logged_in():
            user = self.auth_client.storage.get_user()
            if user:
                self.pb_log(f"Logged in: [green]{user['username']}[/]", "ok")
        else:
            self.pb_log("Use [cyan]!login[/] or [cyan]pb --login[/] to sign in", "info")

        self.pb_log("[cyan]Ctrl+B[/] to switch panels, [cyan]F1[/] for help", "info")

        # Focus terminal by default
        terminal = self.query_one("#terminal-panel", TerminalPanel)
        terminal.focus()

    async def start_cli(self) -> None:
        """Start the CLI process in PTY"""
        if not PTY_AVAILABLE:
            return

        self.pty_process = PTYProcess(self.cli_name)
        if self.pty_process.start():
            # Connect PTY to terminal panel
            terminal = self.query_one("#terminal-panel", TerminalPanel)
            terminal.pty_process = self.pty_process
            # Best-effort initial resize to match the UI pane.
            try:
                cols = max(20, terminal.size.width - 2)
                rows = max(5, terminal.size.height - 2)
                self.pty_process.resize(rows=rows, cols=cols)
            except Exception:
                pass
            terminal.write_output(f"─── {self.cli_name} started ───")
            # Start reading output
            self._read_task = asyncio.create_task(self._read_pty_output())
        else:
            self.pb_log(f"Failed to start {self.cli_name}", "error")

    def on_resize(self, event: events.Resize) -> None:
        """Propagate UI size changes to the underlying PTY."""
        if not self.pty_process or not self.pty_process.running:
            return
        try:
            terminal = self.query_one("#terminal-panel", TerminalPanel)
            cols = max(20, terminal.size.width - 2)
            rows = max(5, terminal.size.height - 2)
            self.pty_process.resize(rows=rows, cols=cols)
        except Exception:
            pass

    async def _read_pty_output(self) -> None:
        """Continuously read PTY output"""
        terminal = self.query_one("#terminal-panel", TerminalPanel)
        while self.pty_process and self.pty_process.running:
            try:
                data = self.pty_process.read()
                if data:
                    terminal.write_output(data.rstrip())
                await asyncio.sleep(0.05)
            except Exception:
                break

    def pb_log(self, message: str, status: str = "info") -> None:
        """Log to PromptBridge panel"""
        log = self.query_one("#pb-log", RichLog)
        prefix = {"ok": "[green]✓[/]", "error": "[red]✗[/]", "info": "[#7f19e6]>[/]"}
        log.write(f"{prefix.get(status, prefix['info'])} {message}")

    def update_status(self) -> None:
        self.query_one("#status-bar", Static).update(self._get_status_text())

    def action_toggle_focus(self) -> None:
        """Toggle focus between top and terminal panels"""
        self._terminal_focused = not self._terminal_focused
        indicator = self.query_one("#focus-indicator", Static)

        if self._terminal_focused:
            terminal = self.query_one("#terminal-panel", TerminalPanel)
            terminal.focus()
            indicator.remove_class("top-focused")
            indicator.update("[Ctrl+B] Switch Panel | [F1] Help | Terminal Active")
            # Hide PB input
            self.query_one("#prompt-input", Input).remove_class("visible")
        else:
            pb_input = self.query_one("#prompt-input", Input)
            pb_input.add_class("visible")
            pb_input.focus()
            indicator.add_class("top-focused")
            indicator.update("[Ctrl+B] Switch Panel | PromptBridge Input Active")

    def action_toggle_pb_input(self) -> None:
        """Toggle PromptBridge input visibility"""
        if self._terminal_focused:
            self.action_toggle_focus()

    @on(TerminalPanel.Focused)
    def on_terminal_focused(self, event: TerminalPanel.Focused) -> None:
        self._terminal_focused = True
        indicator = self.query_one("#focus-indicator", Static)
        indicator.remove_class("top-focused")
        indicator.update("[Ctrl+B] Switch Panel | [F1] Help | Terminal Active")

    @on(TerminalPanel.Blurred)
    def on_terminal_blurred(self, event: TerminalPanel.Blurred) -> None:
        pass  # Handled by focus change

    @on(Input.Submitted, "#prompt-input")
    async def handle_pb_input(self, event: Input.Submitted) -> None:
        """Handle PromptBridge command input"""
        user_input = event.value.strip()
        event.input.value = ""

        if not user_input:
            return

        cmd = user_input.lower()

        # PromptBridge commands
        if cmd in ["!quit", "!exit"]:
            await self.action_quit()
        elif cmd == "!help":
            self.action_show_help()
        elif cmd == "!login":
            self.do_login()
        elif cmd == "!logout":
            self.do_logout()
        elif cmd == "!status":
            self.show_status()
        elif cmd == "!clear":
            self.action_clear()
        elif cmd == "!terminal" or cmd == "!t":
            self.action_toggle_focus()
        elif user_input.lower().startswith("!opt "):
            prompt = user_input[5:].strip()
            if prompt:
                await self.optimize_prompt(prompt)
            else:
                self.pb_log("Usage: !opt <prompt>", "error")
        elif user_input.startswith("!"):
            self.pb_log(f"Unknown command: {user_input}", "error")
        else:
            # Send to CLI
            if self.pty_process and self.pty_process.running:
                line_ending = "\r\n" if IS_WINDOWS else "\n"
                self.pty_process.write(user_input + line_ending)
                self.pb_log(f"Sent: {user_input[:50]}{'...' if len(user_input) > 50 else ''}", "ok")
            else:
                self.pb_log("CLI not running", "error")

    def action_show_help(self) -> None:
        self.pb_log("[bold]━━━ PromptBridge Commands ━━━[/]", "info")
        self.pb_log("[cyan]!opt <text>[/]  Optimize prompt & send to CLI", "info")
        self.pb_log("[cyan]!login[/]       Login (or use: pb --login)", "info")
        self.pb_log("[cyan]!logout[/]      Logout", "info")
        self.pb_log("[cyan]!status[/]      Check balance", "info")
        self.pb_log("[cyan]!clear[/]       Clear screens", "info")
        self.pb_log("[cyan]!terminal[/]    Switch to terminal (or Ctrl+B)", "info")
        self.pb_log("[cyan]!quit[/]        Exit (or Ctrl+Q)", "info")
        self.pb_log("[bold]━━━ Keyboard Shortcuts ━━━[/]", "info")
        self.pb_log("[cyan]Ctrl+B[/]  Switch between panels", "info")
        self.pb_log("[cyan]F2[/]      Open PromptBridge input", "info")
        self.pb_log("[dim]In terminal: type directly to CLI[/]", "info")

    def do_login(self) -> None:
        if self.auth_client and self.auth_client.is_logged_in():
            self.pb_log("Already logged in", "info")
            return
        self.pb_log("To login, run from terminal first:", "info")
        self.pb_log("  [cyan]pb --login[/]", "info")
        self.pb_log("Or visit [link]https://promptbridge.store-art.com[/]", "info")

    def do_logout(self) -> None:
        if self.auth_client:
            self.auth_client.logout()
            self.pb_log("Logged out", "ok")
            self.update_status()

    def show_status(self) -> None:
        if not self.auth_client or not self.auth_client.is_logged_in():
            self.pb_log("Not logged in", "error")
            return

        success, status = self.auth_client.get_status()
        if success:
            self.pb_log(f"User: [green]{status['user']['username']}[/]", "ok")
            self.pb_log(f"Balance: [#7f19e6]{status['balance']}[/] credits", "ok")
            self.update_status()
        else:
            self.pb_log(f"Error: {status.get('error')}", "error")

    async def optimize_prompt(self, prompt: str) -> None:
        if not self.auth_client or not self.auth_client.is_logged_in():
            self.pb_log("Login required. Run: pb --login", "error")
            return

        self.pb_log("Optimizing...", "info")

        success, result = self.auth_client.optimize_prompt(prompt)
        if success:
            optimized = result.get("optimized_prompt", prompt)
            credits = result.get("credits_deducted", "0")

            self.pb_log(f"[green]✓ Optimized![/] (-{credits} credits)", "ok")

            # Send to CLI
            if self.pty_process and self.pty_process.running:
                line_ending = "\r\n" if IS_WINDOWS else "\n"
                self.pty_process.write(optimized + line_ending)
                self.pb_log("Sent to CLI", "ok")

            self.update_status()
        else:
            self.pb_log(f"Failed: {result.get('error')}", "error")

    def action_clear(self) -> None:
        self.query_one("#pb-log", RichLog).clear()
        terminal = self.query_one("#terminal-panel", TerminalPanel)
        terminal.clear()

    async def action_quit(self) -> None:
        if self.pty_process:
            self.pty_process.stop()
        if self._read_task:
            self._read_task.cancel()
        self.exit()


def run_tui(cli_name: str = "bash") -> None:
    """Run the TUI application"""
    if not TEXTUAL_AVAILABLE:
        print("Error: Textual not installed")
        print("Install: pip install textual")
        sys.exit(1)

    app = PromptBridgeTUI(cli_name=cli_name)
    app.run()


def check_tui_available() -> bool:
    return TEXTUAL_AVAILABLE and PTY_AVAILABLE


def get_tui_info() -> str:
    """Return a short diagnostic string for --check outputs."""
    if not IS_WINDOWS:
        return " (pty)"

    if not PTY_AVAILABLE:
        return ""

    api_parts = []
    if WINPTY_HAS_PTY:
        api_parts.append("winpty.PTY")
    if WINPTY_HAS_PTYPROCESS:
        api_parts.append("winpty.PtyProcess")

    preferred = "winpty.PTY" if WINPTY_HAS_PTY else ("winpty.PtyProcess" if WINPTY_HAS_PTYPROCESS else "unknown")

    parts = []
    if PYWINPTY_VERSION:
        parts.append(f"pywinpty {PYWINPTY_VERSION}")
    if api_parts:
        parts.append("+".join(api_parts))
    parts.append(f"preferred={preferred}")

    return " (" + "; ".join(parts) + ")"


def get_requirements() -> str:
    """Get installation requirements for current platform"""
    reqs = ["textual"]
    if IS_WINDOWS:
        reqs.append("pywinpty")
    return " ".join(reqs)


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="PromptBridge TUI (Cross-platform)")
    parser.add_argument("cli", nargs="?", default="bash", help="CLI to run")
    parser.add_argument("--check", action="store_true", help="Check requirements")
    args = parser.parse_args()

    if args.check:
        print(f"Platform: {'Windows' if IS_WINDOWS else 'Unix/macOS'}")
        print(f"Textual: {'OK' if TEXTUAL_AVAILABLE else 'Missing'}")
        print(f"PTY: {'OK' if PTY_AVAILABLE else 'Missing'}{get_tui_info()}")
        print(f"Install: pip install {get_requirements()}")
        sys.exit(0)

    run_tui(args.cli)
